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