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

Commit ff057d4

Browse files
lydellxiaoxiangmoe
andauthoredDec 28, 2019
Fix utf8 support for dataUri base64 (#15)
Co-authored-by: ZHAO Jinxiang <xiaoxiangmoe@gmail.com>
1 parent 858cd9e commit ff057d4

5 files changed

+172
-24
lines changed
 

‎.jshintrc

+2-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"define": false,
4242
"window": false,
4343
"atob": true,
44-
"JSON": false
44+
"JSON": false,
45+
"TextDecoder": true
4546
}
4647
}

‎LICENSE

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
The MIT License (MIT)
22

3-
Copyright (c) 2014, 2015, 2016, 2017 Simon Lydell
3+
Copyright (c) 2014, 2015, 2016, 2017, 2019 Simon Lydell
4+
Copyright (c) 2019 ZHAO Jinxiang
45

56
Permission is hereby granted, free of charge, to any person obtaining a copy
67
of this software and associated documentation files (the "Software"), to deal

‎lib/source-map-resolve-node.js

+50-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
// Copyright 2014, 2015, 2016, 2017 Simon Lydell
1+
// Copyright 2014, 2015, 2016, 2017, 2019 Simon Lydell
2+
// Copyright 2019 ZHAO Jinxiang
23
// X11 (“MIT”) Licensed. (See LICENSE.)
34

45
var sourceMappingURL = require("source-map-url")
@@ -71,8 +72,45 @@ function resolveSourceMapSync(code, codeUrl, read) {
7172
}
7273

7374
var dataUriRegex = /^data:([^,;]*)(;[^,;]*)*(?:,(.*))?$/
75+
76+
/**
77+
* The media type for JSON text is application/json.
78+
*
79+
* {@link https://tools.ietf.org/html/rfc8259#section-11 | IANA Considerations }
80+
*
81+
* `text/json` is non-standard media type
82+
*/
7483
var jsonMimeTypeRegex = /^(?:application|text)\/json$/
7584

85+
/**
86+
* JSON text exchanged between systems that are not part of a closed ecosystem
87+
* MUST be encoded using UTF-8.
88+
*
89+
* {@link https://tools.ietf.org/html/rfc8259#section-8.1 | Character Encoding}
90+
*/
91+
var jsonCharacterEncoding = "utf-8"
92+
93+
function base64ToBuf(b64) {
94+
var binStr = atob(b64)
95+
var len = binStr.length
96+
var arr = new Uint8Array(len)
97+
for (var i = 0; i < len; i++) {
98+
arr[i] = binStr.charCodeAt(i)
99+
}
100+
return arr
101+
}
102+
103+
function decodeBase64String(b64) {
104+
if (typeof TextDecoder === "undefined" || typeof Uint8Array === "undefined") {
105+
return atob(b64)
106+
}
107+
var buf = base64ToBuf(b64);
108+
// Note: `decoder.decode` method will throw a `DOMException` with the
109+
// `"EncodingError"` value when an coding error is found.
110+
var decoder = new TextDecoder(jsonCharacterEncoding, {fatal: true})
111+
return decoder.decode(buf);
112+
}
113+
76114
function resolveSourceMapHelper(code, codeUrl) {
77115
codeUrl = urix(codeUrl)
78116

@@ -83,7 +121,7 @@ function resolveSourceMapHelper(code, codeUrl) {
83121

84122
var dataUri = url.match(dataUriRegex)
85123
if (dataUri) {
86-
var mimeType = dataUri[1]
124+
var mimeType = dataUri[1] || "text/plain"
87125
var lastParameter = dataUri[2] || ""
88126
var encoded = dataUri[3] || ""
89127
var data = {
@@ -93,14 +131,19 @@ function resolveSourceMapHelper(code, codeUrl) {
93131
map: encoded
94132
}
95133
if (!jsonMimeTypeRegex.test(mimeType)) {
96-
var error = new Error("Unuseful data uri mime type: " + (mimeType || "text/plain"))
134+
var error = new Error("Unuseful data uri mime type: " + mimeType)
135+
error.sourceMapData = data
136+
throw error
137+
}
138+
try {
139+
data.map = parseMapToJSON(
140+
lastParameter === ";base64" ? decodeBase64String(encoded) : decodeURIComponent(encoded),
141+
data
142+
)
143+
} catch (error) {
97144
error.sourceMapData = data
98145
throw error
99146
}
100-
data.map = parseMapToJSON(
101-
lastParameter === ";base64" ? atob(encoded) : decodeURIComponent(encoded),
102-
data
103-
)
104147
return data
105148
}
106149

‎source-map-resolve.js

+48-6
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,45 @@ void (function(root, factory) {
7979
}
8080

8181
var dataUriRegex = /^data:([^,;]*)(;[^,;]*)*(?:,(.*))?$/
82+
83+
/**
84+
* The media type for JSON text is application/json.
85+
*
86+
* {@link https://tools.ietf.org/html/rfc8259#section-11 | IANA Considerations }
87+
*
88+
* `text/json` is non-standard media type
89+
*/
8290
var jsonMimeTypeRegex = /^(?:application|text)\/json$/
8391

92+
/**
93+
* JSON text exchanged between systems that are not part of a closed ecosystem
94+
* MUST be encoded using UTF-8.
95+
*
96+
* {@link https://tools.ietf.org/html/rfc8259#section-8.1 | Character Encoding}
97+
*/
98+
var jsonCharacterEncoding = "utf-8"
99+
100+
function base64ToBuf(b64) {
101+
var binStr = atob(b64)
102+
var len = binStr.length
103+
var arr = new Uint8Array(len)
104+
for (var i = 0; i < len; i++) {
105+
arr[i] = binStr.charCodeAt(i)
106+
}
107+
return arr
108+
}
109+
110+
function decodeBase64String(b64) {
111+
if (typeof TextDecoder === "undefined" || typeof Uint8Array === "undefined") {
112+
return atob(b64)
113+
}
114+
var buf = base64ToBuf(b64);
115+
// Note: `decoder.decode` method will throw a `DOMException` with the
116+
// `"EncodingError"` value when an coding error is found.
117+
var decoder = new TextDecoder(jsonCharacterEncoding, {fatal: true})
118+
return decoder.decode(buf);
119+
}
120+
84121
function resolveSourceMapHelper(code, codeUrl) {
85122
var url = sourceMappingURL.getFrom(code)
86123
if (!url) {
@@ -89,7 +126,7 @@ void (function(root, factory) {
89126

90127
var dataUri = url.match(dataUriRegex)
91128
if (dataUri) {
92-
var mimeType = dataUri[1]
129+
var mimeType = dataUri[1] || "text/plain"
93130
var lastParameter = dataUri[2] || ""
94131
var encoded = dataUri[3] || ""
95132
var data = {
@@ -99,14 +136,19 @@ void (function(root, factory) {
99136
map: encoded
100137
}
101138
if (!jsonMimeTypeRegex.test(mimeType)) {
102-
var error = new Error("Unuseful data uri mime type: " + (mimeType || "text/plain"))
139+
var error = new Error("Unuseful data uri mime type: " + mimeType)
140+
error.sourceMapData = data
141+
throw error
142+
}
143+
try {
144+
data.map = parseMapToJSON(
145+
lastParameter === ";base64" ? decodeBase64String(encoded) : decodeURIComponent(encoded),
146+
data
147+
)
148+
} catch (error) {
103149
error.sourceMapData = data
104150
throw error
105151
}
106-
data.map = parseMapToJSON(
107-
lastParameter === ";base64" ? atob(encoded) : decodeURIComponent(encoded),
108-
data
109-
)
110152
return data
111153
}
112154

‎test/source-map-resolve.js

+70-9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
// Copyright 2014, 2015, 2016, 2017 Simon Lydell
1+
// Copyright 2014, 2015, 2016, 2017, 2019 Simon Lydell
2+
// Copyright 2019 ZHAO Jinxiang
23
// X11 (“MIT”) Licensed. (See LICENSE.)
34

45
var test = require("tape")
@@ -61,6 +62,12 @@ var map = {
6162
sources: [],
6263
names: []
6364
},
65+
utf8 : {
66+
mappings: "AAAA",
67+
sources: ["foo.js"],
68+
sourcesContent: ["中文😊"],
69+
names: []
70+
},
6471
empty: {}
6572
}
6673
map.simpleString = JSON.stringify(map.simple)
@@ -75,7 +82,8 @@ var code = {
7582
"%7B%22mappings%22%3A%22AAAA%22%2C%22sources%22%3A%5B%22" +
7683
"foo.js%22%5D%2C%22names%22%3A%5B%5D%7D"),
7784
base64: u("data:application/json;base64," +
78-
"eyJtYXBwaW5ncyI6IkFBQUEiLCJzb3VyY2VzIjpbImZvby5qcyJdLCJuYW1lcyI6W119"),
85+
"eyJtYXBwaW5ncyI6IkFBQUEiLCJzb3VyY2VzIjpbImZvby5qcyJdLCJzb3VyY2VzQ29udGVudCI6WyLkuK3mlofwn5iKIl0sIm5hbWVzIjpbXX0="), // jshint ignore:line
86+
base64InvalidUtf8: u("data:application/json;base64,abc"),
7987
dataUriText: u("data:text/json," +
8088
"%7B%22mappings%22%3A%22AAAA%22%2C%22sources%22%3A%5B%22" +
8189
"foo.js%22%5D%2C%22names%22%3A%5B%5D%7D"),
@@ -85,6 +93,7 @@ var code = {
8593
dataUriNoMime: u("data:,foo"),
8694
dataUriInvalidMime: u("data:text/html,foo"),
8795
dataUriInvalidJSON: u("data:application/json,foo"),
96+
dataUriInvalidCode: u("data:application/json,%"),
8897
dataUriXSSIsafe: u("data:application/json," + ")%5D%7D%27" +
8998
"%7B%22mappings%22%3A%22AAAA%22%2C%22sources%22%3A%5B%22" +
9099
"foo.js%22%5D%2C%22names%22%3A%5B%5D%7D"),
@@ -99,7 +108,7 @@ function testResolveSourceMap(method, sync) {
99108

100109
var codeUrl = "http://example.com/a/b/c/foo.js"
101110

102-
t.plan(1 + 12*3 + 6*4)
111+
t.plan(1 + 12*3 + 8*4)
103112

104113
t.equal(typeof method, "function", "is a function")
105114

@@ -171,14 +180,27 @@ function testResolveSourceMap(method, sync) {
171180
t.error(error)
172181
t.deepEqual(result, {
173182
sourceMappingURL: "data:application/json;base64," +
174-
"eyJtYXBwaW5ncyI6IkFBQUEiLCJzb3VyY2VzIjpbImZvby5qcyJdLCJuYW1lcyI6W119",
183+
"eyJtYXBwaW5ncyI6IkFBQUEiLCJzb3VyY2VzIjpbImZvby5qcyJdLCJzb3VyY2VzQ29udGVudCI6WyLkuK3mlofwn5iKIl0sIm5hbWVzIjpbXX0=", // jshint ignore:line
175184
url: null,
176185
sourcesRelativeTo: codeUrl,
177-
map: map.simple
186+
map: map.utf8
178187
}, "base64")
179188
isAsync()
180189
})
181190

191+
method(code.base64InvalidUtf8, codeUrl, wrap(Throws), function(error, result) {
192+
t.deepEqual(error.sourceMapData, {
193+
sourceMappingURL: "data:application/json;base64,abc",
194+
url: null,
195+
sourcesRelativeTo: codeUrl,
196+
map: "abc"
197+
}, "base64InvalidUtf8 .sourceMapData")
198+
t.ok(error instanceof TypeError && error.message !== "data:application/json;base64,abc",
199+
"base64InvalidUtf8")
200+
t.notOk(result)
201+
isAsync()
202+
})
203+
182204
method(code.dataUriText, codeUrl, wrap(Throws), function(error, result) {
183205
t.error(error)
184206
t.deepEqual(result, {
@@ -242,6 +264,19 @@ function testResolveSourceMap(method, sync) {
242264
isAsync()
243265
})
244266

267+
method(code.dataUriInvalidCode, codeUrl, wrap(Throws), function(error, result) {
268+
t.deepEqual(error.sourceMapData, {
269+
sourceMappingURL: "data:application/json,%",
270+
url: null,
271+
sourcesRelativeTo: codeUrl,
272+
map: "%"
273+
}, "dataUriInvalidCode .sourceMapData")
274+
t.ok(error instanceof URIError && error.message !== "data:application/json,%",
275+
"dataUriInvalidCode")
276+
t.notOk(result)
277+
isAsync()
278+
})
279+
245280
method(code.dataUriXSSIsafe, codeUrl, wrap(Throws), function(error, result) {
246281
t.error(error)
247282
t.deepEqual(result, {
@@ -599,7 +634,7 @@ function testResolve(method, sync) {
599634

600635
var codeUrl = "http://example.com/a/b/c/foo.js"
601636

602-
t.plan(1 + 15*3 + 21*4 + 4)
637+
t.plan(1 + 15*3 + 23*4 + 4)
603638

604639
t.equal(typeof method, "function", "is a function")
605640

@@ -683,16 +718,29 @@ function testResolve(method, sync) {
683718
t.error(error)
684719
t.deepEqual(result, {
685720
sourceMappingURL: "data:application/json;base64," +
686-
"eyJtYXBwaW5ncyI6IkFBQUEiLCJzb3VyY2VzIjpbImZvby5qcyJdLCJuYW1lcyI6W119",
721+
"eyJtYXBwaW5ncyI6IkFBQUEiLCJzb3VyY2VzIjpbImZvby5qcyJdLCJzb3VyY2VzQ29udGVudCI6WyLkuK3mlofwn5iKIl0sIm5hbWVzIjpbXX0=", // jshint ignore:line
687722
url: null,
688723
sourcesRelativeTo: codeUrl,
689-
map: map.simple,
724+
map: map.utf8,
690725
sourcesResolved: ["http://example.com/a/b/c/foo.js"],
691-
sourcesContent: ["http://example.com/a/b/c/foo.js"]
726+
sourcesContent: ["中文😊"]
692727
}, "base64")
693728
isAsync()
694729
})
695730

731+
method(code.base64InvalidUtf8, codeUrl, wrap(Throws), function(error, result) {
732+
t.deepEqual(error.sourceMapData, {
733+
sourceMappingURL: "data:application/json;base64,abc",
734+
url: null,
735+
sourcesRelativeTo: codeUrl,
736+
map: "abc"
737+
}, "base64InvalidUtf8 .sourceMapData")
738+
t.ok(error instanceof TypeError && error.message !== "data:application/json;base64,abc",
739+
"base64InvalidUtf8")
740+
t.notOk(result)
741+
isAsync()
742+
})
743+
696744
method(code.dataUriText, codeUrl, wrapMap(Throws, identity), function(error, result) {
697745
t.error(error)
698746
t.deepEqual(result, {
@@ -760,6 +808,19 @@ function testResolve(method, sync) {
760808
isAsync()
761809
})
762810

811+
method(code.dataUriInvalidCode, codeUrl, wrap(Throws), function(error, result) {
812+
t.deepEqual(error.sourceMapData, {
813+
sourceMappingURL: "data:application/json,%",
814+
url: null,
815+
sourcesRelativeTo: codeUrl,
816+
map: "%"
817+
}, "dataUriInvalidCode .sourceMapData")
818+
t.ok(error instanceof URIError && error.message !== "data:application/json,%",
819+
"dataUriInvalidCode")
820+
t.notOk(result)
821+
isAsync()
822+
})
823+
763824
method(code.dataUriXSSIsafe, codeUrl, wrapMap(Throws, identity), function(error, result) {
764825
t.error(error)
765826
t.deepEqual(result, {

0 commit comments

Comments
 (0)
This repository has been archived.