diff --git a/CHANGELOG.md b/CHANGELOG.md index b8f11e2..3ad915a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,11 @@ All major and minor version changes will be documented in this file. Details of patch-level version changes can be found in [commit messages](https://github.com/gchq/CyberChef/commits/master). +### [8.24.0] - 2019-01-18 +- 'Convert co-ordinate format' operation added [@j433866] | [#476] + ### [8.23.0] - 2019-01-18 -- 'YARA Rules' operatio added [@artemisbot] | [#468] +- 'YARA Rules' operation added [@artemisbot] | [#468] ### [8.22.0] - 2019-01-10 - 'Subsection' operation added [@j433866] | [#467] @@ -100,6 +103,7 @@ All major and minor version changes will be documented in this file. Details of +[8.24.0]: https://github.com/gchq/CyberChef/releases/tag/v8.24.0 [8.23.0]: https://github.com/gchq/CyberChef/releases/tag/v8.23.0 [8.22.0]: https://github.com/gchq/CyberChef/releases/tag/v8.22.0 [8.21.0]: https://github.com/gchq/CyberChef/releases/tag/v8.21.0 @@ -181,3 +185,4 @@ All major and minor version changes will be documented in this file. Details of [#461]: https://github.com/gchq/CyberChef/pull/461 [#467]: https://github.com/gchq/CyberChef/pull/467 [#468]: https://github.com/gchq/CyberChef/pull/468 +[#476]: https://github.com/gchq/CyberChef/pull/476 diff --git a/src/core/lib/ConvertCoordinates.mjs b/src/core/lib/ConvertCoordinates.mjs index d4641fe..ad592b7 100644 --- a/src/core/lib/ConvertCoordinates.mjs +++ b/src/core/lib/ConvertCoordinates.mjs @@ -8,6 +8,7 @@ import geohash from "ngeohash"; import geodesy from "geodesy"; +import OperationError from "../errors/OperationError"; /** * Co-ordinate formats @@ -34,6 +35,7 @@ const NO_CHANGE = [ /** * Convert a given latitude and longitude into a different format. + * * @param {string} input - Input string to be converted * @param {string} inFormat - Format of the input coordinates * @param {string} inDelim - The delimiter splitting the lat/long of the input @@ -49,29 +51,40 @@ export function convertCoordinates (input, inFormat, inDelim, outFormat, outDeli latlon, convLat, convLon, - conv; + conv, + hash, + utm, + mgrs, + osng, + splitLat, + splitLong, + lat, + lon; // Can't have a precision less than 0! if (precision < 0) { precision = 0; } + if (inDelim === "Auto") { // Try to detect a delimiter in the input. inDelim = findDelim(input); if (inDelim === null) { - throw "Unable to detect the input delimiter automatically."; + throw new OperationError("Unable to detect the input delimiter automatically."); } } else { // Convert the delimiter argument value to the actual character inDelim = realDelim(inDelim); } + if (inFormat === "Auto") { // Try to detect the format of the input data inFormat = findFormat(input, inDelim); if (inFormat === null) { - throw "Unable to detect the input format automatically."; + throw new OperationError("Unable to detect the input format automatically."); } } + // Convert the output delimiter argument to the real character outDelim = realDelim(outDelim); @@ -91,83 +104,91 @@ export function convertCoordinates (input, inFormat, inDelim, outFormat, outDeli } // Conversions from the input format into a geodesy latlon object - if (inFormat === "Geohash") { - const hash = geohash.decode(input.replace(/[^A-Za-z0-9]/g, "")); - latlon = new geodesy.LatLonEllipsoidal(hash.latitude, hash.longitude); - } else if (inFormat === "Military Grid Reference System") { - const utm = geodesy.Mgrs.parse(input.replace(/[^A-Za-z0-9]/g, "")).toUtm(); - latlon = utm.toLatLonE(); - } else if (inFormat === "Ordnance Survey National Grid") { - const osng = geodesy.OsGridRef.parse(input.replace(/[^A-Za-z0-9]/g, "")); - latlon = geodesy.OsGridRef.osGridToLatLon(osng); - } else if (inFormat === "Universal Transverse Mercator") { - // Geodesy needs a space between the first 2 digits and the next letter - if (/^[\d]{2}[A-Za-z]/.test(input)) { - input = input.slice(0, 2) + " " + input.slice(2); - } - const utm = geodesy.Utm.parse(input); - latlon = utm.toLatLonE(); - } else if (inFormat === "Degrees Minutes Seconds") { - if (isPair) { - // Split up the lat/long into degrees / minutes / seconds values - const splitLat = splitInput(split[0]), + switch (inFormat) { + case "Geohash": + hash = geohash.decode(input.replace(/[^A-Za-z0-9]/g, "")); + latlon = new geodesy.LatLonEllipsoidal(hash.latitude, hash.longitude); + break; + case "Military Grid Reference System": + utm = geodesy.Mgrs.parse(input.replace(/[^A-Za-z0-9]/g, "")).toUtm(); + latlon = utm.toLatLonE(); + break; + case "Ordnance Survey National Grid": + osng = geodesy.OsGridRef.parse(input.replace(/[^A-Za-z0-9]/g, "")); + latlon = geodesy.OsGridRef.osGridToLatLon(osng); + break; + case "Universal Transverse Mercator": + // Geodesy needs a space between the first 2 digits and the next letter + if (/^[\d]{2}[A-Za-z]/.test(input)) { + input = input.slice(0, 2) + " " + input.slice(2); + } + utm = geodesy.Utm.parse(input); + latlon = utm.toLatLonE(); + break; + case "Degrees Minutes Seconds": + if (isPair) { + // Split up the lat/long into degrees / minutes / seconds values + splitLat = splitInput(split[0]); splitLong = splitInput(split[1]); - if (splitLat.length >= 3 && splitLong.length >= 3) { - const lat = convDMSToDD(splitLat[0], splitLat[1], splitLat[2], 10); - const long = convDMSToDD(splitLong[0], splitLong[1], splitLong[2], 10); - latlon = new geodesy.LatLonEllipsoidal(lat.degrees, long.degrees); + if (splitLat.length >= 3 && splitLong.length >= 3) { + lat = convDMSToDD(splitLat[0], splitLat[1], splitLat[2], 10); + lon = convDMSToDD(splitLong[0], splitLong[1], splitLong[2], 10); + latlon = new geodesy.LatLonEllipsoidal(lat.degrees, lon.degrees); + } else { + throw new OperationError("Invalid co-ordinate format for Degrees Minutes Seconds"); + } } else { - throw "Invalid co-ordinate format for Degrees Minutes Seconds"; + // Not a pair, so only try to convert one set of co-ordinates + splitLat = splitInput(split[0]); + if (splitLat.length >= 3) { + lat = convDMSToDD(splitLat[0], splitLat[1], splitLat[2]); + latlon = new geodesy.LatLonEllipsoidal(lat.degrees, lat.degrees); + } else { + throw new OperationError("Invalid co-ordinate format for Degrees Minutes Seconds"); + } } - } else { - // Not a pair, so only try to convert one set of co-ordinates - const splitLat = splitInput(split[0]); - if (splitLat.length >= 3) { - const lat = convDMSToDD(splitLat[0], splitLat[1], splitLat[2]); + break; + case "Degrees Decimal Minutes": + if (isPair) { + splitLat = splitInput(split[0]); + splitLong = splitInput(split[1]); + if (splitLat.length !== 2 || splitLong.length !== 2) { + throw new OperationError("Invalid co-ordinate format for Degrees Decimal Minutes."); + } + // Convert to decimal degrees, and then convert to a geodesy object + lat = convDDMToDD(splitLat[0], splitLat[1], 10); + lon = convDDMToDD(splitLong[0], splitLong[1], 10); + latlon = new geodesy.LatLonEllipsoidal(lat.degrees, lon.degrees); + } else { + // Not a pair, so only try to convert one set of co-ordinates + splitLat = splitInput(input); + if (splitLat.length !== 2) { + throw new OperationError("Invalid co-ordinate format for Degrees Decimal Minutes."); + } + lat = convDDMToDD(splitLat[0], splitLat[1], 10); latlon = new geodesy.LatLonEllipsoidal(lat.degrees, lat.degrees); + } + break; + case "Decimal Degrees": + if (isPair) { + splitLat = splitInput(split[0]); + splitLong = splitInput(split[1]); + if (splitLat.length !== 1 || splitLong.length !== 1) { + throw new OperationError("Invalid co-ordinate format for Decimal Degrees."); + } + latlon = new geodesy.LatLonEllipsoidal(splitLat[0], splitLong[0]); } else { - throw "Invalid co-ordinate format for Degrees Minutes Seconds"; + // Not a pair, so only try to convert one set of co-ordinates + splitLat = splitInput(split[0]); + if (splitLat.length !== 1) { + throw new OperationError("Invalid co-ordinate format for Decimal Degrees."); + } + latlon = new geodesy.LatLonEllipsoidal(splitLat[0], splitLat[0]); } - } - } else if (inFormat === "Degrees Decimal Minutes") { - if (isPair) { - const splitLat = splitInput(split[0]); - const splitLong = splitInput(split[1]); - if (splitLat.length !== 2 || splitLong.length !== 2) { - throw "Invalid co-ordinate format for Degrees Decimal Minutes."; - } - // Convert to decimal degrees, and then convert to a geodesy object - const lat = convDDMToDD(splitLat[0], splitLat[1], 10); - const long = convDDMToDD(splitLong[0], splitLong[1], 10); - latlon = new geodesy.LatLonEllipsoidal(lat.degrees, long.degrees); - } else { - // Not a pair, so only try to convert one set of co-ordinates - const splitLat = splitInput(input); - if (splitLat.length !== 2) { - throw "Invalid co-ordinate format for Degrees Decimal Minutes."; - } - const lat = convDDMToDD(splitLat[0], splitLat[1], 10); - latlon = new geodesy.LatLonEllipsoidal(lat.degrees, lat.degrees); - } - } else if (inFormat === "Decimal Degrees") { - if (isPair) { - const splitLat = splitInput(split[0]); - const splitLong = splitInput(split[1]); - if (splitLat.length !== 1 || splitLong.length !== 1) { - throw "Invalid co-ordinate format for Decimal Degrees."; - } - latlon = new geodesy.LatLonEllipsoidal(splitLat[0], splitLong[0]); - } else { - // Not a pair, so only try to convert one set of co-ordinates - const splitLat = splitInput(split[0]); - if (splitLat.length !== 1) { - throw "Invalid co-ordinate format for Decimal Degrees."; - } - latlon = new geodesy.LatLonEllipsoidal(splitLat[0], splitLat[0]); - } - } else { - throw `Unknown input format '${inFormat}'`; + break; + default: + throw new OperationError(`Unknown input format '${inFormat}'`); } // Everything is now a geodesy latlon object @@ -179,68 +200,78 @@ export function convertCoordinates (input, inFormat, inDelim, outFormat, outDeli if (dirs && dirs.length >= 1) { // Make positive lat/lon values with S/W directions into negative values if (dirs[0] === "S" || dirs[0] === "W" && latlon.lat > 0) { - latlon.lat = 0 - latlon.lat; + latlon.lat = -latlon.lat; } if (dirs.length >= 2) { if (dirs[1] === "S" || dirs[1] === "W" && latlon.lon > 0) { - latlon.lon = 0 - latlon.lon; + latlon.lon = -latlon.lon; } } } } + // Try to find the compass directions of the lat and long const [latDir, longDir] = findDirs(latlon.lat + "," + latlon.lon, ","); + // Output conversions for each output format - if (outFormat === "Decimal Degrees") { - // We could use the built in latlon.toString(), - // but this makes adjusting the output harder - const lat = convDDToDD(latlon.lat, precision); - const lon = convDDToDD(latlon.lon, precision); - convLat = lat.string; - convLon = lon.string; - } else if (outFormat === "Degrees Decimal Minutes") { - const lat = convDDToDDM(latlon.lat, precision); - const lon = convDDToDDM(latlon.lon, precision); - convLat = lat.string; - convLon = lon.string; - } else if (outFormat === "Degrees Minutes Seconds") { - const lat = convDDToDMS(latlon.lat, precision); - const lon = convDDToDMS(latlon.lon, precision); - convLat = lat.string; - convLon = lon.string; - } else if (outFormat === "Geohash") { - convLat = geohash.encode(latlon.lat, latlon.lon, precision); - } else if (outFormat === "Military Grid Reference System") { - const utm = latlon.toUtm(); - const mgrs = utm.toMgrs(); - // MGRS wants a precision that's an even number between 2 and 10 - if (precision % 2 !== 0) { - precision = precision + 1; - } - if (precision > 10) { - precision = 10; - } - convLat = mgrs.toString(precision); - } else if (outFormat === "Ordnance Survey National Grid") { - const osng = geodesy.OsGridRef.latLonToOsGrid(latlon); - if (osng.toString() === "") { - throw "Could not convert co-ordinates to OS National Grid. Are the co-ordinates in range?"; - } - // OSNG wants a precision that's an even number between 2 and 10 - if (precision % 2 !== 0) { - precision = precision + 1; - } - if (precision > 10) { - precision = 10; - } - convLat = osng.toString(precision); - } else if (outFormat === "Universal Transverse Mercator") { - const utm = latlon.toUtm(); - convLat = utm.toString(precision); + switch (outFormat) { + case "Decimal Degrees": + // We could use the built in latlon.toString(), + // but this makes adjusting the output harder + lat = convDDToDD(latlon.lat, precision); + lon = convDDToDD(latlon.lon, precision); + convLat = lat.string; + convLon = lon.string; + break; + case "Degrees Decimal Minutes": + lat = convDDToDDM(latlon.lat, precision); + lon = convDDToDDM(latlon.lon, precision); + convLat = lat.string; + convLon = lon.string; + break; + case "Degrees Minutes Seconds": + lat = convDDToDMS(latlon.lat, precision); + lon = convDDToDMS(latlon.lon, precision); + convLat = lat.string; + convLon = lon.string; + break; + case "Geohash": + convLat = geohash.encode(latlon.lat, latlon.lon, precision); + break; + case "Military Grid Reference System": + utm = latlon.toUtm(); + mgrs = utm.toMgrs(); + // MGRS wants a precision that's an even number between 2 and 10 + if (precision % 2 !== 0) { + precision = precision + 1; + } + if (precision > 10) { + precision = 10; + } + convLat = mgrs.toString(precision); + break; + case "Ordnance Survey National Grid": + osng = geodesy.OsGridRef.latLonToOsGrid(latlon); + if (osng.toString() === "") { + throw new OperationError("Could not convert co-ordinates to OS National Grid. Are the co-ordinates in range?"); + } + // OSNG wants a precision that's an even number between 2 and 10 + if (precision % 2 !== 0) { + precision = precision + 1; + } + if (precision > 10) { + precision = 10; + } + convLat = osng.toString(precision); + break; + case "Universal Transverse Mercator": + utm = latlon.toUtm(); + convLat = utm.toString(precision); + break; } if (convLat === undefined) { - throw "Error converting co-ordinates."; + throw new OperationError("Error converting co-ordinates."); } if (outFormat.includes("Degrees")) { @@ -283,6 +314,7 @@ export function convertCoordinates (input, inFormat, inDelim, outFormat, outDeli /** * Split up the input using a space or degrees signs, and sanitise the result + * * @param {string} input - The input data to be split * @returns {number[]} An array of the different items in the string, stored as floats */ @@ -302,6 +334,7 @@ function splitInput (input){ /** * Convert Degrees Minutes Seconds to Decimal Degrees + * * @param {number} degrees - The degrees of the input co-ordinates * @param {number} minutes - The minutes of the input co-ordinates * @param {number} seconds - The seconds of the input co-ordinates @@ -324,6 +357,7 @@ function convDMSToDD (degrees, minutes, seconds, precision){ /** * Convert Decimal Degrees Minutes to Decimal Degrees + * * @param {number} degrees - The input degrees to be converted * @param {number} minutes - The input minutes to be converted * @param {number} precision - The precision which the result should be rounded to @@ -345,6 +379,7 @@ function convDDMToDD (degrees, minutes, precision) { /** * Convert Decimal Degrees to Decimal Degrees + * * Doesn't affect the input, just puts it into an object * @param {number} degrees - The input degrees to be converted * @param {number} precision - The precision which the result should be rounded to @@ -359,6 +394,7 @@ function convDDToDD (degrees, precision) { /** * Convert Decimal Degrees to Degrees Minutes Seconds + * * @param {number} decDegrees - The input data to be converted * @param {number} precision - The precision which the result should be rounded to * @returns {{string: string, degrees: number, minutes: number, seconds: number}} An object containing the raw converted value as separate numbers (.degrees, .minutes, .seconds), and a formatted string version (obj.string) @@ -383,6 +419,7 @@ function convDDToDMS (decDegrees, precision) { /** * Convert Decimal Degrees to Degrees Decimal Minutes + * * @param {number} decDegrees - The input degrees to be converted * @param {number} precision - The precision the input data should be rounded to * @returns {{string: string, degrees: number, minutes: number}} An object containing the raw converted value as separate numbers (.degrees, .minutes), and a formatted string version (obj.string) @@ -407,6 +444,7 @@ function convDDToDDM (decDegrees, precision) { /** * Finds and returns the compass directions in an input string + * * @param {string} input - The input co-ordinates containing the direction * @param {string} delim - The delimiter separating latitide and longitude * @returns {string[]} String array containing the latitude and longitude directions @@ -421,13 +459,10 @@ export function findDirs(input, delim) { // If there's actually compass directions // in the input, use these to work out the direction if (dirs.length <= 2 && dirs.length >= 1) { - if (dirs.length === 2) { - return [dirs[0], dirs[1]]; - } else { - return [dirs[0], ""]; - } + return dirs.length === 2 ? [dirs[0], dirs[1]] : [dirs[0], ""]; } } + // Nothing was returned, so guess the directions let lat = upperInput, long, @@ -440,43 +475,29 @@ export function findDirs(input, delim) { if (split[0] !== "") { lat = split[0]; } - if (split.length >= 2) { - if (split[1] !== "") { - long = split[1]; - } + if (split.length >= 2 && split[1] !== "") { + long = split[1]; } } } } else { const split = upperInput.split(dirExp); if (split.length > 1) { - if (split[0] === "") { - lat = split[1]; - } else { - lat = split[0]; - } - if (split.length > 2) { - if (split[2] !== "") { - long = split[2]; - } + lat = split[0] === "" ? split[1] : split[0]; + if (split.length > 2 && split[2] !== "") { + long = split[2]; } } } + if (lat) { lat = parseFloat(lat); - if (lat < 0) { - latDir = "S"; - } else { - latDir = "N"; - } + latDir = lat < 0 ? "S" : "N"; } + if (long) { long = parseFloat(long); - if (long < 0) { - longDir = "W"; - } else { - longDir = "E"; - } + longDir = long < 0 ? "W" : "E"; } return [latDir, longDir]; @@ -484,6 +505,7 @@ export function findDirs(input, delim) { /** * Detects the co-ordinate format of the input data + * * @param {string} input - The input data whose format we need to detect * @param {string} delim - The delimiter separating the data in input * @returns {string} The input format @@ -495,25 +517,19 @@ export function findFormat (input, delim) { geohashPattern = new RegExp(/^[0123456789BCDEFGHJKMNPQRSTUVWXYZ]+$/), utmPattern = new RegExp(/^[0-9]{2}\s?[C-HJ-NP-X]\s[0-9.]+\s?[0-9.]+$/), degPattern = new RegExp(/[°'"]/g); + input = input.trim(); + if (delim !== null && delim.includes("Direction")) { const split = input.split(/[NnEeSsWw]/); if (split.length > 1) { - if (split[0] === "") { - testData = split[1]; - } else { - testData = split[0]; - } + testData = split[0] === "" ? split[1] : split[0]; } } else if (delim !== null && delim !== "") { if (input.includes(delim)) { const split = input.split(delim); if (split.length > 1) { - if (split[0] === "") { - testData = split[1]; - } else { - testData = split[0]; - } + testData = split[0] === "" ? split[1] : split[0]; } } else { testData = input; @@ -523,20 +539,17 @@ export function findFormat (input, delim) { // Test non-degrees formats if (!degPattern.test(input)) { const filteredInput = input.toUpperCase().replace(delim, ""); - const isMgrs = mgrsPattern.test(filteredInput); - const isOsng = osngPattern.test(filteredInput); - const isGeohash = geohashPattern.test(filteredInput); - const isUtm = utmPattern.test(filteredInput); - if (isUtm) { + + if (utmPattern.test(filteredInput)) { return "Universal Transverse Mercator"; } - if (isMgrs) { + if (mgrsPattern.test(filteredInput)) { return "Military Grid Reference System"; } - if (isOsng) { + if (osngPattern.test(filteredInput)) { return "Ordnance Survey National Grid"; } - if (isGeohash) { + if (geohashPattern.test(filteredInput)) { return "Geohash"; } } @@ -558,6 +571,7 @@ export function findFormat (input, delim) { /** * Automatically find the delimeter type from the given input + * * @param {string} input * @returns {string} Delimiter type */ @@ -594,6 +608,7 @@ export function findDelim (input) { /** * Gets the real string for a delimiter name. + * * @param {string} delim The delimiter to be matched * @returns {string} */ @@ -610,7 +625,9 @@ export function realDelim (delim) { /** * Returns true if a zero is negative + * * @param {number} zero + * @returns {boolean} */ function isNegativeZero(zero) { return zero === 0 && (1/zero < 0); @@ -618,6 +635,7 @@ function isNegativeZero(zero) { /** * Rounds a number to a specified number of decimal places + * * @param {number} input - The number to be rounded * @param {precision} precision - The number of decimal places the number should be rounded to * @returns {number} diff --git a/src/core/operations/ConvertCoordinateFormat.mjs b/src/core/operations/ConvertCoordinateFormat.mjs index 09e1620..87e44bf 100644 --- a/src/core/operations/ConvertCoordinateFormat.mjs +++ b/src/core/operations/ConvertCoordinateFormat.mjs @@ -5,7 +5,6 @@ */ import Operation from "../Operation"; -import OperationError from "../errors/OperationError"; import {FORMATS, convertCoordinates} from "../lib/ConvertCoordinates"; /** @@ -21,7 +20,7 @@ class ConvertCoordinateFormat extends Operation { this.name = "Convert co-ordinate format"; this.module = "Hashing"; - this.description = "Convert geographical coordinates between different formats.

Supported formats:
The operation can try to detect the input co-ordinate format and delimiter automatically, but this may not always work correctly."; + this.description = "Converts geographical coordinates between different formats.

Supported formats:
The operation can try to detect the input co-ordinate format and delimiter automatically, but this may not always work correctly."; this.infoURL = "https://wikipedia.org/wiki/Geographic_coordinate_conversion"; this.inputType = "string"; this.outputType = "string"; @@ -85,12 +84,8 @@ class ConvertCoordinateFormat extends Operation { run(input, args) { if (input.replace(/[\s+]/g, "") !== "") { const [inFormat, inDelim, outFormat, outDelim, incDirection, precision] = args; - try { - const result = convertCoordinates(input, inFormat, inDelim, outFormat, outDelim, incDirection, precision); - return result; - } catch (error) { - throw new OperationError(error); - } + const result = convertCoordinates(input, inFormat, inDelim, outFormat, outDelim, incDirection, precision); + return result; } else { return input; }