blob: 540d279d701fe15fb134089a48eb310f8be696d6 [file] [log] [blame]
// 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 <chromeos/http/http_form_data.h>
#include <limits>
#include <base/format_macros.h>
#include <base/rand_util.h>
#include <base/strings/stringprintf.h>
#include <chromeos/errors/error_codes.h>
#include <chromeos/http/http_transport.h>
#include <chromeos/mime_utils.h>
namespace chromeos {
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} {
}
uint64_t TextFormField::GetDataSize() const {
return data_.GetDataSize();
}
bool TextFormField::ReadData(void* buffer,
size_t size_to_read,
size_t* size_read,
chromeos::ErrorPtr* error) {
return data_.ReadData(buffer, size_to_read, size_read, error);
}
FileFormField::FileFormField(const std::string& name,
base::File file,
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},
file_{file.Pass()},
file_name_{file_name} {
}
uint64_t FileFormField::GetDataSize() const {
// Unfortunately base::File::GetLength() is not a const method of the class,
// for no apparent reason, so have to const_cast the file reference.
// TODO(avakulenko): Remove this cast once the base::File::GetLength() is
// fixed. See crbug.com/446528
int64_t size = const_cast<base::File&>(file_).GetLength();
return size < 0 ? 0 : size;
}
std::string FileFormField::GetContentDisposition() const {
std::string disposition = FormField::GetContentDisposition();
base::StringAppendF(&disposition, "; filename=\"%s\"", file_name_.c_str());
return disposition;
}
bool FileFormField::ReadData(void* buffer,
size_t size_to_read,
size_t* size_read,
chromeos::ErrorPtr* error) {
// base::File uses 'int' for sizes. On 64 bit systems size_t can be larger
// than what 'int' can contain, so limit the reading to the max size.
// This is acceptable because ReadData is not guaranteed to read all the data
// at once. The caller is expected to keep calling this function until all
// the data have been read.
const size_t max_size = std::numeric_limits<int>::max();
if (size_to_read > max_size)
size_to_read = max_size;
int read = file_.ReadAtCurrentPosNoBestEffort(reinterpret_cast<char*>(buffer),
size_to_read);
if (read < 0) {
chromeos::errors::system::AddSystemError(error, FROM_HERE, errno);
return false;
}
*size_read = read;
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());
}
uint64_t MultiPartFormField::GetDataSize() const {
uint64_t boundary_size = GetBoundaryStart().size();
uint64_t size = 0;
for (const auto& part : parts_) {
size += boundary_size;
size += part->GetContentHeader().size();
size += part->GetDataSize();
size += 2; // CRLF
}
if (!parts_.empty())
size += GetBoundaryEnd().size();
return size;
}
std::string MultiPartFormField::GetContentType() const {
return base::StringPrintf(
"%s; boundary=\"%s\"", content_type_.c_str(), boundary_.c_str());
}
bool MultiPartFormField::ReadData(void* buffer,
size_t size_to_read,
size_t* size_read,
chromeos::ErrorPtr* error) {
if (read_stage_ == ReadStage::kStart) {
if (parts_.empty()) {
*size_read = 0;
return true;
}
read_stage_ = ReadStage::kBoundarySetup;
read_part_index_ = 0;
}
for (;;) {
if (read_stage_ == ReadStage::kBoundarySetup &&
read_part_index_ < parts_.size()) {
// Starting a new part. Read the part boundary and headers first.
read_stage_ = ReadStage::kBoundaryData;
std::string boundary;
if (read_part_index_ > 0)
boundary = "\r\n";
boundary +=
GetBoundaryStart() + parts_[read_part_index_]->GetContentHeader();
boundary_reader_.SetData(boundary);
} else if (read_stage_ == ReadStage::kBoundarySetup &&
read_part_index_ >= parts_.size()) {
// Final part has been read. Read the closing boundary.
read_stage_ = ReadStage::kEnd;
boundary_reader_.SetData("\r\n" + GetBoundaryEnd());
} else if (read_stage_ == ReadStage::kBoundaryData ||
read_stage_ == ReadStage::kEnd) {
// Reading the boundary data (possibly the closing boundary).
if (!boundary_reader_.ReadData(buffer, size_to_read, size_read, error))
return false;
// Remain in the current stage for as long as there is data in the buffer,
// or we're in kEnd (since there is no next stage for kEnd).
if (*size_read > 0 || read_stage_ == ReadStage::kEnd)
return true;
read_stage_ = ReadStage::kPart;
} else if (read_stage_ == ReadStage::kPart) {
// Reading the actual part data.
if (!parts_[read_part_index_]->ReadData(
buffer, size_to_read, size_read, error)) {
return false;
}
if (*size_read > 0)
return true;
read_part_index_++;
read_stage_ = ReadStage::kBoundarySetup;
}
}
return false;
}
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,
chromeos::ErrorPtr* error) {
base::File file{file_path, base::File::FLAG_OPEN | base::File::FLAG_READ};
if (!file.IsValid()) {
chromeos::errors::system::AddSystemError(error, FROM_HERE, errno);
return false;
}
std::string file_name = file_path.BaseName().value();
std::unique_ptr<FormField> file_field{new FileFormField{name,
file.Pass(),
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--", 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,
chromeos::ErrorPtr* error) {
return form_data_.AddFileField(
name, file_path, content_disposition::kFormData, content_type, error);
}
std::string FormData::GetContentType() const {
return form_data_.GetContentType();
}
uint64_t FormData::GetDataSize() const {
return form_data_.GetDataSize();
}
bool FormData::ReadData(void* buffer,
size_t size_to_read,
size_t* size_read,
chromeos::ErrorPtr* error) {
return form_data_.ReadData(buffer, size_to_read, size_read, error);
}
} // namespace http
} // namespace chromeos