Skip to content

Commit

Permalink
support percent-escaped sourceMappingURL comments
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Mar 7, 2021
1 parent eb2d5f2 commit 9dc94a3
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 19 deletions.
53 changes: 34 additions & 19 deletions internal/bundler/bundler.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import (
"crypto/sha1"
"encoding/base32"
"encoding/base64"
"errors"
"fmt"
"mime"
"net/http"
"net/url"
"sort"
"strings"
"sync"
Expand Down Expand Up @@ -524,27 +526,14 @@ func extractSourceMapFromComment(
comment js_ast.Span,
absResolveDir string,
) (logger.Path, *string) {
// Data URL
// Support data URLs
if strings.HasPrefix(comment.Text, "data:") {
if strings.HasPrefix(comment.Text, "data:application/json;") {
// Scan for the base64 part to support URLs like "data:application/json;charset=utf-8;base64,"
if index := strings.Index(comment.Text, ";base64,"); index != -1 {
n := int32(index + len(";base64,"))
encoded := comment.Text[n:]
decoded, err := base64.StdEncoding.DecodeString(encoded)
if err != nil {
r := logger.Range{Loc: logger.Loc{Start: comment.Range.Loc.Start + n}, Len: comment.Range.Len - n}
log.AddRangeWarning(source, r, "Invalid base64 data in source map")
return logger.Path{}, nil
}
contents := string(decoded)
return logger.Path{Text: source.PrettyPath + ".sourceMappingURL"}, &contents
}
if contents, err := dataFromDataURL(comment.Text); err == nil {
return logger.Path{Text: source.PrettyPath, IgnoredSuffix: "#sourceMappingURL"}, &contents
} else {
log.AddRangeWarning(source, comment.Range, fmt.Sprintf("Unsupported source map comment: %s", err.Error()))
return logger.Path{}, nil
}

// Anything else is unsupported
log.AddRangeWarning(source, comment.Range, "Unsupported source map comment")
return logger.Path{}, nil
}

// Relative path in a file with an absolute path
Expand All @@ -568,6 +557,32 @@ func extractSourceMapFromComment(
return logger.Path{}, nil
}

func dataFromDataURL(dataURL string) (string, error) {
if strings.HasPrefix(dataURL, "data:") {
if comma := strings.IndexByte(dataURL, ','); comma != -1 {
b64 := ";base64,"

// Try to read base64 data
if pos := comma - len(b64) + 1; pos >= 0 && dataURL[pos:pos+len(b64)] == b64 {
bytes, err := base64.StdEncoding.DecodeString(dataURL[comma+1:])
if err != nil {
return "", fmt.Errorf("Could not decode base64 data: %s", err.Error())
}
return string(bytes), nil
}

// Try to read percent-escaped data
content, err := url.QueryUnescape(dataURL[comma+1:])
if err != nil {
return "", fmt.Errorf("Could not decode percent-escaped data: %s", err.Error())
}
return content, nil
}
}

return "", errors.New("Invalid data URL")
}

func sanetizeLocation(res resolver.Resolver, loc *logger.MsgLocation) {
if loc != nil {
if loc.Namespace == "" {
Expand Down
20 changes: 20 additions & 0 deletions scripts/verify-source-map.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,21 @@ console.log("entry");
`,
}

const testCasePartialMappingsPercentEscape = {
// The "mappings" value is "A,Q,I;A,Q,I;A,Q,I;AAMA,QAAQ,IAAI;" which contains
// partial mappings without original locations. This used to throw things off.
'entry.js': `console.log(1);
console.log(2);
console.log(3);
console.log("entry");
//# sourceMappingURL=data:,%7B%22version%22%3A3%2C%22sources%22%3A%5B%22entr` +
`y.js%22%5D%2C%22sourcesContent%22%3A%5B%22console.log(1)%5Cn%5Cnconsole` +
`.log(2)%5Cn%5Cnconsole.log(3)%5Cn%5Cnconsole.log(%5C%22entry%5C%22)%5Cn` +
`%22%5D%2C%22mappings%22%3A%22A%2CQ%2CI%3BA%2CQ%2CI%3BA%2CQ%2CI%3BAAMA%2` +
`CQAAQ%2CIAAI%3B%22%2C%22names%22%3A%5B%5D%7D
`,
}

const toSearchPartialMappings = {
entry: 'entry.js',
}
Expand Down Expand Up @@ -445,6 +460,11 @@ async function main() {
entryPoints: ['entry.js'],
crlf,
}),
check('dummy' + suffix, testCasePartialMappingsPercentEscape, toSearchPartialMappings, {
flags: flags.concat('--outfile=out.js', '--bundle'),
entryPoints: ['entry.js'],
crlf,
}),
check('banner-footer' + suffix, testCaseES6, toSearchBundle, {
flags: flags.concat('--outfile=out.js', '--bundle', '--banner="/* LICENSE abc */"', '--footer="/* end of file banner */"'),
entryPoints: ['a.js'],
Expand Down

0 comments on commit 9dc94a3

Please sign in to comment.