blob: 58fcbb3bfcbf8331ffbfc20a2158264b3cbe781f [file] [log] [blame]
// 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 <assert.h>
#include <stdio.h>
#include <Windows.h>
#include <WinInet.h>
#include <vector>
#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<char>::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<typename T>
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<wstring, wstring>* parameters) {
for (map<wstring, wstring>::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<wstring, wstring>* parameters,
string *content, int *status_code) {
assert(content);
AutoPtr<HttpClient> 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<wstring, wstring>::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<char>::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<char> response_buffer = vector<char>(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<DWORD>(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