Browse Source

LibWeb: Include the Content-Type boundary directive in form submissions

The spec requires that "multipart/form-data" Content-Type headers also
include a boundary directive. This allows the content server to validate
the submitted form data.

Google Lens, for example, rejects forms missing this directive.
Timothy Flynn 10 months ago
parent
commit
b16b9709b9

+ 3 - 1
Userland/Libraries/LibWeb/HTML/HTMLFormElement.cpp

@@ -747,6 +747,7 @@ ErrorOr<void> HTMLFormElement::submit_as_entity_body(URL::URL parsed_action, Vec
     // 1. Assert: method is POST.
 
     POSTResource::RequestContentType mime_type {};
+    Vector<POSTResource::Directive> mime_type_directives;
     ByteBuffer body;
 
     // 2. Switch on enctype:
@@ -775,6 +776,7 @@ ErrorOr<void> HTMLFormElement::submit_as_entity_body(URL::URL parsed_action, Vec
         // 2. Let mimeType be the isomorphic encoding of the concatenation of "multipart/form-data; boundary=" and the multipart/form-data
         //    boundary string generated by the multipart/form-data encoding algorithm.
         mime_type = POSTResource::RequestContentType::MultipartFormData;
+        mime_type_directives.empend("boundary"sv, move(body_and_mime_type.boundary));
         break;
     }
     case EncodingTypeAttributeState::PlainText: {
@@ -796,7 +798,7 @@ ErrorOr<void> HTMLFormElement::submit_as_entity_body(URL::URL parsed_action, Vec
     }
 
     // 3. Plan to navigate to parsed action given a POST resource whose request body is body and request content-type is mimeType.
-    plan_to_navigate_to(parsed_action, POSTResource { .request_body = move(body), .request_content_type = mime_type }, target_navigable, history_handling, user_involvement);
+    plan_to_navigate_to(parsed_action, POSTResource { .request_body = move(body), .request_content_type = mime_type, .request_content_type_directives = move(mime_type_directives) }, target_navigable, history_handling, user_involvement);
     return {};
 }
 

+ 15 - 5
Userland/Libraries/LibWeb/HTML/Navigable.cpp

@@ -702,7 +702,7 @@ static WebIDL::ExceptionOr<Navigable::NavigationParamsVariant> create_navigation
     request->set_referrer(entry->document_state()->request_referrer());
 
     // 4. If documentResource is a POST resource, then:
-    if (document_resource.has<POSTResource>()) {
+    if (auto* post_resource = document_resource.get_pointer<POSTResource>()) {
         // 1. Set request's method to `POST`.
         request->set_method(TRY_OR_THROW_OOM(vm, ByteBuffer::copy("POST"sv.bytes())));
 
@@ -710,9 +710,8 @@ static WebIDL::ExceptionOr<Navigable::NavigationParamsVariant> create_navigation
         request->set_body(document_resource.get<POSTResource>().request_body.value());
 
         // 3. Set `Content-Type` to documentResource's request content-type in request's header list.
-        auto request_content_type = document_resource.get<POSTResource>().request_content_type;
-        auto request_content_type_string = [request_content_type]() {
-            switch (request_content_type) {
+        auto request_content_type = [&]() {
+            switch (post_resource->request_content_type) {
             case POSTResource::RequestContentType::ApplicationXWWWFormUrlencoded:
                 return "application/x-www-form-urlencoded"sv;
             case POSTResource::RequestContentType::MultipartFormData:
@@ -723,7 +722,18 @@ static WebIDL::ExceptionOr<Navigable::NavigationParamsVariant> create_navigation
                 VERIFY_NOT_REACHED();
             }
         }();
-        auto header = Fetch::Infrastructure::Header::from_string_pair("Content-Type"sv, request_content_type_string);
+
+        StringBuilder request_content_type_buffer;
+        if (!post_resource->request_content_type_directives.is_empty()) {
+            request_content_type_buffer.append(request_content_type);
+
+            for (auto const& directive : post_resource->request_content_type_directives)
+                request_content_type_buffer.appendff("; {}={}", directive.type, directive.value);
+
+            request_content_type = request_content_type_buffer.string_view();
+        }
+
+        auto header = Fetch::Infrastructure::Header::from_string_pair("Content-Type"sv, request_content_type);
         request->header_list()->append(move(header));
     }
 

+ 9 - 0
Userland/Libraries/LibWeb/HTML/POSTResource.h

@@ -7,6 +7,9 @@
 #pragma once
 
 #include <AK/ByteBuffer.h>
+#include <AK/String.h>
+#include <AK/StringView.h>
+#include <AK/Vector.h>
 
 namespace Web::HTML {
 
@@ -26,6 +29,12 @@ struct POSTResource {
     // https://html.spec.whatwg.org/multipage/browsing-the-web.html#post-resource-request-content-type
     // A request content-type, which is `application/x-www-form-urlencoded`, `multipart/form-data`, or `text/plain`.
     RequestContentType request_content_type {};
+
+    struct Directive {
+        StringView type;
+        String value;
+    };
+    Vector<Directive> request_content_type_directives {};
 };
 
 }