Skip to content
File

Blob: src/workerd/api/data-url.c++

2.4 KB
1#include "data-url.h"
2 
3#include <workerd/api/encoding.h>
4#include <workerd/util/strings.h>
5 
6#include <kj/encoding.h>
7 
8namespace workerd::api {
9 
10kj::Maybe<DataUrl> DataUrl::tryParse(kj::StringPtr url) {
11 KJ_IF_SOME(url, jsg::Url::tryParse(url)) {
12 return from(url);
13 }
14 return kj::none;
15}
16 
17kj::Maybe<DataUrl> DataUrl::from(const jsg::Url& url) {
18 if (url.getProtocol() != "data:"_kj) return kj::none;
19 auto clone = url.clone(jsg::Url::EquivalenceOption::IGNORE_FRAGMENTS);
20 
21 // Remove the "data:" prefix.
22 auto href = clone.getHref().slice(5);
23 
24 // We scan for the first comma, which separates the MIME type from the data.
25 // Per the fetch spec, it doesn't matter if the comma is within a quoted
26 // string value in the MIME type... which is fun.
27 
28 static const auto isBase64 = [](kj::ArrayPtr<const char> label) -> bool {
29 KJ_IF_SOME(pos, label.findLast(';')) {
30 auto res = trimLeadingAndTrailingWhitespace(label.slice(pos + 1));
31 return res.size() == 6 && (res[0] | 0x20) == 'b' && (res[1] | 0x20) == 'a' &&
32 (res[2] | 0x20) == 's' && (res[3] | 0x20) == 'e' && (res[4] == '6') && (res[5] == '4');
33 }
34 return false;
35 };
36 
37 static const auto create = [](auto input, auto decoded) {
38 KJ_IF_SOME(parsed, MimeType::tryParse(input)) {
39 return DataUrl(kj::mv(parsed), kj::mv(decoded));
40 } else {
41 return DataUrl(MimeType::PLAINTEXT_ASCII.clone(), kj::mv(decoded));
42 }
43 };
44 
45 KJ_IF_SOME(pos, href.findFirst(',')) {
46 auto unparsed = href.first(pos);
47 auto data = href.slice(pos + 1);
48 
49 // We need to trim leading and trailing whitespace from the mimetype
50 unparsed = trimLeadingAndTrailingWhitespace(unparsed);
51 
52 // Determine if the data is base64 encoded
53 kj::Array<kj::byte> decoded = nullptr;
54 if (isBase64(unparsed)) {
55 unparsed = unparsed.first(KJ_ASSERT_NONNULL(unparsed.findLast(';')));
56 decoded =
57 kj::decodeBase64(stripInnerWhitespace(jsg::Url::percentDecode(data.asBytes())).asChars());
58 } else {
59 decoded = jsg::Url::percentDecode(data.asBytes());
60 }
61 
62 if (unparsed.startsWith(";"_kj)) {
63 // If the mime type starts with ;, then the spec tells us to
64 // prepend "text/plain" to the mime type.
65 auto fixed = kj::str("text/plain", unparsed);
66 return create(fixed.asPtr(), kj::mv(decoded));
67 }
68 
69 return create(unparsed, kj::mv(decoded));
70 }
71 
72 // If there are no commas, the data: url is invalid.
73 return kj::none;
74}
75 
76} // namespace workerd::api