Skip to content
This repository has been archived by the owner on Nov 4, 2023. It is now read-only.

Fix utf8 support for dataUri base64 #15

Merged
merged 6 commits into from
Dec 28, 2019
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .jshintrc
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"define": false,
"window": false,
"atob": true,
"JSON": false
"JSON": false,
"TextDecoder": true
}
}
43 changes: 40 additions & 3 deletions lib/source-map-resolve-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,45 @@ function resolveSourceMapSync(code, codeUrl, read) {
}

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)
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the warning because I’m worried it would spam the console. I don’t think many people will end up here though.

}
var buf = base64ToBuf(b64);
// Note: `decoder.decode` method will throw a `DOMException` with the
// `"EncodingError"` value when an coding error is found.
lydell marked this conversation as resolved.
Show resolved Hide resolved
var decoder = new TextDecoder(jsonCharacterEncoding, {fatal: true})
return decoder.decode(buf);
}

function resolveSourceMapHelper(code, codeUrl) {
codeUrl = urix(codeUrl)

Expand All @@ -83,7 +120,7 @@ function resolveSourceMapHelper(code, codeUrl) {

var dataUri = url.match(dataUriRegex)
if (dataUri) {
var mimeType = dataUri[1]
var mimeType = dataUri[1] || "text/plain"
var lastParameter = dataUri[2] || ""
var encoded = dataUri[3] || ""
var data = {
Expand All @@ -93,12 +130,12 @@ function resolveSourceMapHelper(code, codeUrl) {
map: encoded
}
if (!jsonMimeTypeRegex.test(mimeType)) {
var error = new Error("Unuseful data uri mime type: " + (mimeType || "text/plain"))
var error = new Error("Unuseful data uri mime type: " + mimeType)
error.sourceMapData = data
throw error
}
data.map = parseMapToJSON(
lastParameter === ";base64" ? atob(encoded) : decodeURIComponent(encoded),
lastParameter === ";base64" ? decodeBase64String(encoded) : decodeURIComponent(encoded),
data
)
return data
Expand Down
43 changes: 40 additions & 3 deletions source-map-resolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,45 @@ void (function(root, factory) {
}

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) {
var url = sourceMappingURL.getFrom(code)
if (!url) {
Expand All @@ -89,7 +126,7 @@ void (function(root, factory) {

var dataUri = url.match(dataUriRegex)
if (dataUri) {
var mimeType = dataUri[1]
var mimeType = dataUri[1] || "text/plain"
var lastParameter = dataUri[2] || ""
var encoded = dataUri[3] || ""
var data = {
Expand All @@ -99,12 +136,12 @@ void (function(root, factory) {
map: encoded
}
if (!jsonMimeTypeRegex.test(mimeType)) {
var error = new Error("Unuseful data uri mime type: " + (mimeType || "text/plain"))
var error = new Error("Unuseful data uri mime type: " + mimeType)
error.sourceMapData = data
throw error
}
data.map = parseMapToJSON(
lastParameter === ";base64" ? atob(encoded) : decodeURIComponent(encoded),
lastParameter === ";base64" ? decodeBase64String(encoded) : decodeURIComponent(encoded),
data
)
return data
Expand Down
18 changes: 12 additions & 6 deletions test/source-map-resolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ var map = {
sources: [],
names: []
},
utf8 : {
mappings: "AAAA",
sources: ["foo.js"],
sourcesContent: ["中文😊"],
names: []
},
empty: {}
}
map.simpleString = JSON.stringify(map.simple)
Expand All @@ -75,7 +81,7 @@ var code = {
"%7B%22mappings%22%3A%22AAAA%22%2C%22sources%22%3A%5B%22" +
"foo.js%22%5D%2C%22names%22%3A%5B%5D%7D"),
base64: u("data:application/json;base64," +
"eyJtYXBwaW5ncyI6IkFBQUEiLCJzb3VyY2VzIjpbImZvby5qcyJdLCJuYW1lcyI6W119"),
"eyJtYXBwaW5ncyI6IkFBQUEiLCJzb3VyY2VzIjpbImZvby5qcyJdLCJzb3VyY2VzQ29udGVudCI6WyLkuK3mlofwn5iKIl0sIm5hbWVzIjpbXX0="), // jshint ignore:line
dataUriText: u("data:text/json," +
"%7B%22mappings%22%3A%22AAAA%22%2C%22sources%22%3A%5B%22" +
"foo.js%22%5D%2C%22names%22%3A%5B%5D%7D"),
Expand Down Expand Up @@ -171,10 +177,10 @@ function testResolveSourceMap(method, sync) {
t.error(error)
t.deepEqual(result, {
sourceMappingURL: "data:application/json;base64," +
"eyJtYXBwaW5ncyI6IkFBQUEiLCJzb3VyY2VzIjpbImZvby5qcyJdLCJuYW1lcyI6W119",
"eyJtYXBwaW5ncyI6IkFBQUEiLCJzb3VyY2VzIjpbImZvby5qcyJdLCJzb3VyY2VzQ29udGVudCI6WyLkuK3mlofwn5iKIl0sIm5hbWVzIjpbXX0=", // jshint ignore:line
url: null,
sourcesRelativeTo: codeUrl,
map: map.simple
map: map.utf8
}, "base64")
isAsync()
})
Expand Down Expand Up @@ -683,12 +689,12 @@ function testResolve(method, sync) {
t.error(error)
t.deepEqual(result, {
sourceMappingURL: "data:application/json;base64," +
"eyJtYXBwaW5ncyI6IkFBQUEiLCJzb3VyY2VzIjpbImZvby5qcyJdLCJuYW1lcyI6W119",
"eyJtYXBwaW5ncyI6IkFBQUEiLCJzb3VyY2VzIjpbImZvby5qcyJdLCJzb3VyY2VzQ29udGVudCI6WyLkuK3mlofwn5iKIl0sIm5hbWVzIjpbXX0=", // jshint ignore:line
url: null,
sourcesRelativeTo: codeUrl,
map: map.simple,
map: map.utf8,
sourcesResolved: ["http://example.com/a/b/c/foo.js"],
sourcesContent: ["http://example.com/a/b/c/foo.js"]
sourcesContent: ["中文😊"]
}, "base64")
isAsync()
})
Expand Down