// 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 %lu 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 %lu 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 %lu 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 %lu 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 %lu for %ws\n", GetLastError(), url.c_str()); return false; } if (!http_client->ReceiveResponse(request.get())) { fprintf(stderr, "HttpClient::ReceiveResponse: error %lu 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 %lu 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 %lu for %ws\n", GetLastError(), url.c_str()); return false; } else if (size_read != 0) { fprintf(stderr, "HttpClient::ReadData: error %lu/%lu 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