2 +----------------------------------------------------------------------+
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>
25 #include "hphp/util/logger.h"
26 #include "hphp/util/ssl-init.h"
29 ///////////////////////////////////////////////////////////////////////////////
31 //so that curl_global_init() is called ahead of time, avoiding crash
32 struct StaticInitializer
{
34 curl_global_init(CURL_GLOBAL_ALL
);
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),
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
,
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
);
63 size_t HttpClient::curl_header(char *data
, size_t size
, size_t nmemb
,
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' &&
71 m_responseHeaders
->push_back(String(data
, length
- 2, CopyString
));
76 void HttpClient::auth(const std::string
& username
, const std::string
& password
,
77 bool /*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
/* = "" */) {
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
);
110 s_verify_peer("verify_peer"),
111 s_verify_peer_name("verify_peer_name"),
114 s_local_cert("local_cert"),
115 s_passphrase("passphrase"),
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
);
162 curl_easy_setopt(cp
, CURLOPT_FOLLOWLOCATION
, 0);
165 curl_easy_setopt(cp
, CURLOPT_HTTP_VERSION
, CURL_HTTP_VERSION_1_0
);
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());
205 curl_easy_setopt(cp
, CURLOPT_HTTPHEADER
, slist
);
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
);
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() ? \
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());
269 IOStatusHelper
io("http", url
);
270 CURLcode error_no
= curl_easy_perform(cp
);
271 if (error_no
!= CURLE_OK
) {
274 curl_easy_getinfo(cp
, CURLINFO_RESPONSE_CODE
, &code
);
278 set_curl_statuses(cp
, url
);
281 curl_slist_free_all(slist
);
284 curl_easy_cleanup(cp
);
288 ///////////////////////////////////////////////////////////////////////////////