diff --git a/src/common/windows/common_windows.gyp b/src/common/windows/common_windows.gyp index c98333a3..6626c576 100644 --- a/src/common/windows/common_windows.gyp +++ b/src/common/windows/common_windows.gyp @@ -84,6 +84,8 @@ 'pdb_source_line_writer.h', 'string_utils.cc', 'string_utils-inl.h', + 'symbol_collector_client.cc', + 'symbol_collector_client.h', ], 'dependencies': [ 'dia_sdk', diff --git a/src/common/windows/http_upload.cc b/src/common/windows/http_upload.cc index 7676bdc5..94789792 100644 --- a/src/common/windows/http_upload.cc +++ b/src/common/windows/http_upload.cc @@ -33,388 +33,478 @@ #pragma warning(disable:4530) #include +#include #include "common/windows/string_utils-inl.h" #include "common/windows/http_upload.h" -namespace google_breakpad { +namespace { + using std::string; + using std::wstring; + using std::map; + using std::vector; + using std::ifstream; + using std::ios; -using std::ifstream; -using std::ios; + const wchar_t kUserAgent[] = L"Breakpad/1.0 (Windows)"; -static const wchar_t kUserAgent[] = L"Breakpad/1.0 (Windows)"; - -// Helper class which closes an internet handle when it goes away -class HTTPUpload::AutoInternetHandle { - public: - explicit AutoInternetHandle(HINTERNET handle) : handle_(handle) {} - ~AutoInternetHandle() { - if (handle_) { - InternetCloseHandle(handle_); - } - } - - HINTERNET get() { return handle_; } - - private: - HINTERNET handle_; -}; - -// static -bool HTTPUpload::SendRequest(const wstring &url, - const map ¶meters, - const map &files, - int *timeout, - wstring *response_body, - int *response_code) { - if (response_code) { - *response_code = 0; - } - - // TODO(bryner): support non-ASCII parameter names - if (!CheckParameters(parameters)) { - return false; - } - - // Break up the URL and make sure we can handle it - wchar_t scheme[16], host[256], path[256]; - URL_COMPONENTS components; - memset(&components, 0, sizeof(components)); - components.dwStructSize = sizeof(components); - components.lpszScheme = scheme; - components.dwSchemeLength = sizeof(scheme) / sizeof(scheme[0]); - components.lpszHostName = host; - components.dwHostNameLength = sizeof(host) / sizeof(host[0]); - components.lpszUrlPath = path; - components.dwUrlPathLength = sizeof(path) / sizeof(path[0]); - if (!InternetCrackUrl(url.c_str(), static_cast(url.size()), - 0, &components)) { - return false; - } - bool secure = false; - if (wcscmp(scheme, L"https") == 0) { - secure = true; - } else if (wcscmp(scheme, L"http") != 0) { - return false; - } - - AutoInternetHandle internet(InternetOpen(kUserAgent, - INTERNET_OPEN_TYPE_PRECONFIG, - NULL, // proxy name - NULL, // proxy bypass - 0)); // flags - if (!internet.get()) { - return false; - } - - AutoInternetHandle connection(InternetConnect(internet.get(), - host, - components.nPort, - NULL, // user name - NULL, // password - INTERNET_SERVICE_HTTP, - 0, // flags - NULL)); // context - if (!connection.get()) { - return false; - } - - DWORD http_open_flags = secure ? INTERNET_FLAG_SECURE : 0; - http_open_flags |= INTERNET_FLAG_NO_COOKIES; - AutoInternetHandle request(HttpOpenRequest(connection.get(), - L"POST", - path, - NULL, // version - NULL, // referer - NULL, // agent type - http_open_flags, - NULL)); // context - if (!request.get()) { - return false; - } - - wstring boundary = GenerateMultipartBoundary(); - wstring content_type_header = GenerateRequestHeader(boundary); - HttpAddRequestHeaders(request.get(), - content_type_header.c_str(), - static_cast(-1), - HTTP_ADDREQ_FLAG_ADD); - - string request_body; - if (!GenerateRequestBody(parameters, files, boundary, &request_body)) { - return false; - } - - if (timeout) { - if (!InternetSetOption(request.get(), - INTERNET_OPTION_SEND_TIMEOUT, - timeout, - sizeof(*timeout))) { - fwprintf(stderr, L"Could not unset send timeout, continuing...\n"); + // Helper class which closes an internet handle when it goes away + class AutoInternetHandle { + public: + explicit AutoInternetHandle(HINTERNET handle) : handle_(handle) {} + ~AutoInternetHandle() { + if (handle_) { + InternetCloseHandle(handle_); + } } - if (!InternetSetOption(request.get(), - INTERNET_OPTION_RECEIVE_TIMEOUT, - timeout, - sizeof(*timeout))) { - fwprintf(stderr, L"Could not unset receive timeout, continuing...\n"); + HINTERNET get() { return handle_; } + + private: + HINTERNET handle_; + }; + + bool GetFileContents(const wstring &filename, vector *contents) { + bool rv = false; + // The "open" method on pre-MSVC8 ifstream implementations doesn't accept a + // wchar_t* filename, so use _wfopen directly in that case. For VC8 and + // later, _wfopen has been deprecated in favor of _wfopen_s, which does + // not exist in earlier versions, so let the ifstream open the file itself. + // GCC doesn't support wide file name and opening on FILE* requires ugly + // hacks, so fallback to multi byte file. +#ifdef _MSC_VER + ifstream file; + file.open(filename.c_str(), ios::binary); +#else // GCC + ifstream file(WideToMBCP(filename, CP_ACP).c_str(), ios::binary); +#endif // _MSC_VER >= 1400 + if (file.is_open()) { + file.seekg(0, ios::end); + std::streamoff length = file.tellg(); + // Check for loss of data when converting lenght from std::streamoff into + // std::vector::size_type + std::vector::size_type vector_size = + static_cast::size_type>(length); + if (static_cast(vector_size) == length) { + contents->resize(vector_size); + if (length != 0) { + file.seekg(0, ios::beg); + file.read(&((*contents)[0]), length); + } + rv = true; + } + file.close(); } + return rv; } - if (!HttpSendRequest(request.get(), NULL, 0, - const_cast(request_body.data()), - static_cast(request_body.size()))) { - return false; - } - - // The server indicates a successful upload with HTTP status 200. - wchar_t http_status[4]; - DWORD http_status_size = sizeof(http_status); - if (!HttpQueryInfo(request.get(), HTTP_QUERY_STATUS_CODE, - static_cast(&http_status), &http_status_size, - 0)) { - return false; - } - - int http_response = wcstol(http_status, NULL, 10); - if (response_code) { - *response_code = http_response; - } - - bool result = (http_response == 200); - - if (result) { - result = ReadResponse(request.get(), response_body); - } - - return result; -} - -// static -bool HTTPUpload::ReadResponse(HINTERNET request, wstring *response) { - bool has_content_length_header = false; - wchar_t content_length[32]; - DWORD content_length_size = sizeof(content_length); - DWORD claimed_size = 0; - string response_body; - - if (HttpQueryInfo(request, HTTP_QUERY_CONTENT_LENGTH, - static_cast(&content_length), - &content_length_size, 0)) { - has_content_length_header = true; - claimed_size = wcstol(content_length, NULL, 10); - response_body.reserve(claimed_size); - } - - - DWORD bytes_available; - DWORD total_read = 0; - BOOL return_code; - - while (((return_code = InternetQueryDataAvailable(request, &bytes_available, - 0, 0)) != 0) && bytes_available > 0) { - vector response_buffer(bytes_available); - DWORD size_read; - - return_code = InternetReadFile(request, - &response_buffer[0], - bytes_available, &size_read); - - if (return_code && size_read > 0) { - total_read += size_read; - response_body.append(&response_buffer[0], size_read); - } else { - break; + wstring UTF8ToWide(const string &utf8) { + if (utf8.length() == 0) { + return wstring(); } + + // compute the length of the buffer we'll need + int charcount = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, NULL, 0); + + if (charcount == 0) { + return wstring(); + } + + // convert + wchar_t* buf = new wchar_t[charcount]; + MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, buf, charcount); + wstring result(buf); + delete[] buf; + return result; } - bool succeeded = return_code && (!has_content_length_header || - (total_read == claimed_size)); - if (succeeded && response) { - *response = UTF8ToWide(response_body); + string WideToMBCP(const wstring &wide, unsigned int cp) { + if (wide.length() == 0) { + return string(); + } + + // compute the length of the buffer we'll need + int charcount = WideCharToMultiByte(cp, 0, wide.c_str(), -1, + NULL, 0, NULL, NULL); + if (charcount == 0) { + return string(); + } + + // convert + char *buf = new char[charcount]; + WideCharToMultiByte(cp, 0, wide.c_str(), -1, buf, charcount, + NULL, NULL); + + string result(buf); + delete[] buf; + return result; } - return succeeded; -} - -// static -wstring HTTPUpload::GenerateMultipartBoundary() { - // The boundary has 27 '-' characters followed by 16 hex digits - static const wchar_t kBoundaryPrefix[] = L"---------------------------"; - static const int kBoundaryLength = 27 + 16 + 1; - - // Generate some random numbers to fill out the boundary - int r0 = rand(); - int r1 = rand(); - - wchar_t temp[kBoundaryLength]; - swprintf(temp, kBoundaryLength, L"%s%08X%08X", kBoundaryPrefix, r0, r1); - - // remove when VC++7.1 is no longer supported - temp[kBoundaryLength - 1] = L'\0'; - - return wstring(temp); -} - -// static -wstring HTTPUpload::GenerateRequestHeader(const wstring &boundary) { - wstring header = L"Content-Type: multipart/form-data; boundary="; - header += boundary; - return header; -} - -// static -bool HTTPUpload::GenerateRequestBody(const map ¶meters, - const map &files, - const wstring &boundary, - string *request_body) { - string boundary_str = WideToUTF8(boundary); - if (boundary_str.empty()) { - return false; + bool CheckParameters(const map ¶meters) { + for (map::const_iterator pos = parameters.begin(); + pos != parameters.end(); ++pos) { + const wstring &str = pos->first; + if (str.size() == 0) { + return false; // disallow empty parameter names + } + for (unsigned int i = 0; i < str.size(); ++i) { + wchar_t c = str[i]; + if (c < 32 || c == '"' || c > 127) { + return false; + } + } + } + return true; } - request_body->clear(); - - // Append each of the parameter pairs as a form-data part - for (map::const_iterator pos = parameters.begin(); - pos != parameters.end(); ++pos) { - request_body->append("--" + boundary_str + "\r\n"); - request_body->append("Content-Disposition: form-data; name=\"" + - WideToUTF8(pos->first) + "\"\r\n\r\n" + - WideToUTF8(pos->second) + "\r\n"); + // Converts a UTF16 string to UTF8. + string WideToUTF8(const wstring &wide) { + return WideToMBCP(wide, CP_UTF8); } - for (map::const_iterator pos = files.begin(); - pos != files.end(); ++pos) { - vector contents; - if (!GetFileContents(pos->second, &contents)) { + bool ReadResponse(HINTERNET request, wstring *response) { + bool has_content_length_header = false; + wchar_t content_length[32]; + DWORD content_length_size = sizeof(content_length); + DWORD claimed_size = 0; + string response_body; + + if (HttpQueryInfo(request, HTTP_QUERY_CONTENT_LENGTH, + static_cast(&content_length), + &content_length_size, 0)) { + has_content_length_header = true; + claimed_size = wcstol(content_length, NULL, 10); + response_body.reserve(claimed_size); + } + + DWORD bytes_available; + DWORD total_read = 0; + BOOL return_code; + + while (((return_code = InternetQueryDataAvailable(request, &bytes_available, + 0, 0)) != 0) && bytes_available > 0) { + vector response_buffer(bytes_available); + DWORD size_read; + + return_code = InternetReadFile(request, + &response_buffer[0], + bytes_available, &size_read); + + if (return_code && size_read > 0) { + total_read += size_read; + response_body.append(&response_buffer[0], size_read); + } + else { + break; + } + } + + bool succeeded = return_code && (!has_content_length_header || + (total_read == claimed_size)); + if (succeeded && response) { + *response = UTF8ToWide(response_body); + } + + return succeeded; + } + + bool 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) { + if (response_code) { + *response_code = 0; + } + + // Break up the URL and make sure we can handle it + wchar_t scheme[16], host[256], path[1024]; + URL_COMPONENTS components; + memset(&components, 0, sizeof(components)); + components.dwStructSize = sizeof(components); + components.lpszScheme = scheme; + components.dwSchemeLength = sizeof(scheme) / sizeof(scheme[0]); + components.lpszHostName = host; + components.dwHostNameLength = sizeof(host) / sizeof(host[0]); + components.lpszUrlPath = path; + components.dwUrlPathLength = sizeof(path) / sizeof(path[0]); + if (!InternetCrackUrl(url.c_str(), static_cast(url.size()), + 0, &components)) { + DWORD err = GetLastError(); + wprintf(L"%d\n", err); + return false; + } + bool secure = false; + if (wcscmp(scheme, L"https") == 0) { + secure = true; + } + else if (wcscmp(scheme, L"http") != 0) { return false; } - // Now append the upload files as a binary (octet-stream) part - string filename_utf8 = WideToUTF8(pos->second); - if (filename_utf8.empty()) { + AutoInternetHandle internet(InternetOpen(kUserAgent, + INTERNET_OPEN_TYPE_PRECONFIG, + NULL, // proxy name + NULL, // proxy bypass + 0)); // flags + if (!internet.get()) { return false; } - string file_part_name_utf8 = WideToUTF8(pos->first); + AutoInternetHandle connection(InternetConnect(internet.get(), + host, + components.nPort, + NULL, // user name + NULL, // password + INTERNET_SERVICE_HTTP, + 0, // flags + NULL)); // context + if (!connection.get()) { + return false; + } + + DWORD http_open_flags = secure ? INTERNET_FLAG_SECURE : 0; + http_open_flags |= INTERNET_FLAG_NO_COOKIES; + AutoInternetHandle request(HttpOpenRequest(connection.get(), + http_method.c_str(), + path, + NULL, // version + NULL, // referer + NULL, // agent type + http_open_flags, + NULL)); // context + if (!request.get()) { + return false; + } + + if (!content_type_header.empty()) { + HttpAddRequestHeaders(request.get(), + content_type_header.c_str(), + static_cast(-1), + HTTP_ADDREQ_FLAG_ADD); + } + + if (timeout_ms) { + if (!InternetSetOption(request.get(), + INTERNET_OPTION_SEND_TIMEOUT, + timeout_ms, + sizeof(*timeout_ms))) { + fwprintf(stderr, L"Could not unset send timeout, continuing...\n"); + } + + if (!InternetSetOption(request.get(), + INTERNET_OPTION_RECEIVE_TIMEOUT, + timeout_ms, + sizeof(*timeout_ms))) { + fwprintf(stderr, L"Could not unset receive timeout, continuing...\n"); + } + } + + if (!HttpSendRequest(request.get(), NULL, 0, + const_cast(request_body.data()), + static_cast(request_body.size()))) { + return false; + } + + // The server indicates a successful upload with HTTP status 200. + wchar_t http_status[4]; + DWORD http_status_size = sizeof(http_status); + if (!HttpQueryInfo(request.get(), HTTP_QUERY_STATUS_CODE, + static_cast(&http_status), &http_status_size, + 0)) { + return false; + } + + int http_response = wcstol(http_status, NULL, 10); + if (response_code) { + *response_code = http_response; + } + + bool result = (http_response == 200); + + if (result) { + result = ReadResponse(request.get(), response_body); + } + + return result; + } + + wstring GenerateMultipartBoundary() { + // The boundary has 27 '-' characters followed by 16 hex digits + static const wchar_t kBoundaryPrefix[] = L"---------------------------"; + static const int kBoundaryLength = 27 + 16 + 1; + + // Generate some random numbers to fill out the boundary + int r0 = rand(); + int r1 = rand(); + + wchar_t temp[kBoundaryLength]; + swprintf(temp, kBoundaryLength, L"%s%08X%08X", kBoundaryPrefix, r0, r1); + + // remove when VC++7.1 is no longer supported + temp[kBoundaryLength - 1] = L'\0'; + + return wstring(temp); + } + + wstring GenerateMultipartPostRequestHeader(const wstring &boundary) { + wstring header = L"Content-Type: multipart/form-data; boundary="; + header += boundary; + return header; + } + + bool AppendFileToRequestBody( + const wstring& file_part_name, + const wstring& filename, + string* request_body) { + string file_part_name_utf8 = WideToUTF8(file_part_name); if (file_part_name_utf8.empty()) { return false; } - request_body->append("--" + boundary_str + "\r\n"); + string filename_utf8 = WideToUTF8(filename); + if (filename_utf8.empty()) { + return false; + } + request_body->append("Content-Disposition: form-data; " - "name=\"" + file_part_name_utf8 + "\"; " - "filename=\"" + filename_utf8 + "\"\r\n"); + "name=\"" + file_part_name_utf8 + "\"; " + "filename=\"" + filename_utf8 + "\"\r\n"); request_body->append("Content-Type: application/octet-stream\r\n"); request_body->append("\r\n"); + vector contents; + if (!GetFileContents(filename, &contents)) { + return false; + } + if (!contents.empty()) { request_body->append(&(contents[0]), contents.size()); } request_body->append("\r\n"); - } - request_body->append("--" + boundary_str + "--\r\n"); - return true; -} -// static -bool HTTPUpload::GetFileContents(const wstring &filename, - vector *contents) { - bool rv = false; - // The "open" method on pre-MSVC8 ifstream implementations doesn't accept a - // wchar_t* filename, so use _wfopen directly in that case. For VC8 and - // later, _wfopen has been deprecated in favor of _wfopen_s, which does - // not exist in earlier versions, so let the ifstream open the file itself. - // GCC doesn't support wide file name and opening on FILE* requires ugly - // hacks, so fallback to multi byte file. -#ifdef _MSC_VER - ifstream file; - file.open(filename.c_str(), ios::binary); -#else // GCC - ifstream file(WideToMBCP(filename, CP_ACP).c_str(), ios::binary); -#endif // _MSC_VER >= 1400 - if (file.is_open()) { - file.seekg(0, ios::end); - std::streamoff length = file.tellg(); - // Check for loss of data when converting lenght from std::streamoff into - // std::vector::size_type - std::vector::size_type vector_size = - static_cast::size_type>(length); - if (static_cast(vector_size) == length) { - contents->resize(vector_size); - if (length != 0) { - file.seekg(0, ios::beg); - file.read(&((*contents)[0]), length); - } - rv = true; + return true; + } + + bool GenerateRequestBody(const map ¶meters, + const map &files, + const wstring &boundary, + string *request_body) { + string boundary_str = WideToUTF8(boundary); + if (boundary_str.empty()) { + return false; } - file.close(); - } - return rv; -} -// static -wstring HTTPUpload::UTF8ToWide(const string &utf8) { - if (utf8.length() == 0) { - return wstring(); - } + request_body->clear(); - // compute the length of the buffer we'll need - int charcount = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, NULL, 0); - - if (charcount == 0) { - return wstring(); - } - - // convert - wchar_t* buf = new wchar_t[charcount]; - MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, buf, charcount); - wstring result(buf); - delete[] buf; - return result; -} - -// static -string HTTPUpload::WideToMBCP(const wstring &wide, unsigned int cp) { - if (wide.length() == 0) { - return string(); - } - - // compute the length of the buffer we'll need - int charcount = WideCharToMultiByte(cp, 0, wide.c_str(), -1, - NULL, 0, NULL, NULL); - if (charcount == 0) { - return string(); - } - - // convert - char *buf = new char[charcount]; - WideCharToMultiByte(cp, 0, wide.c_str(), -1, buf, charcount, - NULL, NULL); - - string result(buf); - delete[] buf; - return result; -} - -// static -bool HTTPUpload::CheckParameters(const map ¶meters) { - for (map::const_iterator pos = parameters.begin(); - pos != parameters.end(); ++pos) { - const wstring &str = pos->first; - if (str.size() == 0) { - return false; // disallow empty parameter names + // Append each of the parameter pairs as a form-data part + for (map::const_iterator pos = parameters.begin(); + pos != parameters.end(); ++pos) { + request_body->append("--" + boundary_str + "\r\n"); + request_body->append("Content-Disposition: form-data; name=\"" + + WideToUTF8(pos->first) + "\"\r\n\r\n" + + WideToUTF8(pos->second) + "\r\n"); } - for (unsigned int i = 0; i < str.size(); ++i) { - wchar_t c = str[i]; - if (c < 32 || c == '"' || c > 127) { + + // Now append each upload file as a binary (octet-stream) part + for (map::const_iterator pos = files.begin(); + pos != files.end(); ++pos) { + request_body->append("--" + boundary_str + "\r\n"); + + if (!AppendFileToRequestBody(pos->first, pos->second, request_body)) { return false; } } + request_body->append("--" + boundary_str + "--\r\n"); + return true; } - return true; } +namespace google_breakpad { + bool HTTPUpload::SendPutRequest( + const wstring& url, + const wstring& path, + int* timeout_ms, + wstring* response_body, + int* response_code) { + string request_body; + if (!AppendFileToRequestBody(L"symbol_file", path, &request_body)) { + return false; + } + + return SendRequestInner( + url, + L"PUT", + L"", + request_body, + timeout_ms, + response_body, + response_code); + } + + bool HTTPUpload::SendGetRequest( + const wstring& url, + int* timeout_ms, + wstring* response_body, + int* response_code) { + return SendRequestInner( + url, + L"GET", + L"", + "", + timeout_ms, + response_body, + response_code); + } + + bool HTTPUpload::SendMultipartPostRequest( + const wstring& url, + const map& parameters, + const map& files, + int* timeout_ms, + wstring* response_body, + int* response_code) { + // TODO(bryner): support non-ASCII parameter names + if (!CheckParameters(parameters)) { + return false; + } + + wstring boundary = GenerateMultipartBoundary(); + wstring content_type_header = GenerateMultipartPostRequestHeader(boundary); + + string request_body; + if (!GenerateRequestBody(parameters, files, boundary, &request_body)) { + return false; + } + + return SendRequestInner( + url, + L"POST", + content_type_header, + request_body, + timeout_ms, + response_body, + response_code); + } + + bool HTTPUpload::SendSimplePostRequest( + const wstring& url, + const wstring& body, + const wstring& content_type, + int *timeout_ms, + wstring *response_body, + int *response_code) { + return SendRequestInner( + url, + L"POST", + content_type, + WideToUTF8(body), + timeout_ms, + response_body, + response_code); + } } // namespace google_breakpad diff --git a/src/common/windows/http_upload.h b/src/common/windows/http_upload.h index f8d48cb1..57e526e3 100644 --- a/src/common/windows/http_upload.h +++ b/src/common/windows/http_upload.h @@ -42,18 +42,41 @@ #include #include -#include -#include namespace google_breakpad { using std::string; using std::wstring; using std::map; -using std::vector; class HTTPUpload { public: + // Sends a PUT request containing the data in |path| to the given + // URL. + // Only HTTP(S) URLs are currently supported. Returns true on success. + // If the request is successful and response_body is non-NULL, + // the response body will be returned in response_body. + // If response_code is non-NULL, it will be set to the HTTP response code + // received (or 0 if the request failed before getting an HTTP response). + static bool SendPutRequest( + const wstring& url, + const wstring& path, + int* timeout_ms, + wstring* response_body, + int* response_code); + + // Sends a GET request to the given URL. + // Only HTTP(S) URLs are currently supported. Returns true on success. + // If the request is successful and response_body is non-NULL, + // the response body will be returned in response_body. + // If response_code is non-NULL, it will be set to the HTTP response code + // received (or 0 if the request failed before getting an HTTP response). + static bool SendGetRequest( + const wstring& url, + int* timeout_ms, + wstring* response_body, + int* response_code); + // Sends the given sets of parameters and files as a multipart POST // request to the given URL. // Each key in |files| is the name of the file part of the request @@ -65,55 +88,29 @@ class HTTPUpload { // the response body will be returned in response_body. // If response_code is non-NULL, it will be set to the HTTP response code // received (or 0 if the request failed before getting an HTTP response). - static bool SendRequest(const wstring &url, - const map ¶meters, - const map &files, - int *timeout, - wstring *response_body, - int *response_code); + static bool SendMultipartPostRequest( + const wstring& url, + const map& parameters, + const map& files, + int *timeout_ms, + wstring *response_body, + int *response_code); + + // Sends a POST request, with the body set to |body|, to the given URL. + // Only HTTP(S) URLs are currently supported. Returns true on success. + // If the request is successful and response_body is non-NULL, + // the response body will be returned in response_body. + // If response_code is non-NULL, it will be set to the HTTP response code + // received (or 0 if the request failed before getting an HTTP response). + static bool SendSimplePostRequest( + const wstring& url, + const wstring& body, + const wstring& content_type, + int *timeout_ms, + wstring *response_body, + int *response_code); private: - class AutoInternetHandle; - - // Retrieves the HTTP response. If NULL is passed in for response, - // this merely checks (via the return value) that we were successfully - // able to retrieve exactly as many bytes of content in the response as - // were specified in the Content-Length header. - static bool ReadResponse(HINTERNET request, wstring* response); - - // Generates a new multipart boundary for a POST request - static wstring GenerateMultipartBoundary(); - - // Generates a HTTP request header for a multipart form submit. - static wstring GenerateRequestHeader(const wstring &boundary); - - // Given a set of parameters, a set of upload files, and a file part name, - // generates a multipart request body string with these parameters - // and minidump contents. Returns true on success. - static bool GenerateRequestBody(const map ¶meters, - const map &files, - const wstring &boundary, - string *request_body); - - // Fills the supplied vector with the contents of filename. - static bool GetFileContents(const wstring &filename, vector *contents); - - // Converts a UTF8 string to UTF16. - static wstring UTF8ToWide(const string &utf8); - - // Converts a UTF16 string to UTF8. - static string WideToUTF8(const wstring &wide) { - return WideToMBCP(wide, CP_UTF8); - } - - // Converts a UTF16 string to specified code page. - static string WideToMBCP(const wstring &wide, unsigned int cp); - - // Checks that the given list of parameters has only printable - // ASCII characters in the parameter name, and does not contain - // any quote (") characters. Returns true if so. - static bool CheckParameters(const map ¶meters); - // No instances of this class should be created. // Disallow all constructors, destructors, and operator=. HTTPUpload(); diff --git a/src/common/windows/symbol_collector_client.cc b/src/common/windows/symbol_collector_client.cc new file mode 100644 index 00000000..30c663ed --- /dev/null +++ b/src/common/windows/symbol_collector_client.cc @@ -0,0 +1,155 @@ +#include "common/windows/symbol_collector_client.h" + +#include + +#include + +#include "common/windows/http_upload.h" + +namespace google_breakpad { + + // static + bool SymbolCollectorClient::CreateUploadUrl( + wstring& api_url, + wstring& api_key, + UploadUrlResponse *uploadUrlResponse) { + wstring url = api_url + + L"/v1/uploads:create" + L"?key=" + api_key; + wstring response; + int response_code; + + if (!HTTPUpload::SendSimplePostRequest( + url, + L"", + L"", + NULL, + &response, + &response_code)) { + wprintf(L"Failed to create upload url.\n"); + wprintf(L"Response code: %ld\n", response_code); + wprintf(L"Response:\n"); + wprintf(L"%s\n", response.c_str()); + return false; + } + + // Note camel-case rather than underscores. + std::wregex upload_url_regex(L"\"uploadUrl\": \"([^\"]+)\""); + std::wregex upload_key_regex(L"\"uploadKey\": \"([^\"]+)\""); + + std::wsmatch upload_url_match; + if (!std::regex_search(response, upload_url_match, upload_url_regex) || + upload_url_match.size() != 2) { + wprintf(L"Failed to parse create url response."); + wprintf(L"Response:\n"); + wprintf(L"%s\n", response.c_str()); + return false; + } + wstring upload_url = upload_url_match[1].str(); + + std::wsmatch upload_key_match; + if (!std::regex_search(response, upload_key_match, upload_key_regex) || + upload_key_match.size() != 2) { + wprintf(L"Failed to parse create url response."); + wprintf(L"Response:\n"); + wprintf(L"%s\n", response.c_str()); + return false; + } + wstring upload_key = upload_key_match[1].str(); + + uploadUrlResponse->upload_url = upload_url; + uploadUrlResponse->upload_key = upload_key; + return true; + } + + // static + CompleteUploadResult SymbolCollectorClient::CompleteUpload( + wstring& api_url, + wstring& api_key, + const wstring& upload_key, + const wstring& debug_file, + const wstring& debug_id) { + wstring url = api_url + + L"/v1/uploads/" + upload_key + L":complete" + L"?key=" + api_key; + wstring body = + L"{ symbol_id: {" + L"debug_file: \"" + debug_file + L"\", " + L"debug_id: \"" + debug_id + L"\" " + L"} }"; + wstring response; + int response_code; + + if (!HTTPUpload::SendSimplePostRequest( + url, + body, + L"application/json", + NULL, + &response, + &response_code)) { + wprintf(L"Failed to complete upload.\n"); + wprintf(L"Response code: %ld\n", response_code); + wprintf(L"Response:\n"); + wprintf(L"%s\n", response.c_str()); + return CompleteUploadResult::Error; + } + + std::wregex result_regex(L"\"result\": \"([^\"]+)\""); + std::wsmatch result_match; + if (!std::regex_search(response, result_match, result_regex) || + result_match.size() != 2) { + wprintf(L"Failed to parse complete upload response."); + wprintf(L"Response:\n"); + wprintf(L"%s\n", response.c_str()); + return CompleteUploadResult::Error; + } + wstring result = result_match[1].str(); + + if (result.compare(L"DUPLICATE_DATA") == 0) { + return CompleteUploadResult::DuplicateData; + } + + return CompleteUploadResult::Ok; + } + + // static + SymbolStatus SymbolCollectorClient::CheckSymbolStatus( + wstring& api_url, + wstring& api_key, + const wstring& debug_file, + const wstring& debug_id) { + wstring response; + int response_code; + wstring url = api_url + + L"/v1/symbols/" + debug_file + L"/" + debug_id + L":checkStatus" + L"?key=" + api_key; + + if (!HTTPUpload::SendGetRequest( + url, + NULL, + &response, + &response_code)) { + wprintf(L"Failed to check symbol status.\n"); + wprintf(L"Response code: %ld\n", response_code); + wprintf(L"Response:\n"); + wprintf(L"%s\n", response.c_str()); + return SymbolStatus::Unknown; + } + + std::wregex status_regex(L"\"status\": \"([^\"]+)\""); + std::wsmatch status_match; + if (!std::regex_search(response, status_match, status_regex) || + status_match.size() != 2) { + wprintf(L"Failed to parse check symbol status response."); + wprintf(L"Response:\n"); + wprintf(L"%s\n", response.c_str()); + return SymbolStatus::Unknown; + } + wstring status = status_match[1].str(); + + return (status.compare(L"FOUND") == 0) ? + SymbolStatus::Found : + SymbolStatus::Missing; + } + +} // namespace google_breakpad \ No newline at end of file diff --git a/src/common/windows/symbol_collector_client.h b/src/common/windows/symbol_collector_client.h new file mode 100644 index 00000000..30e0cb32 --- /dev/null +++ b/src/common/windows/symbol_collector_client.h @@ -0,0 +1,89 @@ +// Copyright (c) 2019, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef COMMON_WINDOWS_SYMBOL_COLLECTOR_CLIENT_H_ +#define COMMON_WINDOWS_SYMBOL_COLLECTOR_CLIENT_H_ + +#include + +namespace google_breakpad { + + using std::wstring; + + struct UploadUrlResponse { + // URL at which to HTTP PUT symbol file. + wstring upload_url; + // Unique key used to complete upload of symbol file. + wstring upload_key; + }; + + enum SymbolStatus { + Found, + Missing, + Unknown + }; + + enum CompleteUploadResult { + Ok, + DuplicateData, + Error + }; + + // Client to interact with sym-upload-v2 API server via HTTP/REST. + class SymbolCollectorClient { + public: + // Returns a URL at which a symbol file can be HTTP PUT without + // authentication, along with an upload key that can be used to + // complete the upload process with CompleteUpload. + static bool CreateUploadUrl( + wstring& api_url, + wstring& api_key, + UploadUrlResponse *uploadUrlResponse); + + // Notify the API that symbol file upload is finished and its contents + // are ready to be read and/or used for further processing. + static CompleteUploadResult CompleteUpload( + wstring& api_url, + wstring& api_key, + const wstring& upload_key, + const wstring& debug_file, + const wstring& debug_id); + + // Returns whether or not a symbol file corresponding to the debug_file/ + // debug_id pair is already present in symbol storage. + static SymbolStatus CheckSymbolStatus( + wstring& api_url, + wstring& api_key, + const wstring& debug_file, + const wstring& debug_id); + }; + +} // namespace google_breakpad + +#endif // COMMON_WINDOWS_SYMBOL_COLLECTOR_CLIENT_H_ diff --git a/src/tools/windows/symupload/symupload.cc b/src/tools/windows/symupload/symupload.cc index ef444798..708a0bdf 100644 --- a/src/tools/windows/symupload/symupload.cc +++ b/src/tools/windows/symupload/symupload.cc @@ -56,12 +56,17 @@ #include "common/windows/http_upload.h" #include "common/windows/pdb_source_line_writer.h" +#include "common/windows/symbol_collector_client.h" using std::string; using std::wstring; using std::vector; using std::map; using google_breakpad::HTTPUpload; +using google_breakpad::SymbolCollectorClient; +using google_breakpad::SymbolStatus; +using google_breakpad::UploadUrlResponse; +using google_breakpad::CompleteUploadResult; using google_breakpad::PDBModuleInfo; using google_breakpad::PDBSourceLineWriter; using google_breakpad::WindowsStringUtils; @@ -154,6 +159,92 @@ static bool DumpSymbolsToTempFile(const wchar_t *file, return writer.GetModuleInfo(pdb_info); } +static bool DoSymUploadV2( + const wchar_t* api_url, + const wchar_t* api_key, + const wstring& debug_file, + const wstring& debug_id, + const wstring& symbol_file, + bool force) { + wstring url(api_url); + wstring key(api_key); + + if (!force) { + SymbolStatus symbolStatus = SymbolCollectorClient::CheckSymbolStatus( + url, + key, + debug_file, + debug_id); + if (symbolStatus == SymbolStatus::Found) { + wprintf(L"Symbol file already exists, upload aborted." + L" Use \"-f\" to overwrite.\n"); + return true; + } + else if (symbolStatus == SymbolStatus::Unknown) { + wprintf(L"Failed to get check for existing symbol.\n"); + return false; + } + } + + UploadUrlResponse uploadUrlResponse; + if (!SymbolCollectorClient::CreateUploadUrl( + url, + key, + &uploadUrlResponse)) { + wprintf(L"Failed to create upload URL.\n"); + return false; + } + + wstring signed_url = uploadUrlResponse.upload_url; + wstring upload_key = uploadUrlResponse.upload_key; + wstring response; + int response_code; + bool success = HTTPUpload::SendPutRequest( + signed_url, + symbol_file, + /* timeout = */ NULL, + &response, + &response_code); + if (!success) { + wprintf(L"Failed to send symbol file.\n"); + wprintf(L"Response code: %ld\n", response_code); + wprintf(L"Response:\n"); + wprintf(L"%s\n", response.c_str()); + return false; + } + else if (response_code == 0) { + wprintf(L"Failed to send symbol file: No response code\n"); + return false; + } + else if (response_code != 200) { + wprintf(L"Failed to send symbol file: Response code %ld\n", response_code); + wprintf(L"Response:\n"); + wprintf(L"%s\n", response.c_str()); + return false; + } + + CompleteUploadResult completeUploadResult = + SymbolCollectorClient::CompleteUpload( + url, + key, + upload_key, + debug_file, + debug_id); + if (completeUploadResult == CompleteUploadResult::Error) { + wprintf(L"Failed to complete upload.\n"); + return false; + } + else if (completeUploadResult == CompleteUploadResult::DuplicateData) { + wprintf(L"Uploaded file checksum matched existing file checksum," + L" no change necessary.\n"); + } + else { + wprintf(L"Successfully sent the symbol file.\n"); + } + + return true; +} + __declspec(noreturn) void printUsageAndExit() { wprintf(L"Usage:\n\n" L" symupload [--timeout NN] [--product product_name] ^\n" @@ -166,13 +257,28 @@ __declspec(noreturn) void printUsageAndExit() { wprintf(L"Example:\n\n" L" symupload.exe --timeout 0 --product Chrome ^\n" L" chrome.dll http://no.free.symbol.server.for.you\n"); + wprintf(L"\n"); + wprintf(L"sym-upload-v2 usage:\n" + L" symupload -p [-f] \n"); + wprintf(L"\n"); + wprintf(L"sym_upload_v2 Options:\n"); + wprintf(L" is the sym_upload_v2 API URL.\n"); + wprintf(L" is a secret used to authenticate with the API.\n"); + wprintf(L" -p:\t Use sym_upload_v2 protocol.\n"); + wprintf(L" -f:\t Force symbol upload if already exists.\n"); + exit(0); } + int wmain(int argc, wchar_t *argv[]) { const wchar_t *module; const wchar_t *product = nullptr; int timeout = -1; int currentarg = 1; + bool use_sym_upload_v2 = false; + bool force = false; + const wchar_t* api_url = nullptr; + const wchar_t* api_key = nullptr; while (argc > currentarg + 1) { if (!wcscmp(L"--timeout", argv[currentarg])) { timeout = _wtoi(argv[currentarg + 1]); @@ -184,6 +290,16 @@ int wmain(int argc, wchar_t *argv[]) { currentarg += 2; continue; } + if (!wcscmp(L"-p", argv[currentarg])) { + use_sym_upload_v2 = true; + ++currentarg; + continue; + } + if (!wcscmp(L"-f", argv[currentarg])) { + force = true; + ++currentarg; + continue; + } break; } @@ -200,50 +316,69 @@ int wmain(int argc, wchar_t *argv[]) { } wstring code_file = WindowsStringUtils::GetBaseName(wstring(module)); - - map parameters; - parameters[L"code_file"] = code_file; - parameters[L"debug_file"] = pdb_info.debug_file; - parameters[L"debug_identifier"] = pdb_info.debug_identifier; - parameters[L"os"] = L"windows"; // This version of symupload is Windows-only - parameters[L"cpu"] = pdb_info.cpu; - - // Don't make a missing product name a hard error. Issue a warning and let - // the server decide whether to reject files without product name. - if (product) { - parameters[L"product"] = product; - } else { - fwprintf( - stderr, - L"Warning: No product name (flag --product) was specified for %s\n", - module); - } - + wstring file_version; // Don't make a missing version a hard error. Issue a warning, and let the // server decide whether to reject files without versions. - wstring file_version; - if (GetFileVersionString(module, &file_version)) { - parameters[L"version"] = file_version; - } else { + if (!GetFileVersionString(module, &file_version)) { fwprintf(stderr, L"Warning: Could not get file version for %s\n", module); } - map files; - files[L"symbol_file"] = symbol_file; - bool success = true; - while (currentarg < argc) { - int response_code; - if (!HTTPUpload::SendRequest(argv[currentarg], parameters, files, - timeout == -1 ? NULL : &timeout, - nullptr, &response_code)) { - success = false; - fwprintf(stderr, - L"Symbol file upload to %s failed. Response code = %ld\n", - argv[currentarg], response_code); + if (use_sym_upload_v2) { + if (argc >= currentarg + 2) { + api_url = argv[currentarg++]; + api_key = argv[currentarg++]; + + success = DoSymUploadV2( + api_url, + api_key, + pdb_info.debug_file, + pdb_info.debug_identifier, + symbol_file, + force); + } else { + printUsageAndExit(); + } + } else { + map parameters; + parameters[L"code_file"] = code_file; + parameters[L"debug_file"] = pdb_info.debug_file; + parameters[L"debug_identifier"] = pdb_info.debug_identifier; + parameters[L"os"] = L"windows"; // This version of symupload is Windows-only + parameters[L"cpu"] = pdb_info.cpu; + + map files; + files[L"symbol_file"] = symbol_file; + + if (!file_version.empty()) { + parameters[L"version"] = file_version; + } + + // Don't make a missing product name a hard error. Issue a warning and let + // the server decide whether to reject files without product name. + if (product) { + parameters[L"product"] = product; + } + else { + fwprintf( + stderr, + L"Warning: No product name (flag --product) was specified for %s\n", + module); + } + + while (currentarg < argc) { + int response_code; + if (!HTTPUpload::SendMultipartPostRequest(argv[currentarg], parameters, files, + timeout == -1 ? NULL : &timeout, + nullptr, &response_code)) { + success = false; + fwprintf(stderr, + L"Symbol file upload to %s failed. Response code = %ld\n", + argv[currentarg], response_code); + } + currentarg++; } - currentarg++; } _wunlink(symbol_file.c_str());