crashpad/util/net/http_multipart_builder.cc
Mark Mentovai ffe4c1018c net: Update Blink source code references
The references to RFC 2388 §3 and RFC 2047 are removed. RFC 7578 has
replaced RFC 2388, and RFC 7578 acknowledges that the area of RFC 2388
called into question by the previous comment in this code was not widely
adopted. The code does not violate RFC 7578, so the TODO is removed.

Change-Id: Ie68cba49f9fbc95a4ae3a156783a6db3b406950c
Reviewed-on: https://chromium-review.googlesource.com/481244
Reviewed-by: Robert Sesek <rsesek@chromium.org>
2017-04-19 16:08:53 +00:00

215 lines
6.7 KiB
C++

// Copyright 2014 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "util/net/http_multipart_builder.h"
#include <sys/types.h>
#include <utility>
#include <vector>
#include "base/logging.h"
#include "base/rand_util.h"
#include "base/strings/stringprintf.h"
#include "util/net/http_body.h"
#include "util/net/http_body_gzip.h"
namespace crashpad {
namespace {
const char kCRLF[] = "\r\n";
const char kBoundaryCRLF[] = "\r\n\r\n";
// Generates a random string suitable for use as a multipart boundary.
std::string GenerateBoundaryString() {
// RFC 2046 §5.1.1 says that the boundary string may be 1 to 70 characters
// long, choosing from the set of alphanumeric characters along with
// characters from the set “'()+_,-./:=? ”, and not ending in a space.
// However, some servers have been observed as dealing poorly with certain
// nonalphanumeric characters. See
// blink/Source/platform/network/FormDataEncoder.cpp
// blink::FormDataEncoder::GenerateUniqueBoundaryString().
//
// This implementation produces a 56-character string with over 190 bits of
// randomness (62^32 > 2^190).
std::string boundary_string = "---MultipartBoundary-";
for (int index = 0; index < 32; ++index) {
const char kCharacters[] =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
int random_value =
base::RandInt(0, static_cast<int>(strlen(kCharacters)) - 1);
boundary_string += kCharacters[random_value];
}
boundary_string += "---";
return boundary_string;
}
// Escapes the specified name to be suitable for the name field of a
// form-data part.
std::string EncodeMIMEField(const std::string& name) {
// This URL-escapes the quote character and newline characters, per Blink. See
// blink/Source/platform/network/FormDataEncoder.cpp
// blink::AppendQuotedString(). %-encoding is endorsed by RFC 7578 §2, with
// approval for otherwise unencoded UTF-8 given by RFC 7578 §5.1. Blink does
// not escape the '%' character, but it seems appropriate to do so in order to
// be able to decode the string properly.
std::string encoded;
for (char character : name) {
switch (character) {
case '\r':
case '\n':
case '"':
case '%':
encoded += base::StringPrintf("%%%02x", character);
break;
default:
encoded += character;
break;
}
}
return encoded;
}
// Returns a string, formatted with a multipart boundary and a field name,
// after which the contents of the part at |name| can be appended.
std::string GetFormDataBoundary(const std::string& boundary,
const std::string& name) {
return base::StringPrintf(
"--%s%sContent-Disposition: form-data; name=\"%s\"",
boundary.c_str(),
kCRLF,
EncodeMIMEField(name).c_str());
}
void AssertSafeMIMEType(const std::string& string) {
for (size_t i = 0; i < string.length(); ++i) {
char c = string[i];
CHECK((c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') ||
c == '/' ||
c == '.' ||
c == '_' ||
c == '+' ||
c == '-');
}
}
} // namespace
HTTPMultipartBuilder::HTTPMultipartBuilder()
: boundary_(GenerateBoundaryString()),
form_data_(),
file_attachments_(),
gzip_enabled_(false) {}
HTTPMultipartBuilder::~HTTPMultipartBuilder() {
}
void HTTPMultipartBuilder::SetGzipEnabled(bool gzip_enabled) {
gzip_enabled_ = gzip_enabled;
}
void HTTPMultipartBuilder::SetFormData(const std::string& key,
const std::string& value) {
EraseKey(key);
form_data_[key] = value;
}
void HTTPMultipartBuilder::SetFileAttachment(
const std::string& key,
const std::string& upload_file_name,
const base::FilePath& path,
const std::string& content_type) {
EraseKey(upload_file_name);
FileAttachment attachment;
attachment.filename = EncodeMIMEField(upload_file_name);
attachment.path = path;
if (content_type.empty()) {
attachment.content_type = "application/octet-stream";
} else {
AssertSafeMIMEType(content_type);
attachment.content_type = content_type;
}
file_attachments_[key] = attachment;
}
std::unique_ptr<HTTPBodyStream> HTTPMultipartBuilder::GetBodyStream() {
// The objects inserted into this vector will be owned by the returned
// CompositeHTTPBodyStream. Take care to not early-return without deleting
// this memory.
std::vector<HTTPBodyStream*> streams;
for (const auto& pair : form_data_) {
std::string field = GetFormDataBoundary(boundary_, pair.first);
field += kBoundaryCRLF;
field += pair.second;
field += kCRLF;
streams.push_back(new StringHTTPBodyStream(field));
}
for (const auto& pair : file_attachments_) {
const FileAttachment& attachment = pair.second;
std::string header = GetFormDataBoundary(boundary_, pair.first);
header += base::StringPrintf("; filename=\"%s\"%s",
attachment.filename.c_str(), kCRLF);
header += base::StringPrintf("Content-Type: %s%s",
attachment.content_type.c_str(), kBoundaryCRLF);
streams.push_back(new StringHTTPBodyStream(header));
streams.push_back(new FileHTTPBodyStream(attachment.path));
streams.push_back(new StringHTTPBodyStream(kCRLF));
}
streams.push_back(
new StringHTTPBodyStream("--" + boundary_ + "--" + kCRLF));
auto composite =
std::unique_ptr<HTTPBodyStream>(new CompositeHTTPBodyStream(streams));
if (gzip_enabled_) {
return std::unique_ptr<HTTPBodyStream>(
new GzipHTTPBodyStream(std::move(composite)));
}
return composite;
}
void HTTPMultipartBuilder::PopulateContentHeaders(
HTTPHeaders* http_headers) const {
std::string content_type =
base::StringPrintf("multipart/form-data; boundary=%s", boundary_.c_str());
(*http_headers)[kContentType] = content_type;
if (gzip_enabled_) {
(*http_headers)[kContentEncoding] = "gzip";
}
}
void HTTPMultipartBuilder::EraseKey(const std::string& key) {
auto data_it = form_data_.find(key);
if (data_it != form_data_.end())
form_data_.erase(data_it);
auto file_it = file_attachments_.find(key);
if (file_it != file_attachments_.end())
file_attachments_.erase(file_it);
}
} // namespace crashpad