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