2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
6 | Copyright (c) 1997-2010 The PHP Group |
7 +----------------------------------------------------------------------+
8 | This source file is subject to version 3.01 of the PHP license, |
9 | that is bundled with this package in the file LICENSE, and is |
10 | available through the world-wide-web at the following url: |
11 | http://www.php.net/license/3_01.txt |
12 | If you did not receive a copy of the PHP license and are unable to |
13 | obtain it through the world-wide-web, please send a note to |
14 | license@php.net so we can mail you a copy immediately. |
15 +----------------------------------------------------------------------+
18 #include "hphp/runtime/ext/ext_curl.h"
19 #include "hphp/runtime/ext/ext_function.h"
20 #include "hphp/runtime/base/util/string_buffer.h"
21 #include "hphp/runtime/base/util/libevent_http_client.h"
22 #include "hphp/runtime/base/util/curl_tls_workarounds.h"
23 #include "hphp/runtime/base/runtime_option.h"
24 #include "hphp/runtime/base/server/server_stats.h"
25 #include "hphp/runtime/vm/jit/translator-inline.h"
27 #define CURLOPT_RETURNTRANSFER 19913
28 #define CURLOPT_BINARYTRANSFER 19914
29 #define PHP_CURL_STDOUT 0
30 #define PHP_CURL_FILE 1
31 #define PHP_CURL_USER 2
32 #define PHP_CURL_DIRECT 3
33 #define PHP_CURL_RETURN 4
34 #define PHP_CURL_ASCII 5
35 #define PHP_CURL_BINARY 6
36 #define PHP_CURL_IGNORE 7
40 IMPLEMENT_DEFAULT_EXTENSION(curl
);
42 static StaticString
s_exception("exception");
43 static StaticString
s_previous("previous");
45 ///////////////////////////////////////////////////////////////////////////////
46 // helper data structure
48 class CurlResource
: public SweepableResourceData
{
50 DECLARE_OBJECT_ALLOCATION(CurlResource
)
54 WriteHandler() : method(0), type(0) {}
58 SmartResource
<File
> fp
;
66 ReadHandler() : method(0) {}
70 SmartResource
<File
> fp
;
73 DECLARE_BOOST_TYPES(ToFree
);
77 vector
<curl_httppost
*> post
;
78 vector
<curl_slist
*> slist
;
81 for (unsigned int i
= 0; i
< str
.size(); i
++) {
84 for (unsigned int i
= 0; i
< post
.size(); i
++) {
85 curl_formfree(post
[i
]);
87 for (unsigned int i
= 0; i
< slist
.size(); i
++) {
88 curl_slist_free_all(slist
[i
]);
94 static StaticString s_class_name
;
95 // overriding ResourceData
96 virtual CStrRef
o_getClassNameHook() const { return s_class_name
; }
98 explicit CurlResource(CStrRef url
)
99 : m_exception(nullptr), m_phpException(false), m_emptyPost(true) {
100 m_cp
= curl_easy_init();
103 memset(m_error_str
, 0, sizeof(m_error_str
));
104 m_error_no
= CURLE_OK
;
105 m_to_free
= ToFreePtr(new ToFree());
107 m_write
.method
= PHP_CURL_STDOUT
;
108 m_write
.type
= PHP_CURL_ASCII
;
109 m_read
.method
= PHP_CURL_DIRECT
;
110 m_write_header
.method
= PHP_CURL_IGNORE
;
112 curl_easy_setopt(m_cp
, CURLOPT_NOPROGRESS
, 1);
113 curl_easy_setopt(m_cp
, CURLOPT_VERBOSE
, 0);
114 curl_easy_setopt(m_cp
, CURLOPT_ERRORBUFFER
, m_error_str
);
115 curl_easy_setopt(m_cp
, CURLOPT_WRITEFUNCTION
, curl_write
);
116 curl_easy_setopt(m_cp
, CURLOPT_FILE
, (void*)this);
117 curl_easy_setopt(m_cp
, CURLOPT_READFUNCTION
, curl_read
);
118 curl_easy_setopt(m_cp
, CURLOPT_INFILE
, (void*)this);
119 curl_easy_setopt(m_cp
, CURLOPT_HEADERFUNCTION
, curl_write_header
);
120 curl_easy_setopt(m_cp
, CURLOPT_WRITEHEADER
, (void*)this);
121 curl_easy_setopt(m_cp
, CURLOPT_DNS_USE_GLOBAL_CACHE
, 0); // for thread-safe
122 curl_easy_setopt(m_cp
, CURLOPT_DNS_CACHE_TIMEOUT
, 120);
123 curl_easy_setopt(m_cp
, CURLOPT_MAXREDIRS
, 20); // no infinite redirects
124 curl_easy_setopt(m_cp
, CURLOPT_NOSIGNAL
, 1); // for multithreading mode
125 curl_easy_setopt(m_cp
, CURLOPT_SSL_CTX_FUNCTION
, curl_tls_workarounds_cb
);
127 curl_easy_setopt(m_cp
, CURLOPT_TIMEOUT
,
128 RuntimeOption::HttpDefaultTimeout
);
129 curl_easy_setopt(m_cp
, CURLOPT_CONNECTTIMEOUT
,
130 RuntimeOption::HttpDefaultTimeout
);
133 #if LIBCURL_VERSION_NUM >= 0x071100
134 /* Strings passed to libcurl as 'char *' arguments, are copied by
135 the library... NOTE: before 7.17.0 strings were not copied. */
136 curl_easy_setopt(m_cp
, CURLOPT_URL
, url
.c_str());
138 char *urlcopy
= strndup(url
.data(), url
.size());
139 curl_easy_setopt(m_cp
, CURLOPT_URL
, urlcopy
);
140 m_to_free
->str
.push_back(urlcopy
);
145 explicit CurlResource(CurlResource
*src
)
146 : m_exception(nullptr), m_phpException(false) {
147 assert(src
&& src
!= this);
148 assert(!src
->m_exception
);
150 m_cp
= curl_easy_duphandle(src
->get());
153 memset(m_error_str
, 0, sizeof(m_error_str
));
154 m_error_no
= CURLE_OK
;
156 m_write
.method
= src
->m_write
.method
;
157 m_write
.type
= src
->m_write
.type
;
158 m_read
.method
= src
->m_read
.method
;
159 m_write_header
.method
= src
->m_write_header
.method
;
161 m_write
.fp
= src
->m_write
.fp
;
162 m_write_header
.fp
= src
->m_write_header
.fp
;
163 m_read
.fp
= src
->m_read
.fp
;
165 m_write
.callback
= src
->m_write
.callback
;
166 m_read
.callback
= src
->m_read
.callback
;
167 m_write_header
.callback
= src
->m_write_header
.callback
;
169 curl_easy_setopt(m_cp
, CURLOPT_ERRORBUFFER
, m_error_str
);
170 curl_easy_setopt(m_cp
, CURLOPT_FILE
, (void*)this);
171 curl_easy_setopt(m_cp
, CURLOPT_INFILE
, (void*)this);
172 curl_easy_setopt(m_cp
, CURLOPT_WRITEHEADER
, (void*)this);
174 m_to_free
= src
->m_to_free
;
175 m_emptyPost
= src
->m_emptyPost
;
182 void closeForSweep() {
183 assert(!m_exception
);
185 curl_easy_cleanup(m_cp
);
196 void check_exception() {
198 if (m_phpException
) {
199 Object
e((ObjectData
*)m_exception
);
201 e
.get()->decRefCount();
204 Exception
*e
= (Exception
*)m_exception
;
211 ObjectData
* getAndClearPhpException() {
212 if (m_exception
&& m_phpException
) {
213 ObjectData
* ret
= (ObjectData
*)m_exception
;
214 m_exception
= nullptr;
220 Exception
* getAndClearCppException() {
221 if (!m_phpException
) {
222 Exception
* e
= (Exception
*)m_exception
;
223 m_exception
= nullptr;
230 assert(!m_exception
);
235 // As per curl docs, an empty post must set POSTFIELDSIZE to be 0 or
236 // the reader function will be called
237 curl_easy_setopt(m_cp
, CURLOPT_POSTFIELDSIZE
, 0);
240 m_write
.content
.clear();
242 memset(m_error_str
, 0, sizeof(m_error_str
));
245 IOStatusHelper
io("curl_easy_perform", m_url
.data());
246 SYNC_VM_REGS_SCOPED();
247 m_error_no
= curl_easy_perform(m_cp
);
250 set_curl_statuses(m_cp
, m_url
.data());
252 /* CURLE_PARTIAL_FILE is returned by HEAD requests */
253 if (m_error_no
!= CURLE_OK
&& m_error_no
!= CURLE_PARTIAL_FILE
) {
255 m_write
.content
.clear();
259 if (m_write
.method
== PHP_CURL_RETURN
) {
260 if (!m_write
.buf
.empty()) {
261 m_write
.content
= m_write
.buf
.detach();
263 if (!m_write
.content
.empty()) {
264 return m_write
.content
;
267 if (m_write
.method
== PHP_CURL_RETURN
) {
281 String
getContents() {
282 if (m_write
.method
== PHP_CURL_RETURN
) {
283 if (!m_write
.buf
.empty()) {
284 m_write
.content
= m_write
.buf
.detach();
286 return m_write
.content
;
291 bool setOption(long option
, CVarRef value
) {
295 m_error_no
= CURLE_OK
;
298 case CURLOPT_INFILESIZE
:
299 case CURLOPT_VERBOSE
:
301 case CURLOPT_NOPROGRESS
:
303 case CURLOPT_FAILONERROR
:
306 case CURLOPT_FTPLISTONLY
:
307 case CURLOPT_FTPAPPEND
:
310 case CURLOPT_TIMEOUT
:
311 #if LIBCURL_VERSION_NUM >= 0x071002
312 case CURLOPT_TIMEOUT_MS
:
314 case CURLOPT_FTP_USE_EPSV
:
315 case CURLOPT_LOW_SPEED_LIMIT
:
316 case CURLOPT_SSLVERSION
:
317 case CURLOPT_LOW_SPEED_TIME
:
318 case CURLOPT_RESUME_FROM
:
319 case CURLOPT_TIMEVALUE
:
320 case CURLOPT_TIMECONDITION
:
321 case CURLOPT_TRANSFERTEXT
:
322 case CURLOPT_HTTPPROXYTUNNEL
:
323 case CURLOPT_FILETIME
:
324 case CURLOPT_MAXREDIRS
:
325 case CURLOPT_MAXCONNECTS
:
326 case CURLOPT_CLOSEPOLICY
:
327 case CURLOPT_FRESH_CONNECT
:
328 case CURLOPT_FORBID_REUSE
:
329 case CURLOPT_CONNECTTIMEOUT
:
330 #if LIBCURL_VERSION_NUM >= 0x071002
331 case CURLOPT_CONNECTTIMEOUT_MS
:
333 case CURLOPT_SSL_VERIFYHOST
:
334 case CURLOPT_SSL_VERIFYPEER
:
335 //case CURLOPT_DNS_USE_GLOBAL_CACHE: not thread-safe when set to true
336 case CURLOPT_NOSIGNAL
:
337 case CURLOPT_PROXYTYPE
:
338 case CURLOPT_BUFFERSIZE
:
339 case CURLOPT_HTTPGET
:
340 case CURLOPT_HTTP_VERSION
:
342 case CURLOPT_DNS_CACHE_TIMEOUT
:
343 case CURLOPT_PROXYPORT
:
344 case CURLOPT_FTP_USE_EPRT
:
345 case CURLOPT_HTTPAUTH
:
346 case CURLOPT_PROXYAUTH
:
347 case CURLOPT_FTP_CREATE_MISSING_DIRS
:
348 case CURLOPT_FTPSSLAUTH
:
349 case CURLOPT_FTP_SSL
:
350 case CURLOPT_UNRESTRICTED_AUTH
:
352 case CURLOPT_AUTOREFERER
:
353 case CURLOPT_COOKIESESSION
:
354 case CURLOPT_TCP_NODELAY
:
355 case CURLOPT_IPRESOLVE
:
356 case CURLOPT_FOLLOWLOCATION
:
357 m_error_no
= curl_easy_setopt(m_cp
, (CURLoption
)option
, value
.toInt64());
359 case CURLOPT_RETURNTRANSFER
:
360 m_write
.method
= value
.toBoolean() ? PHP_CURL_RETURN
: PHP_CURL_STDOUT
;
362 case CURLOPT_BINARYTRANSFER
:
363 m_write
.type
= value
.toBoolean() ? PHP_CURL_BINARY
: PHP_CURL_ASCII
;
365 case CURLOPT_PRIVATE
:
368 case CURLOPT_USERPWD
:
369 case CURLOPT_PROXYUSERPWD
:
371 case CURLOPT_CUSTOMREQUEST
:
372 case CURLOPT_USERAGENT
:
373 case CURLOPT_FTPPORT
:
375 case CURLOPT_REFERER
:
376 case CURLOPT_INTERFACE
:
377 case CURLOPT_KRB4LEVEL
:
378 case CURLOPT_EGDSOCKET
:
381 case CURLOPT_SSL_CIPHER_LIST
:
383 case CURLOPT_SSLKEYTYPE
:
384 case CURLOPT_SSLKEYPASSWD
:
385 case CURLOPT_SSLENGINE
:
386 case CURLOPT_SSLENGINE_DEFAULT
:
387 case CURLOPT_SSLCERTTYPE
:
388 case CURLOPT_ENCODING
:
389 case CURLOPT_COOKIEJAR
:
390 case CURLOPT_SSLCERT
:
391 case CURLOPT_RANDOM_FILE
:
392 case CURLOPT_COOKIEFILE
:
394 String svalue
= value
.toString();
395 #if LIBCURL_VERSION_NUM >= 0x071100
396 /* Strings passed to libcurl as 'char *' arguments, are copied
397 by the library... NOTE: before 7.17.0 strings were not copied. */
398 m_error_no
= curl_easy_setopt(m_cp
, (CURLoption
)option
, svalue
.c_str());
400 char *copystr
= strndup(svalue
.data(), svalue
.size());
401 m_to_free
->str
.push_back(copystr
);
402 m_error_no
= curl_easy_setopt(m_cp
, (CURLoption
)option
, copystr
);
404 if (option
== CURLOPT_URL
) m_url
= value
;
409 case CURLOPT_WRITEHEADER
:
412 if (!value
.isResource()) {
416 Resource obj
= value
.toResource();
417 if (obj
.isNull() || obj
.getTyped
<File
>(true) == NULL
) {
424 m_write
.method
= PHP_CURL_FILE
;
426 case CURLOPT_WRITEHEADER
:
427 m_write_header
.fp
= obj
;
428 m_write_header
.method
= PHP_CURL_FILE
;
435 if (obj
.getTyped
<PlainFile
>(true) == NULL
) {
438 FILE *fp
= obj
.getTyped
<PlainFile
>()->getStream();
442 m_error_no
= curl_easy_setopt(m_cp
, (CURLoption
)option
, fp
);
448 case CURLOPT_WRITEFUNCTION
:
449 m_write
.callback
= value
;
450 m_write
.method
= PHP_CURL_USER
;
452 case CURLOPT_READFUNCTION
:
453 m_read
.callback
= value
;
454 m_read
.method
= PHP_CURL_USER
;
457 case CURLOPT_HEADERFUNCTION
:
458 m_write_header
.callback
= value
;
459 m_write_header
.method
= PHP_CURL_USER
;
461 case CURLOPT_POSTFIELDS
:
463 if (value
.is(KindOfArray
) || value
.is(KindOfObject
)) {
464 Array arr
= value
.toArray();
465 curl_httppost
*first
= NULL
;
466 curl_httppost
*last
= NULL
;
467 for (ArrayIter
iter(arr
); iter
; ++iter
) {
468 String key
= iter
.first().toString();
469 String val
= iter
.second().toString();
470 const char *postval
= val
.data();
472 /* The arguments after _NAMELENGTH and _CONTENTSLENGTH
473 * must be explicitly cast to long in curl_formadd
474 * use since curl needs a long not an int. */
475 if (*postval
== '@') {
477 m_error_no
= (CURLcode
)curl_formadd
479 CURLFORM_COPYNAME
, key
.data(),
480 CURLFORM_NAMELENGTH
, (long)key
.size(),
481 CURLFORM_FILE
, postval
,
484 m_error_no
= (CURLcode
)curl_formadd
486 CURLFORM_COPYNAME
, key
.data(),
487 CURLFORM_NAMELENGTH
, (long)key
.size(),
488 CURLFORM_COPYCONTENTS
, postval
,
489 CURLFORM_CONTENTSLENGTH
,(long)val
.size(),
494 if (m_error_no
!= CURLE_OK
) {
498 m_to_free
->post
.push_back(first
);
499 m_error_no
= curl_easy_setopt(m_cp
, CURLOPT_HTTPPOST
, first
);
502 String svalue
= value
.toString();
503 #if LIBCURL_VERSION_NUM >= 0x071100
504 /* with curl 7.17.0 and later, we can use COPYPOSTFIELDS,
505 but we have to provide size before */
506 m_error_no
= curl_easy_setopt(m_cp
, CURLOPT_POSTFIELDSIZE
,
508 m_error_no
= curl_easy_setopt(m_cp
, CURLOPT_COPYPOSTFIELDS
,
511 char *post
= strndup(svalue
.data(), svalue
.size());
512 m_to_free
->str
.push_back(post
);
514 m_error_no
= curl_easy_setopt(m_cp
, CURLOPT_POSTFIELDS
, post
);
515 m_error_no
= curl_easy_setopt(m_cp
, CURLOPT_POSTFIELDSIZE
,
520 case CURLOPT_HTTPHEADER
:
522 case CURLOPT_HTTP200ALIASES
:
523 case CURLOPT_POSTQUOTE
:
524 if (value
.is(KindOfArray
) || value
.is(KindOfObject
)) {
525 Array arr
= value
.toArray();
526 curl_slist
*slist
= NULL
;
527 for (ArrayIter
iter(arr
); iter
; ++iter
) {
528 String key
= iter
.first().toString();
529 String val
= iter
.second().toString();
531 slist
= curl_slist_append(slist
, val
.c_str());
533 raise_warning("Could not build curl_slist");
538 m_to_free
->slist
.push_back(slist
);
539 m_error_no
= curl_easy_setopt(m_cp
, (CURLoption
)option
, slist
);
542 raise_warning("You must pass either an object or an array with "
543 "the CURLOPT_HTTPHEADER, CURLOPT_QUOTE, "
544 "CURLOPT_HTTP200ALIASES and CURLOPT_POSTQUOTE "
550 case CURLINFO_HEADER_OUT
:
551 if (value
.toInt64() == 1) {
552 curl_easy_setopt(m_cp
, CURLOPT_DEBUGFUNCTION
, curl_debug
);
553 curl_easy_setopt(m_cp
, CURLOPT_DEBUGDATA
, (void *)this);
554 curl_easy_setopt(m_cp
, CURLOPT_VERBOSE
, 1);
556 curl_easy_setopt(m_cp
, CURLOPT_DEBUGFUNCTION
, NULL
);
557 curl_easy_setopt(m_cp
, CURLOPT_DEBUGDATA
, NULL
);
558 curl_easy_setopt(m_cp
, CURLOPT_VERBOSE
, 0);
563 m_error_no
= CURLE_FAILED_INIT
;
564 throw_invalid_argument("option: %ld", option
);
568 m_opts
.set(int64_t(option
), value
);
570 return m_error_no
== CURLE_OK
;
573 Variant
getOption(long option
) {
576 if (!m_opts
.exists(int64_t(option
))) {
579 return m_opts
[int64_t(option
)];
585 static int curl_debug(CURL
*cp
, curl_infotype type
, char *buf
,
586 size_t buf_len
, void *ctx
) {
587 CurlResource
*ch
= (CurlResource
*)ctx
;
588 if (type
== CURLINFO_HEADER_OUT
&& buf_len
> 0) {
589 ch
->m_header
= String(buf
, buf_len
, CopyString
);
594 Variant
do_callback(CVarRef cb
, CArrRef args
) {
595 assert(!m_exception
);
597 return vm_call_user_func(cb
, args
);
598 } catch (Object
&e
) {
599 ObjectData
*od
= e
.get();
602 m_phpException
= true;
603 } catch (Exception
&e
) {
604 m_exception
= e
.clone();
605 m_phpException
= false;
607 return uninit_null();
610 static size_t curl_read(char *data
, size_t size
, size_t nmemb
, void *ctx
) {
611 CurlResource
*ch
= (CurlResource
*)ctx
;
612 ReadHandler
*t
= &ch
->m_read
;
616 case PHP_CURL_DIRECT
:
617 if (!t
->fp
.isNull()) {
618 int data_size
= size
* nmemb
;
619 String ret
= t
->fp
->read(data_size
);
622 memcpy(data
, ret
.data(), length
);
628 int data_size
= size
* nmemb
;
629 Variant ret
= ch
->do_callback(
630 t
->callback
, CREATE_VECTOR3(Resource(ch
), t
->fp
->fd(), data_size
));
631 if (ret
.isString()) {
632 String sret
= ret
.toString();
633 length
= data_size
< sret
.size() ? data_size
: sret
.size();
634 memcpy(data
, sret
.data(), length
);
642 static size_t curl_write(char *data
, size_t size
, size_t nmemb
, void *ctx
) {
643 CurlResource
*ch
= (CurlResource
*)ctx
;
644 WriteHandler
*t
= &ch
->m_write
;
645 size_t length
= size
* nmemb
;
648 case PHP_CURL_STDOUT
:
649 g_context
->write(data
, length
);
652 return t
->fp
->write(String(data
, length
, AttachLiteral
), length
);
653 case PHP_CURL_RETURN
:
655 t
->buf
.append(data
, (int)length
);
660 Variant ret
= ch
->do_callback(
662 CREATE_VECTOR2(Resource(ch
), String(data
, length
, CopyString
)));
663 length
= ret
.toInt64();
671 static size_t curl_write_header(char *data
, size_t size
, size_t nmemb
,
673 CurlResource
*ch
= (CurlResource
*)ctx
;
674 WriteHandler
*t
= &ch
->m_write_header
;
675 size_t length
= size
* nmemb
;
678 case PHP_CURL_STDOUT
:
679 // Handle special case write when we're returning the entire transfer
680 if (ch
->m_write
.method
== PHP_CURL_RETURN
&& length
> 0) {
681 ch
->m_write
.buf
.append(data
, (int)length
);
683 g_context
->write(data
, length
);
687 return t
->fp
->write(String(data
, length
, AttachLiteral
), length
);
690 Variant ret
= ch
->do_callback(
692 CREATE_VECTOR2(Resource(ch
), String(data
, length
, CopyString
)));
693 length
= ret
.toInt64();
696 case PHP_CURL_IGNORE
:
705 CURL
*get(bool nullOkay
= false) {
706 if (m_cp
== NULL
&& !nullOkay
) {
707 throw NullPointerException();
716 String
getErrorString() {
717 return String(m_error_str
, CopyString
);
724 char m_error_str
[CURL_ERROR_SIZE
+ 1];
733 WriteHandler m_write
;
734 WriteHandler m_write_header
;
740 IMPLEMENT_OBJECT_ALLOCATION_NO_DEFAULT_SWEEP(CurlResource
);
741 void CurlResource::sweep() {
742 m_write
.buf
.release();
743 m_write_header
.buf
.release();
747 StaticString
CurlResource::s_class_name("cURL handle");
749 ///////////////////////////////////////////////////////////////////////////////
751 #define CHECK_RESOURCE(curl) \
752 CurlResource *curl = ch.getTyped<CurlResource>(true, true); \
753 if (curl == NULL) { \
754 raise_warning("supplied argument is not a valid cURL handle resource"); \
758 Variant f_curl_init(CStrRef url /* = null_string */) {
759 return NEWOBJ(CurlResource
)(url
);
762 Variant
f_curl_copy_handle(CResRef ch
) {
763 CHECK_RESOURCE(curl
);
764 return NEWOBJ(CurlResource
)(curl
);
768 s_version_number("version_number"),
770 s_features("features"),
771 s_ssl_version_number("ssl_version_number"),
772 s_version("version"),
774 s_ssl_version("ssl_version"),
775 s_libz_version("libz_version"),
776 s_protocols("protocols");
778 Variant
f_curl_version(int uversion
/* = k_CURLVERSION_NOW */) {
779 curl_version_info_data
*d
= curl_version_info((CURLversion
)uversion
);
785 ret
.set(s_version_number
, (int)d
->version_num
);
786 ret
.set(s_age
, d
->age
);
787 ret
.set(s_features
, d
->features
);
788 ret
.set(s_ssl_version_number
, d
->ssl_version_num
);
789 ret
.set(s_version
, d
->version
);
790 ret
.set(s_host
, d
->host
);
791 ret
.set(s_ssl_version
, d
->ssl_version
);
792 ret
.set(s_libz_version
, d
->libz_version
);
794 // Add an array of protocols
795 char **p
= (char **) d
->protocols
;
798 protocol_list
.append(String(*p
++, CopyString
));
800 ret
.set(s_protocols
, protocol_list
);
804 bool f_curl_setopt(CResRef ch
, int option
, CVarRef value
) {
805 CHECK_RESOURCE(curl
);
806 return curl
->setOption(option
, value
);
809 bool f_curl_setopt_array(CResRef ch
, CArrRef options
) {
810 CHECK_RESOURCE(curl
);
811 for (ArrayIter
iter(options
); iter
; ++iter
) {
812 if (!curl
->setOption(iter
.first().toInt32(), iter
.second())) {
819 Variant
f_fb_curl_getopt(CResRef ch
, int opt
/* = 0 */) {
820 CHECK_RESOURCE(curl
);
821 return curl
->getOption(opt
);
824 Variant
f_curl_exec(CResRef ch
) {
825 CHECK_RESOURCE(curl
);
826 return curl
->execute();
831 s_content_type("content_type"),
832 s_http_code("http_code"),
833 s_header_size("header_size"),
834 s_request_size("request_size"),
835 s_filetime("filetime"),
836 s_ssl_verify_result("ssl_verify_result"),
837 s_redirect_count("redirect_count"),
838 s_local_port("local_port"),
839 s_total_time("total_time"),
840 s_namelookup_time("namelookup_time"),
841 s_connect_time("connect_time"),
842 s_pretransfer_time("pretransfer_time"),
843 s_size_upload("size_upload"),
844 s_size_download("size_download"),
845 s_speed_download("speed_download"),
846 s_speed_upload("speed_upload"),
847 s_download_content_length("download_content_length"),
848 s_upload_content_length("upload_content_length"),
849 s_starttransfer_time("starttransfer_time"),
850 s_redirect_time("redirect_time"),
851 s_request_header("request_header");
853 Variant
f_curl_getinfo(CResRef ch
, int opt
/* = 0 */) {
854 CHECK_RESOURCE(curl
);
855 CURL
*cp
= curl
->get();
863 if (curl_easy_getinfo(cp
, CURLINFO_EFFECTIVE_URL
, &s_code
) == CURLE_OK
) {
864 ret
.set(s_url
, String(s_code
, CopyString
));
866 if (curl_easy_getinfo(cp
, CURLINFO_CONTENT_TYPE
, &s_code
) == CURLE_OK
) {
867 if (s_code
!= NULL
) {
868 ret
.set(s_content_type
, String(s_code
, CopyString
));
870 ret
.set(s_content_type
, uninit_null());
873 if (curl_easy_getinfo(cp
, CURLINFO_HTTP_CODE
, &l_code
) == CURLE_OK
) {
874 ret
.set(s_http_code
, l_code
);
876 if (curl_easy_getinfo(cp
, CURLINFO_HEADER_SIZE
, &l_code
) == CURLE_OK
) {
877 ret
.set(s_header_size
, l_code
);
879 if (curl_easy_getinfo(cp
, CURLINFO_REQUEST_SIZE
, &l_code
) == CURLE_OK
) {
880 ret
.set(s_request_size
, l_code
);
882 if (curl_easy_getinfo(cp
, CURLINFO_FILETIME
, &l_code
) == CURLE_OK
) {
883 ret
.set(s_filetime
, l_code
);
885 if (curl_easy_getinfo(cp
, CURLINFO_SSL_VERIFYRESULT
, &l_code
) ==
887 ret
.set(s_ssl_verify_result
, l_code
);
889 if (curl_easy_getinfo(cp
, CURLINFO_REDIRECT_COUNT
, &l_code
) == CURLE_OK
) {
890 ret
.set(s_redirect_count
, l_code
);
892 #if LIBCURL_VERSION_NUM >= 0x071500
893 if (curl_easy_getinfo(cp
, CURLINFO_LOCAL_PORT
, &l_code
) == CURLE_OK
) {
894 ret
.set(s_local_port
, l_code
);
897 if (curl_easy_getinfo(cp
, CURLINFO_TOTAL_TIME
, &d_code
) == CURLE_OK
) {
898 ret
.set(s_total_time
, d_code
);
900 if (curl_easy_getinfo(cp
, CURLINFO_NAMELOOKUP_TIME
, &d_code
) == CURLE_OK
) {
901 ret
.set(s_namelookup_time
, d_code
);
903 if (curl_easy_getinfo(cp
, CURLINFO_CONNECT_TIME
, &d_code
) == CURLE_OK
) {
904 ret
.set(s_connect_time
, d_code
);
906 if (curl_easy_getinfo(cp
, CURLINFO_PRETRANSFER_TIME
, &d_code
) ==
908 ret
.set(s_pretransfer_time
, d_code
);
910 if (curl_easy_getinfo(cp
, CURLINFO_SIZE_UPLOAD
, &d_code
) == CURLE_OK
) {
911 ret
.set(s_size_upload
, d_code
);
913 if (curl_easy_getinfo(cp
, CURLINFO_SIZE_DOWNLOAD
, &d_code
) == CURLE_OK
) {
914 ret
.set(s_size_download
, d_code
);
916 if (curl_easy_getinfo(cp
, CURLINFO_SPEED_DOWNLOAD
, &d_code
) == CURLE_OK
) {
917 ret
.set(s_speed_download
, d_code
);
919 if (curl_easy_getinfo(cp
, CURLINFO_SPEED_UPLOAD
, &d_code
) == CURLE_OK
) {
920 ret
.set(s_speed_upload
, d_code
);
922 if (curl_easy_getinfo(cp
, CURLINFO_CONTENT_LENGTH_DOWNLOAD
, &d_code
) ==
924 ret
.set(s_download_content_length
, d_code
);
926 if (curl_easy_getinfo(cp
, CURLINFO_CONTENT_LENGTH_UPLOAD
, &d_code
) ==
928 ret
.set(s_upload_content_length
, d_code
);
930 if (curl_easy_getinfo(cp
, CURLINFO_STARTTRANSFER_TIME
, &d_code
) ==
932 ret
.set(s_starttransfer_time
, d_code
);
934 if (curl_easy_getinfo(cp
, CURLINFO_REDIRECT_TIME
, &d_code
) == CURLE_OK
) {
935 ret
.set(s_redirect_time
, d_code
);
937 String header
= curl
->getHeader();
938 if (!header
.empty()) {
939 ret
.set(s_request_header
, header
);
945 case CURLINFO_PRIVATE
:
946 case CURLINFO_EFFECTIVE_URL
:
947 case CURLINFO_CONTENT_TYPE
: {
949 if (curl_easy_getinfo(cp
, (CURLINFO
)opt
, &s_code
) == CURLE_OK
&&
951 return String(s_code
, CopyString
);
955 case CURLINFO_HTTP_CODE
:
956 case CURLINFO_HEADER_SIZE
:
957 case CURLINFO_REQUEST_SIZE
:
958 case CURLINFO_FILETIME
:
959 case CURLINFO_SSL_VERIFYRESULT
:
960 #if LIBCURL_VERSION_NUM >= 0x071500
961 case CURLINFO_LOCAL_PORT
:
963 case CURLINFO_REDIRECT_COUNT
: {
965 if (curl_easy_getinfo(cp
, (CURLINFO
)opt
, &code
) == CURLE_OK
) {
970 case CURLINFO_TOTAL_TIME
:
971 case CURLINFO_NAMELOOKUP_TIME
:
972 case CURLINFO_CONNECT_TIME
:
973 case CURLINFO_PRETRANSFER_TIME
:
974 case CURLINFO_SIZE_UPLOAD
:
975 case CURLINFO_SIZE_DOWNLOAD
:
976 case CURLINFO_SPEED_DOWNLOAD
:
977 case CURLINFO_SPEED_UPLOAD
:
978 case CURLINFO_CONTENT_LENGTH_DOWNLOAD
:
979 case CURLINFO_CONTENT_LENGTH_UPLOAD
:
980 case CURLINFO_STARTTRANSFER_TIME
:
981 case CURLINFO_REDIRECT_TIME
: {
983 if (curl_easy_getinfo(cp
, (CURLINFO
)opt
, &code
) == CURLE_OK
) {
988 case CURLINFO_HEADER_OUT
:
990 String header
= curl
->getHeader();
991 if (!header
.empty()) {
998 return uninit_null();
1001 Variant
f_curl_errno(CResRef ch
) {
1002 CHECK_RESOURCE(curl
);
1003 return curl
->getError();
1006 Variant
f_curl_error(CResRef ch
) {
1007 CHECK_RESOURCE(curl
);
1008 return curl
->getErrorString();
1011 Variant
f_curl_close(CResRef ch
) {
1012 CHECK_RESOURCE(curl
);
1014 return uninit_null();
1017 ///////////////////////////////////////////////////////////////////////////////
1019 class CurlMultiResource
: public SweepableResourceData
{
1021 DECLARE_OBJECT_ALLOCATION(CurlMultiResource
)
1023 static StaticString s_class_name
;
1024 // overriding ResourceData
1025 CStrRef
o_getClassNameHook() const { return s_class_name
; }
1027 CurlMultiResource() {
1028 m_multi
= curl_multi_init();
1031 ~CurlMultiResource() {
1037 curl_multi_cleanup(m_multi
);
1043 void add(CResRef ch
) {
1047 void remove(CurlResource
*curle
) {
1048 for (ArrayIter
iter(m_easyh
); iter
; ++iter
) {
1049 if (iter
.second().toResource().getTyped
<CurlResource
>()->get(true) ==
1051 m_easyh
.remove(iter
.first());
1057 Resource
find(CURL
*cp
) {
1058 for (ArrayIter
iter(m_easyh
); iter
; ++iter
) {
1059 if (iter
.second().toResource().
1060 getTyped
<CurlResource
>()->get(true) == cp
) {
1061 return iter
.second().toResource();
1067 void check_exceptions() {
1068 ObjectData
* phpException
= 0;
1069 Exception
* cppException
= 0;
1070 for (ArrayIter
iter(m_easyh
); iter
; ++iter
) {
1071 CurlResource
* curl
= iter
.second().toResource().getTyped
<CurlResource
>();
1072 if (ObjectData
* e
= curl
->getAndClearPhpException()) {
1074 e
->o_set(s_previous
, Variant(phpException
), s_exception
);
1075 phpException
->decRefCount();
1078 } else if (Exception
*e
= curl
->getAndClearCppException()) {
1079 delete cppException
;
1084 if (phpException
) decRefObj(phpException
);
1085 cppException
->throwException();
1088 Object
e(phpException
);
1089 phpException
->decRefCount();
1095 if (m_multi
== NULL
) {
1096 throw NullPointerException();
1105 IMPLEMENT_OBJECT_ALLOCATION_NO_DEFAULT_SWEEP(CurlMultiResource
);
1106 void CurlMultiResource::sweep() {
1108 curl_multi_cleanup(m_multi
);
1112 StaticString
CurlMultiResource::s_class_name("cURL Multi Handle");
1114 ///////////////////////////////////////////////////////////////////////////////
1116 #define CHECK_MULTI_RESOURCE(curlm) \
1117 CurlMultiResource *curlm = mh.getTyped<CurlMultiResource>(true, true); \
1118 if (curlm == NULL) { \
1119 raise_warning("expects parameter 1 to be cURL multi resource"); \
1120 return uninit_null(); \
1123 Resource f_curl_multi_init() {
1124 return NEWOBJ(CurlMultiResource
)();
1127 Variant
f_curl_multi_add_handle(CResRef mh
, CResRef ch
) {
1128 CHECK_MULTI_RESOURCE(curlm
);
1129 CurlResource
*curle
= ch
.getTyped
<CurlResource
>();
1131 return curl_multi_add_handle(curlm
->get(), curle
->get());
1134 Variant
f_curl_multi_remove_handle(CResRef mh
, CResRef ch
) {
1135 CHECK_MULTI_RESOURCE(curlm
);
1136 CurlResource
*curle
= ch
.getTyped
<CurlResource
>();
1137 curlm
->remove(curle
);
1138 return curl_multi_remove_handle(curlm
->get(), curle
->get());
1141 Variant
f_curl_multi_exec(CResRef mh
, VRefParam still_running
) {
1142 CHECK_MULTI_RESOURCE(curlm
);
1143 int running
= still_running
;
1144 IOStatusHelper
io("curl_multi_exec");
1145 SYNC_VM_REGS_SCOPED();
1146 int result
= curl_multi_perform(curlm
->get(), &running
);
1147 curlm
->check_exceptions();
1148 still_running
= running
;
1152 /* Fallback implementation of curl_multi_select() for
1153 * libcurl < 7.28.0 without FB's curl_multi_select() patch
1155 * This allows the OSS build to work with older package
1156 * versions of libcurl, but will fail with file descriptors
1160 static void hphp_curl_multi_select(CURLM
*mh
, int timeout_ms
, int *ret
) {
1161 fd_set read_fds
, write_fds
, except_fds
;
1162 int maxfds
, nfds
= -1;
1166 FD_ZERO(&write_fds
);
1167 FD_ZERO(&except_fds
);
1169 tv
.tv_sec
= timeout_ms
/ 1000;
1170 tv
.tv_usec
= (timeout_ms
* 1000) % 1000000;
1172 curl_multi_fdset(mh
, &read_fds
, &write_fds
, &except_fds
, &maxfds
);
1173 if (maxfds
< 1024) {
1174 nfds
= select(maxfds
+ 1, &read_fds
, &write_fds
, &except_fds
, &tv
);
1176 /* fd_set can only hold sockets from 0 to 1023,
1177 * anything higher is ignored by FD_SET()
1178 * avoid "unexplained" behavior by failing outright
1180 raise_warning("libcurl versions < 7.28.0 do not support selecting on "
1181 "file descriptors of 1024 or higher.");
1188 #ifndef HAVE_CURL_MULTI_SELECT
1189 # ifdef HAVE_CURL_MULTI_WAIT
1190 # define curl_multi_select(mh, tm, ret) curl_multi_wait((mh), NULL, 0, (tm), (ret))
1192 # define curl_multi_select hphp_curl_multi_select
1196 Variant
f_curl_multi_select(CResRef mh
, double timeout
/* = 1.0 */) {
1197 CHECK_MULTI_RESOURCE(curlm
);
1199 unsigned long timeout_ms
= (unsigned long)(timeout
* 1000.0);
1200 IOStatusHelper
io("curl_multi_select");
1201 curl_multi_select(curlm
->get(), timeout_ms
, &ret
);
1205 Variant
f_curl_multi_getcontent(CResRef ch
) {
1206 CHECK_RESOURCE(curl
);
1207 return curl
->getContents();
1210 Array
f_curl_convert_fd_to_stream(fd_set
*fd
, int max_fd
) {
1211 Array ret
= Array::Create();
1212 for (int i
=0; i
<=max_fd
; i
++) {
1213 if (FD_ISSET(i
, fd
)) {
1214 BuiltinFile
*file
= NEWOBJ(BuiltinFile
)(i
);
1221 Variant
f_fb_curl_multi_fdset(CResRef mh
,
1222 VRefParam read_fd_set
,
1223 VRefParam write_fd_set
,
1224 VRefParam exc_fd_set
,
1225 VRefParam max_fd
/* = null_object */) {
1226 CHECK_MULTI_RESOURCE(curlm
);
1234 FD_ZERO(&write_set
);
1237 int r
= curl_multi_fdset(curlm
->get(), &read_set
, &write_set
, &exc_set
, &max
);
1238 read_fd_set
= f_curl_convert_fd_to_stream(&read_set
, max
);
1239 write_fd_set
= f_curl_convert_fd_to_stream(&write_set
, max
);
1240 exc_fd_set
= f_curl_convert_fd_to_stream(&exc_set
, max
);
1250 s_headers("headers"),
1251 s_requests("requests");
1253 Variant
f_curl_multi_info_read(CResRef mh
,
1254 VRefParam msgs_in_queue
/* = null */) {
1255 CHECK_MULTI_RESOURCE(curlm
);
1258 CURLMsg
*tmp_msg
= curl_multi_info_read(curlm
->get(), &queued_msgs
);
1259 curlm
->check_exceptions();
1260 if (tmp_msg
== NULL
) {
1263 msgs_in_queue
= queued_msgs
;
1266 ret
.set(s_msg
, tmp_msg
->msg
);
1267 ret
.set(s_result
, tmp_msg
->data
.result
);
1268 Resource curle
= curlm
->find(tmp_msg
->easy_handle
);
1269 if (!curle
.isNull()) {
1270 ret
.set(s_handle
, curle
);
1275 Variant
f_curl_multi_close(CResRef mh
) {
1276 CHECK_MULTI_RESOURCE(curlm
);
1278 return uninit_null();
1281 ///////////////////////////////////////////////////////////////////////////////
1284 class LibEventHttpHandle
: public SweepableResourceData
{
1286 DECLARE_OBJECT_ALLOCATION(LibEventHttpHandle
)
1288 static StaticString s_class_name
;
1289 // overriding ResourceData
1290 virtual CStrRef
o_getClassNameHook() const { return s_class_name
; }
1292 explicit LibEventHttpHandle(LibEventHttpClientPtr client
) : m_client(client
) {
1295 ~LibEventHttpHandle() {
1297 m_client
->release();
1301 LibEventHttpClientPtr m_client
;
1303 IMPLEMENT_OBJECT_ALLOCATION(LibEventHttpHandle
)
1305 StaticString
LibEventHttpHandle::s_class_name("LibEventHttp");
1307 static LibEventHttpClientPtr prepare_client
1308 (CStrRef url
, CStrRef data
, CArrRef headers
, int timeout
,
1309 bool async
, bool post
) {
1310 string sUrl
= url
.data();
1311 if (sUrl
.size() < 7 || sUrl
.substr(0, 7) != "http://") {
1312 raise_warning("Invalid URL: %s", sUrl
.c_str());
1313 return LibEventHttpClientPtr();
1316 // parsing server address
1317 size_t pos
= sUrl
.find('/', 7);
1319 if (pos
== string::npos
) {
1320 pos
= sUrl
.length();
1322 } else if (pos
== 7) {
1323 raise_warning("Invalid URL: %s", sUrl
.c_str());
1324 return LibEventHttpClientPtr();
1326 path
= sUrl
.substr(pos
);
1328 string address
= sUrl
.substr(7, pos
- 7);
1330 // parsing server port
1331 pos
= address
.find(':');
1333 if (pos
!= string::npos
) {
1334 if (pos
< address
.length() - 1) {
1335 string sport
= address
.substr(pos
+ 1, address
.length() - pos
- 1);
1336 port
= atoi(sport
.c_str());
1338 address
= address
.substr(0, pos
);
1341 LibEventHttpClientPtr client
= LibEventHttpClient::Get(address
, port
);
1346 vector
<string
> sheaders
;
1347 for (ArrayIter
iter(headers
); iter
; ++iter
) {
1348 sheaders
.push_back(iter
.second().toString().data());
1350 if (!client
->send(path
.c_str(), sheaders
, timeout
, async
,
1351 post
? (void*)data
.data() : NULL
,
1352 post
? data
.size() : 0)) {
1353 return LibEventHttpClientPtr();
1360 s_response("response");
1362 static Array
prepare_response(LibEventHttpClientPtr client
) {
1364 char *res
= client
->recv(len
); // block on return
1367 ret
.set(s_code
, client
->getCode());
1368 ret
.set(s_response
, String(res
, len
, AttachString
));
1370 Array headers
= Array::Create();
1371 const vector
<string
> &responseHeaders
= client
->getResponseHeaders();
1372 for (unsigned int i
= 0; i
< responseHeaders
.size(); i
++) {
1373 headers
.append(String(responseHeaders
[i
]));
1375 ret
.set(s_headers
, headers
);
1376 ret
.set(s_requests
, client
->getRequests());
1377 return ret
.create();
1380 ///////////////////////////////////////////////////////////////////////////////
1382 void f_evhttp_set_cache(CStrRef address
, int max_conn
, int port
/* = 80 */) {
1383 if (RuntimeOption::ServerHttpSafeMode
) {
1384 throw_fatal("evhttp_set_cache is disabled");
1386 LibEventHttpClient::SetCache(address
.data(), port
, max_conn
);
1389 Variant
f_evhttp_get(CStrRef url
, CArrRef headers
/* = null_array */,
1390 int timeout
/* = 5 */) {
1391 if (RuntimeOption::ServerHttpSafeMode
) {
1392 throw_fatal("evhttp_set_cache is disabled");
1394 LibEventHttpClientPtr client
= prepare_client(url
, "", headers
, timeout
,
1397 Variant ret
= prepare_response(client
);
1404 Variant
f_evhttp_post(CStrRef url
, CStrRef data
,
1405 CArrRef headers
/* = null_array */,
1406 int timeout
/* = 5 */) {
1407 if (RuntimeOption::ServerHttpSafeMode
) {
1408 throw_fatal("evhttp_post is disabled");
1410 LibEventHttpClientPtr client
= prepare_client(url
, data
, headers
, timeout
,
1413 Variant ret
= prepare_response(client
);
1420 Variant
f_evhttp_async_get(CStrRef url
, CArrRef headers
/* = null_array */,
1421 int timeout
/* = 5 */) {
1422 if (RuntimeOption::ServerHttpSafeMode
) {
1423 throw_fatal("evhttp_async_get is disabled");
1425 LibEventHttpClientPtr client
= prepare_client(url
, "", headers
, timeout
,
1428 return Resource(NEWOBJ(LibEventHttpHandle
)(client
));
1433 Variant
f_evhttp_async_post(CStrRef url
, CStrRef data
,
1434 CArrRef headers
/* = null_array */,
1435 int timeout
/* = 5 */) {
1436 if (RuntimeOption::ServerHttpSafeMode
) {
1437 throw_fatal("evhttp_async_post is disabled");
1439 LibEventHttpClientPtr client
= prepare_client(url
, data
, headers
, timeout
,
1442 return Resource(NEWOBJ(LibEventHttpHandle
)(client
));
1447 Variant
f_evhttp_recv(CResRef handle
) {
1448 if (RuntimeOption::ServerHttpSafeMode
) {
1449 throw_fatal("evhttp_recv is disabled");
1451 LibEventHttpHandle
*obj
= handle
.getTyped
<LibEventHttpHandle
>();
1452 if (obj
->m_client
) {
1453 return prepare_response(obj
->m_client
);
1458 #if LIBCURL_VERSION_NUM >= 0x071500
1459 const int64_t k_CURLINFO_LOCAL_PORT
= CURLINFO_LOCAL_PORT
;
1461 const int64_t k_CURLINFO_LOCAL_PORT
= CURLINFO_NONE
;
1464 #if LIBCURL_VERSION_NUM >= 0x071002
1465 const int64_t k_CURLOPT_TIMEOUT_MS
= CURLOPT_TIMEOUT_MS
;
1466 const int64_t k_CURLOPT_CONNECTTIMEOUT_MS
= CURLOPT_CONNECTTIMEOUT_MS
;
1468 const int64_t k_CURLOPT_TIMEOUT_MS
= CURLOPT_LASTENTRY
;
1469 const int64_t k_CURLOPT_CONNECTTIMEOUT_MS
= CURLOPT_LASTENTRY
;
1472 ///////////////////////////////////////////////////////////////////////////////