From 92032389a6a028cbc896a8f3d4508b525e014637 Mon Sep 17 00:00:00 2001 From: Nelson Billing Date: Fri, 14 Jun 2019 15:51:23 -0700 Subject: Move Windows Symbol Converter to breakpad - First step, this is just enough to get it generating a msbuild project with GYP, which in turn can build the executable. - Tests need to be redesigned because there isn't an available server. Change-Id: I45440fd32b3ede29666c127703bcd441f0e4288e Reviewed-on: https://chromium-review.googlesource.com/c/breakpad/breakpad/+/1661134 Reviewed-by: Ivan Penkov --- .../windows/converter_exe/binary/configure.cmd | 33 + .../converter_exe/binary/missing_symbols_test.txt | 2 + src/tools/windows/converter_exe/binary/sleep.exe | Bin 0 -> 13312 bytes src/tools/windows/converter_exe/binary/symsrv.yes | 2 + src/tools/windows/converter_exe/configure.cmd | 33 + src/tools/windows/converter_exe/converter.cc | 807 +++++++++++++++++++++ src/tools/windows/converter_exe/converter.gyp | 57 ++ src/tools/windows/converter_exe/escaping.cc | 757 +++++++++++++++++++ src/tools/windows/converter_exe/escaping.h | 99 +++ src/tools/windows/converter_exe/http_client.h | 96 +++ src/tools/windows/converter_exe/http_download.cc | 326 +++++++++ src/tools/windows/converter_exe/http_download.h | 62 ++ .../windows/converter_exe/missing_symbols_test.txt | 2 + src/tools/windows/converter_exe/symsrv.yes | 2 + src/tools/windows/converter_exe/tokenizer.cc | 61 ++ src/tools/windows/converter_exe/tokenizer.h | 51 ++ src/tools/windows/converter_exe/winhttp_client.cc | 307 ++++++++ src/tools/windows/converter_exe/winhttp_client.h | 40 + src/tools/windows/converter_exe/wininet_client.cc | 278 +++++++ src/tools/windows/converter_exe/wininet_client.h | 40 + src/tools/windows/tools_windows.gyp | 1 + 21 files changed, 3056 insertions(+) create mode 100644 src/tools/windows/converter_exe/binary/configure.cmd create mode 100644 src/tools/windows/converter_exe/binary/missing_symbols_test.txt create mode 100644 src/tools/windows/converter_exe/binary/sleep.exe create mode 100644 src/tools/windows/converter_exe/binary/symsrv.yes create mode 100644 src/tools/windows/converter_exe/configure.cmd create mode 100644 src/tools/windows/converter_exe/converter.cc create mode 100644 src/tools/windows/converter_exe/converter.gyp create mode 100644 src/tools/windows/converter_exe/escaping.cc create mode 100644 src/tools/windows/converter_exe/escaping.h create mode 100644 src/tools/windows/converter_exe/http_client.h create mode 100644 src/tools/windows/converter_exe/http_download.cc create mode 100644 src/tools/windows/converter_exe/http_download.h create mode 100644 src/tools/windows/converter_exe/missing_symbols_test.txt create mode 100644 src/tools/windows/converter_exe/symsrv.yes create mode 100644 src/tools/windows/converter_exe/tokenizer.cc create mode 100644 src/tools/windows/converter_exe/tokenizer.h create mode 100644 src/tools/windows/converter_exe/winhttp_client.cc create mode 100644 src/tools/windows/converter_exe/winhttp_client.h create mode 100644 src/tools/windows/converter_exe/wininet_client.cc create mode 100644 src/tools/windows/converter_exe/wininet_client.h diff --git a/src/tools/windows/converter_exe/binary/configure.cmd b/src/tools/windows/converter_exe/binary/configure.cmd new file mode 100644 index 00000000..39b1d2a5 --- /dev/null +++ b/src/tools/windows/converter_exe/binary/configure.cmd @@ -0,0 +1,33 @@ +@if "%ECHOON%"=="" @echo off +SETLOCAL + +REM ****************************************************************** +REM Please, make sure to run this in an Elevated Command Prompt. +REM Usage: +REM configure.cmd +REM ****************************************************************** + +REM ****************************************************************** +REM Initialize +REM ****************************************************************** +SET SCRIPT_LOCATION=%~dp0 + +REM ****************************************************************** +REM Go to script location +REM ****************************************************************** +pushd %SCRIPT_LOCATION% + +REM ****************************************************************** +REM Register msdia120.dll. +REM ****************************************************************** +SET MSG=Failed to register msdia120.dll. Make sure to run this in elevated command prompt. +%systemroot%\SysWoW64\regsvr32.exe /s msdia120.dll & if errorlevel 1 echo %MSG% & goto :fail + +:success +echo Configuration was successful. +ENDLOCAL +exit /b 0 + +:fail +ENDLOCAL +exit /b 1 diff --git a/src/tools/windows/converter_exe/binary/missing_symbols_test.txt b/src/tools/windows/converter_exe/binary/missing_symbols_test.txt new file mode 100644 index 00000000..251b4eca --- /dev/null +++ b/src/tools/windows/converter_exe/binary/missing_symbols_test.txt @@ -0,0 +1,2 @@ +msctf.pdb|6A5BABB8E88644C696530BFE3C90F32F2|6.1.7600.16385|msctf.dll|4A5BDFAA109000 +imm32.pdb|98F27BA5AEE541ECBEE00CD03AD50FEE2|6.1.7600.16385|imm32.dll|4A5BDF402e000 diff --git a/src/tools/windows/converter_exe/binary/sleep.exe b/src/tools/windows/converter_exe/binary/sleep.exe new file mode 100644 index 00000000..d178e17e Binary files /dev/null and b/src/tools/windows/converter_exe/binary/sleep.exe differ diff --git a/src/tools/windows/converter_exe/binary/symsrv.yes b/src/tools/windows/converter_exe/binary/symsrv.yes new file mode 100644 index 00000000..1d01dda7 --- /dev/null +++ b/src/tools/windows/converter_exe/binary/symsrv.yes @@ -0,0 +1,2 @@ +See breakpad/tools/windows/converter/ms_symbol_server_converter.h for a +description of this file's function. diff --git a/src/tools/windows/converter_exe/configure.cmd b/src/tools/windows/converter_exe/configure.cmd new file mode 100644 index 00000000..39b1d2a5 --- /dev/null +++ b/src/tools/windows/converter_exe/configure.cmd @@ -0,0 +1,33 @@ +@if "%ECHOON%"=="" @echo off +SETLOCAL + +REM ****************************************************************** +REM Please, make sure to run this in an Elevated Command Prompt. +REM Usage: +REM configure.cmd +REM ****************************************************************** + +REM ****************************************************************** +REM Initialize +REM ****************************************************************** +SET SCRIPT_LOCATION=%~dp0 + +REM ****************************************************************** +REM Go to script location +REM ****************************************************************** +pushd %SCRIPT_LOCATION% + +REM ****************************************************************** +REM Register msdia120.dll. +REM ****************************************************************** +SET MSG=Failed to register msdia120.dll. Make sure to run this in elevated command prompt. +%systemroot%\SysWoW64\regsvr32.exe /s msdia120.dll & if errorlevel 1 echo %MSG% & goto :fail + +:success +echo Configuration was successful. +ENDLOCAL +exit /b 0 + +:fail +ENDLOCAL +exit /b 1 diff --git a/src/tools/windows/converter_exe/converter.cc b/src/tools/windows/converter_exe/converter.cc new file mode 100644 index 00000000..23216f92 --- /dev/null +++ b/src/tools/windows/converter_exe/converter.cc @@ -0,0 +1,807 @@ +// Copyright 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. + +#pragma comment(lib, "winhttp.lib") +#pragma comment(lib, "wininet.lib") +#pragma comment(lib, "diaguids.lib") +#pragma comment(lib, "imagehlp.lib") + +#include +#include +#include +#include +#include +#include +#include + +#include "tools/windows/converter_exe/escaping.h" +#include "tools/windows/converter_exe/http_download.h" +#include "tools/windows/converter_exe/tokenizer.h" +#include "common/windows/http_upload.h" +#include "common/windows/string_utils-inl.h" +#include "tools/windows/converter/ms_symbol_server_converter.h" + +using strings::WebSafeBase64Unescape; +using strings::WebSafeBase64Escape; + +namespace { + +using std::map; +using std::string; +using std::vector; +using std::wstring; +using crash::HTTPDownload; +using crash::Tokenizer; +using google_breakpad::HTTPUpload; +using google_breakpad::MissingSymbolInfo; +using google_breakpad::MSSymbolServerConverter; +using google_breakpad::WindowsStringUtils; + +const char *kMissingStringDelimiters = "|"; +const char *kLocalCachePath = "c:\\symbols"; +const char *kNoExeMSSSServer = "http://msdl.microsoft.com/download/symbols/"; + +const int kMatchArrSize = 64; + +// Windows stdio doesn't do line buffering. Use this function to flush after +// writing to stdout and stderr so that a log will be available if the +// converter crashes. +static int FprintfFlush(FILE *file, const char *format, ...) { + va_list arguments; + va_start(arguments, format); + int retval = vfprintf(file, format, arguments); + va_end(arguments); + fflush(file); + return retval; +} + +static string CurrentDateAndTime() { + const string kUnknownDateAndTime = "????-??-?? ??:??:??"; + + time_t current_time; + time(¤t_time); + + // localtime_s is safer but is only available in MSVC8. Use localtime + // in earlier environments. + struct tm *time_pointer; +#if _MSC_VER >= 1400 // MSVC 2005/8 + struct tm time_struct; + time_pointer = &time_struct; + if (localtime_s(time_pointer, ¤t_time) != 0) { + return kUnknownDateAndTime; + } +#else // _MSC_VER >= 1400 + time_pointer = localtime(¤t_time); + if (!time_pointer) { + return kUnknownDateAndTime; + } +#endif // _MSC_VER >= 1400 + + char buffer[256]; + if (!strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", time_pointer)) { + return kUnknownDateAndTime; + } + + return string(buffer); +} + +// ParseMissingString turns |missing_string| into a MissingSymbolInfo +// structure. It returns true on success, and false if no such conversion +// is possible. +static bool ParseMissingString(const string &missing_string, + MissingSymbolInfo *missing_info) { + assert(missing_info); + + vector tokens; + Tokenizer::Tokenize(kMissingStringDelimiters, missing_string, &tokens); + if (tokens.size() != 5) { + return false; + } + + missing_info->debug_file = tokens[0]; + missing_info->debug_identifier = tokens[1]; + missing_info->version = tokens[2]; + missing_info->code_file = tokens[3]; + missing_info->code_identifier = tokens[4]; + + return true; +} + +// StringMapToWStringMap takes each element in a map that associates +// (narrow) strings to strings and converts the keys and values to wstrings. +// Returns true on success and false on failure, printing an error message. +static bool StringMapToWStringMap(const map &smap, + map *wsmap) { + assert(wsmap); + wsmap->clear(); + + for (map::const_iterator iterator = smap.begin(); + iterator != smap.end(); + ++iterator) { + wstring key; + if (!WindowsStringUtils::safe_mbstowcs(iterator->first, &key)) { + FprintfFlush(stderr, + "StringMapToWStringMap: safe_mbstowcs failed for key %s\n", + iterator->first.c_str()); + return false; + } + + wstring value; + if (!WindowsStringUtils::safe_mbstowcs(iterator->second, &value)) { + FprintfFlush(stderr, "StringMapToWStringMap: safe_mbstowcs failed " + "for value %s\n", + iterator->second.c_str()); + return false; + } + + wsmap->insert(make_pair(key, value)); + } + + return true; +} + +// MissingSymbolInfoToParameters turns a MissingSymbolInfo structure into a +// map of parameters suitable for passing to HTTPDownload or HTTPUpload. +// Returns true on success and false on failure, printing an error message. +static bool MissingSymbolInfoToParameters(const MissingSymbolInfo &missing_info, + map *wparameters) { + assert(wparameters); + + map parameters; + string encoded_param; + // Indicate the params are encoded. + parameters["encoded"] = "true"; // The string value here does not matter. + + WebSafeBase64Escape(missing_info.code_file, &encoded_param); + parameters["code_file"] = encoded_param; + + WebSafeBase64Escape(missing_info.code_identifier, &encoded_param); + parameters["code_identifier"] = encoded_param; + + WebSafeBase64Escape(missing_info.debug_file, &encoded_param); + parameters["debug_file"] = encoded_param; + + WebSafeBase64Escape(missing_info.debug_identifier, &encoded_param); + parameters["debug_identifier"] = encoded_param; + + if (!missing_info.version.empty()) { + // The version is optional. + WebSafeBase64Escape(missing_info.version, &encoded_param); + parameters["version"] = encoded_param; + } + + WebSafeBase64Escape("WinSymConv", &encoded_param); + parameters["product"] = encoded_param; + + if (!StringMapToWStringMap(parameters, wparameters)) { + // StringMapToWStringMap will have printed an error. + return false; + } + + return true; +} + +// UploadSymbolFile sends |converted_file| as identified by |missing_info| +// to the symbol server rooted at |upload_symbol_url|. Returns true on +// success and false on failure, printing an error message. +static bool UploadSymbolFile(const wstring &upload_symbol_url, + const MissingSymbolInfo &missing_info, + const string &converted_file) { + map parameters; + if (!MissingSymbolInfoToParameters(missing_info, ¶meters)) { + // MissingSymbolInfoToParameters or a callee will have printed an error. + return false; + } + + wstring converted_file_w; + + if (!WindowsStringUtils::safe_mbstowcs(converted_file, &converted_file_w)) { + FprintfFlush(stderr, "UploadSymbolFile: safe_mbstowcs failed for %s\n", + converted_file.c_str()); + return false; + } + map files; + files[L"symbol_file"] = converted_file_w; + + FprintfFlush(stderr, "Uploading %s\n", converted_file.c_str()); + if (!HTTPUpload::SendRequest( + upload_symbol_url, parameters, + files, NULL, NULL, NULL)) { + FprintfFlush(stderr, "UploadSymbolFile: HTTPUpload::SendRequest failed " + "for %s %s %s\n", + missing_info.debug_file.c_str(), + missing_info.debug_identifier.c_str(), + missing_info.version.c_str()); + return false; + } + + return true; +} + +// SendFetchFailedPing informs the symbol server based at +// |fetch_symbol_failure_url| that the symbol file identified by +// |missing_info| could authoritatively not be located. Returns +// true on success and false on failure. +static bool SendFetchFailedPing(const wstring &fetch_symbol_failure_url, + const MissingSymbolInfo &missing_info) { + map parameters; + if (!MissingSymbolInfoToParameters(missing_info, ¶meters)) { + // MissingSymbolInfoToParameters or a callee will have printed an error. + return false; + } + + string content; + if (!HTTPDownload::Download(fetch_symbol_failure_url, + ¶meters, + &content, + NULL)) { + FprintfFlush(stderr, "SendFetchFailedPing: HTTPDownload::Download failed " + "for %s %s %s\n", + missing_info.debug_file.c_str(), + missing_info.debug_identifier.c_str(), + missing_info.version.c_str()); + return false; + } + + return true; +} + +// Returns true if it's safe to make an external request for the symbol +// file described in missing_info. It's considered safe to make an +// external request unless the symbol file's debug_file string matches +// the given blacklist regular expression. +// The debug_file name is used from the MissingSymbolInfo struct, +// matched against the PCRE blacklist_regex. +static bool SafeToMakeExternalRequest(const MissingSymbolInfo &missing_info, + std::regex blacklist_regex) { + string file_name = missing_info.debug_file; + if (std::regex_match(file_name, blacklist_regex)) { + FprintfFlush(stderr, "Not safe to make external request for file %s\n", + file_name.c_str()); + return false; + } + + return true; +} + +// Converter options derived from command line parameters. +struct ConverterOptions { + ConverterOptions() + : report_fetch_failures(true), + blacklist_regex(nullptr) { + } + + ~ConverterOptions() { + } + + // Names of MS Symbol Supplier Servers that are internal to Google, and may + // have symbols for any request. + vector full_internal_msss_servers; + + // Names of MS Symbol Supplier Servers that are internal to Google, and + // shouldn't be checked for symbols for any .exe files. + vector full_external_msss_servers; + + // Names of MS Symbol Supplier Servers that are external to Google, and may + // have symbols for any request. + vector no_exe_internal_msss_servers; + + // Names of MS Symbol Supplier Servers that are external to Google, and + // shouldn't be checked for symbols for any .exe files. + vector no_exe_external_msss_servers; + + // Temporary local storage for symbols. + string local_cache_path; + + // URL for uploading symbols. + wstring upload_symbols_url; + + // URL to fetch list of missing symbols. + wstring missing_symbols_url; + + // URL to report symbol fetch failure. + wstring fetch_symbol_failure_url; + + // Are symbol fetch failures reported. + bool report_fetch_failures; + + // File containing the list of missing symbols. Fetch failures are not + // reported if such file is provided. + string missing_symbols_file; + + // Regex used to blacklist files to prevent external symbol requests. + // Owned and cleaned up by this struct. + std::regex blacklist_regex; + + private: + // DISABLE_COPY_AND_ASSIGN + ConverterOptions(const ConverterOptions&); + ConverterOptions& operator=(const ConverterOptions&); +}; + +// ConverMissingSymbolFile takes a single MissingSymbolInfo structure and +// attempts to locate it from the symbol servers provided in the +// |options.*_msss_servers| arguments. "Full" servers are those that will be +// queried for all symbol files; "No-EXE" servers will only be queried for +// modules whose missing symbol data indicates are not main program executables. +// Results will be sent to the |options.upload_symbols_url| on success or +// |options.fetch_symbol_failure_url| on failure, and the local cache will be +// stored at |options.local_cache_path|. Because nothing can be done even in +// the event of a failure, this function returns no value, although it +// may result in error messages being printed. +static void ConvertMissingSymbolFile(const MissingSymbolInfo &missing_info, + const ConverterOptions &options) { + string time_string = CurrentDateAndTime(); + FprintfFlush(stdout, "converter: %s: attempting %s %s %s\n", + time_string.c_str(), + missing_info.debug_file.c_str(), + missing_info.debug_identifier.c_str(), + missing_info.version.c_str()); + + // The first lookup is always to internal symbol servers. + // Always ask the symbol servers identified as "full." + vector msss_servers = options.full_internal_msss_servers; + + // If the file is not an .exe file, also ask an additional set of symbol + // servers, such as Microsoft's public symbol server. + bool is_exe = false; + + if (missing_info.code_file.length() >= 4) { + string code_extension = + missing_info.code_file.substr(missing_info.code_file.size() - 4); + + // Firefox is a special case: .dll-only servers should be consulted for + // its symbols. This enables us to get its symbols from Mozilla's + // symbol server when crashes occur in Google extension code hosted by a + // Firefox process. + if (_stricmp(code_extension.c_str(), ".exe") == 0 && + _stricmp(missing_info.code_file.c_str(), "firefox.exe") != 0) { + is_exe = true; + } + } + + if (!is_exe) { + msss_servers.insert(msss_servers.end(), + options.no_exe_internal_msss_servers.begin(), + options.no_exe_internal_msss_servers.end()); + } + + // If there are any suitable internal symbol servers, make a request. + MSSymbolServerConverter::LocateResult located = + MSSymbolServerConverter::LOCATE_FAILURE; + string converted_file; + if (msss_servers.size() > 0) { + // Attempt to fetch the symbol file and convert it. + FprintfFlush(stderr, "Making internal request for %s (%s)\n", + missing_info.debug_file.c_str(), + missing_info.debug_identifier.c_str()); + MSSymbolServerConverter converter(options.local_cache_path, msss_servers); + located = converter.LocateAndConvertSymbolFile(missing_info, + false, // keep_symbol_file + false, // keep_pe_file + &converted_file, + NULL, // symbol_file + NULL); // pe_file + switch (located) { + case MSSymbolServerConverter::LOCATE_SUCCESS: + FprintfFlush(stderr, "LocateResult = LOCATE_SUCCESS\n"); + // Upload it. Don't bother checking the return value. If this + // succeeds, it should disappear from the missing symbol list. + // If it fails, something will print an error message indicating + // the cause of the failure, and the item will remain on the + // missing symbol list. + UploadSymbolFile(options.upload_symbols_url, missing_info, + converted_file); + remove(converted_file.c_str()); + + // Note: this does leave some directories behind that could be + // cleaned up. The directories inside options.local_cache_path for + // debug_file/debug_identifier can be removed at this point. + break; + + case MSSymbolServerConverter::LOCATE_NOT_FOUND: + FprintfFlush(stderr, "LocateResult = LOCATE_NOT_FOUND\n"); + // The symbol file definitively did not exist. Fall through, + // so we can attempt an external query if it's safe to do so. + break; + + case MSSymbolServerConverter::LOCATE_RETRY: + FprintfFlush(stderr, "LocateResult = LOCATE_RETRY\n"); + // Fall through in case we should make an external request. + // If not, or if an external request fails in the same way, + // we'll leave the entry in the symbol file list and + // try again on a future pass. Print a message so that there's + // a record. + break; + + case MSSymbolServerConverter::LOCATE_FAILURE: + FprintfFlush(stderr, "LocateResult = LOCATE_FAILURE\n"); + // LocateAndConvertSymbolFile printed an error message. + break; + + default: + FprintfFlush( + stderr, + "FATAL: Unexpected return value '%d' from " + "LocateAndConvertSymbolFile()\n", + located); + assert(0); + break; + } + } else { + // No suitable internal symbol servers. This is fine because the converter + // is mainly used for downloading and converting of external symbols. + } + + // Make a request to an external server if the internal request didn't + // succeed, and it's safe to do so. + if (located != MSSymbolServerConverter::LOCATE_SUCCESS && + SafeToMakeExternalRequest(missing_info, options.blacklist_regex)) { + msss_servers = options.full_external_msss_servers; + if (!is_exe) { + msss_servers.insert(msss_servers.end(), + options.no_exe_external_msss_servers.begin(), + options.no_exe_external_msss_servers.end()); + } + if (msss_servers.size() > 0) { + FprintfFlush(stderr, "Making external request for %s (%s)\n", + missing_info.debug_file.c_str(), + missing_info.debug_identifier.c_str()); + MSSymbolServerConverter external_converter(options.local_cache_path, + msss_servers); + located = external_converter.LocateAndConvertSymbolFile( + missing_info, + false, // keep_symbol_file + false, // keep_pe_file + &converted_file, + NULL, // symbol_file + NULL); // pe_file + } else { + FprintfFlush(stderr, "ERROR: No suitable external symbol servers.\n"); + } + } + + // Final handling for this symbol file is based on the result from the + // external request (if performed above), or on the result from the + // previous internal lookup. + switch (located) { + case MSSymbolServerConverter::LOCATE_SUCCESS: + FprintfFlush(stderr, "LocateResult = LOCATE_SUCCESS\n"); + // Upload it. Don't bother checking the return value. If this + // succeeds, it should disappear from the missing symbol list. + // If it fails, something will print an error message indicating + // the cause of the failure, and the item will remain on the + // missing symbol list. + UploadSymbolFile(options.upload_symbols_url, missing_info, + converted_file); + remove(converted_file.c_str()); + + // Note: this does leave some directories behind that could be + // cleaned up. The directories inside options.local_cache_path for + // debug_file/debug_identifier can be removed at this point. + break; + + case MSSymbolServerConverter::LOCATE_NOT_FOUND: + // The symbol file definitively didn't exist. Inform the server. + // If this fails, something will print an error message indicating + // the cause of the failure, but there's really nothing more to + // do. If this succeeds, the entry should be removed from the + // missing symbols list. + if (!options.report_fetch_failures) { + FprintfFlush(stderr, "SendFetchFailedPing skipped\n"); + } else if (SendFetchFailedPing(options.fetch_symbol_failure_url, + missing_info)) { + FprintfFlush(stderr, "SendFetchFailedPing succeeded\n"); + } else { + FprintfFlush(stderr, "SendFetchFailedPing failed\n"); + } + break; + + case MSSymbolServerConverter::LOCATE_RETRY: + FprintfFlush(stderr, "LocateResult = LOCATE_RETRY\n"); + // Nothing to do but leave the entry in the symbol file list and + // try again on a future pass. Print a message so that there's + // a record. + FprintfFlush(stderr, "ConvertMissingSymbolFile: deferring retry " + "for %s %s %s\n", + missing_info.debug_file.c_str(), + missing_info.debug_identifier.c_str(), + missing_info.version.c_str()); + break; + + case MSSymbolServerConverter::LOCATE_FAILURE: + FprintfFlush(stderr, "LocateResult = LOCATE_FAILURE\n"); + // LocateAndConvertSymbolFile printed an error message. + + // This is due to a bad debug file name, so fetch failed. + if (!options.report_fetch_failures) { + FprintfFlush(stderr, "SendFetchFailedPing skipped\n"); + } else if (SendFetchFailedPing(options.fetch_symbol_failure_url, + missing_info)) { + FprintfFlush(stderr, "SendFetchFailedPing succeeded\n"); + } else { + FprintfFlush(stderr, "SendFetchFailedPing failed\n"); + } + break; + + default: + FprintfFlush( + stderr, + "FATAL: Unexpected return value '%d' from " + "LocateAndConvertSymbolFile()\n", + located); + assert(0); + break; + } +} + + +// Reads the contents of file |file_name| and populates |contents|. +// Returns true on success. +static bool ReadFile(string file_name, string *contents) { + char buffer[1024 * 8]; + FILE *fp = fopen(file_name.c_str(), "rt"); + if (!fp) { + return false; + } + contents->clear(); + while (fgets(buffer, sizeof(buffer), fp) != NULL) { + contents->append(buffer); + } + fclose(fp); + return true; +} + +// ConvertMissingSymbolsList obtains a missing symbol list from +// |options.missing_symbols_url| or |options.missing_symbols_file| and calls +// ConvertMissingSymbolFile for each missing symbol file in the list. +static bool ConvertMissingSymbolsList(const ConverterOptions &options) { + // Set param to indicate requesting for encoded response. + map parameters; + parameters[L"product"] = L"WinSymConv"; + parameters[L"encoded"] = L"true"; + // Get the missing symbol list. + string missing_symbol_list; + if (!options.missing_symbols_file.empty()) { + if (!ReadFile(options.missing_symbols_file, &missing_symbol_list)) { + return false; + } + } else if (!HTTPDownload::Download(options.missing_symbols_url, ¶meters, + &missing_symbol_list, NULL)) { + return false; + } + + // Tokenize the content into a vector. + vector missing_symbol_lines; + Tokenizer::Tokenize("\n", missing_symbol_list, &missing_symbol_lines); + + FprintfFlush(stderr, "Found %d missing symbol files in list.\n", + missing_symbol_lines.size() - 1); // last line is empty. + int convert_attempts = 0; + for (vector::const_iterator iterator = missing_symbol_lines.begin(); + iterator != missing_symbol_lines.end(); + ++iterator) { + // Decode symbol line. + const string &encoded_line = *iterator; + // Skip lines that are blank. + if (encoded_line.empty()) { + continue; + } + + string line; + if (!WebSafeBase64Unescape(encoded_line, &line)) { + // If decoding fails, assume the line is not encoded. + // This is helpful when the program connects to a debug server without + // encoding. + line = encoded_line; + } + + FprintfFlush(stderr, "\nLine: %s\n", line.c_str()); + + // Turn each element into a MissingSymbolInfo structure. + MissingSymbolInfo missing_info; + if (!ParseMissingString(line, &missing_info)) { + FprintfFlush(stderr, "ConvertMissingSymbols: ParseMissingString failed " + "for %s from %ws\n", + line.c_str(), options.missing_symbols_url.c_str()); + continue; + } + + ++convert_attempts; + ConvertMissingSymbolFile(missing_info, options); + } + + // Say something reassuring, since ConvertMissingSymbolFile was never called + // and therefore never reported any progress. + if (convert_attempts == 0) { + string current_time = CurrentDateAndTime(); + FprintfFlush(stdout, "converter: %s: nothing to convert\n", + current_time.c_str()); + } + + return true; +} + +// usage prints the usage message. It returns 1 as a convenience, to be used +// as a return value from main. +static int usage(const char *program_name) { + FprintfFlush(stderr, + "usage: %s [options]\n" + " -f MS servers to ask for all symbols\n" + " -n same, but prevent asking for EXEs\n" + " -l Temporary local storage for symbols\n" + " -s URL for uploading symbols\n" + " -m URL to fetch list of missing symbols\n" + " -mf File containing the list of missing\n" + " symbols. Fetch failures are not\n" + " reported if such file is provided.\n" + " -t URL to report symbol fetch failure\n" + " -b Regex used to blacklist files to\n" + " prevent external symbol requests\n" + " Note that any server specified by -f or -n that starts with \\filer\n" + " will be treated as internal, and all others as external.\n", + program_name); + + return 1; +} + +// "Internal" servers consist only of those whose names start with +// the literal string "\\filer\". +static bool IsInternalServer(const string &server_name) { + if (server_name.find("\\\\filer\\") == 0) { + return true; + } + return false; +} + +// Adds a server with the given name to the list of internal or external +// servers, as appropriate. +static void AddServer(const string &server_name, + vector *internal_servers, + vector *external_servers) { + if (IsInternalServer(server_name)) { + internal_servers->push_back(server_name); + } else { + external_servers->push_back(server_name); + } +} + +} // namespace + +int main(int argc, char **argv) { + string time_string = CurrentDateAndTime(); + FprintfFlush(stdout, "converter: %s: starting\n", time_string.c_str()); + + ConverterOptions options; + options.report_fetch_failures = true; + + // All arguments are paired. + if (argc % 2 != 1) { + return usage(argv[0]); + } + string blacklist_regex_str; + bool have_any_msss_servers = false; + for (int argi = 1; argi < argc; argi += 2) { + string option = argv[argi]; + string value = argv[argi + 1]; + + if (option == "-f") { + AddServer(value, &options.full_internal_msss_servers, + &options.full_external_msss_servers); + have_any_msss_servers = true; + } else if (option == "-n") { + AddServer(value, &options.no_exe_internal_msss_servers, + &options.no_exe_external_msss_servers); + have_any_msss_servers = true; + } else if (option == "-l") { + if (!options.local_cache_path.empty()) { + return usage(argv[0]); + } + options.local_cache_path = value; + } else if (option == "-s") { + if (!WindowsStringUtils::safe_mbstowcs(value, + &options.upload_symbols_url)) { + FprintfFlush(stderr, "main: safe_mbstowcs failed for %s\n", + value.c_str()); + return 1; + } + } else if (option == "-m") { + if (!WindowsStringUtils::safe_mbstowcs(value, + &options.missing_symbols_url)) { + FprintfFlush(stderr, "main: safe_mbstowcs failed for %s\n", + value.c_str()); + return 1; + } + } else if (option == "-mf") { + options.missing_symbols_file = value; + printf("Getting the list of missing symbols from a file. Fetch failures" + " will not be reported.\n"); + options.report_fetch_failures = false; + } else if (option == "-t") { + if (!WindowsStringUtils::safe_mbstowcs( + value, + &options.fetch_symbol_failure_url)) { + FprintfFlush(stderr, "main: safe_mbstowcs failed for %s\n", + value.c_str()); + return 1; + } + } else if (option == "-b") { + blacklist_regex_str = value; + } else { + return usage(argv[0]); + } + } + + if (blacklist_regex_str.empty()) { + FprintfFlush(stderr, "No blacklist specified.\n"); + return usage(argv[0]); + } + + // Compile the blacklist regular expression for later use. + options.blacklist_regex = std::regex(blacklist_regex_str.c_str(), + std::regex_constants::icase); + + // Set the defaults. If the user specified any MSSS servers, don't use + // any default. + if (!have_any_msss_servers) { + AddServer(kNoExeMSSSServer, &options.no_exe_internal_msss_servers, + &options.no_exe_external_msss_servers); + } + + if (options.local_cache_path.empty()) { + options.local_cache_path = kLocalCachePath; + } + + if (options.upload_symbols_url.empty()) { + FprintfFlush(stderr, "No upload symbols URL specified.\n"); + return usage(argv[0]); + } + if (options.missing_symbols_url.empty()) { + FprintfFlush(stderr, "No missing symbols URL specified.\n"); + return usage(argv[0]); + } + if (options.fetch_symbol_failure_url.empty()) { + FprintfFlush(stderr, "No fetch symbol failure URL specified.\n"); + return usage(argv[0]); + } + + FprintfFlush(stdout, + "# of Symbol Servers (int/ext): %d/%d full, %d/%d no_exe\n", + options.full_internal_msss_servers.size(), + options.full_external_msss_servers.size(), + options.no_exe_internal_msss_servers.size(), + options.no_exe_external_msss_servers.size()); + + if (!ConvertMissingSymbolsList(options)) { + return 1; + } + + time_string = CurrentDateAndTime(); + FprintfFlush(stdout, "converter: %s: finished\n", time_string.c_str()); + return 0; +} diff --git a/src/tools/windows/converter_exe/converter.gyp b/src/tools/windows/converter_exe/converter.gyp new file mode 100644 index 00000000..fe443d1b --- /dev/null +++ b/src/tools/windows/converter_exe/converter.gyp @@ -0,0 +1,57 @@ +# Copyright 2013 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. + +{ + 'includes': [ + '../../../build/common.gypi', + ], + 'targets': [ + { + 'target_name': 'converter_exe', + 'type': 'executable', + 'sources': [ + 'converter.cc', + 'escaping.cc', + 'escaping.h', + 'http_client.h', + 'http_download.cc', + 'http_download.h', + 'tokenizer.cc', + 'tokenizer.h', + 'winhttp_client.cc', + 'winhttp_client.h', + 'wininet_client.cc', + 'wininet_client.h', + ], + 'dependencies': [ + '../../../common/windows/common_windows.gyp:common_windows_lib', + '../converter/ms_symbol_server_converter.gyp:ms_symbol_server_converter', + ], + }, + ], +} diff --git a/src/tools/windows/converter_exe/escaping.cc b/src/tools/windows/converter_exe/escaping.cc new file mode 100644 index 00000000..e26bbeb3 --- /dev/null +++ b/src/tools/windows/converter_exe/escaping.cc @@ -0,0 +1,757 @@ +// Copyright 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. + +#include "tools/windows/converter_exe/escaping.h" + +#include + +#define kApb kAsciiPropertyBits + +const unsigned char kAsciiPropertyBits[256] = { + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, // 0x00 + 0x40, 0x68, 0x48, 0x48, 0x48, 0x48, 0x40, 0x40, + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, // 0x10 + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, + 0x28, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, // 0x20 + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x84, 0x84, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x05, // 0x40 + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, // 0x50 + 0x05, 0x05, 0x05, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x05, // 0x60 + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, // 0x70 + 0x05, 0x05, 0x05, 0x10, 0x10, 0x10, 0x10, 0x40, +}; + +// Use !! to suppress the warning C4800 of forcing 'int' to 'bool'. +static inline bool ascii_isspace(unsigned char c) { return !!(kApb[c] & 0x08); } + +/////////////////////////////////// +// scoped_array +/////////////////////////////////// +// scoped_array is like scoped_ptr, except that the caller must allocate +// with new [] and the destructor deletes objects with delete []. +// +// As with scoped_ptr, a scoped_array either points to an object +// or is NULL. A scoped_array owns the object that it points to. +// scoped_array is thread-compatible, and once you index into it, +// the returned objects have only the threadsafety guarantees of T. +// +// Size: sizeof(scoped_array) == sizeof(C*) +template +class scoped_array { + public: + + // The element type + typedef C element_type; + + // Constructor. Defaults to intializing with NULL. + // There is no way to create an uninitialized scoped_array. + // The input parameter must be allocated with new []. + explicit scoped_array(C* p = NULL) : array_(p) { } + + // Destructor. If there is a C object, delete it. + // We don't need to test ptr_ == NULL because C++ does that for us. + ~scoped_array() { + enum { type_must_be_complete = sizeof(C) }; + delete[] array_; + } + + // Reset. Deletes the current owned object, if any. + // Then takes ownership of a new object, if given. + // this->reset(this->get()) works. + void reset(C* p = NULL) { + if (p != array_) { + enum { type_must_be_complete = sizeof(C) }; + delete[] array_; + array_ = p; + } + } + + // Get one element of the current object. + // Will assert() if there is no current object, or index i is negative. + C& operator[](std::ptrdiff_t i) const { + assert(i >= 0); + assert(array_ != NULL); + return array_[i]; + } + + // Get a pointer to the zeroth element of the current object. + // If there is no current object, return NULL. + C* get() const { + return array_; + } + + // Comparison operators. + // These return whether a scoped_array and a raw pointer refer to + // the same array, not just to two different but equal arrays. + bool operator==(const C* p) const { return array_ == p; } + bool operator!=(const C* p) const { return array_ != p; } + + // Swap two scoped arrays. + void swap(scoped_array& p2) { + C* tmp = array_; + array_ = p2.array_; + p2.array_ = tmp; + } + + // Release an array. + // The return value is the current pointer held by this object. + // If this object holds a NULL pointer, the return value is NULL. + // After this operation, this object will hold a NULL pointer, + // and will not own the object any more. + C* release() { + C* retVal = array_; + array_ = NULL; + return retVal; + } + + private: + C* array_; + + // Forbid comparison of different scoped_array types. + template bool operator==(scoped_array const& p2) const; + template bool operator!=(scoped_array const& p2) const; + + // Disallow evil constructors + scoped_array(const scoped_array&); + void operator=(const scoped_array&); +}; + + +/////////////////////////////////// +// Escape methods +/////////////////////////////////// + +namespace strings { + +// Return a mutable char* pointing to a string's internal buffer, +// which may not be null-terminated. Writing through this pointer will +// modify the string. +// +// string_as_array(&str)[i] is valid for 0 <= i < str.size() until the +// next call to a string method that invalidates iterators. +// +// As of 2006-04, there is no standard-blessed way of getting a +// mutable reference to a string's internal buffer. However, issue 530 +// (http://www.open-std.org/JTC1/SC22/WG21/docs/lwg-active.html#530) +// proposes this as the method. According to Matt Austern, this should +// already work on all current implementations. +inline char* string_as_array(string* str) { + // DO NOT USE const_cast(str->data())! See the unittest for why. + return str->empty() ? NULL : &*str->begin(); +} + +int CalculateBase64EscapedLen(int input_len, bool do_padding) { + // these formulae were copied from comments that used to go with the base64 + // encoding functions + int intermediate_result = 8 * input_len + 5; + assert(intermediate_result > 0); // make sure we didn't overflow + int len = intermediate_result / 6; + if (do_padding) len = ((len + 3) / 4) * 4; + return len; +} + +// Base64Escape does padding, so this calculation includes padding. +int CalculateBase64EscapedLen(int input_len) { + return CalculateBase64EscapedLen(input_len, true); +} + +// ---------------------------------------------------------------------- +// int Base64Unescape() - base64 decoder +// int Base64Escape() - base64 encoder +// int WebSafeBase64Unescape() - Google's variation of base64 decoder +// int WebSafeBase64Escape() - Google's variation of base64 encoder +// +// Check out +// http://www.cis.ohio-state.edu/htbin/rfc/rfc2045.html for formal +// description, but what we care about is that... +// Take the encoded stuff in groups of 4 characters and turn each +// character into a code 0 to 63 thus: +// A-Z map to 0 to 25 +// a-z map to 26 to 51 +// 0-9 map to 52 to 61 +// +(- for WebSafe) maps to 62 +// /(_ for WebSafe) maps to 63 +// There will be four numbers, all less than 64 which can be represented +// by a 6 digit binary number (aaaaaa, bbbbbb, cccccc, dddddd respectively). +// Arrange the 6 digit binary numbers into three bytes as such: +// aaaaaabb bbbbcccc ccdddddd +// Equals signs (one or two) are used at the end of the encoded block to +// indicate that the text was not an integer multiple of three bytes long. +// ---------------------------------------------------------------------- + +int Base64UnescapeInternal(const char *src, int szsrc, + char *dest, int szdest, + const signed char* unbase64) { + static const char kPad64 = '='; + + int decode = 0; + int destidx = 0; + int state = 0; + unsigned int ch = 0; + unsigned int temp = 0; + + // The GET_INPUT macro gets the next input character, skipping + // over any whitespace, and stopping when we reach the end of the + // string or when we read any non-data character. The arguments are + // an arbitrary identifier (used as a label for goto) and the number + // of data bytes that must remain in the input to avoid aborting the + // loop. +#define GET_INPUT(label, remain) \ + label: \ + --szsrc; \ + ch = *src++; \ + decode = unbase64[ch]; \ + if (decode < 0) { \ + if (ascii_isspace((char)ch) && szsrc >= remain) \ + goto label; \ + state = 4 - remain; \ + break; \ + } + + // if dest is null, we're just checking to see if it's legal input + // rather than producing output. (I suspect this could just be done + // with a regexp...). We duplicate the loop so this test can be + // outside it instead of in every iteration. + + if (dest) { + // This loop consumes 4 input bytes and produces 3 output bytes + // per iteration. We can't know at the start that there is enough + // data left in the string for a full iteration, so the loop may + // break out in the middle; if so 'state' will be set to the + // number of input bytes read. + + while (szsrc >= 4) { + // We'll start by optimistically assuming that the next four + // bytes of the string (src[0..3]) are four good data bytes + // (that is, no nulls, whitespace, padding chars, or illegal + // chars). We need to test src[0..2] for nulls individually + // before constructing temp to preserve the property that we + // never read past a null in the string (no matter how long + // szsrc claims the string is). + + if (!src[0] || !src[1] || !src[2] || + (temp = ((unbase64[src[0]] << 18) | + (unbase64[src[1]] << 12) | + (unbase64[src[2]] << 6) | + (unbase64[src[3]]))) & 0x80000000) { + // Iff any of those four characters was bad (null, illegal, + // whitespace, padding), then temp's high bit will be set + // (because unbase64[] is -1 for all bad characters). + // + // We'll back up and resort to the slower decoder, which knows + // how to handle those cases. + + GET_INPUT(first, 4); + temp = decode; + GET_INPUT(second, 3); + temp = (temp << 6) | decode; + GET_INPUT(third, 2); + temp = (temp << 6) | decode; + GET_INPUT(fourth, 1); + temp = (temp << 6) | decode; + } else { + // We really did have four good data bytes, so advance four + // characters in the string. + + szsrc -= 4; + src += 4; + decode = -1; + ch = '\0'; + } + + // temp has 24 bits of input, so write that out as three bytes. + + if (destidx+3 > szdest) return -1; + dest[destidx+2] = (char)temp; + temp >>= 8; + dest[destidx+1] = (char)temp; + temp >>= 8; + dest[destidx] = (char)temp; + destidx += 3; + } + } else { + while (szsrc >= 4) { + if (!src[0] || !src[1] || !src[2] || + (temp = ((unbase64[src[0]] << 18) | + (unbase64[src[1]] << 12) | + (unbase64[src[2]] << 6) | + (unbase64[src[3]]))) & 0x80000000) { + GET_INPUT(first_no_dest, 4); + GET_INPUT(second_no_dest, 3); + GET_INPUT(third_no_dest, 2); + GET_INPUT(fourth_no_dest, 1); + } else { + szsrc -= 4; + src += 4; + decode = -1; + ch = '\0'; + } + destidx += 3; + } + } + +#undef GET_INPUT + + // if the loop terminated because we read a bad character, return + // now. + if (decode < 0 && ch != '\0' && ch != kPad64 && !ascii_isspace((char)ch)) + return -1; + + if (ch == kPad64) { + // if we stopped by hitting an '=', un-read that character -- we'll + // look at it again when we count to check for the proper number of + // equals signs at the end. + ++szsrc; + --src; + } else { + // This loop consumes 1 input byte per iteration. It's used to + // clean up the 0-3 input bytes remaining when the first, faster + // loop finishes. 'temp' contains the data from 'state' input + // characters read by the first loop. + while (szsrc > 0) { + --szsrc; + ch = *src++; + decode = unbase64[ch]; + if (decode < 0) { + if (ascii_isspace((char)ch)) { + continue; + } else if (ch == '\0') { + break; + } else if (ch == kPad64) { + // back up one character; we'll read it again when we check + // for the correct number of equals signs at the end. + ++szsrc; + --src; + break; + } else { + return -1; + } + } + + // Each input character gives us six bits of output. + temp = (temp << 6) | decode; + ++state; + if (state == 4) { + // If we've accumulated 24 bits of output, write that out as + // three bytes. + if (dest) { + if (destidx+3 > szdest) return -1; + dest[destidx+2] = (char)temp; + temp >>= 8; + dest[destidx+1] = (char)temp; + temp >>= 8; + dest[destidx] = (char)temp; + } + destidx += 3; + state = 0; + temp = 0; + } + } + } + + // Process the leftover data contained in 'temp' at the end of the input. + int expected_equals = 0; + switch (state) { + case 0: + // Nothing left over; output is a multiple of 3 bytes. + break; + + case 1: + // Bad input; we have 6 bits left over. + return -1; + + case 2: + // Produce one more output byte from the 12 input bits we have left. + if (dest) { + if (destidx+1 > szdest) return -1; + temp >>= 4; + dest[destidx] = (char)temp; + } + ++destidx; + expected_equals = 2; + break; + + case 3: + // Produce two more output bytes from the 18 input bits we have left. + if (dest) { + if (destidx+2 > szdest) return -1; + temp >>= 2; + dest[destidx+1] = (char)temp; + temp >>= 8; + dest[destidx] = (char)temp; + } + destidx += 2; + expected_equals = 1; + break; + + default: + // state should have no other values at this point. + fprintf(stdout, "This can't happen; base64 decoder state = %d", state); + } + + // The remainder of the string should be all whitespace, mixed with + // exactly 0 equals signs, or exactly 'expected_equals' equals + // signs. (Always accepting 0 equals signs is a google extension + // not covered in the RFC.) + + int equals = 0; + while (szsrc > 0 && *src) { + if (*src == kPad64) + ++equals; + else if (!ascii_isspace(*src)) + return -1; + --szsrc; + ++src; + } + + return (equals == 0 || equals == expected_equals) ? destidx : -1; +} + +int Base64Unescape(const char *src, int szsrc, char *dest, int szdest) { + static const signed char UnBase64[] = { + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 62/*+*/, -1, -1, -1, 63/*/ */, + 52/*0*/, 53/*1*/, 54/*2*/, 55/*3*/, 56/*4*/, 57/*5*/, 58/*6*/, 59/*7*/, + 60/*8*/, 61/*9*/, -1, -1, -1, -1, -1, -1, + -1, 0/*A*/, 1/*B*/, 2/*C*/, 3/*D*/, 4/*E*/, 5/*F*/, 6/*G*/, + 7/*H*/, 8/*I*/, 9/*J*/, 10/*K*/, 11/*L*/, 12/*M*/, 13/*N*/, 14/*O*/, + 15/*P*/, 16/*Q*/, 17/*R*/, 18/*S*/, 19/*T*/, 20/*U*/, 21/*V*/, 22/*W*/, + 23/*X*/, 24/*Y*/, 25/*Z*/, -1, -1, -1, -1, -1, + -1, 26/*a*/, 27/*b*/, 28/*c*/, 29/*d*/, 30/*e*/, 31/*f*/, 32/*g*/, + 33/*h*/, 34/*i*/, 35/*j*/, 36/*k*/, 37/*l*/, 38/*m*/, 39/*n*/, 40/*o*/, + 41/*p*/, 42/*q*/, 43/*r*/, 44/*s*/, 45/*t*/, 46/*u*/, 47/*v*/, 48/*w*/, + 49/*x*/, 50/*y*/, 51/*z*/, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1 + }; + // The above array was generated by the following code + // #include + // #include + // #include + // main() + // { + // static const char Base64[] = + // "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + // char *pos; + // int idx, i, j; + // printf(" "); + // for (i = 0; i < 255; i += 8) { + // for (j = i; j < i + 8; j++) { + // pos = strchr(Base64, j); + // if ((pos == NULL) || (j == 0)) + // idx = -1; + // else + // idx = pos - Base64; + // if (idx == -1) + // printf(" %2d, ", idx); + // else + // printf(" %2d/*%c*/,", idx, j); + // } + // printf("\n "); + // } + // } + + return Base64UnescapeInternal(src, szsrc, dest, szdest, UnBase64); +} + +bool Base64Unescape(const char *src, int slen, string* dest) { + // Determine the size of the output string. Base64 encodes every 3 bytes into + // 4 characters. any leftover chars are added directly for good measure. + // This is documented in the base64 RFC: http://www.ietf.org/rfc/rfc3548.txt + const int dest_len = 3 * (slen / 4) + (slen % 4); + + dest->resize(dest_len); + + // We are getting the destination buffer by getting the beginning of the + // string and converting it into a char *. + const int len = Base64Unescape(src, slen, + string_as_array(dest), dest->size()); + if (len < 0) { + return false; + } + + // could be shorter if there was padding + assert(len <= dest_len); + dest->resize(len); + + return true; +} + +// Base64Escape +// +// NOTE: We have to use an unsigned type for src because code built +// in the the /google tree treats characters as signed unless +// otherwised specified. +// +// TODO(who?): Move this function to use the char* type for "src" +int Base64EscapeInternal(const unsigned char *src, int szsrc, + char *dest, int szdest, const char *base64, + bool do_padding) { + static const char kPad64 = '='; + + if (szsrc <= 0) return 0; + + char *cur_dest = dest; + const unsigned char *cur_src = src; + + // Three bytes of data encodes to four characters of cyphertext. + // So we can pump through three-byte chunks atomically. + while (szsrc > 2) { /* keep going until we have less than 24 bits */ + if ((szdest -= 4) < 0) return 0; + cur_dest[0] = base64[cur_src[0] >> 2]; + cur_dest[1] = base64[((cur_src[0] & 0x03) << 4) + (cur_src[1] >> 4)]; + cur_dest[2] = base64[((cur_src[1] & 0x0f) << 2) + (cur_src[2] >> 6)]; + cur_dest[3] = base64[cur_src[2] & 0x3f]; + + cur_dest += 4; + cur_src += 3; + szsrc -= 3; + } + + /* now deal with the tail (<=2 bytes) */ + switch (szsrc) { + case 0: + // Nothing left; nothing more to do. + break; + case 1: + // One byte left: this encodes to two characters, and (optionally) + // two pad characters to round out the four-character cypherblock. + if ((szdest -= 2) < 0) return 0; + cur_dest[0] = base64[cur_src[0] >> 2]; + cur_dest[1] = base64[(cur_src[0] & 0x03) << 4]; + cur_dest += 2; + if (do_padding) { + if ((szdest -= 2) < 0) return 0; + cur_dest[0] = kPad64; + cur_dest[1] = kPad64; + cur_dest += 2; + } + break; + case 2: + // Two bytes left: this encodes to three characters, and (optionally) + // one pad character to round out the four-character cypherblock. + if ((szdest -= 3) < 0) return 0; + cur_dest[0] = base64[cur_src[0] >> 2]; + cur_dest[1] = base64[((cur_src[0] & 0x03) << 4) + (cur_src[1] >> 4)]; + cur_dest[2] = base64[(cur_src[1] & 0x0f) << 2]; + cur_dest += 3; + if (do_padding) { + if ((szdest -= 1) < 0) return 0; + cur_dest[0] = kPad64; + cur_dest += 1; + } + break; + default: + // Should not be reached: blocks of 3 bytes are handled + // in the while loop before this switch statement. + fprintf(stderr, "Logic problem? szsrc = %d", szsrc); + assert(false); + break; + } + return (cur_dest - dest); +} + +static const char kBase64Chars[] = +"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +static const char kWebSafeBase64Chars[] = +"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + +int Base64Escape(const unsigned char *src, int szsrc, char *dest, int szdest) { + return Base64EscapeInternal(src, szsrc, dest, szdest, kBase64Chars, true); +} + +void Base64Escape(const unsigned char *src, int szsrc, + string* dest, bool do_padding) { + const int max_escaped_size = + CalculateBase64EscapedLen(szsrc, do_padding); + dest->clear(); + dest->resize(max_escaped_size + 1, '\0'); + const int escaped_len = Base64EscapeInternal(src, szsrc, + &*dest->begin(), dest->size(), + kBase64Chars, + do_padding); + assert(max_escaped_size <= escaped_len); + dest->resize(escaped_len); +} + +void Base64Escape(const string& src, string* dest) { + Base64Escape(reinterpret_cast(src.c_str()), + src.size(), dest, true); +} + +//////////////////////////////////////////////////// +// WebSafe methods +//////////////////////////////////////////////////// + +int WebSafeBase64Unescape(const char *src, int szsrc, char *dest, int szdest) { + static const signed char UnBase64[] = { + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 62/*-*/, -1, -1, + 52/*0*/, 53/*1*/, 54/*2*/, 55/*3*/, 56/*4*/, 57/*5*/, 58/*6*/, 59/*7*/, + 60/*8*/, 61/*9*/, -1, -1, -1, -1, -1, -1, + -1, 0/*A*/, 1/*B*/, 2/*C*/, 3/*D*/, 4/*E*/, 5/*F*/, 6/*G*/, + 7/*H*/, 8/*I*/, 9/*J*/, 10/*K*/, 11/*L*/, 12/*M*/, 13/*N*/, 14/*O*/, + 15/*P*/, 16/*Q*/, 17/*R*/, 18/*S*/, 19/*T*/, 20/*U*/, 21/*V*/, 22/*W*/, + 23/*X*/, 24/*Y*/, 25/*Z*/, -1, -1, -1, -1, 63/*_*/, + -1, 26/*a*/, 27/*b*/, 28/*c*/, 29/*d*/, 30/*e*/, 31/*f*/, 32/*g*/, + 33/*h*/, 34/*i*/, 35/*j*/, 36/*k*/, 37/*l*/, 38/*m*/, 39/*n*/, 40/*o*/, + 41/*p*/, 42/*q*/, 43/*r*/, 44/*s*/, 45/*t*/, 46/*u*/, 47/*v*/, 48/*w*/, + 49/*x*/, 50/*y*/, 51/*z*/, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1 + }; + // The above array was generated by the following code + // #include + // #include + // #include + // main() + // { + // static const char Base64[] = + // "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + // char *pos; + // int idx, i, j; + // printf(" "); + // for (i = 0; i < 255; i += 8) { + // for (j = i; j < i + 8; j++) { + // pos = strchr(Base64, j); + // if ((pos == NULL) || (j == 0)) + // idx = -1; + // else + // idx = pos - Base64; + // if (idx == -1) + // printf(" %2d, ", idx); + // else + // printf(" %2d/*%c*/,", idx, j); + // } + // printf("\n "); + // } + // } + + return Base64UnescapeInternal(src, szsrc, dest, szdest, UnBase64); +} + +bool WebSafeBase64Unescape(const char *src, int slen, string* dest) { + int dest_len = 3 * (slen / 4) + (slen % 4); + dest->clear(); + dest->resize(dest_len); + int len = WebSafeBase64Unescape(src, slen, &*dest->begin(), dest->size()); + if (len < 0) { + dest->clear(); + return false; + } + // could be shorter if there was padding + assert(len <= dest_len); + dest->resize(len); + return true; +} + +bool WebSafeBase64Unescape(const string& src, string* dest) { + return WebSafeBase64Unescape(src.data(), src.size(), dest); +} + +int WebSafeBase64Escape(const unsigned char *src, int szsrc, char *dest, + int szdest, bool do_padding) { + return Base64EscapeInternal(src, szsrc, dest, szdest, + kWebSafeBase64Chars, do_padding); +} + +void WebSafeBase64Escape(const unsigned char *src, int szsrc, + string *dest, bool do_padding) { + const int max_escaped_size = + CalculateBase64EscapedLen(szsrc, do_padding); + dest->clear(); + dest->resize(max_escaped_size + 1, '\0'); + const int escaped_len = Base64EscapeInternal(src, szsrc, + &*dest->begin(), dest->size(), + kWebSafeBase64Chars, + do_padding); + assert(max_escaped_size <= escaped_len); + dest->resize(escaped_len); +} + +void WebSafeBase64EscapeInternal(const string& src, + string* dest, + bool do_padding) { + int encoded_len = CalculateBase64EscapedLen(src.size()); + scoped_array buf(new char[encoded_len]); + int len = WebSafeBase64Escape(reinterpret_cast(src.c_str()), + src.size(), buf.get(), + encoded_len, do_padding); + dest->assign(buf.get(), len); +} + +void WebSafeBase64Escape(const string& src, string* dest) { + WebSafeBase64EscapeInternal(src, dest, false); +} + +void WebSafeBase64EscapeWithPadding(const string& src, string* dest) { + WebSafeBase64EscapeInternal(src, dest, true); +} + +} // namespace strings diff --git a/src/tools/windows/converter_exe/escaping.h b/src/tools/windows/converter_exe/escaping.h new file mode 100644 index 00000000..c8aa90b7 --- /dev/null +++ b/src/tools/windows/converter_exe/escaping.h @@ -0,0 +1,99 @@ +// Copyright 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. +// +// Base64 escaping methods to encode/decode strings. + +#ifndef TOOLS_WINDOWS_CONVERTER_EXE_ESCAPING_H_ +#define TOOLS_WINDOWS_CONVERTER_EXE_ESCAPING_H_ + +#include + +namespace strings { + +using std::string; + +// ---------------------------------------------------------------------- +// Base64Escape() +// WebSafeBase64Escape() +// Encode "src" to "dest" using base64 encoding. +// src is not null terminated, instead specify len. +// 'dest' should have at least CalculateBase64EscapedLen() length. +// RETURNS the length of dest. +// The WebSafe variation use '-' instead of '+' and '_' instead of '/' +// so that we can place the out in the URL or cookies without having +// to escape them. It also has an extra parameter "do_padding", +// which when set to false will prevent padding with "=". +// ---------------------------------------------------------------------- +void Base64Escape(const string& src, string* dest); +int Base64Escape(const unsigned char* src, int slen, char* dest, int szdest); +// Encode src into dest with padding. +void Base64Escape(const unsigned char* src, int szsrc, + string* dest, bool do_padding); + +int WebSafeBase64Escape(const unsigned char* src, int slen, char* dest, + int szdest, bool do_padding); +// Encode src into dest web-safely without padding. +void WebSafeBase64Escape(const string& src, string* dest); +// Encode src into dest web-safely with padding. +void WebSafeBase64EscapeWithPadding(const string& src, string* dest); +void WebSafeBase64Escape(const unsigned char* src, int szsrc, + string* dest, bool do_padding); + +// ---------------------------------------------------------------------- +// Base64Unescape() +// WebSafeBase64Unescape() +// Copies "src" to "dest", where src is in base64 and is written to its +// ASCII equivalents. src is not null terminated, instead specify len. +// I recommend that slen +#include +#include + +typedef void* HttpHandle; + +namespace crash { + +// HttpClient provides an abstract layer for HTTP APIs. The actual +// implementation can be based on either WinHttp or WinInet. +class HttpClient { + public: + enum AccessType { + ACCESS_TYPE_PRECONFIG, + ACCESS_TYPE_DIRECT, + ACCESS_TYPE_PROXY, + }; + + virtual ~HttpClient() {} + + virtual bool CrackUrl(const TCHAR* url, + DWORD flags, + TCHAR* scheme, + size_t scheme_buffer_length, + TCHAR* host, + size_t host_buffer_length, + TCHAR* uri, + size_t uri_buffer_length, + int* port) const = 0; + virtual bool Open(const TCHAR* user_agent, + DWORD access_type, + const TCHAR* proxy_name, + const TCHAR* proxy_bypass, + HttpHandle* session_handle) const = 0; + virtual bool Connect(HttpHandle session_handle, + const TCHAR* server, + int port, + HttpHandle* connection_handle) const = 0; + virtual bool OpenRequest(HttpHandle connection_handle, + const TCHAR* verb, + const TCHAR* uri, + const TCHAR* version, + const TCHAR* referrer, + bool is_secure, + HttpHandle* request_handle) const = 0; + virtual bool SendRequest(HttpHandle request_handle, + const TCHAR* headers, + DWORD headers_length) const = 0; + virtual bool ReceiveResponse(HttpHandle request_handle) const = 0; + virtual bool GetHttpStatusCode(HttpHandle request_handle, + int* status_code) const = 0; + virtual bool GetContentLength(HttpHandle request_handle, + DWORD* content_length) const = 0; + virtual bool ReadData(HttpHandle request_handle, + void* buffer, + DWORD buffer_length, + DWORD* bytes_read) const = 0; + virtual bool Close(HttpHandle handle) const = 0; + + static const DWORD kUnknownContentLength = (DWORD)-1; +}; + +} // namespace crash + +#endif // TOOLS_CRASH_CONVERTER_WINDOWS_HTTP_CLIENT_H_ diff --git a/src/tools/windows/converter_exe/http_download.cc b/src/tools/windows/converter_exe/http_download.cc new file mode 100644 index 00000000..b037662f --- /dev/null +++ b/src/tools/windows/converter_exe/http_download.cc @@ -0,0 +1,326 @@ +// Copyright 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. + +#include +#include +#include +#include + +#include + +#include "tools/windows/converter_exe/http_download.h" +#include "tools/windows/converter_exe/winhttp_client.h" +#include "tools/windows/converter_exe/wininet_client.h" + +namespace crash { +static const std::vector::size_type kVectorChunkSize = 4096; // 4 KB + +using std::vector; + +// Class that atuo closes the contained HttpHandle when the object +// goes out of scope. +class AutoHttpHandle { + public: + AutoHttpHandle() : handle_(NULL) {} + explicit AutoHttpHandle(HttpHandle handle) : handle_(handle) {} + ~AutoHttpHandle() { + if (handle_) { + InternetCloseHandle(handle_); + } + } + + HttpHandle get() { return handle_; } + HttpHandle* get_handle_addr () { return &handle_; } + + private: + HttpHandle handle_; +}; + +// Template class for auto releasing the contained pointer when +// the object goes out of scope. +template +class AutoPtr { + public: + explicit AutoPtr(T* ptr) : ptr_(ptr) {} + ~AutoPtr() { + if (ptr_) { + delete ptr_; + } + } + + T* get() { return ptr_; } + T* operator -> () { return ptr_; } + + private: + T* ptr_; +}; + +// CheckParameters ensures that the parameters in |parameters| are safe for +// use in an HTTP URL. Returns true if they are, false if unsafe characters +// are present. +static bool CheckParameters(const map *parameters) { + for (map::const_iterator iterator = parameters->begin(); + iterator != parameters->end(); + ++iterator) { + const wstring &key = iterator->first; + if (key.empty()) { + // Disallow empty parameter names. + return false; + } + for (unsigned int i = 0; i < key.size(); ++i) { + wchar_t c = key[i]; + if (c < 32 || c == '"' || c == '?' || c == '&' || c > 127) { + return false; + } + } + + const wstring &value = iterator->second; + for (unsigned int i = 0; i < value.size(); ++i) { + wchar_t c = value[i]; + if (c < 32 || c == '"' || c == '?' || c == '&' || c > 127) { + return false; + } + } + } + + return true; +} + +HttpClient* HTTPDownload::CreateHttpClient(const wchar_t* url) { + const TCHAR* kHttpApiPolicyEnvironmentVariable = TEXT("USE_WINHTTP"); + TCHAR buffer[2] = {0}; + HttpClient* http_client = NULL; + + if (::GetEnvironmentVariable(kHttpApiPolicyEnvironmentVariable, + buffer, + sizeof(buffer)/sizeof(buffer[0])) > 0) { + fprintf(stdout, + "Environment variable [%ws] is set, use WinHttp\n", + kHttpApiPolicyEnvironmentVariable); + http_client = CreateWinHttpClient(url); + if (http_client == NULL) { + fprintf(stderr, "WinHttpClient not created, Is the protocol HTTPS? " + "Fall back to WinInet API.\n"); + } + } else { + fprintf(stderr, + "Environment variable [%ws] is NOT set, use WinInet API\n", + kHttpApiPolicyEnvironmentVariable); + } + + if (http_client == NULL) { + return CreateWinInetClient(url); + } + + return http_client; +} + +// static +bool HTTPDownload::Download(const wstring &url, + const map *parameters, + string *content, int *status_code) { + assert(content); + AutoPtr http_client(CreateHttpClient(url.c_str())); + + if (!http_client.get()) { + fprintf(stderr, "Failed to create any http client.\n"); + return false; + } + + if (status_code) { + *status_code = 0; + } + + wchar_t scheme[16] = {0}; + wchar_t host[256] = {0}; + wchar_t path[256] = {0}; + int port = 0; + if (!http_client->CrackUrl(url.c_str(), + 0, + scheme, + sizeof(scheme)/sizeof(scheme[0]), + host, + sizeof(host)/sizeof(host[0]), + path, + sizeof(path)/sizeof(path[0]), + &port)) { + fprintf(stderr, + "HTTPDownload::Download: InternetCrackUrl: error %d for %ws\n", + GetLastError(), url.c_str()); + return false; + } + + bool secure = false; + if (_wcsicmp(scheme, L"https") == 0) { + secure = true; + } else if (wcscmp(scheme, L"http") != 0) { + fprintf(stderr, + "HTTPDownload::Download: scheme must be http or https for %ws\n", + url.c_str()); + return false; + } + + AutoHttpHandle internet; + if (!http_client->Open(NULL, // user agent + HttpClient::ACCESS_TYPE_PRECONFIG, + NULL, // proxy name + NULL, // proxy bypass + internet.get_handle_addr())) { + fprintf(stderr, + "HTTPDownload::Download: Open: error %d for %ws\n", + GetLastError(), url.c_str()); + return false; + } + + AutoHttpHandle connection; + if (!http_client->Connect(internet.get(), + host, + port, + connection.get_handle_addr())) { + fprintf(stderr, + "HTTPDownload::Download: InternetConnect: error %d for %ws\n", + GetLastError(), url.c_str()); + return false; + } + + wstring request_string = path; + if (parameters) { + // TODO(mmentovai): escape bad characters in parameters instead of + // forbidding them. + if (!CheckParameters(parameters)) { + fprintf(stderr, + "HTTPDownload::Download: invalid characters in parameters\n"); + return false; + } + + bool added_parameter = false; + for (map::const_iterator iterator = parameters->begin(); + iterator != parameters->end(); + ++iterator) { + request_string.append(added_parameter ? L"&" : L"?"); + request_string.append(iterator->first); + request_string.append(L"="); + request_string.append(iterator->second); + added_parameter = true; + } + } + + AutoHttpHandle request; + if (!http_client->OpenRequest(connection.get(), + L"GET", + request_string.c_str(), + NULL, // version + NULL, // referer + secure, + request.get_handle_addr())) { + fprintf(stderr, + "HttpClient::OpenRequest: error %d for %ws, request: %ws\n", + GetLastError(), url.c_str(), request_string.c_str()); + return false; + } + + if (!http_client->SendRequest(request.get(), NULL, 0)) { + fprintf(stderr, + "HttpClient::SendRequest: error %d for %ws\n", + GetLastError(), url.c_str()); + return false; + } + + if (!http_client->ReceiveResponse(request.get())) { + fprintf(stderr, + "HttpClient::ReceiveResponse: error %d for %ws\n", + GetLastError(), url.c_str()); + return false; + } + + int http_status = 0; + if (!http_client->GetHttpStatusCode(request.get(), &http_status)) { + fprintf(stderr, + "HttpClient::GetHttpStatusCode: error %d for %ws\n", + GetLastError(), url.c_str()); + return false; + } + if (http_status != 200) { + fprintf(stderr, + "HTTPDownload::Download: HTTP status code %d for %ws\n", + http_status, url.c_str()); + return false; + } + + DWORD content_length = 0; + vector::size_type buffer_size = 0; + http_client->GetContentLength(request.get(), &content_length); + if (content_length == HttpClient::kUnknownContentLength) { + buffer_size = kVectorChunkSize; + } else { + buffer_size = content_length; + } + + if (content_length != 0) { + vector response_buffer = vector(buffer_size+1); + DWORD size_read; + DWORD total_read = 0; + bool read_result; + do { + if (content_length == HttpClient::kUnknownContentLength + && buffer_size == total_read) { + // The content length wasn't specified in the response header, so we + // have to keep growing the buffer until we're done reading. + buffer_size += kVectorChunkSize; + response_buffer.resize(buffer_size); + } + read_result = !!http_client->ReadData( + request.get(), + &response_buffer[total_read], + static_cast(buffer_size) - total_read, + &size_read); + total_read += size_read; + } while (read_result && (size_read != 0)); + + if (!read_result) { + fprintf(stderr, + "HttpClient::ReadData: error %d for %ws\n", + GetLastError(), + url.c_str()); + return false; + } else if (size_read != 0) { + fprintf(stderr, + "HttpClient::ReadData: error %d/%d for %ws\n", + total_read, + content_length, + url.c_str()); + return false; + } + content->assign(&response_buffer[0], total_read); + } else { + content->clear(); + } + return true; +} + +} // namespace crash diff --git a/src/tools/windows/converter_exe/http_download.h b/src/tools/windows/converter_exe/http_download.h new file mode 100644 index 00000000..2d705d5e --- /dev/null +++ b/src/tools/windows/converter_exe/http_download.h @@ -0,0 +1,62 @@ +// Copyright 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 TOOLS_WINDOWS_CONVERTER_EXE_HTTP_DOWNLOAD_H_ +#define TOOLS_WINDOWS_CONVERTER_EXE_HTTP_DOWNLOAD_H_ + +#include +#include +#include "tools/windows/converter_exe/winhttp_client.h" + +namespace crash { + +using std::map; +using std::string; +using std::wstring; + +class HTTPDownload { + public: + // Retrieves the resource located at |url|, a http or https URL, via WinInet. + // The request is fetched with GET request; the optional |parameters| are + // appended to the URL. Returns true on success, placing the content of the + // retrieved resource in |content|. Returns false on failure. HTTP status + // codes other than 200 cause Download to return false. If |status_code| is + // supplied, it will be set to the value of the HTTP status code, if an HTTP + // transaction occurs. If Download fails before a transaction can occur, + // |status_code| will be set to 0. Any failures will result in messages + // being printed to stderr. + static bool Download(const wstring &url, + const map *parameters, + string *content, int *status_code); + private: + static HttpClient* CreateHttpClient(const wchar_t*); +}; + +} // namespace crash + +#endif // TOOLS_WINDOWS_CONVERTER_EXE_HTTP_DOWNLOAD_H_ diff --git a/src/tools/windows/converter_exe/missing_symbols_test.txt b/src/tools/windows/converter_exe/missing_symbols_test.txt new file mode 100644 index 00000000..251b4eca --- /dev/null +++ b/src/tools/windows/converter_exe/missing_symbols_test.txt @@ -0,0 +1,2 @@ +msctf.pdb|6A5BABB8E88644C696530BFE3C90F32F2|6.1.7600.16385|msctf.dll|4A5BDFAA109000 +imm32.pdb|98F27BA5AEE541ECBEE00CD03AD50FEE2|6.1.7600.16385|imm32.dll|4A5BDF402e000 diff --git a/src/tools/windows/converter_exe/symsrv.yes b/src/tools/windows/converter_exe/symsrv.yes new file mode 100644 index 00000000..1d01dda7 --- /dev/null +++ b/src/tools/windows/converter_exe/symsrv.yes @@ -0,0 +1,2 @@ +See breakpad/tools/windows/converter/ms_symbol_server_converter.h for a +description of this file's function. diff --git a/src/tools/windows/converter_exe/tokenizer.cc b/src/tools/windows/converter_exe/tokenizer.cc new file mode 100644 index 00000000..992694cd --- /dev/null +++ b/src/tools/windows/converter_exe/tokenizer.cc @@ -0,0 +1,61 @@ +// Copyright 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. + +#include + +#include "tools/windows/converter_exe/tokenizer.h" + +namespace crash { + +// static +void Tokenizer::Tokenize(const string &delimiters, const string &input, + vector *output) { + assert(output); + output->clear(); + + string::size_type position = 0; // Where to begin looking for a delimiter + string::size_type new_position; // Position of found delimiter + string token; + + while ((new_position = input.find_first_of(delimiters, position)) != + string::npos) { + token = input.substr(position, new_position - position); + output->push_back(token); + + // Next time, begin looking right after this delimiter. + position = new_position + 1; + } + + // There are no more delimiters in the string. Take everything from the + // final delimiter up to the end of the string as a token. This may be + // an empty string. + token = input.substr(position); + output->push_back(token); +} + +} // namespace crash diff --git a/src/tools/windows/converter_exe/tokenizer.h b/src/tools/windows/converter_exe/tokenizer.h new file mode 100644 index 00000000..f4bbcfd0 --- /dev/null +++ b/src/tools/windows/converter_exe/tokenizer.h @@ -0,0 +1,51 @@ +// Copyright 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 TOOLS_WINDOWS_CONVERTER_EXE_TOKENIZER_H_ +#define TOOLS_WINDOWS_CONVERTER_EXE_TOKENIZER_H_ + +#include +#include + +namespace crash { + +using std::string; +using std::vector; + +class Tokenizer { + public: + // Splits |input| into a series of tokens delimited in the input string by + // any of the characters in |delimiters|. The tokens are passed back in the + // |output| vector. + static void Tokenize(const string &delimiters, const string &input, + vector *output); +}; + +} // namespace crash + +#endif // TOOLS_WINDOWS_CONVERTER_EXE_TOKENIZER_H_ diff --git a/src/tools/windows/converter_exe/winhttp_client.cc b/src/tools/windows/converter_exe/winhttp_client.cc new file mode 100644 index 00000000..8a8ade3b --- /dev/null +++ b/src/tools/windows/converter_exe/winhttp_client.cc @@ -0,0 +1,307 @@ +// Copyright 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. + +#include "tools/windows/converter_exe/winhttp_client.h" + +#include +#include +#include +#include +#include + +namespace crash { + +namespace internal { + +// This class implements HttpClient based on WinInet APIs. +class WinHttpClient : public HttpClient { + public: + virtual ~WinHttpClient() {} + virtual bool CrackUrl(const TCHAR* url, + DWORD flags, + TCHAR* scheme, + size_t scheme_buffer_length, + TCHAR* host, + size_t host_buffer_length, + TCHAR* uri, + size_t uri_buffer_length, + int* port) const; + virtual bool Open(const TCHAR* user_agent, + DWORD access_type, + const TCHAR* proxy_name, + const TCHAR* proxy_bypass, + HttpHandle* session_handle) const; + virtual bool Connect(HttpHandle session_handle, + const TCHAR* server, + int port, + HttpHandle* connection_handle) const; + virtual bool OpenRequest(HttpHandle connection_handle, + const TCHAR* verb, + const TCHAR* uri, + const TCHAR* version, + const TCHAR* referrer, + bool is_secure, + HttpHandle* request_handle) const; + virtual bool SendRequest(HttpHandle request_handle, + const TCHAR* headers, + DWORD headers_length) const; + virtual bool ReceiveResponse(HttpHandle request_handle) const; + virtual bool GetHttpStatusCode(HttpHandle request_handle, + int* status_code) const; + virtual bool GetContentLength(HttpHandle request_handle, + DWORD* content_length) const; + virtual bool ReadData(HttpHandle request_handle, + void* buffer, + DWORD buffer_length, + DWORD* bytes_read) const; + virtual bool Close(HttpHandle handle) const; + + private: + static DWORD MapAccessType(DWORD access_type); + static HINTERNET ToHINTERNET(HttpHandle handle); + static HttpHandle FromHINTERNET(HINTERNET handle); +}; + +bool WinHttpClient::CrackUrl(const TCHAR* url, + DWORD flags, + TCHAR* scheme, + size_t scheme_buffer_length, + TCHAR* host, + size_t host_buffer_length, + TCHAR* uri, + size_t uri_buffer_length, + int* port) const { + assert(url); + assert(scheme); + assert(host); + assert(uri); + assert(port); + + URL_COMPONENTS url_comp = {0}; + url_comp.dwStructSize = sizeof(url_comp); + url_comp.lpszScheme = scheme; + url_comp.dwSchemeLength = static_cast(scheme_buffer_length); + url_comp.lpszHostName = host; + url_comp.dwHostNameLength = static_cast(host_buffer_length); + url_comp.lpszUrlPath = uri; + url_comp.dwUrlPathLength = static_cast(uri_buffer_length); + + bool result = !!::WinHttpCrackUrl(url, 0, flags, &url_comp); + if (result) { + *port = static_cast(url_comp.nPort); + } + return result; +} + +bool WinHttpClient::Open(const TCHAR* user_agent, + DWORD access_type, + const TCHAR* proxy_name, + const TCHAR* proxy_bypass, + HttpHandle* session_handle) const { + *session_handle = FromHINTERNET(::WinHttpOpen(user_agent, + MapAccessType(access_type), + proxy_name, + proxy_bypass, + 0)); + + return !!(*session_handle); +} + +bool WinHttpClient::Connect(HttpHandle session_handle, + const TCHAR* server, + int port, + HttpHandle* connection_handle) const { + assert(server); + + // Uses NULL user name and password to connect. + *connection_handle = FromHINTERNET(::WinHttpConnect( + ToHINTERNET(session_handle), + server, + static_cast(port), + NULL)); + return !!(*connection_handle); +} + +bool WinHttpClient::OpenRequest(HttpHandle connection_handle, + const TCHAR* verb, + const TCHAR* uri, + const TCHAR* version, + const TCHAR* referrer, + bool is_secure, + HttpHandle* request_handle) const { + assert(connection_handle); + assert(verb); + assert(uri); + assert(request_handle); + + *request_handle = FromHINTERNET(::WinHttpOpenRequest( + ToHINTERNET(connection_handle), + verb, + uri, + version, + referrer, + WINHTTP_DEFAULT_ACCEPT_TYPES, + is_secure ? WINHTTP_FLAG_SECURE : 0)); + return !!(*request_handle); +} + +bool WinHttpClient::SendRequest(HttpHandle request_handle, + const TCHAR* headers, + DWORD headers_length) const { + assert(request_handle); + + return !!::WinHttpSendRequest(ToHINTERNET(request_handle), + headers, + headers_length, + NULL, + 0, + WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH, + NULL); +} + +bool WinHttpClient::ReceiveResponse(HttpHandle request_handle) const { + assert(request_handle); + + return !!::WinHttpReceiveResponse(ToHINTERNET(request_handle), NULL); +} + +bool WinHttpClient::GetHttpStatusCode(HttpHandle request_handle, + int* status_code) const { + TCHAR http_status_string[4] = {0}; + DWORD http_status_string_size = sizeof(http_status_string); + if (!::WinHttpQueryHeaders(ToHINTERNET(request_handle), + WINHTTP_QUERY_STATUS_CODE, + WINHTTP_HEADER_NAME_BY_INDEX, + static_cast(&http_status_string), + &http_status_string_size, 0)) { + return false; + } + + *status_code = static_cast(_tcstol(http_status_string, NULL, 10)); + return true; +} + +bool WinHttpClient::GetContentLength(HttpHandle request_handle, + DWORD* content_length) const { + assert(request_handle); + assert(content_length); + + TCHAR content_length_string[11] = {0}; + DWORD content_length_string_size = sizeof(content_length_string); + if (!::WinHttpQueryHeaders(ToHINTERNET(request_handle), + WINHTTP_QUERY_CONTENT_LENGTH, + WINHTTP_HEADER_NAME_BY_INDEX, + static_cast(&content_length_string), + &content_length_string_size, 0)) { + *content_length = kUnknownContentLength; + } else { + *content_length = + static_cast(wcstol(content_length_string, NULL, 10)); + } + return true; +} + +bool WinHttpClient::ReadData(HttpHandle request_handle, + void* buffer, + DWORD buffer_length, + DWORD* bytes_read) const { + assert(request_handle); + assert(buffer); + assert(bytes_read); + + DWORD bytes_read_local = 0; + if (!::WinHttpReadData(ToHINTERNET(request_handle), + buffer, + buffer_length, + &bytes_read_local)) { + return false; + } + *bytes_read = bytes_read_local; + return true; +} + +bool WinHttpClient::Close(HttpHandle handle) const { + assert(handle); + return !!::WinHttpCloseHandle(ToHINTERNET(handle)); +} + +DWORD WinHttpClient::MapAccessType(DWORD access_type) { + switch (static_cast(access_type)) { + case ACCESS_TYPE_PRECONFIG: + default: + return WINHTTP_ACCESS_TYPE_DEFAULT_PROXY; + case ACCESS_TYPE_DIRECT: + return WINHTTP_ACCESS_TYPE_NO_PROXY; + case ACCESS_TYPE_PROXY: + return WINHTTP_ACCESS_TYPE_NAMED_PROXY; + } +} + + +HINTERNET WinHttpClient::ToHINTERNET(HttpHandle handle) { + return static_cast(handle); +} + +HttpHandle WinHttpClient::FromHINTERNET(HINTERNET handle) { + return static_cast(handle); +} + +} // namespace internal + +HttpClient* CreateWinHttpClient(const TCHAR* url) { + assert(url); + + internal::WinHttpClient winhttp; + wchar_t scheme[16] = {0}; + wchar_t host[256] = {0}; + wchar_t path[256] = {0}; + int port = 0; + + if (!winhttp.CrackUrl(url, + 0, + scheme, + sizeof(scheme)/sizeof(scheme[0]), + host, + sizeof(host)/sizeof(host[0]), + path, + sizeof(path)/sizeof(path[0]), + &port)) { + return NULL; + } + + if (_wcsicmp(scheme, L"https") == 0) { + // Winhttp under WINE doesn't support wildcard certificates, so avoid + // to use it if the scheme is https. The caller should fall back to + // use wininet if NULL is returned. + return NULL; + } + + return new internal::WinHttpClient(); +} + +} // namespace crash diff --git a/src/tools/windows/converter_exe/winhttp_client.h b/src/tools/windows/converter_exe/winhttp_client.h new file mode 100644 index 00000000..819d610f --- /dev/null +++ b/src/tools/windows/converter_exe/winhttp_client.h @@ -0,0 +1,40 @@ +// Copyright 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 TOOLS_WINDOWS_CONVERTER_EXE_WINHTTP_CLIENT_H_ +#define TOOLS_WINDOWS_CONVERTER_EXE_WINHTTP_CLIENT_H_ + +#include "tools/windows/converter_exe/http_client.h" + +namespace crash { + +HttpClient* CreateWinHttpClient(const TCHAR* url); + +} // namespace crash + +#endif // TOOLS_WINDOWS_CONVERTER_EXE_WINHTTP_CLIENT_H_ diff --git a/src/tools/windows/converter_exe/wininet_client.cc b/src/tools/windows/converter_exe/wininet_client.cc new file mode 100644 index 00000000..3e542db2 --- /dev/null +++ b/src/tools/windows/converter_exe/wininet_client.cc @@ -0,0 +1,278 @@ +// Copyright 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. + +#include "tools/windows/converter_exe/wininet_client.h" + +#include +#include +#include +#include + +namespace crash { + +namespace internal { + +// This class implements HttpClient based on WinInet APIs. +class WinInetClient : public HttpClient { + public: + virtual ~WinInetClient() {} + virtual bool CrackUrl(const TCHAR* url, + DWORD flags, + TCHAR* scheme, + size_t scheme_buffer_length, + TCHAR* host, + size_t host_buffer_length, + TCHAR* uri, + size_t uri_buffer_length, + int* port) const; + virtual bool Open(const TCHAR* user_agent, + DWORD access_type, + const TCHAR* proxy_name, + const TCHAR* proxy_bypass, + HttpHandle* session_handle) const; + virtual bool Connect(HttpHandle session_handle, + const TCHAR* server, + int port, + HttpHandle* connection_handle) const; + virtual bool OpenRequest(HttpHandle connection_handle, + const TCHAR* verb, + const TCHAR* uri, + const TCHAR* version, + const TCHAR* referrer, + bool is_secure, + HttpHandle* request_handle) const; + virtual bool SendRequest(HttpHandle request_handle, + const TCHAR* headers, + DWORD headers_length) const; + virtual bool ReceiveResponse(HttpHandle request_handle) const; + virtual bool GetHttpStatusCode(HttpHandle request_handle, + int* status_code) const; + virtual bool GetContentLength(HttpHandle request_handle, + DWORD* content_length) const; + virtual bool ReadData(HttpHandle request_handle, + void* buffer, + DWORD buffer_length, + DWORD* bytes_read) const; + virtual bool Close(HttpHandle handle) const; + + private: + static DWORD MapAccessType(DWORD access_type); + static HINTERNET ToHINTERNET(HttpHandle handle); + static HttpHandle FromHINTERNET(HINTERNET handle); +}; + +bool WinInetClient::CrackUrl(const TCHAR* url, + DWORD flags, + TCHAR* scheme, + size_t scheme_buffer_length, + TCHAR* host, + size_t host_buffer_length, + TCHAR* uri, + size_t uri_buffer_length, + int* port) const { + assert(url); + assert(scheme); + assert(host); + assert(uri); + assert(port); + + URL_COMPONENTS url_comp = {0}; + url_comp.dwStructSize = sizeof(url_comp); + url_comp.lpszScheme = scheme; + url_comp.dwSchemeLength = static_cast(scheme_buffer_length); + url_comp.lpszHostName = host; + url_comp.dwHostNameLength = static_cast(host_buffer_length); + url_comp.lpszUrlPath = uri; + url_comp.dwUrlPathLength = static_cast(uri_buffer_length); + + bool result = !!::InternetCrackUrl(url, 0, flags, &url_comp); + if (result) { + *port = static_cast(url_comp.nPort); + } + return result; +} + +bool WinInetClient::Open(const TCHAR* user_agent, + DWORD access_type, + const TCHAR* proxy_name, + const TCHAR* proxy_bypass, + HttpHandle* session_handle) const { + *session_handle = FromHINTERNET(::InternetOpen(user_agent, + MapAccessType(access_type), + proxy_name, + proxy_bypass, + 0)); + return !!(*session_handle); +} + +bool WinInetClient::Connect(HttpHandle session_handle, + const TCHAR* server, + int port, + HttpHandle* connection_handle) const { + assert(server); + + // Uses NULL user name and password to connect. Always uses http service. + *connection_handle = FromHINTERNET(::InternetConnect( + ToHINTERNET(session_handle), + server, + static_cast(port), + NULL, + NULL, + INTERNET_SERVICE_HTTP, + 0, + 0)); + return !!(*connection_handle); +} + +bool WinInetClient::OpenRequest(HttpHandle connection_handle, + const TCHAR* verb, + const TCHAR* uri, + const TCHAR* version, + const TCHAR* referrer, + bool is_secure, + HttpHandle* request_handle) const { + assert(connection_handle); + assert(verb); + assert(uri); + + *request_handle = FromHINTERNET(::HttpOpenRequest( + ToHINTERNET(connection_handle), + verb, + uri, + version, + referrer, + NULL, + is_secure ? INTERNET_FLAG_SECURE : 0, + NULL)); + return !!(*request_handle); +} + +bool WinInetClient::SendRequest(HttpHandle request_handle, + const TCHAR* headers, + DWORD headers_length) const { + assert(request_handle); + + return !!::HttpSendRequest(ToHINTERNET(request_handle), + headers, + headers_length, + NULL, + 0); +} + +bool WinInetClient::ReceiveResponse(HttpHandle) const { + return true; +} + +bool WinInetClient::GetHttpStatusCode(HttpHandle request_handle, + int* status_code) const { + assert(request_handle); + + TCHAR http_status_string[4] = {0}; + DWORD http_status_string_size = sizeof(http_status_string); + if (!::HttpQueryInfo(ToHINTERNET(request_handle), + HTTP_QUERY_STATUS_CODE, + static_cast(&http_status_string), + &http_status_string_size, + 0)) { + return false; + } + + *status_code = _tcstol(http_status_string, NULL, 10); + return true; +} + +bool WinInetClient::GetContentLength(HttpHandle request_handle, + DWORD* content_length) const { + assert(request_handle); + assert(content_length); + + TCHAR content_length_string[11]; + DWORD content_length_string_size = sizeof(content_length_string); + if (!::HttpQueryInfo(ToHINTERNET(request_handle), + HTTP_QUERY_CONTENT_LENGTH, + static_cast(&content_length_string), + &content_length_string_size, + 0)) { + *content_length = kUnknownContentLength; + } else { + *content_length = wcstol(content_length_string, NULL, 10); + } + return true; +} + +bool WinInetClient::ReadData(HttpHandle request_handle, + void* buffer, + DWORD buffer_length, + DWORD* bytes_read) const { + assert(request_handle); + assert(buffer); + assert(bytes_read); + + DWORD bytes_read_local = 0; + if (!::InternetReadFile(ToHINTERNET(request_handle), + buffer, + buffer_length, + &bytes_read_local)) { + return false; + } + *bytes_read = bytes_read_local; + return true; +} + +bool WinInetClient::Close(HttpHandle handle) const { + assert(handle); + return !!::InternetCloseHandle(ToHINTERNET(handle)); +} + +DWORD WinInetClient::MapAccessType(DWORD access_type) { + switch (static_cast(access_type)) { + case ACCESS_TYPE_PRECONFIG: + default: + return INTERNET_OPEN_TYPE_PRECONFIG; + case ACCESS_TYPE_DIRECT: + return INTERNET_OPEN_TYPE_DIRECT; + case ACCESS_TYPE_PROXY: + return INTERNET_OPEN_TYPE_PROXY; + } +} + +HINTERNET WinInetClient::ToHINTERNET(HttpHandle handle) { + return static_cast(handle); +} + +HttpHandle WinInetClient::FromHINTERNET(HINTERNET handle) { + return static_cast(handle); +} + +} // namespace internal + +HttpClient* CreateWinInetClient(const TCHAR*) { + return new internal::WinInetClient(); +} + +} // namespace crash diff --git a/src/tools/windows/converter_exe/wininet_client.h b/src/tools/windows/converter_exe/wininet_client.h new file mode 100644 index 00000000..bd04b605 --- /dev/null +++ b/src/tools/windows/converter_exe/wininet_client.h @@ -0,0 +1,40 @@ +// Copyright 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 TOOLS_WINDOWS_CONVERTER_EXE_WININET_CLIENT_H_ +#define TOOLS_WINDOWS_CONVERTER_EXE_WININET_CLIENT_H_ + +#include "tools/windows/converter_exe/http_client.h" + +namespace crash { + +HttpClient* CreateWinInetClient(const TCHAR* url); + +} // namespace crash + +#endif // TOOLS_WINDOWS_CONVERTER_EXE_WININET_CLIENT_H_ diff --git a/src/tools/windows/tools_windows.gyp b/src/tools/windows/tools_windows.gyp index cc0da728..17b88b4a 100644 --- a/src/tools/windows/tools_windows.gyp +++ b/src/tools/windows/tools_windows.gyp @@ -37,6 +37,7 @@ 'type': 'none', 'dependencies': [ './converter/ms_symbol_server_converter.gyp:*', + './converter_exe/converter.gyp:*', './dump_syms/dump_syms.gyp:*', './symupload/symupload.gyp:*', ], -- cgit v1.2.1