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 11 tháng trước cách đây
mục cha
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 {};
 };
 
 }