• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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