Preserve the order of multipart form parts (#2524)

FormFields and FormFiles were std::multimaps, which sort by field name.
RFC 7578 5.2 says a form processor "SHOULD send back results in order"
and that "Intermediaries MUST NOT reorder the results", so a handler
walking req.form.fields saw the parts alphabetised rather than as they
were sent, and a body received for forwarding could not be reproduced.

Point both at the insertion-ordered container #2523 generalized, with
std::equal_to since field names are case-sensitive. Entries sharing a
name already kept their relative order under std::multimap; what is
recovered here is the order across different names.

Server::read_content() keeps a FormFields::iterator alive across the
content callbacks that fill the part it points at, which is the one
thing this container could have broken: it is vector-backed, so a later
emplace can reallocate and leave an older iterator dangling. The code is
safe because the iterator is reassigned by the same emplace that could
reallocate, and is only read while the flag set alongside it says so.
ContentSurvivesContainerGrowth pins that down with 64 parts, enough to
grow the vector through seven reallocations; reverting the reassignment
makes it abort under ASan rather than fail quietly.

Growth also never copies a part's payload: the parser inserts the entry
with an empty content and appends the body bytes afterwards, and both
mapped types are nothrow-move-constructible, so a reallocation steals
the string buffers rather than deep-copying them.
This commit is contained in:
yhirose authored and GitHub committed 2026-08-02 18:37:20 -04:00
1 parent 7963c382d6
commit 23f67f25c2
2 files changed
+143 -2

No files matched your search

+9 -2
View File
@@ -1362,9 +1362,16 @@ struct FormField {
std::string content;
Headers headers;
};
using FormFields = std::multimap<std::string, FormField>;
// RFC 7578 5.2: a form processor "SHOULD send back results in order" and
// "Intermediaries MUST NOT reorder the results", so a handler walking these
// should see the parts as they were sent. A std::multimap sorts by field name
// and loses that. Field names are case-sensitive, hence std::equal_to rather
// than the case-insensitive predicate Headers uses.
using FormFields =
detail::insertion_ordered_multimap<FormField, std::equal_to<std::string>>;
using FormFiles = std::multimap<std::string, FormData>;
using FormFiles =
detail::insertion_ordered_multimap<FormData, std::equal_to<std::string>>;
struct MultipartFormData {
FormFields fields; // Text fields from multipart