mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 21:22:46 +00:00 
			
		
		
		
	LibWeb: Implement multipart/form-data encoding algorithm
This commit is contained in:
		
							parent
							
								
									69e8216f2c
								
							
						
					
					
						commit
						84722ae2ef
					
				
					 3 changed files with 90 additions and 0 deletions
				
			
		|  | @ -348,6 +348,7 @@ struct PolicyContainer; | ||||||
| class PromiseRejectionEvent; | class PromiseRejectionEvent; | ||||||
| class WorkerDebugConsoleClient; | class WorkerDebugConsoleClient; | ||||||
| struct SandboxingFlagSet; | struct SandboxingFlagSet; | ||||||
|  | struct SerializedFormData; | ||||||
| class Storage; | class Storage; | ||||||
| class SubmitEvent; | class SubmitEvent; | ||||||
| class TextMetrics; | class TextMetrics; | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ | ||||||
|  * SPDX-License-Identifier: BSD-2-Clause |  * SPDX-License-Identifier: BSD-2-Clause | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
|  | #include <AK/GenericLexer.h> | ||||||
| #include <LibWeb/HTML/FormControlInfrastructure.h> | #include <LibWeb/HTML/FormControlInfrastructure.h> | ||||||
| #include <LibWeb/HTML/FormDataEvent.h> | #include <LibWeb/HTML/FormDataEvent.h> | ||||||
| #include <LibWeb/HTML/HTMLButtonElement.h> | #include <LibWeb/HTML/HTMLButtonElement.h> | ||||||
|  | @ -175,4 +176,86 @@ WebIDL::ExceptionOr<Optional<Vector<XHR::FormDataEntry>>> construct_entry_list(J | ||||||
|     return entry_list; |     return entry_list; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#multipart-form-data
 | ||||||
|  | ErrorOr<SerializedFormData> serialize_to_multipart_form_data(Vector<XHR::FormDataEntry> const& entry_list) | ||||||
|  | { | ||||||
|  |     auto normalize_line_breaks = [](StringView value) -> ErrorOr<String> { | ||||||
|  |         StringBuilder builder; | ||||||
|  |         GenericLexer lexer { value }; | ||||||
|  |         while (!lexer.is_eof()) { | ||||||
|  |             TRY(builder.try_append(lexer.consume_until(is_any_of("\r\n"sv)))); | ||||||
|  |             if ((lexer.peek() == '\r' && lexer.peek(1) != '\n') || lexer.peek() == '\n') { | ||||||
|  |                 TRY(builder.try_append("\r\n"sv)); | ||||||
|  |                 lexer.ignore(1); | ||||||
|  |             } else { | ||||||
|  |                 lexer.ignore(2); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return builder.to_string(); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     auto escape_line_feed_carriage_return_double_quote = [](StringView value) -> ErrorOr<String> { | ||||||
|  |         StringBuilder builder; | ||||||
|  |         GenericLexer lexer { value }; | ||||||
|  |         while (!lexer.is_eof()) { | ||||||
|  |             TRY(builder.try_append(lexer.consume_until(is_any_of("\r\n\""sv)))); | ||||||
|  |             switch (lexer.peek()) { | ||||||
|  |             case '\r': | ||||||
|  |                 TRY(builder.try_append("%0D"sv)); | ||||||
|  |                 break; | ||||||
|  |             case '\n': | ||||||
|  |                 TRY(builder.try_append("%0A"sv)); | ||||||
|  |                 break; | ||||||
|  |             case '\"': | ||||||
|  |                 TRY(builder.try_append("%22"sv)); | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |             lexer.ignore(1); | ||||||
|  |         } | ||||||
|  |         return builder.to_string(); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     // The boundary used by the user agent in generating the return value of this algorithm is the multipart/form-data boundary string.
 | ||||||
|  |     auto boundary = TRY(String::formatted("---------------------------{}", get_random<u64>())); | ||||||
|  |     StringBuilder builder; | ||||||
|  |     // 1. For each entry of entry list:
 | ||||||
|  |     for (auto const& entry : entry_list) { | ||||||
|  |         TRY(builder.try_append(TRY(String::formatted("--{}\r\n"sv, boundary)))); | ||||||
|  | 
 | ||||||
|  |         // Replace every occurrence of U+000D (CR) not followed by U+000A (LF), and every occurrence of U+000A (LF) not preceded by U+000D (CR) by a string consisting of a U+000D (CR) and U+000A (LF).
 | ||||||
|  |         auto normalized_name = TRY(normalize_line_breaks(entry.name)); | ||||||
|  |         // For field names replace any 0x0A (LF) bytes with the byte sequence `%0A`, 0x0D (CR) with `%0D` and 0x22 (") with `%22`
 | ||||||
|  |         auto escaped_name = TRY(escape_line_feed_carriage_return_double_quote(normalized_name)); | ||||||
|  | 
 | ||||||
|  |         TRY(entry.value.visit( | ||||||
|  |             [&](JS::Handle<FileAPI::File> const& file) -> ErrorOr<void> { | ||||||
|  |                 // For filenames replace any 0x0A (LF) bytes with the byte sequence `%0A`, 0x0D (CR) with `%0D` and 0x22 (") with `%22`
 | ||||||
|  |                 auto escaped_filename = TRY(escape_line_feed_carriage_return_double_quote(file->name())); | ||||||
|  |                 // Add a `Content-Disposition` header with a `name` set to entry's name and `filename` set to entry's filename.
 | ||||||
|  |                 TRY(builder.try_append(TRY(String::formatted("Content-Disposition: form-data; name=\"{}\"; filename=\"{}\"\r\n"sv, escaped_name, escaped_filename)))); | ||||||
|  |                 // The parts of the generated multipart/form-data resource that correspond to file fields must have a `Content-Type` header specified.
 | ||||||
|  |                 TRY(builder.try_append(TRY(String::formatted("Content-Type: {}\r\n\r\n"sv, file->type())))); | ||||||
|  |                 // FIXME: Serialize the contents of the file.
 | ||||||
|  |                 TRY(builder.try_append(TRY(String::formatted("\r\n"sv)))); | ||||||
|  |                 return {}; | ||||||
|  |             }, | ||||||
|  |             [&](String const& string) -> ErrorOr<void> { | ||||||
|  |                 // Replace every occurrence of U+000D (CR) not followed by U+000A (LF), and every occurrence of U+000A (LF) not preceded by U+000D (CR) by a string consisting of a U+000D (CR) and U+000A (LF).
 | ||||||
|  |                 auto normalized_value = TRY(normalize_line_breaks(string)); | ||||||
|  |                 // Add a `Content-Disposition` header with a `name` set to entry's name.
 | ||||||
|  |                 TRY(builder.try_append(TRY(String::formatted("Content-Disposition: form-data; name=\"{}\"\r\n\r\n"sv, escaped_name)))); | ||||||
|  |                 TRY(builder.try_append(TRY(String::formatted("{}\r\n", normalized_value)))); | ||||||
|  |                 return {}; | ||||||
|  |             })); | ||||||
|  |     } | ||||||
|  |     TRY(builder.try_append(TRY(String::formatted("--{}--\r\n", boundary)))); | ||||||
|  | 
 | ||||||
|  |     // 2. Return the byte sequence resulting from encoding the entry list using the rules described by RFC 7578, Returning Values from Forms: multipart/form-data, given the following conditions: [RFC7578]
 | ||||||
|  |     auto serialized_data = TRY(builder.to_byte_buffer()); | ||||||
|  |     return SerializedFormData { | ||||||
|  |         .boundary = move(boundary), | ||||||
|  |         .serialized_data = move(serialized_data) | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -10,7 +10,13 @@ | ||||||
| 
 | 
 | ||||||
| namespace Web::HTML { | namespace Web::HTML { | ||||||
| 
 | 
 | ||||||
|  | struct SerializedFormData { | ||||||
|  |     String boundary; | ||||||
|  |     ByteBuffer serialized_data; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| WebIDL::ExceptionOr<XHR::FormDataEntry> create_entry(JS::Realm& realm, String const& name, Variant<JS::NonnullGCPtr<FileAPI::Blob>, String> const& value, Optional<String> const& filename = {}); | WebIDL::ExceptionOr<XHR::FormDataEntry> create_entry(JS::Realm& realm, String const& name, Variant<JS::NonnullGCPtr<FileAPI::Blob>, String> const& value, Optional<String> const& filename = {}); | ||||||
| WebIDL::ExceptionOr<Optional<Vector<XHR::FormDataEntry>>> construct_entry_list(JS::Realm&, HTMLFormElement&); | WebIDL::ExceptionOr<Optional<Vector<XHR::FormDataEntry>>> construct_entry_list(JS::Realm&, HTMLFormElement&); | ||||||
|  | ErrorOr<SerializedFormData> serialize_to_multipart_form_data(Vector<XHR::FormDataEntry> const& entry_list); | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Kenneth Myhra
						Kenneth Myhra