blob: 46ba8327329d66f47f813a6dbd40d9fce0c33f60 [file] [log] [blame]
// Copyright 2010-2015, 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 "net/http_client.h"
#ifdef MOZC_ENABLE_HTTP_CLIENT
#if defined(OS_WIN)
#include <windows.h>
#include <wininet.h>
#elif defined(OS_ANDROID)
#include "base/android_jni_proxy.h"
#elif defined(HAVE_CURL)
#include <curl/curl.h>
#endif
#endif // MOZC_ENABLE_HTTP_CLIENT
#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/port.h"
#include "base/singleton.h"
#include "base/stopwatch.h"
#include "base/util.h"
#include "net/http_client_common.h"
#ifdef MOZC_ENABLE_HTTP_CLIENT
#include "net/http_client_mac.h"
#include "net/http_client_pepper.h"
#include "net/proxy_manager.h"
#endif // MOZC_ENABLE_HTTP_CLIENT
namespace mozc {
// We use a dummy user agent.
const char *kUserAgent = "Mozilla/5.0";
const int kOKResponseCode = 200;
namespace {
#if defined(MOZC_ENABLE_HTTP_CLIENT)
class HTTPStream {
public:
HTTPStream(string *output_string, size_t max_data_size)
: output_string_(output_string),
max_data_size_(max_data_size),
output_size_(0) {
if (NULL != output_string_) {
output_string_->clear();
}
VLOG(2) << "max_data_size=" << max_data_size;
}
virtual ~HTTPStream() {
VLOG(2) << output_size_ << " bytes received";
}
size_t Append(const char *buf, size_t size) {
if (output_size_ + size >= max_data_size_) {
size = (max_data_size_ - output_size_);
LOG(WARNING) << "too long data max_data_size=" << max_data_size_;
}
if (output_string_ != NULL) {
VLOG(2) << "Recived: " << size << " bytes to std::string";
output_string_->append(buf, size);
}
output_size_ += size;
return size;
}
private:
string *output_string_;
size_t max_data_size_;
size_t output_size_;
};
#ifdef OS_WIN
// RAII class for HINTERNET
class ScopedHINTERNET {
public:
explicit ScopedHINTERNET(HINTERNET internet)
: internet_(internet) {}
virtual ~ScopedHINTERNET() {
if (NULL != internet_) {
VLOG(2) << "InternetCloseHandle() called";
::InternetCloseHandle(internet_);
}
internet_ = NULL;
}
HINTERNET get() {
return internet_;
}
private:
HINTERNET internet_;
};
void CALLBACK StatusCallback(HINTERNET internet,
DWORD_PTR context,
DWORD status,
LPVOID status_info,
DWORD status_info_size) {
if (status == INTERNET_STATUS_REQUEST_COMPLETE) {
::SetEvent(reinterpret_cast<HANDLE>(context));
}
if (status == INTERNET_STATUS_HANDLE_CLOSING) {
::CloseHandle(reinterpret_cast<HANDLE>(context));
}
}
bool CheckTimeout(HANDLE event, int64 elapsed_msec, int32 timeout_msec) {
const DWORD error = ::GetLastError();
if (error != ERROR_IO_PENDING) {
LOG(ERROR) << "Unexpected error state: " << error;
return false;
}
const int64 time_left = timeout_msec - elapsed_msec;
if (time_left < 0) {
LOG(WARNING) << "Already timed-out: " << time_left;
return false;
}
DCHECK_GE(elapsed_msec, 0);
DCHECK_LE(time_left, static_cast<int64>(MAXDWORD))
<< "This should always be true because |timeout_msec| <= MAXDWORD";
const DWORD positive_time_left = static_cast<DWORD>(time_left);
const DWORD wait_result = ::WaitForSingleObject(event, positive_time_left);
if (wait_result == WAIT_FAILED) {
const DWORD wait_error = ::GetLastError();
LOG(ERROR) << "WaitForSingleObject failed. error: " << wait_error;
return false;
}
if (wait_result == WAIT_TIMEOUT) {
LOG(WARNING) << "WaitForSingleObject timed out after "
<< positive_time_left << " msec.";
return false;
}
if (wait_result != WAIT_OBJECT_0) {
LOG(ERROR) << "WaitForSingleObject returned unexpected result: "
<< wait_result;
return false;
}
::ResetEvent(event);
return true;
}
bool RequestInternal(HTTPMethodType type,
const string &url,
const char *post_data,
size_t post_size,
const HTTPClient::Option &option,
string *output_string) {
if (option.timeout <= 0) {
LOG(ERROR) << "timeout should not be negative nor 0";
return false;
}
Stopwatch stopwatch = stopwatch.StartNew();
HANDLE event = ::CreateEvent(NULL, FALSE, FALSE, NULL);
if (NULL == event) {
LOG(ERROR) << "CreateEvent() failed: " << ::GetLastError();
return false;
}
ScopedHINTERNET internet(::InternetOpenA(kUserAgent,
INTERNET_OPEN_TYPE_PRECONFIG,
NULL,
NULL,
INTERNET_FLAG_ASYNC));
if (NULL == internet.get()) {
LOG(ERROR) << "InternetOpen() failed: "
<< ::GetLastError() << " " << url;
::CloseHandle(event);
return false;
}
::InternetSetStatusCallback(internet.get(), StatusCallback);
URL_COMPONENTSW uc;
wchar_t Scheme[128];
wchar_t HostName[512];
wchar_t UserName[64];
wchar_t Password[64];
wchar_t UrlPath[256];
wchar_t ExtraInfo[512];
uc.dwStructSize = sizeof(uc);
uc.lpszScheme = Scheme;
uc.lpszHostName = HostName;
uc.lpszUserName = UserName;
uc.lpszPassword = Password;
uc.lpszUrlPath = UrlPath;
uc.lpszExtraInfo = ExtraInfo;
uc.dwSchemeLength = sizeof(Scheme);
uc.dwHostNameLength = sizeof(HostName);
uc.dwUserNameLength = sizeof(UserName);
uc.dwPasswordLength = sizeof(Password);
uc.dwUrlPathLength = sizeof(UrlPath);
uc.dwExtraInfoLength = sizeof(ExtraInfo);
wstring wurl;
Util::UTF8ToWide(url.c_str(), &wurl);
if (!::InternetCrackUrlW(wurl.c_str(), 0, 0, &uc)) {
LOG(WARNING) << "InternetCrackUrl() failed: "
<< ::GetLastError() << " " << url;
return false;
}
if (uc.nScheme != INTERNET_SCHEME_HTTP &&
uc.nScheme != INTERNET_SCHEME_HTTPS) {
LOG(WARNING) << "Only accept HTTP or HTTPS: " << url;
return false;
}
ScopedHINTERNET session(::InternetConnect(internet.get(),
uc.lpszHostName,
uc.nPort,
NULL,
NULL,
INTERNET_SERVICE_HTTP,
0,
0));
if (NULL == session.get()) {
LOG(ERROR) << "InternetConnect() failed: "
<< ::GetLastError() << " " << url;
return false;
}
wstring uri = UrlPath;
if (uc.dwExtraInfoLength != 0) {
uri += uc.lpszExtraInfo;
}
const wchar_t *method_type_string[]
= { L"GET", L"HEAD", L"POST", L"PUT", L"DELETE" };
const wchar_t *method = method_type_string[static_cast<int>(type)];
CHECK(method);
ScopedHINTERNET handle(::HttpOpenRequestW
(session.get(),
method,
uri.c_str(),
NULL, NULL, NULL,
INTERNET_FLAG_RELOAD |
INTERNET_FLAG_DONT_CACHE |
INTERNET_FLAG_NO_UI |
INTERNET_FLAG_PRAGMA_NOCACHE |
(uc.nScheme == INTERNET_SCHEME_HTTPS ?
INTERNET_FLAG_SECURE : 0),
reinterpret_cast<DWORD_PTR>(event)));
if (NULL == handle.get()) {
LOG(ERROR) << "HttpOpenRequest() failed: "
<< ::GetLastError() << " " << url;
return false;
}
for (size_t i = 0; i < option.headers.size(); ++i) {
const string header = option.headers[i] + "\r\n";
wstring wheader;
Util::UTF8ToWide(header.c_str(), &wheader);
if (!::HttpAddRequestHeadersW(handle.get(), wheader.c_str(), -1,
HTTP_ADDREQ_FLAG_ADD |
HTTP_ADDREQ_FLAG_REPLACE)) {
LOG(WARNING) << "HttpAddRequestHeaders() failed: " << option.headers[i]
<< " " << ::GetLastError();
return false;
}
}
if (!::HttpSendRequest(handle.get(),
NULL, 0,
(type == HTTP_POST) ? (LPVOID)post_data : NULL,
(type == HTTP_POST) ? post_size : 0)) {
if (!CheckTimeout(event, stopwatch.GetElapsedMilliseconds(),
option.timeout)) {
LOG(ERROR) << "HttpSendRequest() failed: "
<< ::GetLastError() << " " << url;
return false;
}
} else {
return false;
}
if (VLOG_IS_ON(2)) {
char buf[8192];
DWORD size = sizeof(buf);
if (::HttpQueryInfoA(handle.get(),
HTTP_QUERY_RAW_HEADERS_CRLF |
HTTP_QUERY_FLAG_REQUEST_HEADERS,
buf,
&size,
0)) {
LOG(INFO) << "Request Header: " << buf;
}
}
DWORD code = 0;
{
DWORD code_size = sizeof(code);
if (!::HttpQueryInfoW(handle.get(),
HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER,
&code,
&code_size,
0)) {
LOG(ERROR) << "HttpQueryInfo() failed: "
<< ::GetLastError() << " " << url;
return false;
}
}
// make stream
HTTPStream stream(output_string, option.max_data_size);
if (option.include_header || type == HTTP_HEAD) {
char buf[8192];
DWORD buf_size = sizeof(buf);
if (!::HttpQueryInfoA(handle.get(),
HTTP_QUERY_RAW_HEADERS_CRLF,
buf,
&buf_size,
0)) {
LOG(ERROR) << "HttpQueryInfo() failed: "
<< ::GetLastError() << " " << url;
return false;
}
if (buf_size != stream.Append(buf, buf_size)) {
return false;
}
if (type == HTTP_HEAD) {
return true;
}
}
if (type == HTTP_POST || type == HTTP_GET) {
char buf[8129];
INTERNET_BUFFERSA ibuf;
::ZeroMemory(&ibuf, sizeof(ibuf));
ibuf.dwStructSize = sizeof(ibuf);
while (true) {
ibuf.lpvBuffer = buf;
ibuf.dwBufferLength = sizeof(buf);
if (::InternetReadFileExA(handle.get(),
&ibuf,
WININET_API_FLAG_ASYNC,
reinterpret_cast<DWORD_PTR>(event)) ||
CheckTimeout(event, stopwatch.GetElapsedMilliseconds(),
option.timeout)) {
const DWORD size = ibuf.dwBufferLength;
if (size == 0) {
break;
}
if (size != stream.Append(buf, size)) {
return false;
}
} else {
LOG(ERROR) << "InternetReadFileEx() failed: " << ::GetLastError();
return false;
}
}
}
if (kOKResponseCode != code) {
LOG(WARNING) << "status code is not 200: " << code << " " << url;
return false;
}
return true;
}
#elif defined(OS_MACOSX)
bool RequestInternal(HTTPMethodType type,
const string &url,
const char *post_data,
size_t post_size,
const HTTPClient::Option &option,
string *output_string) {
return MacHTTPRequestHandler::Request(type, url,
post_data, post_size, option,
output_string);
}
#elif defined(__native_client__)
bool RequestInternal(HTTPMethodType type,
const string &url,
const char *post_data,
size_t post_size,
const HTTPClient::Option &option,
string *output_string) {
return PepperHTTPRequestHandler::Request(type, url,
post_data, post_size, option,
output_string);
}
#elif defined(OS_ANDROID)
bool RequestInternal(HTTPMethodType type,
const string &url,
const char *post_data,
size_t post_size,
const HTTPClient::Option &option,
string *output_string) {
// TODO(matsuzakit): Put body field in HTTP response on |output_string|
// if the request arrives to the server and fails.
return jni::JavaHttpClientProxy::Request(type, url, post_data, post_size,
option, output_string);
}
#elif defined(HAVE_CURL)
class CurlInitializer {
public:
CurlInitializer() {
curl_global_init(CURL_GLOBAL_ALL);
}
~CurlInitializer() {
curl_global_cleanup();
}
void Init() {}
};
size_t HTTPOutputCallback(void *ptr, size_t size, size_t nmemb, void *stream) {
HTTPStream *s = reinterpret_cast<HTTPStream *>(stream);
return s->Append(reinterpret_cast<const char *>(ptr), size * nmemb);
}
int HTTPDebugCallback(CURL *curl, curl_infotype type,
char *buf, size_t size, void *data) {
if (CURLINFO_TEXT != type) {
return 0;
}
string *output = reinterpret_cast<string *>(data);
output->append(buf, size);
return 0;
}
bool RequestInternal(HTTPMethodType type,
const string &url,
const char *post_data,
size_t post_size,
const HTTPClient::Option &option,
string *output_string) {
if (option.timeout < 0) {
LOG(ERROR) << "timeout should not be negative nor 0";
return false;
}
Singleton<CurlInitializer>::get()->Init();
CURL *curl = curl_easy_init();
if (NULL == curl) {
LOG(ERROR) << "curl_easy_init() failed";
return false;
}
HTTPStream stream(output_string, option.max_data_size);
string debug;
if (VLOG_IS_ON(2)) {
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, HTTPDebugCallback);
curl_easy_setopt(curl, CURLOPT_DEBUGDATA, reinterpret_cast<void *>(&debug));
}
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
curl_easy_setopt(curl, CURLOPT_AUTOREFERER, 1);
curl_easy_setopt(curl, CURLOPT_USERAGENT, kUserAgent);
curl_easy_setopt(curl, CURLOPT_FORBID_REUSE, 1);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, option.timeout);
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, option.timeout);
curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 5);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &HTTPOutputCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA,
reinterpret_cast<void *>(&stream));
string proxy_host;
string proxy_auth;
if (ProxyManager::GetProxyData(url, &proxy_host, &proxy_auth)) {
curl_easy_setopt(curl, CURLOPT_PROXY, proxy_host.c_str());
if (!proxy_auth.empty()) {
curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, proxy_auth.c_str());
}
}
struct curl_slist *slist = NULL;
for (size_t i = 0; i < option.headers.size(); ++i) {
VLOG(2) << "Add header: " << option.headers[i];
slist = curl_slist_append(slist, option.headers[i].c_str());
}
if (slist != NULL) {
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
}
if (option.include_header) {
curl_easy_setopt(curl, CURLOPT_HEADER, 1);
}
switch (type) {
case HTTP_GET:
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
break;
case HTTP_POST:
curl_easy_setopt(curl, CURLOPT_HTTPPOST, 1);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data);
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, post_size);
break;
case HTTP_HEAD:
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "HEAD");
curl_easy_setopt(curl, CURLOPT_NOBODY, 1);
curl_easy_setopt(curl, CURLOPT_HEADER, 1);
break;
default:
LOG(ERROR) << "Unknown method: " << type;
curl_easy_cleanup(curl);
return false;
break;
}
const CURLcode ret = curl_easy_perform(curl);
bool result = (CURLE_OK == ret);
if (!result) {
LOG(WARNING) << "curl_easy_perform() failed: " << curl_easy_strerror(ret);
}
int code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
if (kOKResponseCode != code) {
LOG(WARNING) << "status code is not 200: " << code;
result = false;
}
curl_easy_cleanup(curl);
if (slist != NULL) {
curl_slist_free_all(slist);
}
if (VLOG_IS_ON(2)) {
LOG(INFO) << debug;
}
return result;
}
#else // defined(HAVE_CURL)
// None of OS_WIN/OS_MACOSX/HAVE_CURL is defined.
#error "HttpClient does not support your platform."
#endif // defined(HAVE_CURL)
#else // defined(MOZC_ENABLE_HTTP_CLIENT)
// MOZC_ENABLE_HTTP_CLIENT is not defined
bool RequestInternal(HTTPMethodType type,
const string &url,
const char *post_data,
size_t post_size,
const HTTPClient::Option &option,
string *output_string) {
// Null implementation.
LOG(ERROR) << "HttpClient is not enabled.";
return false;
}
#endif // MOZC_ENABLE_HTTP_CLIENT
} // namespace
class HTTPClientImpl: public HTTPClientInterface {
public:
virtual bool Get(const string &url, const HTTPClient::Option &option,
string *output_string) const {
return RequestInternal(HTTP_GET, url, NULL, 0, option,
output_string);
}
virtual bool Head(const string &url, const HTTPClient::Option &option,
string *output_string) const {
return RequestInternal(HTTP_HEAD, url, NULL, 0, option,
output_string);
}
virtual bool Post(const string &url, const string &data,
const HTTPClient::Option &option,
string *output_string) const {
return RequestInternal(HTTP_POST, url, data.data(), data.size(),
option, output_string);
}
};
namespace {
const HTTPClientInterface *g_http_connection_handler = NULL;
const HTTPClientInterface &GetHTTPClient() {
if (g_http_connection_handler == NULL) {
g_http_connection_handler = Singleton<HTTPClientImpl>::get();
}
return *g_http_connection_handler;
}
} // namespace
void HTTPClient::SetHTTPClientHandler(
const HTTPClientInterface *handler) {
g_http_connection_handler = handler;
}
bool HTTPClient::Get(const string &url, string *output_string) {
return GetHTTPClient().Get(url, Option(), output_string);
}
bool HTTPClient::Head(const string &url, string *output_string) {
return GetHTTPClient().Head(url, Option(), output_string);
}
bool HTTPClient::Post(const string &url, const string &data,
string *output_string) {
return GetHTTPClient().Post(url, data, Option(), output_string);
}
bool HTTPClient::Get(const string &url, const Option &option,
string *output_string) {
return GetHTTPClient().Get(url, option, output_string);
}
bool HTTPClient::Head(const string &url, const Option &option,
string *output_string) {
return GetHTTPClient().Head(url, option, output_string);
}
bool HTTPClient::Post(const string &url, const string &data,
const Option &option, string *output_string) {
return GetHTTPClient().Post(url, data, option, output_string);
}
} // namespace mozc