xref: /aosp_15_r20/external/google-breakpad/src/common/windows/http_upload.cc (revision 9712c20fc9bbfbac4935993a2ca0b3958c5adad2)
1 // Copyright 2006 Google LLC
2 //
3 // Redistribution and use in source and binary forms, with or without
4 // modification, are permitted provided that the following conditions are
5 // met:
6 //
7 //     * Redistributions of source code must retain the above copyright
8 // notice, this list of conditions and the following disclaimer.
9 //     * Redistributions in binary form must reproduce the above
10 // copyright notice, this list of conditions and the following disclaimer
11 // in the documentation and/or other materials provided with the
12 // distribution.
13 //     * Neither the name of Google LLC nor the names of its
14 // contributors may be used to endorse or promote products derived from
15 // this software without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 
29 #ifdef HAVE_CONFIG_H
30 #include <config.h>  // Must come first
31 #endif
32 
33 #include <assert.h>
34 
35 // Disable exception handler warnings.
36 #pragma warning(disable:4530)
37 
38 #include <fstream>
39 #include <vector>
40 
41 #include "common/windows/string_utils-inl.h"
42 
43 #include "common/windows/http_upload.h"
44 
45 namespace {
46   using std::string;
47   using std::wstring;
48   using std::map;
49   using std::vector;
50   using std::ifstream;
51   using std::ios;
52 
53   const wchar_t kUserAgent[] = L"Breakpad/1.0 (Windows)";
54 
55   // Helper class which closes an internet handle when it goes away
56   class AutoInternetHandle {
57   public:
AutoInternetHandle(HINTERNET handle)58     explicit AutoInternetHandle(HINTERNET handle) : handle_(handle) {}
~AutoInternetHandle()59     ~AutoInternetHandle() {
60       if (handle_) {
61         InternetCloseHandle(handle_);
62       }
63     }
64 
get()65     HINTERNET get() { return handle_; }
66 
67   private:
68     HINTERNET handle_;
69   };
70 
UTF8ToWide(const string & utf8)71   wstring UTF8ToWide(const string& utf8) {
72     if (utf8.length() == 0) {
73       return wstring();
74     }
75 
76     // compute the length of the buffer we'll need
77     int charcount = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, NULL, 0);
78 
79     if (charcount == 0) {
80       return wstring();
81     }
82 
83     // convert
84     wchar_t* buf = new wchar_t[charcount];
85     MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, buf, charcount);
86     wstring result(buf);
87     delete[] buf;
88     return result;
89   }
90 
WideToMBCP(const wstring & wide,unsigned int cp)91   string WideToMBCP(const wstring& wide, unsigned int cp) {
92     if (wide.length() == 0) {
93       return string();
94     }
95 
96     // compute the length of the buffer we'll need
97     int charcount = WideCharToMultiByte(cp, 0, wide.c_str(), -1,
98         NULL, 0, NULL, NULL);
99     if (charcount == 0) {
100       return string();
101     }
102 
103     // convert
104     char *buf = new char[charcount];
105     WideCharToMultiByte(cp, 0, wide.c_str(), -1, buf, charcount,
106         NULL, NULL);
107 
108     string result(buf);
109     delete[] buf;
110     return result;
111   }
112 
GetFileContents(const wstring & filename,vector<char> * contents)113   bool GetFileContents(const wstring& filename, vector<char>* contents) {
114     bool rv = false;
115     // The "open" method on pre-MSVC8 ifstream implementations doesn't accept a
116     // wchar_t* filename, so use _wfopen directly in that case.  For VC8 and
117     // later, _wfopen has been deprecated in favor of _wfopen_s, which does
118     // not exist in earlier versions, so let the ifstream open the file itself.
119     // GCC doesn't support wide file name and opening on FILE* requires ugly
120     // hacks, so fallback to multi byte file.
121 #ifdef _MSC_VER
122     ifstream file;
123     file.open(filename.c_str(), ios::binary);
124 #else // GCC
125     ifstream file(WideToMBCP(filename, CP_ACP).c_str(), ios::binary);
126 #endif  // _MSC_VER >= 1400
127     if (file.is_open()) {
128       file.seekg(0, ios::end);
129       std::streamoff length = file.tellg();
130       // Check for loss of data when converting lenght from std::streamoff into
131       // std::vector<char>::size_type
132       std::vector<char>::size_type vector_size =
133         static_cast<std::vector<char>::size_type>(length);
134       if (static_cast<std::streamoff>(vector_size) == length) {
135         contents->resize(vector_size);
136         if (length != 0) {
137           file.seekg(0, ios::beg);
138           file.read(&((*contents)[0]), length);
139         }
140         rv = true;
141       }
142       file.close();
143     }
144     return rv;
145   }
146 
CheckParameters(const map<wstring,wstring> & parameters)147   bool CheckParameters(const map<wstring, wstring>& parameters) {
148     for (map<wstring, wstring>::const_iterator pos = parameters.begin();
149           pos != parameters.end(); ++pos) {
150       const wstring& str = pos->first;
151       if (str.size() == 0) {
152         return false;  // disallow empty parameter names
153       }
154       for (unsigned int i = 0; i < str.size(); ++i) {
155         wchar_t c = str[i];
156         if (c < 32 || c == '"' || c > 127) {
157           return false;
158         }
159       }
160     }
161     return true;
162   }
163 
164   // Converts a UTF16 string to UTF8.
WideToUTF8(const wstring & wide)165   string WideToUTF8(const wstring& wide) {
166     return WideToMBCP(wide, CP_UTF8);
167   }
168 
ReadResponse(HINTERNET request,wstring * response)169   bool ReadResponse(HINTERNET request, wstring *response) {
170     bool has_content_length_header = false;
171     wchar_t content_length[32];
172     DWORD content_length_size = sizeof(content_length);
173     DWORD claimed_size = 0;
174     string response_body;
175 
176     if (HttpQueryInfo(request, HTTP_QUERY_CONTENT_LENGTH,
177         static_cast<LPVOID>(&content_length),
178         &content_length_size, 0)) {
179       has_content_length_header = true;
180       claimed_size = wcstol(content_length, NULL, 10);
181       response_body.reserve(claimed_size);
182     }
183 
184     DWORD bytes_available;
185     DWORD total_read = 0;
186     BOOL return_code;
187 
188     while (((return_code = InternetQueryDataAvailable(request, &bytes_available,
189         0, 0)) != 0) && bytes_available > 0) {
190       vector<char> response_buffer(bytes_available);
191       DWORD size_read;
192 
193       return_code = InternetReadFile(request,
194           &response_buffer[0],
195           bytes_available, &size_read);
196 
197       if (return_code && size_read > 0) {
198         total_read += size_read;
199         response_body.append(&response_buffer[0], size_read);
200       }
201       else {
202         break;
203       }
204     }
205 
206     bool succeeded = return_code && (!has_content_length_header ||
207         (total_read == claimed_size));
208     if (succeeded && response) {
209       *response = UTF8ToWide(response_body);
210     }
211 
212     return succeeded;
213   }
214 
SendRequestInner(const wstring & url,const wstring & http_method,const wstring & content_type_header,const string & request_body,int * timeout_ms,wstring * response_body,int * response_code)215   bool SendRequestInner(
216       const wstring& url,
217       const wstring& http_method,
218       const wstring& content_type_header,
219       const string& request_body,
220       int* timeout_ms,
221       wstring* response_body,
222       int* response_code) {
223     if (response_code) {
224       *response_code = 0;
225     }
226 
227     // Break up the URL and make sure we can handle it
228     wchar_t scheme[16], host[256], path[1024];
229     URL_COMPONENTS components;
230     memset(&components, 0, sizeof(components));
231     components.dwStructSize = sizeof(components);
232     components.lpszScheme = scheme;
233     components.dwSchemeLength = sizeof(scheme) / sizeof(scheme[0]);
234     components.lpszHostName = host;
235     components.dwHostNameLength = sizeof(host) / sizeof(host[0]);
236     components.lpszUrlPath = path;
237     components.dwUrlPathLength = sizeof(path) / sizeof(path[0]);
238     if (!InternetCrackUrl(url.c_str(), static_cast<DWORD>(url.size()),
239         0, &components)) {
240       DWORD err = GetLastError();
241       wprintf(L"%d\n", err);
242       return false;
243     }
244     bool secure = false;
245     if (wcscmp(scheme, L"https") == 0) {
246       secure = true;
247     }
248     else if (wcscmp(scheme, L"http") != 0) {
249       return false;
250     }
251 
252     AutoInternetHandle internet(InternetOpen(kUserAgent,
253         INTERNET_OPEN_TYPE_PRECONFIG,
254         NULL,  // proxy name
255         NULL,  // proxy bypass
256         0));   // flags
257     if (!internet.get()) {
258       return false;
259     }
260 
261     AutoInternetHandle connection(InternetConnect(internet.get(),
262         host,
263         components.nPort,
264         NULL,    // user name
265         NULL,    // password
266         INTERNET_SERVICE_HTTP,
267         0,       // flags
268         0));  // context
269     if (!connection.get()) {
270       return false;
271     }
272 
273     DWORD http_open_flags = secure ? INTERNET_FLAG_SECURE : 0;
274     http_open_flags |= INTERNET_FLAG_NO_COOKIES;
275     AutoInternetHandle request(HttpOpenRequest(connection.get(),
276         http_method.c_str(),
277         path,
278         NULL,    // version
279         NULL,    // referer
280         NULL,    // agent type
281         http_open_flags,
282         0));  // context
283     if (!request.get()) {
284       return false;
285     }
286 
287     if (!content_type_header.empty()) {
288       HttpAddRequestHeaders(request.get(),
289           content_type_header.c_str(),
290           static_cast<DWORD>(-1),
291           HTTP_ADDREQ_FLAG_ADD);
292     }
293 
294     if (timeout_ms) {
295       if (!InternetSetOption(request.get(),
296           INTERNET_OPTION_SEND_TIMEOUT,
297           timeout_ms,
298           sizeof(*timeout_ms))) {
299         fwprintf(stderr, L"Could not unset send timeout, continuing...\n");
300       }
301 
302       if (!InternetSetOption(request.get(),
303           INTERNET_OPTION_RECEIVE_TIMEOUT,
304           timeout_ms,
305           sizeof(*timeout_ms))) {
306         fwprintf(stderr, L"Could not unset receive timeout, continuing...\n");
307       }
308     }
309 
310     if (!HttpSendRequest(request.get(), NULL, 0,
311         const_cast<char*>(request_body.data()),
312         static_cast<DWORD>(request_body.size()))) {
313       return false;
314     }
315 
316     // The server indicates a successful upload with HTTP status 200.
317     wchar_t http_status[4];
318     DWORD http_status_size = sizeof(http_status);
319     if (!HttpQueryInfo(request.get(), HTTP_QUERY_STATUS_CODE,
320         static_cast<LPVOID>(&http_status), &http_status_size,
321         0)) {
322       return false;
323     }
324 
325     int http_response = wcstol(http_status, NULL, 10);
326     if (response_code) {
327       *response_code = http_response;
328     }
329 
330     bool result = (http_response == 200);
331 
332     if (result) {
333       result = ReadResponse(request.get(), response_body);
334     }
335 
336     return result;
337   }
338 
GenerateMultipartBoundary()339   wstring GenerateMultipartBoundary() {
340     // The boundary has 27 '-' characters followed by 16 hex digits
341     static const wchar_t kBoundaryPrefix[] = L"---------------------------";
342     static const int kBoundaryLength = 27 + 16 + 1;
343 
344     // Generate some random numbers to fill out the boundary
345     int r0 = rand();
346     int r1 = rand();
347 
348     wchar_t temp[kBoundaryLength];
349     swprintf(temp, kBoundaryLength, L"%s%08X%08X", kBoundaryPrefix, r0, r1);
350 
351     // remove when VC++7.1 is no longer supported
352     temp[kBoundaryLength - 1] = L'\0';
353 
354     return wstring(temp);
355   }
356 
GenerateMultipartPostRequestHeader(const wstring & boundary)357   wstring GenerateMultipartPostRequestHeader(const wstring& boundary) {
358     wstring header = L"Content-Type: multipart/form-data; boundary=";
359     header += boundary;
360     return header;
361   }
362 
AppendFileToRequestBody(const wstring & file_part_name,const wstring & filename,string * request_body,bool set_content_type=true)363   bool AppendFileToRequestBody(const wstring& file_part_name,
364                                const wstring& filename,
365                                string* request_body,
366                                bool set_content_type = true) {
367     string file_part_name_utf8 = WideToUTF8(file_part_name);
368     if (file_part_name_utf8.empty()) {
369       return false;
370     }
371 
372     string filename_utf8 = WideToUTF8(filename);
373     if (filename_utf8.empty()) {
374       return false;
375     }
376 
377     if (set_content_type) {
378       request_body->append(
379           "Content-Disposition: form-data; "
380           "name=\"" +
381           file_part_name_utf8 +
382           "\"; "
383           "filename=\"" +
384           filename_utf8 + "\"\r\n");
385       request_body->append("Content-Type: application/octet-stream\r\n");
386       request_body->append("\r\n");
387     }
388 
389     vector<char> contents;
390     if (!GetFileContents(filename, &contents)) {
391       return false;
392     }
393 
394     if (!contents.empty()) {
395       request_body->append(&(contents[0]), contents.size());
396     }
397 
398     return true;
399   }
400 
GenerateRequestBody(const map<wstring,wstring> & parameters,const map<wstring,wstring> & files,const wstring & boundary,string * request_body)401   bool GenerateRequestBody(const map<wstring, wstring>& parameters,
402       const map<wstring, wstring>& files,
403       const wstring& boundary,
404       string *request_body) {
405     string boundary_str = WideToUTF8(boundary);
406     if (boundary_str.empty()) {
407       return false;
408     }
409 
410     request_body->clear();
411 
412     // Append each of the parameter pairs as a form-data part
413     for (map<wstring, wstring>::const_iterator pos = parameters.begin();
414         pos != parameters.end(); ++pos) {
415       request_body->append("--" + boundary_str + "\r\n");
416       request_body->append("Content-Disposition: form-data; name=\"" +
417           WideToUTF8(pos->first) + "\"\r\n\r\n" +
418           WideToUTF8(pos->second) + "\r\n");
419     }
420 
421     // Now append each upload file as a binary (octet-stream) part
422     for (map<wstring, wstring>::const_iterator pos = files.begin();
423         pos != files.end(); ++pos) {
424       request_body->append("--" + boundary_str + "\r\n");
425 
426       if (!AppendFileToRequestBody(pos->first, pos->second, request_body)) {
427         return false;
428       }
429     }
430     request_body->append("--" + boundary_str + "--\r\n");
431     return true;
432   }
433 }
434 
435 namespace google_breakpad {
SendPutRequest(const wstring & url,const wstring & path,int * timeout_ms,wstring * response_body,int * response_code)436   bool HTTPUpload::SendPutRequest(
437       const wstring& url,
438       const wstring& path,
439       int* timeout_ms,
440       wstring* response_body,
441       int* response_code) {
442     string request_body;
443     // Turn off content-type in the body. If content-type is set then binary
444     // files uploaded to GCS end up with the it prepended to the file
445     // contents.
446     if (!AppendFileToRequestBody(L"symbol_file", path, &request_body,
447                                  /*set_content_type=*/false)) {
448       return false;
449     }
450 
451     return SendRequestInner(
452         url,
453         L"PUT",
454         L"",
455         request_body,
456         timeout_ms,
457         response_body,
458         response_code);
459   }
460 
SendGetRequest(const wstring & url,int * timeout_ms,wstring * response_body,int * response_code)461   bool HTTPUpload::SendGetRequest(
462       const wstring& url,
463       int* timeout_ms,
464       wstring* response_body,
465       int* response_code) {
466     return SendRequestInner(
467         url,
468         L"GET",
469         L"",
470         "",
471         timeout_ms,
472         response_body,
473         response_code);
474   }
475 
SendMultipartPostRequest(const wstring & url,const map<wstring,wstring> & parameters,const map<wstring,wstring> & files,int * timeout_ms,wstring * response_body,int * response_code)476   bool HTTPUpload::SendMultipartPostRequest(
477       const wstring& url,
478       const map<wstring, wstring>& parameters,
479       const map<wstring, wstring>& files,
480       int* timeout_ms,
481       wstring* response_body,
482       int* response_code) {
483     // TODO(bryner): support non-ASCII parameter names
484     if (!CheckParameters(parameters)) {
485       return false;
486     }
487 
488     wstring boundary = GenerateMultipartBoundary();
489     wstring content_type_header = GenerateMultipartPostRequestHeader(boundary);
490 
491     string request_body;
492     if (!GenerateRequestBody(parameters, files, boundary, &request_body)) {
493       return false;
494     }
495 
496     return SendRequestInner(
497         url,
498         L"POST",
499         content_type_header,
500         request_body,
501         timeout_ms,
502         response_body,
503         response_code);
504   }
505 
SendSimplePostRequest(const wstring & url,const wstring & body,const wstring & content_type,int * timeout_ms,wstring * response_body,int * response_code)506   bool HTTPUpload::SendSimplePostRequest(
507       const wstring& url,
508       const wstring& body,
509       const wstring& content_type,
510       int *timeout_ms,
511       wstring *response_body,
512       int *response_code) {
513     return SendRequestInner(
514         url,
515         L"POST",
516         content_type,
517         WideToUTF8(body),
518         timeout_ms,
519         response_body,
520         response_code);
521   }
522 }  // namespace google_breakpad
523