var atob = require("atob") var urlLib = require("url") var pathLib = require("path") var decodeUriComponentLib = require("decode-uri-component") function resolveUrl(/* ...urls */) { return Array.prototype.reduce.call(arguments, function(resolved, nextUrl) { return urlLib.resolve(resolved, nextUrl) }) } function convertWindowsPath(aPath) { return pathLib.sep === "\\" ? aPath.replace(/\\/g, "/").replace(/^[a-z]:\/?/i, "/") : aPath } function customDecodeUriComponent(string) { // `decodeUriComponentLib` turns `+` into ` `, but that's not wanted. return decodeUriComponentLib(string.replace(/\+/g, "%2B")) } function callbackAsync(callback, error, result) { setImmediate(function() { callback(error, result) }) } function parseMapToJSON(string, data) { try { return JSON.parse(string.replace(/^\)\]\}'/, "")) } catch (error) { error.sourceMapData = data throw error } } function readSync(read, url, data) { var readUrl = customDecodeUriComponent(url) try { return String(read(readUrl)) } catch (error) { error.sourceMapData = data throw error } } var innerRegex = /[#@] sourceMappingURL=([^\s'"]*)/ var sourceMappingURLRegex = RegExp( "(?:" + "/\\*" + "(?:\\s*\r?\n(?://)?)?" + "(?:" + innerRegex.source + ")" + "\\s*" + "\\*/" + "|" + "//(?:" + innerRegex.source + ")" + ")" + "\\s*" ) function getSourceMappingUrl(code) { var match = code.match(sourceMappingURLRegex) return match ? match[1] || match[2] || "" : null } function resolveSourceMap(code, codeUrl, read, callback) { var mapData try { mapData = resolveSourceMapHelper(code, codeUrl) } catch (error) { return callbackAsync(callback, error) } if (!mapData || mapData.map) { return callbackAsync(callback, null, mapData) } var readUrl = customDecodeUriComponent(mapData.url) read(readUrl, function(error, result) { if (error) { error.sourceMapData = mapData return callback(error) } mapData.map = String(result) try { mapData.map = parseMapToJSON(mapData.map, mapData) } catch (error) { return callback(error) } callback(null, mapData) }) } function resolveSourceMapSync(code, codeUrl, read) { var mapData = resolveSourceMapHelper(code, codeUrl) if (!mapData || mapData.map) { return mapData } mapData.map = readSync(read, mapData.url, mapData) mapData.map = parseMapToJSON(mapData.map, mapData) return mapData } var dataUriRegex = /^data:([^,;]*)(;[^,;]*)*(?:,(.*))?$/ /** * The media type for JSON text is application/json. * * {@link https://tools.ietf.org/html/rfc8259#section-11 | IANA Considerations } * * `text/json` is non-standard media type */ var jsonMimeTypeRegex = /^(?:application|text)\/json$/ /** * JSON text exchanged between systems that are not part of a closed ecosystem * MUST be encoded using UTF-8. * * {@link https://tools.ietf.org/html/rfc8259#section-8.1 | Character Encoding} */ var jsonCharacterEncoding = "utf-8" function base64ToBuf(b64) { var binStr = atob(b64) var len = binStr.length var arr = new Uint8Array(len) for (var i = 0; i < len; i++) { arr[i] = binStr.charCodeAt(i) } return arr } function decodeBase64String(b64) { if (typeof TextDecoder === "undefined" || typeof Uint8Array === "undefined") { return atob(b64) } var buf = base64ToBuf(b64); // Note: `decoder.decode` method will throw a `DOMException` with the // `"EncodingError"` value when an coding error is found. var decoder = new TextDecoder(jsonCharacterEncoding, {fatal: true}) return decoder.decode(buf); } function resolveSourceMapHelper(code, codeUrl) { codeUrl = convertWindowsPath(codeUrl) var url = getSourceMappingUrl(code) if (!url) { return null } var dataUri = url.match(dataUriRegex) if (dataUri) { var mimeType = dataUri[1] || "text/plain" var lastParameter = dataUri[2] || "" var encoded = dataUri[3] || "" var data = { sourceMappingURL: url, url: null, sourcesRelativeTo: codeUrl, map: encoded } if (!jsonMimeTypeRegex.test(mimeType)) { var error = new Error("Unuseful data uri mime type: " + mimeType) error.sourceMapData = data throw error } try { data.map = parseMapToJSON( lastParameter === ";base64" ? decodeBase64String(encoded) : decodeURIComponent(encoded), data ) } catch (error) { error.sourceMapData = data throw error } return data } var mapUrl = resolveUrl(codeUrl, url) return { sourceMappingURL: url, url: mapUrl, sourcesRelativeTo: mapUrl, map: null } } function resolveSources(map, mapUrl, read, options, callback) { if (typeof options === "function") { callback = options options = {} } var pending = map.sources ? map.sources.length : 0 var result = { sourcesResolved: [], sourcesContent: [] } if (pending === 0) { callbackAsync(callback, null, result) return } var done = function() { pending-- if (pending === 0) { callback(null, result) } } resolveSourcesHelper(map, mapUrl, options, function(fullUrl, sourceContent, index) { result.sourcesResolved[index] = fullUrl if (typeof sourceContent === "string") { result.sourcesContent[index] = sourceContent callbackAsync(done, null) } else { var readUrl = customDecodeUriComponent(fullUrl) read(readUrl, function(error, source) { result.sourcesContent[index] = error ? error : String(source) done() }) } }) } function resolveSourcesSync(map, mapUrl, read, options) { var result = { sourcesResolved: [], sourcesContent: [] } if (!map.sources || map.sources.length === 0) { return result } resolveSourcesHelper(map, mapUrl, options, function(fullUrl, sourceContent, index) { result.sourcesResolved[index] = fullUrl if (read !== null) { if (typeof sourceContent === "string") { result.sourcesContent[index] = sourceContent } else { var readUrl = customDecodeUriComponent(fullUrl) try { result.sourcesContent[index] = String(read(readUrl)) } catch (error) { result.sourcesContent[index] = error } } } }) return result } var endingSlash = /\/?$/ function resolveSourcesHelper(map, mapUrl, options, fn) { options = options || {} mapUrl = convertWindowsPath(mapUrl) var fullUrl var sourceContent var sourceRoot for (var index = 0, len = map.sources.length; index < len; index++) { sourceRoot = null if (typeof options.sourceRoot === "string") { sourceRoot = options.sourceRoot } else if (typeof map.sourceRoot === "string" && options.sourceRoot !== false) { sourceRoot = map.sourceRoot } // If the sourceRoot is the empty string, it is equivalent to not setting // the property at all. if (sourceRoot === null || sourceRoot === '') { fullUrl = resolveUrl(mapUrl, map.sources[index]) } else { // Make sure that the sourceRoot ends with a slash, so that `/scripts/subdir` becomes // `/scripts/subdir/`, not `/scripts/`. Pointing to a file as source root // does not make sense. fullUrl = resolveUrl(mapUrl, sourceRoot.replace(endingSlash, "/"), map.sources[index]) } sourceContent = (map.sourcesContent || [])[index] fn(fullUrl, sourceContent, index) } } function resolve(code, codeUrl, read, options, callback) { if (typeof options === "function") { callback = options options = {} } if (code === null) { var mapUrl = codeUrl var data = { sourceMappingURL: null, url: mapUrl, sourcesRelativeTo: mapUrl, map: null } var readUrl = customDecodeUriComponent(mapUrl) read(readUrl, function(error, result) { if (error) { error.sourceMapData = data return callback(error) } data.map = String(result) try { data.map = parseMapToJSON(data.map, data) } catch (error) { return callback(error) } _resolveSources(data) }) } else { resolveSourceMap(code, codeUrl, read, function(error, mapData) { if (error) { return callback(error) } if (!mapData) { return callback(null, null) } _resolveSources(mapData) }) } function _resolveSources(mapData) { resolveSources(mapData.map, mapData.sourcesRelativeTo, read, options, function(error, result) { if (error) { return callback(error) } mapData.sourcesResolved = result.sourcesResolved mapData.sourcesContent = result.sourcesContent callback(null, mapData) }) } } function resolveSync(code, codeUrl, read, options) { var mapData if (code === null) { var mapUrl = codeUrl mapData = { sourceMappingURL: null, url: mapUrl, sourcesRelativeTo: mapUrl, map: null } mapData.map = readSync(read, mapUrl, mapData) mapData.map = parseMapToJSON(mapData.map, mapData) } else { mapData = resolveSourceMapSync(code, codeUrl, read) if (!mapData) { return null } } var result = resolveSourcesSync(mapData.map, mapData.sourcesRelativeTo, read, options) mapData.sourcesResolved = result.sourcesResolved mapData.sourcesContent = result.sourcesContent return mapData } module.exports = { resolveSourceMap: resolveSourceMap, resolveSourceMapSync: resolveSourceMapSync, resolveSources: resolveSources, resolveSourcesSync: resolveSourcesSync, resolve: resolve, resolveSync: resolveSync, parseMapToJSON: parseMapToJSON }