1// Copyright 2014 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <brillo/http/http_form_data.h>
6
7#include <limits>
8
9#include <base/format_macros.h>
10#include <base/rand_util.h>
11#include <base/strings/stringprintf.h>
12
13#include <brillo/errors/error_codes.h>
14#include <brillo/http/http_transport.h>
15#include <brillo/mime_utils.h>
16#include <brillo/streams/file_stream.h>
17#include <brillo/streams/input_stream_set.h>
18#include <brillo/streams/memory_stream.h>
19
20namespace brillo {
21namespace http {
22
23namespace form_header {
24const char kContentDisposition[] = "Content-Disposition";
25const char kContentTransferEncoding[] = "Content-Transfer-Encoding";
26const char kContentType[] = "Content-Type";
27}  // namespace form_header
28
29const char content_disposition::kFile[] = "file";
30const char content_disposition::kFormData[] = "form-data";
31
32FormField::FormField(const std::string& name,
33                     const std::string& content_disposition,
34                     const std::string& content_type,
35                     const std::string& transfer_encoding)
36    : name_{name},
37      content_disposition_{content_disposition},
38      content_type_{content_type},
39      transfer_encoding_{transfer_encoding} {
40}
41
42std::string FormField::GetContentDisposition() const {
43  std::string disposition = content_disposition_;
44  if (!name_.empty())
45    base::StringAppendF(&disposition, "; name=\"%s\"", name_.c_str());
46  return disposition;
47}
48
49std::string FormField::GetContentType() const {
50  return content_type_;
51}
52
53std::string FormField::GetContentHeader() const {
54  HeaderList headers{
55      {form_header::kContentDisposition, GetContentDisposition()}
56  };
57
58  if (!content_type_.empty())
59    headers.emplace_back(form_header::kContentType, GetContentType());
60
61  if (!transfer_encoding_.empty()) {
62    headers.emplace_back(form_header::kContentTransferEncoding,
63                         transfer_encoding_);
64  }
65
66  std::string result;
67  for (const auto& pair : headers) {
68    base::StringAppendF(
69        &result, "%s: %s\r\n", pair.first.c_str(), pair.second.c_str());
70  }
71  result += "\r\n";
72  return result;
73}
74
75TextFormField::TextFormField(const std::string& name,
76                             const std::string& data,
77                             const std::string& content_type,
78                             const std::string& transfer_encoding)
79    : FormField{name,
80                content_disposition::kFormData,
81                content_type,
82                transfer_encoding},
83      data_{data} {
84}
85
86bool TextFormField::ExtractDataStreams(std::vector<StreamPtr>* streams) {
87  streams->push_back(MemoryStream::OpenCopyOf(data_, nullptr));
88  return true;
89}
90
91FileFormField::FileFormField(const std::string& name,
92                             StreamPtr stream,
93                             const std::string& file_name,
94                             const std::string& content_disposition,
95                             const std::string& content_type,
96                             const std::string& transfer_encoding)
97    : FormField{name, content_disposition, content_type, transfer_encoding},
98      stream_{std::move(stream)},
99      file_name_{file_name} {
100}
101
102std::string FileFormField::GetContentDisposition() const {
103  std::string disposition = FormField::GetContentDisposition();
104  base::StringAppendF(&disposition, "; filename=\"%s\"", file_name_.c_str());
105  return disposition;
106}
107
108bool FileFormField::ExtractDataStreams(std::vector<StreamPtr>* streams) {
109  if (!stream_)
110    return false;
111  streams->push_back(std::move(stream_));
112  return true;
113}
114
115MultiPartFormField::MultiPartFormField(const std::string& name,
116                                       const std::string& content_type,
117                                       const std::string& boundary)
118    : FormField{name,
119                content_disposition::kFormData,
120                content_type.empty() ? mime::multipart::kMixed : content_type,
121                {}},
122      boundary_{boundary} {
123  if (boundary_.empty())
124    boundary_ = base::StringPrintf("%016" PRIx64, base::RandUint64());
125}
126
127bool MultiPartFormField::ExtractDataStreams(std::vector<StreamPtr>* streams) {
128  for (auto& part : parts_) {
129    std::string data = GetBoundaryStart() + part->GetContentHeader();
130    streams->push_back(MemoryStream::OpenCopyOf(data, nullptr));
131    if (!part->ExtractDataStreams(streams))
132      return false;
133
134    streams->push_back(MemoryStream::OpenRef("\r\n", nullptr));
135  }
136  if (!parts_.empty()) {
137    std::string data = GetBoundaryEnd();
138    streams->push_back(MemoryStream::OpenCopyOf(data, nullptr));
139  }
140  return true;
141}
142
143std::string MultiPartFormField::GetContentType() const {
144  return base::StringPrintf(
145      "%s; boundary=\"%s\"", content_type_.c_str(), boundary_.c_str());
146}
147
148void MultiPartFormField::AddCustomField(std::unique_ptr<FormField> field) {
149  parts_.push_back(std::move(field));
150}
151
152void MultiPartFormField::AddTextField(const std::string& name,
153                                      const std::string& data) {
154  AddCustomField(std::unique_ptr<FormField>{new TextFormField{name, data}});
155}
156
157bool MultiPartFormField::AddFileField(const std::string& name,
158                                      const base::FilePath& file_path,
159                                      const std::string& content_disposition,
160                                      const std::string& content_type,
161                                      brillo::ErrorPtr* error) {
162  StreamPtr stream = FileStream::Open(file_path, Stream::AccessMode::READ,
163                                      FileStream::Disposition::OPEN_EXISTING,
164                                      error);
165  if (!stream)
166    return false;
167  std::string file_name = file_path.BaseName().value();
168  std::unique_ptr<FormField> file_field{new FileFormField{name,
169                                                          std::move(stream),
170                                                          file_name,
171                                                          content_disposition,
172                                                          content_type,
173                                                          "binary"}};
174  AddCustomField(std::move(file_field));
175  return true;
176}
177
178std::string MultiPartFormField::GetBoundaryStart() const {
179  return base::StringPrintf("--%s\r\n", boundary_.c_str());
180}
181
182std::string MultiPartFormField::GetBoundaryEnd() const {
183  return base::StringPrintf("--%s--", boundary_.c_str());
184}
185
186FormData::FormData() : FormData{std::string{}} {
187}
188
189FormData::FormData(const std::string& boundary)
190    : form_data_{"", mime::multipart::kFormData, boundary} {
191}
192
193void FormData::AddCustomField(std::unique_ptr<FormField> field) {
194  form_data_.AddCustomField(std::move(field));
195}
196
197void FormData::AddTextField(const std::string& name, const std::string& data) {
198  form_data_.AddTextField(name, data);
199}
200
201bool FormData::AddFileField(const std::string& name,
202                            const base::FilePath& file_path,
203                            const std::string& content_type,
204                            brillo::ErrorPtr* error) {
205  return form_data_.AddFileField(
206      name, file_path, content_disposition::kFormData, content_type, error);
207}
208
209std::string FormData::GetContentType() const {
210  return form_data_.GetContentType();
211}
212
213StreamPtr FormData::ExtractDataStream() {
214  std::vector<StreamPtr> source_streams;
215  if (form_data_.ExtractDataStreams(&source_streams))
216    return InputStreamSet::Create(std::move(source_streams), nullptr);
217  return {};
218}
219
220}  // namespace http
221}  // namespace brillo
222