You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

234 lines
7.8 KiB

// Copyright 2014 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <brillo/http/http_form_data.h>
#include <limits>
#include <utility>
#include <base/format_macros.h>
#include <base/rand_util.h>
#include <base/strings/stringprintf.h>
#include <base/strings/string_util.h>
#include <brillo/errors/error_codes.h>
#include <brillo/http/http_transport.h>
#include <brillo/mime_utils.h>
#include <brillo/streams/file_stream.h>
#include <brillo/streams/input_stream_set.h>
#include <brillo/streams/memory_stream.h>
namespace brillo {
namespace http {
namespace form_header {
const char kContentDisposition[] = "Content-Disposition";
const char kContentTransferEncoding[] = "Content-Transfer-Encoding";
const char kContentType[] = "Content-Type";
} // namespace form_header
const char content_disposition::kFile[] = "file";
const char content_disposition::kFormData[] = "form-data";
FormField::FormField(const std::string& name,
const std::string& content_disposition,
const std::string& content_type,
const std::string& transfer_encoding)
: name_{name},
content_disposition_{content_disposition},
content_type_{content_type},
transfer_encoding_{transfer_encoding} {
}
std::string FormField::GetContentDisposition() const {
std::string disposition = content_disposition_;
if (!name_.empty())
base::StringAppendF(&disposition, "; name=\"%s\"", name_.c_str());
return disposition;
}
std::string FormField::GetContentType() const {
return content_type_;
}
std::string FormField::GetContentHeader() const {
HeaderList headers{
{form_header::kContentDisposition, GetContentDisposition()}
};
if (!content_type_.empty())
headers.emplace_back(form_header::kContentType, GetContentType());
if (!transfer_encoding_.empty()) {
headers.emplace_back(form_header::kContentTransferEncoding,
transfer_encoding_);
}
std::string result;
for (const auto& pair : headers) {
base::StringAppendF(
&result, "%s: %s\r\n", pair.first.c_str(), pair.second.c_str());
}
result += "\r\n";
return result;
}
TextFormField::TextFormField(const std::string& name,
const std::string& data,
const std::string& content_type,
const std::string& transfer_encoding)
: FormField{name,
content_disposition::kFormData,
content_type,
transfer_encoding},
data_{data} {
}
bool TextFormField::ExtractDataStreams(std::vector<StreamPtr>* streams) {
streams->push_back(MemoryStream::OpenCopyOf(data_, nullptr));
return true;
}
FileFormField::FileFormField(const std::string& name,
StreamPtr stream,
const std::string& file_name,
const std::string& content_disposition,
const std::string& content_type,
const std::string& transfer_encoding)
: FormField{name, content_disposition, content_type, transfer_encoding},
stream_{std::move(stream)},
file_name_{file_name} {
}
std::string FileFormField::GetContentDisposition() const {
std::string disposition = FormField::GetContentDisposition();
base::StringAppendF(&disposition, "; filename=\"%s\"", file_name_.c_str());
return disposition;
}
bool FileFormField::ExtractDataStreams(std::vector<StreamPtr>* streams) {
if (!stream_)
return false;
streams->push_back(std::move(stream_));
return true;
}
MultiPartFormField::MultiPartFormField(const std::string& name,
const std::string& content_type,
const std::string& boundary)
: FormField{name,
content_disposition::kFormData,
content_type.empty() ? mime::multipart::kMixed : content_type,
{}},
boundary_{boundary} {
if (boundary_.empty())
boundary_ = base::StringPrintf("%016" PRIx64, base::RandUint64());
}
bool MultiPartFormField::ExtractDataStreams(std::vector<StreamPtr>* streams) {
for (auto& part : parts_) {
std::string data = GetBoundaryStart() + part->GetContentHeader();
streams->push_back(MemoryStream::OpenCopyOf(data, nullptr));
if (!part->ExtractDataStreams(streams))
return false;
streams->push_back(MemoryStream::OpenRef("\r\n", nullptr));
}
if (!parts_.empty()) {
std::string data = GetBoundaryEnd();
streams->push_back(MemoryStream::OpenCopyOf(data, nullptr));
}
return true;
}
std::string MultiPartFormField::GetContentType() const {
// Quote the boundary only if it has non-alphanumeric chars in it.
// https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html
bool use_quotes = false;
for (auto ch : boundary_) {
if (!base::IsAsciiAlpha(ch) && !base::IsAsciiDigit(ch)) {
use_quotes = true;
break;
}
}
return base::StringPrintf(
use_quotes ? "%s; boundary=\"%s\"" : "%s; boundary=%s",
content_type_.c_str(), boundary_.c_str());
}
void MultiPartFormField::AddCustomField(std::unique_ptr<FormField> field) {
parts_.push_back(std::move(field));
}
void MultiPartFormField::AddTextField(const std::string& name,
const std::string& data) {
AddCustomField(std::unique_ptr<FormField>{new TextFormField{name, data}});
}
bool MultiPartFormField::AddFileField(const std::string& name,
const base::FilePath& file_path,
const std::string& content_disposition,
const std::string& content_type,
brillo::ErrorPtr* error) {
StreamPtr stream = FileStream::Open(file_path, Stream::AccessMode::READ,
FileStream::Disposition::OPEN_EXISTING,
error);
if (!stream)
return false;
std::string file_name = file_path.BaseName().value();
std::unique_ptr<FormField> file_field{new FileFormField{name,
std::move(stream),
file_name,
content_disposition,
content_type,
"binary"}};
AddCustomField(std::move(file_field));
return true;
}
std::string MultiPartFormField::GetBoundaryStart() const {
return base::StringPrintf("--%s\r\n", boundary_.c_str());
}
std::string MultiPartFormField::GetBoundaryEnd() const {
return base::StringPrintf("--%s--\r\n", boundary_.c_str());
}
FormData::FormData() : FormData{std::string{}} {
}
FormData::FormData(const std::string& boundary)
: form_data_{"", mime::multipart::kFormData, boundary} {
}
void FormData::AddCustomField(std::unique_ptr<FormField> field) {
form_data_.AddCustomField(std::move(field));
}
void FormData::AddTextField(const std::string& name, const std::string& data) {
form_data_.AddTextField(name, data);
}
bool FormData::AddFileField(const std::string& name,
const base::FilePath& file_path,
const std::string& content_type,
brillo::ErrorPtr* error) {
return form_data_.AddFileField(
name, file_path, content_disposition::kFormData, content_type, error);
}
std::string FormData::GetContentType() const {
return form_data_.GetContentType();
}
StreamPtr FormData::ExtractDataStream() {
std::vector<StreamPtr> source_streams;
if (form_data_.ExtractDataStreams(&source_streams))
return InputStreamSet::Create(std::move(source_streams), nullptr);
return {};
}
} // namespace http
} // namespace brillo