Bug 1686765 - Update newline normalization in form payloads. r=smaug

This commit also changes the way escapes work in multipart/form-data names and filenames.

Differential Revision: https://phabricator.services.mozilla.com/D108000
This commit is contained in:
Andreu Botella 2021-05-24 10:51:59 +00:00
Родитель 1af3773c48
Коммит b1453df956
8 изменённых файлов: 90 добавлений и 142 удалений

Просмотреть файл

@ -320,18 +320,11 @@ nsresult FSURLEncoded::GetEncodedSubmission(nsIURI* aURI,
// i18n helper routines
nsresult FSURLEncoded::URLEncode(const nsAString& aStr, nsACString& aEncoded) {
// convert to CRLF breaks
int32_t convertedBufLength = 0;
char16_t* convertedBuf = nsLinebreakConverter::ConvertUnicharLineBreaks(
aStr.BeginReading(), nsLinebreakConverter::eLinebreakAny,
nsLinebreakConverter::eLinebreakNet, aStr.Length(), &convertedBufLength);
NS_ENSURE_TRUE(convertedBuf, NS_ERROR_OUT_OF_MEMORY);
nsAutoString convertedString;
convertedString.Adopt(convertedBuf, convertedBufLength);
nsAutoCString encodedBuf;
nsresult rv = EncodeVal(convertedString, encodedBuf, false);
// We encode with eValueEncode because the urlencoded format needs the newline
// normalizations but percent-escapes characters that eNameEncode doesn't,
// so calling NS_Escape would still be needed.
nsresult rv = EncodeVal(aStr, encodedBuf, EncodeType::eValueEncode);
NS_ENSURE_SUCCESS(rv, rv);
if (NS_WARN_IF(!NS_Escape(encodedBuf, aEncoded, url_XPAlphas))) {
@ -382,30 +375,19 @@ nsIInputStream* FSMultipartFormData::GetSubmissionBody(
nsresult FSMultipartFormData::AddNameValuePair(const nsAString& aName,
const nsAString& aValue) {
nsCString valueStr;
nsAutoCString encodedVal;
nsresult rv = EncodeVal(aValue, encodedVal, false);
nsresult rv = EncodeVal(aValue, encodedVal, EncodeType::eValueEncode);
NS_ENSURE_SUCCESS(rv, rv);
int32_t convertedBufLength = 0;
char* convertedBuf = nsLinebreakConverter::ConvertLineBreaks(
encodedVal.get(), nsLinebreakConverter::eLinebreakAny,
nsLinebreakConverter::eLinebreakNet, encodedVal.Length(),
&convertedBufLength);
valueStr.Adopt(convertedBuf, convertedBufLength);
nsAutoCString nameStr;
rv = EncodeVal(aName, nameStr, true);
rv = EncodeVal(aName, nameStr, EncodeType::eNameEncode);
NS_ENSURE_SUCCESS(rv, rv);
// Make MIME block for name/value pair
// XXX: name parameter should be encoded per RFC 2231
// RFC 2388 specifies that RFC 2047 be used, but I think it's not
// consistent with MIME standard.
mPostDataChunk += "--"_ns + mBoundary + nsLiteralCString(CRLF) +
"Content-Disposition: form-data; name=\""_ns + nameStr +
nsLiteralCString("\"" CRLF CRLF) + valueStr +
nsLiteralCString("\"" CRLF CRLF) + encodedVal +
nsLiteralCString(CRLF);
return NS_OK;
@ -417,7 +399,7 @@ nsresult FSMultipartFormData::AddNameBlobPair(const nsAString& aName,
// Encode the control name
nsAutoCString nameStr;
nsresult rv = EncodeVal(aName, nameStr, true);
nsresult rv = EncodeVal(aName, nameStr, EncodeType::eNameEncode);
NS_ENSURE_SUCCESS(rv, rv);
ErrorResult error;
@ -442,7 +424,7 @@ nsresult FSMultipartFormData::AddNameBlobPair(const nsAString& aName,
}
}
rv = EncodeVal(filename16, filename, true);
rv = EncodeVal(filename16, filename, EncodeType::eFilenameEncode);
NS_ENSURE_SUCCESS(rv, rv);
// Get content type
@ -495,7 +477,7 @@ nsresult FSMultipartFormData::AddNameDirectoryPair(const nsAString& aName,
// Encode the control name
nsAutoCString nameStr;
nsresult rv = EncodeVal(aName, nameStr, true);
nsresult rv = EncodeVal(aName, nameStr, EncodeType::eNameEncode);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString dirname;
@ -514,7 +496,7 @@ nsresult FSMultipartFormData::AddNameDirectoryPair(const nsAString& aName,
RetrieveDirectoryName(aDirectory, dirname16);
}
rv = EncodeVal(dirname16, dirname, true);
rv = EncodeVal(dirname16, dirname, EncodeType::eFilenameEncode);
NS_ENSURE_SUCCESS(rv, rv);
AddDataChunk(nameStr, dirname, "application/octet-stream"_ns, nullptr, 0);
@ -531,9 +513,6 @@ void FSMultipartFormData::AddDataChunk(const nsACString& aName,
//
// more appropriate than always using binary?
mPostDataChunk += "--"_ns + mBoundary + nsLiteralCString(CRLF);
// XXX: name/filename parameter should be encoded per RFC 2231
// RFC 2388 specifies that RFC 2047 be used, but I think it's not
// consistent with the MIME standard.
mPostDataChunk += "Content-Disposition: form-data; name=\""_ns + aName +
"\"; filename=\""_ns + aFilename +
nsLiteralCString("\"" CRLF) + "Content-Type: "_ns +
@ -675,20 +654,13 @@ nsresult FSTextPlain::GetEncodedSubmission(nsIURI* aURI,
rv = NS_MutateURI(aURI).SetPathQueryRef(path).Finalize(aOutURI);
} else {
// Create data stream.
// We do want to send the data through the charset encoder and we want to
// normalize linebreaks to use the "standard net" format (\r\n), but we
// don't want to perform any other encoding. This means that names and
// values which contains '=' or newlines are potentially ambigiously
// encoded, but that how text/plain is specced.
// We use eValueEncode to send the data through the charset encoder and to
// normalize linebreaks to use the "standard net" format (\r\n), but not
// perform any other escaping. This means that names and values which
// contain '=' or newlines are potentially ambiguously encoded, but that is
// how text/plain is specced.
nsCString cbody;
EncodeVal(mBody, cbody, false);
int32_t convertedBufLength = 0;
char* convertedBuf = nsLinebreakConverter::ConvertLineBreaks(
cbody.get(), nsLinebreakConverter::eLinebreakAny,
nsLinebreakConverter::eLinebreakNet, cbody.Length(),
&convertedBufLength);
cbody.Adopt(convertedBuf, convertedBufLength);
EncodeVal(mBody, cbody, EncodeType::eValueEncode);
nsCOMPtr<nsIInputStream> bodyStream;
rv = NS_NewCStringInputStream(getter_AddRefs(bodyStream), std::move(cbody));
@ -743,7 +715,7 @@ EncodingFormSubmission::~EncodingFormSubmission() = default;
// i18n helper routines
nsresult EncodingFormSubmission::EncodeVal(const nsAString& aStr,
nsCString& aOut,
bool aHeaderEncode) {
EncodeType aEncodeType) {
nsresult rv;
const Encoding* ignored;
Tie(rv, ignored) = mEncoding->Encode(aStr, aOut);
@ -751,14 +723,32 @@ nsresult EncodingFormSubmission::EncodeVal(const nsAString& aStr,
return rv;
}
if (aHeaderEncode) {
if (aEncodeType != EncodeType::eFilenameEncode) {
// Normalize newlines
int32_t convertedBufLength = 0;
char* convertedBuf = nsLinebreakConverter::ConvertLineBreaks(
aOut.get(), nsLinebreakConverter::eLinebreakAny,
nsLinebreakConverter::eLinebreakSpace, aOut.Length(),
nsLinebreakConverter::eLinebreakNet, (int32_t)aOut.Length(),
&convertedBufLength);
aOut.Adopt(convertedBuf, convertedBufLength);
aOut.ReplaceSubstring("\""_ns, "\\\""_ns);
}
if (aEncodeType != EncodeType::eValueEncode) {
// Percent-escape LF, CR and double quotes.
int32_t offset = 0;
while ((offset = aOut.FindCharInSet("\n\r\"", offset)) != kNotFound) {
if (aOut[offset] == '\n') {
aOut.ReplaceLiteral(offset, 1, "%0A");
} else if (aOut[offset] == '\r') {
aOut.ReplaceLiteral(offset, 1, "%0D");
} else if (aOut[offset] == '"') {
aOut.ReplaceLiteral(offset, 1, "%22");
} else {
MOZ_ASSERT(false);
offset++;
continue;
}
}
}
return NS_OK;

Просмотреть файл

@ -147,17 +147,30 @@ class EncodingFormSubmission : public HTMLFormSubmission {
virtual ~EncodingFormSubmission();
// Indicates the type of newline normalization and escaping to perform in
// `EncodeVal`, in addition to encoding the string into bytes.
enum EncodeType {
// Normalizes newlines to CRLF and then escapes for use in
// `Content-Disposition`. (Useful for `multipart/form-data` entry names.)
eNameEncode,
// Escapes for use in `Content-Disposition`. (Useful for
// `multipart/form-data` filenames.)
eFilenameEncode,
// Normalizes newlines to CRLF.
eValueEncode,
};
/**
* Encode a Unicode string to bytes using the encoder (or just copy the input
* if there is no encoder).
* Encode a Unicode string to bytes, additionally performing escapes or
* normalizations.
* @param aStr the string to encode
* @param aResult the encoded string [OUT]
* @param aHeaderEncode If true, turns all linebreaks into spaces and escapes
* all quotes
* @param aOut the encoded string [OUT]
* @param aEncodeType The type of escapes or normalizations to perform on the
* encoded string.
* @throws an error if UnicodeToNewBytes fails
*/
nsresult EncodeVal(const nsAString& aStr, nsCString& aResult,
bool aHeaderEncode);
nsresult EncodeVal(const nsAString& aStr, nsCString& aOut,
EncodeType aEncodeType);
};
class DialogFormSubmission final : public HTMLFormSubmission {

Просмотреть файл

@ -609,11 +609,14 @@ function checkMPSubmission(sub, expected, test) {
for (x in o) ++l;
return l;
}
function mpquote(s) {
return s.replace(/\r\n/g, " ")
.replace(/\r/g, " ")
.replace(/\n/g, " ")
.replace(/\"/g, "\\\"");
function mpquote_name(s) {
return s.replace(/\r?\n|\r/g, "%0D%0A")
.replace(/\"/g, "%22");
}
function mpquote_filename(s) {
return s.replace(/\r/g, "%0D")
.replace(/\n/g, "%0A")
.replace(/\"/g, "%22");
}
is(sub.length, expected.length,
@ -627,7 +630,7 @@ function checkMPSubmission(sub, expected, test) {
for (i = 0; i < expected.length; ++i) {
if (!("fileName" in expected[i])) {
is(sub[i].headers["Content-Disposition"],
"form-data; name=\"" + mpquote(expected[i].name) + "\"",
"form-data; name=\"" + mpquote_name(expected[i].name) + "\"",
"Correct name in " + test);
is (getPropCount(sub[i].headers), 1,
"Wrong number of headers in " + test);
@ -637,8 +640,8 @@ function checkMPSubmission(sub, expected, test) {
}
else {
is(sub[i].headers["Content-Disposition"],
"form-data; name=\"" + mpquote(expected[i].name) + "\"; filename=\"" +
mpquote(expected[i].fileName) + "\"",
"form-data; name=\"" + mpquote_name(expected[i].name) + "\"; filename=\"" +
mpquote_filename(expected[i].fileName) + "\"",
"Correct name in " + test);
is(sub[i].headers["Content-Type"],
expected[i].contentType,

Просмотреть файл

@ -2,18 +2,6 @@
expected:
if (processor == "x86") and (os == "win") and debug: ["OK", "TIMEOUT"]
if (processor == "x86") and (os == "win") and not debug: ["OK", "TIMEOUT"]
[Upload file-for-upload-in-form-LF-[\n\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-LF-CR-[\n\r\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-CR-[\r\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-CR-LF-[\r\n\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-ESC-[\x1b\].txt (ASCII) in UTF-8 form]
expected:
if (processor == "x86") and (os == "win"): ["PASS", "TIMEOUT"]

Просмотреть файл

@ -1,9 +1,3 @@
[send-file-form-punctuation.html]
expected:
if (os == "linux") and not webrender and not debug and not fission and (processor == "x86_64"): ["OK", "TIMEOUT"]
[Upload file-for-upload-in-form-QUOTATION-MARK-["\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload "file-for-upload-in-form-double-quoted.txt" (ASCII) in UTF-8 form]
expected: FAIL

Просмотреть файл

@ -1,13 +0,0 @@
[send-file-formdata-controls.html]
[Upload file-for-upload-in-form-LF-[\n\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-LF-CR-[\n\r\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-CR-[\r\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-CR-LF-[\r\n\].txt (ASCII) in fetch with FormData]
expected: FAIL

Просмотреть файл

@ -1,7 +0,0 @@
[send-file-formdata-punctuation.html]
[Upload file-for-upload-in-form-QUOTATION-MARK-["\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload "file-for-upload-in-form-double-quoted.txt" (ASCII) in fetch with FormData]
expected: FAIL

Просмотреть файл

@ -7,43 +7,35 @@
[multipart/form-data: \\n in name (normal form)]
expected:
if (processor == "x86") and not debug: ["FAIL", "NOTRUN"]
FAIL
if (processor == "x86") and not debug: ["PASS", "NOTRUN"]
[multipart/form-data: \\n in name (formdata event)]
expected:
if (processor == "x86") and not debug: ["FAIL", "TIMEOUT", "NOTRUN"]
FAIL
if (processor == "x86") and not debug: ["PASS", "TIMEOUT", "NOTRUN"]
[multipart/form-data: \\r in name (normal form)]
expected:
if (processor == "x86") and not debug: ["FAIL", "NOTRUN"]
FAIL
if (processor == "x86") and not debug: ["PASS", "NOTRUN"]
[multipart/form-data: \\r in name (formdata event)]
expected:
if (processor == "x86") and not debug: ["FAIL", "NOTRUN"]
FAIL
if (processor == "x86") and not debug: ["PASS", "NOTRUN"]
[multipart/form-data: \\r\\n in name (normal form)]
expected:
if (processor == "x86") and not debug: ["FAIL", "NOTRUN"]
FAIL
if (processor == "x86") and not debug: ["PASS", "NOTRUN"]
[multipart/form-data: \\r\\n in name (formdata event)]
expected:
if (processor == "x86") and not debug: ["FAIL", "TIMEOUT", "NOTRUN"]
FAIL
if (processor == "x86") and not debug: ["PASS", "TIMEOUT", "NOTRUN"]
[multipart/form-data: \\n\\r in name (normal form)]
expected:
if (processor == "x86") and not debug: ["FAIL", "NOTRUN"]
FAIL
if (processor == "x86") and not debug: ["PASS", "NOTRUN"]
[multipart/form-data: \\n\\r in name (formdata event)]
expected:
if (processor == "x86") and not debug: ["FAIL", "NOTRUN"]
FAIL
if (processor == "x86") and not debug: ["PASS", "NOTRUN"]
[multipart/form-data: \\n in value (normal form)]
expected:
@ -79,53 +71,43 @@
[multipart/form-data: \\n in filename (normal form)]
expected:
if (processor == "x86") and not debug: ["FAIL", "NOTRUN"]
FAIL
if (processor == "x86") and not debug: ["PASS", "NOTRUN"]
[multipart/form-data: \\n in filename (formdata event)]
expected:
if (processor == "x86") and not debug: ["FAIL", "NOTRUN"]
FAIL
if (processor == "x86") and not debug: ["PASS", "NOTRUN"]
[multipart/form-data: \\r in filename (normal form)]
expected:
if (processor == "x86") and not debug: ["FAIL", "NOTRUN"]
FAIL
if (processor == "x86") and not debug: ["PASS", "NOTRUN"]
[multipart/form-data: \\r in filename (formdata event)]
expected:
if (processor == "x86") and not debug: ["FAIL", "NOTRUN"]
FAIL
if (processor == "x86") and not debug: ["PASS", "NOTRUN"]
[multipart/form-data: \\r\\n in filename (normal form)]
expected:
if (processor == "x86") and not debug: ["FAIL", "NOTRUN"]
FAIL
if (processor == "x86") and not debug: ["PASS", "NOTRUN"]
[multipart/form-data: \\r\\n in filename (formdata event)]
expected:
if (processor == "x86") and not debug: ["FAIL", "NOTRUN"]
FAIL
if (processor == "x86") and not debug: ["PASS", "NOTRUN"]
[multipart/form-data: \\n\\r in filename (normal form)]
expected:
if (processor == "x86") and not debug: ["FAIL", "NOTRUN"]
FAIL
if (processor == "x86") and not debug: ["PASS", "NOTRUN"]
[multipart/form-data: \\n\\r in filename (formdata event)]
expected:
if (processor == "x86") and not debug: ["FAIL", "NOTRUN"]
FAIL
if (processor == "x86") and not debug: ["PASS", "NOTRUN"]
[multipart/form-data: double quote in name (normal form)]
expected:
if (processor == "x86") and not debug: ["FAIL", "NOTRUN"]
FAIL
if (processor == "x86") and not debug: ["PASS", "NOTRUN"]
[multipart/form-data: double quote in name (formdata event)]
expected:
if (processor == "x86") and not debug: ["FAIL", "NOTRUN"]
FAIL
if (processor == "x86") and not debug: ["PASS", "NOTRUN"]
[multipart/form-data: double quote in value (normal form)]
expected:
@ -137,13 +119,11 @@
[multipart/form-data: double quote in filename (normal form)]
expected:
if (processor == "x86") and not debug: ["FAIL", "NOTRUN"]
FAIL
if (processor == "x86") and not debug: ["PASS", "NOTRUN"]
[multipart/form-data: double quote in filename (formdata event)]
expected:
if (processor == "x86") and not debug: ["FAIL", "NOTRUN"]
FAIL
if (processor == "x86") and not debug: ["PASS", "NOTRUN"]
[multipart/form-data: single quote in name (normal form)]
expected: