Add sub-controls for Hack array compat runtime checks
[hiphop-php.git] / hphp / runtime / base / http-client.cpp
blobc1771702e9cec0bd44ae12cb5667f0b0450e9291
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
17 #include "hphp/runtime/base/http-client.h"
18 #include "hphp/runtime/base/runtime-option.h"
19 #include "hphp/runtime/server/server-stats.h"
20 #include "hphp/runtime/base/curl-tls-workarounds.h"
21 #include "hphp/runtime/base/execution-context.h"
22 #include "hphp/util/timer.h"
23 #include <curl/easy.h>
24 #include <vector>
25 #include "hphp/util/logger.h"
26 #include "hphp/util/ssl-init.h"
28 namespace HPHP {
29 ///////////////////////////////////////////////////////////////////////////////
31 //so that curl_global_init() is called ahead of time, avoiding crash
32 struct StaticInitializer {
33 StaticInitializer() {
34 curl_global_init(CURL_GLOBAL_ALL);
35 SSLInit::Init();
38 static StaticInitializer s_initCurl;
40 HttpClient::HttpClient(int timeout /* = 5 */, int maxRedirect /* = 1 */,
41 bool use11 /* = true */, bool decompress /* = false */)
42 : m_timeout(timeout), m_maxRedirect(maxRedirect), m_use11(use11),
43 m_decompress(decompress), m_response(nullptr), m_responseHeaders(nullptr),
44 m_proxyPort(0) {
45 if (m_timeout <= 0) {
46 m_timeout = ThreadInfo::s_threadInfo.getNoCheck()->
47 m_reqInjectionData.getSocketDefaultTimeout();
51 size_t HttpClient::curl_write(char *data, size_t size, size_t nmemb,
52 void *ctx) {
53 return ((HttpClient*)ctx)->write(data, size, nmemb);
55 size_t HttpClient::write(char *data, size_t size, size_t nmemb) {
56 size_t length = size * nmemb;
57 if (length > 0 && m_response) {
58 m_response->append(data, (int)length);
60 return length;
63 size_t HttpClient::curl_header(char *data, size_t size, size_t nmemb,
64 void *ctx) {
65 return ((HttpClient*)ctx)->header(data, size, nmemb);
67 size_t HttpClient::header(char *data, size_t size, size_t nmemb) {
68 size_t length = size * nmemb;
69 if (length > 2 && data[length - 2] == '\r' && data[length - 1] == '\n' &&
70 m_responseHeaders) {
71 m_responseHeaders->push_back(String(data, length - 2, CopyString));
73 return length;
76 void HttpClient::auth(const std::string& username, const std::string& password,
77 bool /*basic*/ /* = true */) {
78 m_basic = true;
79 m_username = username;
80 m_password = password;
83 void HttpClient::proxy(const std::string &host, int port,
84 const std::string &username /* = "" */,
85 const std::string &password /* = "" */) {
86 m_proxyHost = host;
87 m_proxyPort = port;
88 m_proxyUsername = username;
89 m_proxyPassword = password;
92 int HttpClient::get(const char *url, StringBuffer &response,
93 const HeaderMap *requestHeaders /* = NULL */,
94 req::vector<String> *responseHeaders /* = NULL */) {
95 return request(nullptr,
96 url, nullptr, 0, response, requestHeaders, responseHeaders);
99 int HttpClient::post(const char *url, const char *data, size_t size,
100 StringBuffer &response,
101 const HeaderMap *requestHeaders /* = NULL */,
102 req::vector<String> *responseHeaders /* = NULL */) {
103 return request(nullptr,
104 url, data, size, response, requestHeaders, responseHeaders);
107 const StaticString
108 s_ssl("ssl"),
109 s_tls("tls"),
110 s_verify_peer("verify_peer"),
111 s_verify_peer_name("verify_peer_name"),
112 s_capath("capath"),
113 s_cafile("cafile"),
114 s_local_cert("local_cert"),
115 s_passphrase("passphrase"),
116 s_http("http"),
117 s_header("header");
119 int HttpClient::request(const char* verb,
120 const char *url, const char *data, size_t size,
121 StringBuffer &response, const HeaderMap *requestHeaders,
122 req::vector<String> *responseHeaders) {
123 SlowTimer timer(RuntimeOption::HttpSlowQueryThreshold, "curl", url);
125 m_response = &response;
127 char error_str[CURL_ERROR_SIZE + 1];
128 memset(error_str, 0, sizeof(error_str));
130 CURL *cp = curl_easy_init();
131 curl_easy_setopt(cp, CURLOPT_URL, url);
132 curl_easy_setopt(cp, CURLOPT_WRITEFUNCTION, curl_write);
133 curl_easy_setopt(cp, CURLOPT_WRITEDATA, (void*)this);
134 curl_easy_setopt(cp, CURLOPT_ERRORBUFFER, error_str);
135 curl_easy_setopt(cp, CURLOPT_NOPROGRESS, 1);
136 curl_easy_setopt(cp, CURLOPT_VERBOSE, 0);
137 curl_easy_setopt(cp, CURLOPT_NOSIGNAL, 1);
138 curl_easy_setopt(cp, CURLOPT_DNS_USE_GLOBAL_CACHE, 0); // for thread-safe
139 curl_easy_setopt(cp, CURLOPT_DNS_CACHE_TIMEOUT, 120);
140 curl_easy_setopt(cp, CURLOPT_NOSIGNAL, 1); // for multithreading mode
141 curl_easy_setopt(cp, CURLOPT_SSL_VERIFYPEER, 1);
142 // For libcurl the VERIFYHOST "true"/enabled value is '2', NOT '1'!
143 // If libcurl is built with NSS, VERIFYPEER =0 forces VERIFYHOST to =0
144 curl_easy_setopt(cp, CURLOPT_SSL_VERIFYHOST, 2);
145 curl_easy_setopt(cp, CURLOPT_SSL_CTX_FUNCTION, curl_tls_workarounds_cb);
146 curl_easy_setopt(cp, CURLOPT_USE_SSL, m_use_ssl);
147 curl_easy_setopt(cp, CURLOPT_SSLVERSION, m_sslversion);
150 * cipher list varies according to SSL library, and "ALL" is for OpenSSL
152 curl_version_info_data *cver = curl_version_info(CURLVERSION_NOW);
153 if (cver && cver->ssl_version && strstr(cver->ssl_version, "OpenSSL")) {
154 curl_easy_setopt(cp, CURLOPT_SSL_CIPHER_LIST, "ALL");
157 curl_easy_setopt(cp, CURLOPT_TIMEOUT, m_timeout);
158 if (m_maxRedirect > 1) {
159 curl_easy_setopt(cp, CURLOPT_FOLLOWLOCATION, 1);
160 curl_easy_setopt(cp, CURLOPT_MAXREDIRS, m_maxRedirect);
161 } else {
162 curl_easy_setopt(cp, CURLOPT_FOLLOWLOCATION, 0);
164 if (!m_use11) {
165 curl_easy_setopt(cp, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
167 if (m_decompress) {
168 curl_easy_setopt(cp, CURLOPT_ENCODING, "");
171 if (!m_username.empty()) {
172 curl_easy_setopt(cp, CURLOPT_HTTPAUTH,
173 m_basic ? CURLAUTH_BASIC : CURLAUTH_DIGEST);
174 curl_easy_setopt(cp, CURLOPT_USERNAME, m_username.c_str());
175 curl_easy_setopt(cp, CURLOPT_PASSWORD, m_password.c_str());
178 if (!m_proxyHost.empty() && m_proxyPort) {
179 curl_easy_setopt(cp, CURLOPT_PROXY, m_proxyHost.c_str());
180 curl_easy_setopt(cp, CURLOPT_PROXYPORT, m_proxyPort);
181 if (!m_proxyUsername.empty()) {
182 curl_easy_setopt(cp, CURLOPT_PROXYAUTH, CURLAUTH_BASIC);
183 curl_easy_setopt(cp, CURLOPT_PROXYUSERNAME, m_proxyUsername.c_str());
184 curl_easy_setopt(cp, CURLOPT_PROXYPASSWORD, m_proxyPassword.c_str());
188 curl_slist *slist = nullptr;
189 if (requestHeaders) {
190 for (HeaderMap::const_iterator iter = requestHeaders->begin();
191 iter != requestHeaders->end(); ++iter) {
192 for (unsigned int i = 0; i < iter->second.size(); i++) {
193 String header = iter->first + ": " + iter->second[i];
194 slist = curl_slist_append(slist, header.data());
197 if (m_stream_context_options[s_http].isArray()) {
198 const Array http = m_stream_context_options[s_http].toArray();
199 if (http.exists(s_header)) {
200 slist = curl_slist_append(slist,
201 http[s_header].toString().data());
204 if (slist) {
205 curl_easy_setopt(cp, CURLOPT_HTTPHEADER, slist);
209 if (data && size) {
210 curl_easy_setopt(cp, CURLOPT_POST, 1);
211 curl_easy_setopt(cp, CURLOPT_POSTFIELDS, data);
212 if (size <= 0x7fffffffLL) {
213 curl_easy_setopt(cp, CURLOPT_POSTFIELDSIZE, size);
214 } else {
215 curl_easy_setopt(cp, CURLOPT_POSTFIELDSIZE_LARGE, size);
219 if (verb != nullptr) {
220 curl_easy_setopt(cp, CURLOPT_CUSTOMREQUEST, verb);
222 if (strcasecmp(verb, "HEAD") == 0) {
223 curl_easy_setopt(cp, CURLOPT_NOBODY, 1);
227 if (responseHeaders) {
228 m_responseHeaders = responseHeaders;
229 curl_easy_setopt(cp, CURLOPT_HEADERFUNCTION, curl_header);
230 curl_easy_setopt(cp, CURLOPT_WRITEHEADER, (void*)this);
233 if (m_stream_context_options[s_ssl].isArray() ||
234 m_stream_context_options[s_tls].isArray()) {
235 const Array ssl = m_stream_context_options[s_ssl].isArray() ? \
236 m_stream_context_options[s_ssl].toArray() : \
237 m_stream_context_options[s_tls].toArray();
238 if (ssl.exists(s_verify_peer)) {
239 curl_easy_setopt(cp, CURLOPT_SSL_VERIFYPEER,
240 ssl[s_verify_peer].toBoolean());
242 if (ssl.exists(s_verify_peer_name)) {
243 // For libcurl VERIFYHOST the enable/"true" value is '2', NOT '1'!
244 curl_easy_setopt(cp, CURLOPT_SSL_VERIFYHOST,
245 ssl[s_verify_peer_name].toBoolean() ? \
246 2 : 0);
248 if (ssl.exists(s_capath)) {
249 curl_easy_setopt(cp, CURLOPT_CAPATH,
250 ssl[s_capath].toString().data());
252 if (ssl.exists(s_cafile)) {
253 curl_easy_setopt(cp, CURLOPT_CAINFO,
254 ssl[s_cafile].toString().data());
256 if (ssl.exists(s_local_cert)) {
257 curl_easy_setopt(cp, CURLOPT_SSLKEY,
258 ssl[s_local_cert].toString().data());
259 curl_easy_setopt(cp, CURLOPT_SSLKEYTYPE, "PEM");
261 if (ssl.exists(s_passphrase)) {
262 curl_easy_setopt(cp, CURLOPT_KEYPASSWD,
263 ssl[s_passphrase].toString().data());
267 long code = 0;
269 IOStatusHelper io("http", url);
270 CURLcode error_no = curl_easy_perform(cp);
271 if (error_no != CURLE_OK) {
272 m_error = error_str;
273 } else {
274 curl_easy_getinfo(cp, CURLINFO_RESPONSE_CODE, &code);
278 set_curl_statuses(cp, url);
280 if (slist) {
281 curl_slist_free_all(slist);
284 curl_easy_cleanup(cp);
285 return code;
288 ///////////////////////////////////////////////////////////////////////////////