diff --git a/builtins/web/blob.cpp b/builtins/web/blob.cpp index ec604484..344bd7b8 100644 --- a/builtins/web/blob.cpp +++ b/builtins/web/blob.cpp @@ -26,9 +26,56 @@ template bool validate_type(T *chars, size_t strlen) { return true; } + +// https://w3c.github.io/FileAPI/#convert-line-endings-to-native +std::string convert_line_endings_to_native(std::string_view s) { + std::string native_line_ending = "\n"; +#ifdef _WIN32 + native_line_ending = "\r\n"; +#endif + + std::string result; + result.reserve(s.size()); + + size_t i = 0; + while (i < s.size()) { + switch (s[i]) { + case '\r': { + if (i + 1 < s.size() && s[i + 1] == '\n') { + result.append(native_line_ending); + i += 2; + } else { + result.append(native_line_ending); + i += 1; + } + break; + } + case '\n': { + result.append(native_line_ending); + i += 1; + break; + } + default: { + result.push_back(s[i]); + i += 1; + break; + } + } + } + + return result; +} + +} // anonymous namespace + + + +namespace builtins::web::blob { + +// https://w3c.github.io/FileAPI/#dfn-type // 1. If type contains any characters outside the range U+0020 to U+007E, then set t to the empty string. // 2. Convert every character in type to ASCII lowercase. -JSString *normalize_type(JSContext *cx, HandleValue value) { +JSString *Blob::normalize_type(JSContext *cx, JS::HandleValue value) { JS::RootedString value_str(cx); if (value.isObject() || value.isString()) { @@ -60,15 +107,13 @@ JSString *normalize_type(JSContext *cx, HandleValue value) { if (!validate_type(chars, strlen)) { return JS_GetEmptyString(cx); } - normalized = std::string(reinterpret_cast(chars), strlen); } else { JS::AutoCheckCannotGC nogc(cx); - const auto *chars = (JS::GetTwoByteLinearStringChars(nogc, str)); + const auto *chars = JS::GetTwoByteLinearStringChars(nogc, str); if (!validate_type(chars, strlen)) { return JS_GetEmptyString(cx); } - normalized.reserve(strlen); for (size_t i = 0; i < strlen; ++i) { normalized += static_cast(chars[i]); @@ -81,51 +126,6 @@ JSString *normalize_type(JSContext *cx, HandleValue value) { return JS_NewStringCopyN(cx, normalized.c_str(), normalized.length()); } -// https://w3c.github.io/FileAPI/#convert-line-endings-to-native -std::string convert_line_endings_to_native(std::string_view s) { - std::string native_line_ending = "\n"; -#ifdef _WIN32 - native_line_ending = "\r\n"; -#endif - - std::string result; - result.reserve(s.size()); - - size_t i = 0; - while (i < s.size()) { - switch (s[i]) { - case '\r': { - if (i + 1 < s.size() && s[i + 1] == '\n') { - result.append(native_line_ending); - i += 2; - } else { - result.append(native_line_ending); - i += 1; - } - break; - } - case '\n': { - result.append(native_line_ending); - i += 1; - break; - } - default: { - result.push_back(s[i]); - i += 1; - break; - } - } - } - - return result; -} - -} // anonymous namespace - - - -namespace builtins::web::blob { - using js::Vector; using streams::BufReader; using streams::NativeStreamSource; @@ -285,7 +285,7 @@ bool Blob::slice(JSContext *cx, HandleObject self, const CallArgs &args, Mutable if (args.hasDefined(2)) { HandleValue contentType_val = args.get(2); - if ((contentType = normalize_type(cx, contentType_val)) == nullptr) { + if ((contentType = Blob::normalize_type(cx, contentType_val)) == nullptr) { return false; } } @@ -547,7 +547,7 @@ bool Blob::init_options(JSContext *cx, HandleObject self, HandleValue initv) { return false; } - auto *type_str = normalize_type(cx, type); + auto *type_str = Blob::normalize_type(cx, type); if (!type_str) { return false; } diff --git a/builtins/web/blob.h b/builtins/web/blob.h index ab166978..f098e53c 100644 --- a/builtins/web/blob.h +++ b/builtins/web/blob.h @@ -54,6 +54,7 @@ class Blob : public BuiltinImpl { static bool read_blob_slice(JSContext *cx, HandleObject self, std::span buf, size_t start, size_t *read, bool *done); + static JSString *normalize_type(JSContext *cx, JS::HandleValue value); static JSObject *create(JSContext *cx, UniqueChars data, size_t data_len, HandleString type); static bool init_class(JSContext *cx, HandleObject global); diff --git a/builtins/web/fetch/request-response.cpp b/builtins/web/fetch/request-response.cpp index 965f4c47..84ce3c5c 100644 --- a/builtins/web/fetch/request-response.cpp +++ b/builtins/web/fetch/request-response.cpp @@ -611,8 +611,32 @@ bool RequestOrResponse::parse_body(JSContext *cx, JS::HandleObject self, JS::Uni result.setObject(*array_buffer); } else if constexpr (result_type == RequestOrResponse::BodyReadResult::Blob) { JS::RootedString contentType(cx, JS_GetEmptyString(cx)); - JS::RootedObject blob(cx, blob::Blob::create(cx, std::move(buf), len, contentType)); + RootedObject headers(cx, RequestOrResponse::headers(cx, self)); + if (!headers) { + return RejectPromiseWithPendingError(cx, result_promise); + } + auto content_type_key = host_api::HostString("Content-Type"); + auto idx = Headers::lookup(cx, headers, content_type_key); + if (idx) { + auto *values = Headers::get_index(cx, headers, idx.value()); + auto maybe_mime = extract_mime_type(std::get<1>(*values)); + if (!maybe_mime.isErr()) { + auto mime_str = maybe_mime.unwrap().to_string(); + JS::RootedString mime_js_str(cx, JS_NewStringCopyN(cx, mime_str.c_str(), mime_str.size())); + if (!mime_js_str) { + return RejectPromiseWithPendingError(cx, result_promise); + } + JS::RootedValue mime_val(cx, JS::StringValue(mime_js_str)); + auto *normalized = blob::Blob::normalize_type(cx, mime_val); + if (!normalized) { + return RejectPromiseWithPendingError(cx, result_promise); + } + contentType = normalized; + } + } + + JS::RootedObject blob(cx, blob::Blob::create(cx, std::move(buf), len, contentType)); if (!blob) { return RejectPromiseWithPendingError(cx, result_promise); } diff --git a/tests/wpt-harness/expectations/fetch/api/body/formdata.any.js.json b/tests/wpt-harness/expectations/fetch/api/body/formdata.any.js.json index 01366d9d..75f213e3 100644 --- a/tests/wpt-harness/expectations/fetch/api/body/formdata.any.js.json +++ b/tests/wpt-harness/expectations/fetch/api/body/formdata.any.js.json @@ -6,6 +6,6 @@ "status": "PASS" }, "Consume multipart/form-data headers case-insensitively": { - "status": "FAIL" + "status": "PASS" } } \ No newline at end of file