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