b7cc57a8db
This gets rid of the (litstr) StringData and StackStringData constructors, but keeps String(litstr). Also rename all the instances of AttachLiteral to CopyString, since they now mean the same thing.
1470 linhas
43 KiB
C++
1470 linhas
43 KiB
C++
/*
|
|
+----------------------------------------------------------------------+
|
|
| HipHop for PHP |
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) 2010- Facebook, Inc. (http://www.facebook.com) |
|
|
| Copyright (c) 1997-2010 The PHP Group |
|
|
+----------------------------------------------------------------------+
|
|
| This source file is subject to version 3.01 of the PHP license, |
|
|
| that is bundled with this package in the file LICENSE, and is |
|
|
| available through the world-wide-web at the following url: |
|
|
| http://www.php.net/license/3_01.txt |
|
|
| If you did not receive a copy of the PHP license and are unable to |
|
|
| obtain it through the world-wide-web, please send a note to |
|
|
| license@php.net so we can mail you a copy immediately. |
|
|
+----------------------------------------------------------------------+
|
|
*/
|
|
|
|
#include <runtime/ext/ext_curl.h>
|
|
#include <runtime/ext/ext_function.h>
|
|
#include <runtime/base/util/string_buffer.h>
|
|
#include <runtime/base/util/libevent_http_client.h>
|
|
#include <runtime/base/util/curl_tls_workarounds.h>
|
|
#include <runtime/base/runtime_option.h>
|
|
#include <runtime/base/server/server_stats.h>
|
|
#include <runtime/vm/translator/translator-inline.h>
|
|
|
|
#define CURLOPT_RETURNTRANSFER 19913
|
|
#define CURLOPT_BINARYTRANSFER 19914
|
|
#define PHP_CURL_STDOUT 0
|
|
#define PHP_CURL_FILE 1
|
|
#define PHP_CURL_USER 2
|
|
#define PHP_CURL_DIRECT 3
|
|
#define PHP_CURL_RETURN 4
|
|
#define PHP_CURL_ASCII 5
|
|
#define PHP_CURL_BINARY 6
|
|
#define PHP_CURL_IGNORE 7
|
|
|
|
|
|
namespace HPHP {
|
|
IMPLEMENT_DEFAULT_EXTENSION(curl);
|
|
|
|
static StaticString s_exception("exception");
|
|
static StaticString s_previous("previous");
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// helper data structure
|
|
|
|
class CurlResource : public SweepableResourceData {
|
|
private:
|
|
DECLARE_OBJECT_ALLOCATION(CurlResource)
|
|
|
|
class WriteHandler {
|
|
public:
|
|
WriteHandler() : method(0), type(0) {}
|
|
|
|
int method;
|
|
Variant callback;
|
|
SmartObject<File> fp;
|
|
StringBuffer buf;
|
|
String content;
|
|
int type;
|
|
};
|
|
|
|
class ReadHandler {
|
|
public:
|
|
ReadHandler() : method(0) {}
|
|
|
|
int method;
|
|
Variant callback;
|
|
SmartObject<File> fp;
|
|
};
|
|
|
|
DECLARE_BOOST_TYPES(ToFree);
|
|
class ToFree {
|
|
public:
|
|
vector<char*> str;
|
|
vector<curl_httppost*> post;
|
|
vector<curl_slist*> slist;
|
|
|
|
~ToFree() {
|
|
for (unsigned int i = 0; i < str.size(); i++) {
|
|
free(str[i]);
|
|
}
|
|
for (unsigned int i = 0; i < post.size(); i++) {
|
|
curl_formfree(post[i]);
|
|
}
|
|
for (unsigned int i = 0; i < slist.size(); i++) {
|
|
curl_slist_free_all(slist[i]);
|
|
}
|
|
}
|
|
};
|
|
|
|
public:
|
|
static StaticString s_class_name;
|
|
// overriding ResourceData
|
|
virtual CStrRef o_getClassNameHook() const { return s_class_name; }
|
|
|
|
explicit CurlResource(CStrRef url)
|
|
: m_exception(nullptr), m_phpException(false), m_emptyPost(true) {
|
|
m_cp = curl_easy_init();
|
|
m_url = url;
|
|
|
|
memset(m_error_str, 0, sizeof(m_error_str));
|
|
m_error_no = CURLE_OK;
|
|
m_to_free = ToFreePtr(new ToFree());
|
|
|
|
m_write.method = PHP_CURL_STDOUT;
|
|
m_write.type = PHP_CURL_ASCII;
|
|
m_read.method = PHP_CURL_DIRECT;
|
|
m_write_header.method = PHP_CURL_IGNORE;
|
|
|
|
curl_easy_setopt(m_cp, CURLOPT_NOPROGRESS, 1);
|
|
curl_easy_setopt(m_cp, CURLOPT_VERBOSE, 0);
|
|
curl_easy_setopt(m_cp, CURLOPT_ERRORBUFFER, m_error_str);
|
|
curl_easy_setopt(m_cp, CURLOPT_WRITEFUNCTION, curl_write);
|
|
curl_easy_setopt(m_cp, CURLOPT_FILE, (void*)this);
|
|
curl_easy_setopt(m_cp, CURLOPT_READFUNCTION, curl_read);
|
|
curl_easy_setopt(m_cp, CURLOPT_INFILE, (void*)this);
|
|
curl_easy_setopt(m_cp, CURLOPT_HEADERFUNCTION, curl_write_header);
|
|
curl_easy_setopt(m_cp, CURLOPT_WRITEHEADER, (void*)this);
|
|
curl_easy_setopt(m_cp, CURLOPT_DNS_USE_GLOBAL_CACHE, 0); // for thread-safe
|
|
curl_easy_setopt(m_cp, CURLOPT_DNS_CACHE_TIMEOUT, 120);
|
|
curl_easy_setopt(m_cp, CURLOPT_MAXREDIRS, 20); // no infinite redirects
|
|
curl_easy_setopt(m_cp, CURLOPT_NOSIGNAL, 1); // for multithreading mode
|
|
curl_easy_setopt(m_cp, CURLOPT_SSL_CTX_FUNCTION, curl_tls_workarounds_cb);
|
|
|
|
curl_easy_setopt(m_cp, CURLOPT_TIMEOUT,
|
|
RuntimeOption::HttpDefaultTimeout);
|
|
curl_easy_setopt(m_cp, CURLOPT_CONNECTTIMEOUT,
|
|
RuntimeOption::HttpDefaultTimeout);
|
|
|
|
if (!url.empty()) {
|
|
#if LIBCURL_VERSION_NUM >= 0x071100
|
|
/* Strings passed to libcurl as 'char *' arguments, are copied by
|
|
the library... NOTE: before 7.17.0 strings were not copied. */
|
|
curl_easy_setopt(m_cp, CURLOPT_URL, url.c_str());
|
|
#else
|
|
char *urlcopy = strndup(url.data(), url.size());
|
|
curl_easy_setopt(m_cp, CURLOPT_URL, urlcopy);
|
|
m_to_free->str.push_back(urlcopy);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
explicit CurlResource(CurlResource *src)
|
|
: m_exception(nullptr), m_phpException(false) {
|
|
assert(src && src != this);
|
|
assert(!src->m_exception);
|
|
|
|
m_cp = curl_easy_duphandle(src->get());
|
|
m_url = src->m_url;
|
|
|
|
memset(m_error_str, 0, sizeof(m_error_str));
|
|
m_error_no = CURLE_OK;
|
|
|
|
m_write.method = src->m_write.method;
|
|
m_write.type = src->m_write.type;
|
|
m_read.method = src->m_read.method;
|
|
m_write_header.method = src->m_write_header.method;
|
|
|
|
m_write.fp = src->m_write.fp;
|
|
m_write_header.fp = src->m_write_header.fp;
|
|
m_read.fp = src->m_read.fp;
|
|
|
|
m_write.callback = src->m_write.callback;
|
|
m_read.callback = src->m_read.callback;
|
|
m_write_header.callback = src->m_write_header.callback;
|
|
|
|
curl_easy_setopt(m_cp, CURLOPT_ERRORBUFFER, m_error_str);
|
|
curl_easy_setopt(m_cp, CURLOPT_FILE, (void*)this);
|
|
curl_easy_setopt(m_cp, CURLOPT_INFILE, (void*)this);
|
|
curl_easy_setopt(m_cp, CURLOPT_WRITEHEADER, (void*)this);
|
|
|
|
m_to_free = src->m_to_free;
|
|
m_emptyPost = src->m_emptyPost;
|
|
}
|
|
|
|
~CurlResource() {
|
|
close();
|
|
}
|
|
|
|
void closeForSweep() {
|
|
assert(!m_exception);
|
|
if (m_cp) {
|
|
curl_easy_cleanup(m_cp);
|
|
m_cp = NULL;
|
|
}
|
|
m_to_free.reset();
|
|
}
|
|
|
|
void close() {
|
|
closeForSweep();
|
|
m_opts.clear();
|
|
}
|
|
|
|
void check_exception() {
|
|
if (m_exception) {
|
|
if (m_phpException) {
|
|
Object e((ObjectData*)m_exception);
|
|
m_exception = NULL;
|
|
e.get()->decRefCount();
|
|
throw e;
|
|
} else {
|
|
Exception *e = (Exception*)m_exception;
|
|
m_exception = NULL;
|
|
e->throwException();
|
|
}
|
|
}
|
|
}
|
|
|
|
ObjectData* getAndClearPhpException() {
|
|
if (m_exception && m_phpException) {
|
|
ObjectData* ret = (ObjectData*)m_exception;
|
|
m_exception = nullptr;
|
|
return ret;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
Exception* getAndClearCppException() {
|
|
if (!m_phpException) {
|
|
Exception* e = (Exception*)m_exception;
|
|
m_exception = nullptr;
|
|
return e;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
Variant execute() {
|
|
assert(!m_exception);
|
|
if (m_cp == NULL) {
|
|
return false;
|
|
}
|
|
if (m_emptyPost) {
|
|
// As per curl docs, an empty post must set POSTFIELDSIZE to be 0 or
|
|
// the reader function will be called
|
|
curl_easy_setopt(m_cp, CURLOPT_POSTFIELDSIZE, 0);
|
|
}
|
|
m_write.buf.reset();
|
|
m_write.content.clear();
|
|
m_header.clear();
|
|
memset(m_error_str, 0, sizeof(m_error_str));
|
|
|
|
{
|
|
IOStatusHelper io("curl_easy_perform", m_url.data());
|
|
SYNC_VM_REGS_SCOPED();
|
|
m_error_no = curl_easy_perform(m_cp);
|
|
check_exception();
|
|
}
|
|
set_curl_statuses(m_cp, m_url.data());
|
|
|
|
/* CURLE_PARTIAL_FILE is returned by HEAD requests */
|
|
if (m_error_no != CURLE_OK && m_error_no != CURLE_PARTIAL_FILE) {
|
|
m_write.buf.reset();
|
|
m_write.content.clear();
|
|
return false;
|
|
}
|
|
|
|
if (m_write.method == PHP_CURL_RETURN) {
|
|
if (!m_write.buf.empty()) {
|
|
m_write.content = m_write.buf.detach();
|
|
}
|
|
if (!m_write.content.empty()) {
|
|
return m_write.content;
|
|
}
|
|
}
|
|
if (m_write.method == PHP_CURL_RETURN) {
|
|
return String("");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
String getUrl() {
|
|
return m_url;
|
|
}
|
|
|
|
String getHeader() {
|
|
return m_header;
|
|
}
|
|
|
|
String getContents() {
|
|
if (m_write.method == PHP_CURL_RETURN) {
|
|
if (!m_write.buf.empty()) {
|
|
m_write.content = m_write.buf.detach();
|
|
}
|
|
return m_write.content;
|
|
}
|
|
return String();
|
|
}
|
|
|
|
bool setOption(long option, CVarRef value) {
|
|
if (m_cp == NULL) {
|
|
return false;
|
|
}
|
|
m_error_no = CURLE_OK;
|
|
|
|
switch (option) {
|
|
case CURLOPT_INFILESIZE:
|
|
case CURLOPT_VERBOSE:
|
|
case CURLOPT_HEADER:
|
|
case CURLOPT_NOPROGRESS:
|
|
case CURLOPT_NOBODY:
|
|
case CURLOPT_FAILONERROR:
|
|
case CURLOPT_UPLOAD:
|
|
case CURLOPT_POST:
|
|
case CURLOPT_FTPLISTONLY:
|
|
case CURLOPT_FTPAPPEND:
|
|
case CURLOPT_NETRC:
|
|
case CURLOPT_PUT:
|
|
case CURLOPT_TIMEOUT:
|
|
#if LIBCURL_VERSION_NUM >= 0x071002
|
|
case CURLOPT_TIMEOUT_MS:
|
|
#endif
|
|
case CURLOPT_FTP_USE_EPSV:
|
|
case CURLOPT_LOW_SPEED_LIMIT:
|
|
case CURLOPT_SSLVERSION:
|
|
case CURLOPT_LOW_SPEED_TIME:
|
|
case CURLOPT_RESUME_FROM:
|
|
case CURLOPT_TIMEVALUE:
|
|
case CURLOPT_TIMECONDITION:
|
|
case CURLOPT_TRANSFERTEXT:
|
|
case CURLOPT_HTTPPROXYTUNNEL:
|
|
case CURLOPT_FILETIME:
|
|
case CURLOPT_MAXREDIRS:
|
|
case CURLOPT_MAXCONNECTS:
|
|
case CURLOPT_CLOSEPOLICY:
|
|
case CURLOPT_FRESH_CONNECT:
|
|
case CURLOPT_FORBID_REUSE:
|
|
case CURLOPT_CONNECTTIMEOUT:
|
|
#if LIBCURL_VERSION_NUM >= 0x071002
|
|
case CURLOPT_CONNECTTIMEOUT_MS:
|
|
#endif
|
|
case CURLOPT_SSL_VERIFYHOST:
|
|
case CURLOPT_SSL_VERIFYPEER:
|
|
//case CURLOPT_DNS_USE_GLOBAL_CACHE: not thread-safe when set to true
|
|
case CURLOPT_NOSIGNAL:
|
|
case CURLOPT_PROXYTYPE:
|
|
case CURLOPT_BUFFERSIZE:
|
|
case CURLOPT_HTTPGET:
|
|
case CURLOPT_HTTP_VERSION:
|
|
case CURLOPT_CRLF:
|
|
case CURLOPT_DNS_CACHE_TIMEOUT:
|
|
case CURLOPT_PROXYPORT:
|
|
case CURLOPT_FTP_USE_EPRT:
|
|
case CURLOPT_HTTPAUTH:
|
|
case CURLOPT_PROXYAUTH:
|
|
case CURLOPT_FTP_CREATE_MISSING_DIRS:
|
|
case CURLOPT_FTPSSLAUTH:
|
|
case CURLOPT_FTP_SSL:
|
|
case CURLOPT_UNRESTRICTED_AUTH:
|
|
case CURLOPT_PORT:
|
|
case CURLOPT_AUTOREFERER:
|
|
case CURLOPT_COOKIESESSION:
|
|
case CURLOPT_TCP_NODELAY:
|
|
case CURLOPT_IPRESOLVE:
|
|
case CURLOPT_FOLLOWLOCATION:
|
|
m_error_no = curl_easy_setopt(m_cp, (CURLoption)option, value.toInt64());
|
|
break;
|
|
case CURLOPT_RETURNTRANSFER:
|
|
m_write.method = value.toBoolean() ? PHP_CURL_RETURN : PHP_CURL_STDOUT;
|
|
break;
|
|
case CURLOPT_BINARYTRANSFER:
|
|
m_write.type = value.toBoolean() ? PHP_CURL_BINARY : PHP_CURL_ASCII;
|
|
break;
|
|
case CURLOPT_PRIVATE:
|
|
case CURLOPT_URL:
|
|
case CURLOPT_PROXY:
|
|
case CURLOPT_USERPWD:
|
|
case CURLOPT_PROXYUSERPWD:
|
|
case CURLOPT_RANGE:
|
|
case CURLOPT_CUSTOMREQUEST:
|
|
case CURLOPT_USERAGENT:
|
|
case CURLOPT_FTPPORT:
|
|
case CURLOPT_COOKIE:
|
|
case CURLOPT_REFERER:
|
|
case CURLOPT_INTERFACE:
|
|
case CURLOPT_KRB4LEVEL:
|
|
case CURLOPT_EGDSOCKET:
|
|
case CURLOPT_CAINFO:
|
|
case CURLOPT_CAPATH:
|
|
case CURLOPT_SSL_CIPHER_LIST:
|
|
case CURLOPT_SSLKEY:
|
|
case CURLOPT_SSLKEYTYPE:
|
|
case CURLOPT_SSLKEYPASSWD:
|
|
case CURLOPT_SSLENGINE:
|
|
case CURLOPT_SSLENGINE_DEFAULT:
|
|
case CURLOPT_SSLCERTTYPE:
|
|
case CURLOPT_ENCODING:
|
|
case CURLOPT_COOKIEJAR:
|
|
case CURLOPT_SSLCERT:
|
|
case CURLOPT_RANDOM_FILE:
|
|
case CURLOPT_COOKIEFILE:
|
|
{
|
|
String svalue = value.toString();
|
|
#if LIBCURL_VERSION_NUM >= 0x071100
|
|
/* Strings passed to libcurl as 'char *' arguments, are copied
|
|
by the library... NOTE: before 7.17.0 strings were not copied. */
|
|
m_error_no = curl_easy_setopt(m_cp, (CURLoption)option, svalue.c_str());
|
|
#else
|
|
char *copystr = strndup(svalue.data(), svalue.size());
|
|
m_to_free->str.push_back(copystr);
|
|
m_error_no = curl_easy_setopt(m_cp, (CURLoption)option, copystr);
|
|
#endif
|
|
if (option == CURLOPT_URL) m_url = value;
|
|
}
|
|
break;
|
|
case CURLOPT_FILE:
|
|
case CURLOPT_INFILE:
|
|
case CURLOPT_WRITEHEADER:
|
|
case CURLOPT_STDERR:
|
|
{
|
|
if (!value.is(KindOfObject)) {
|
|
return false;
|
|
}
|
|
|
|
Object obj = value.toObject();
|
|
if (obj.isNull() || obj.getTyped<File>(true) == NULL) {
|
|
return false;
|
|
}
|
|
|
|
switch (option) {
|
|
case CURLOPT_FILE:
|
|
m_write.fp = obj;
|
|
m_write.method = PHP_CURL_FILE;
|
|
break;
|
|
case CURLOPT_WRITEHEADER:
|
|
m_write_header.fp = obj;
|
|
m_write_header.method = PHP_CURL_FILE;
|
|
break;
|
|
case CURLOPT_INFILE:
|
|
m_read.fp = obj;
|
|
m_emptyPost = false;
|
|
break;
|
|
default: {
|
|
if (obj.getTyped<PlainFile>(true) == NULL) {
|
|
return false;
|
|
}
|
|
FILE *fp = obj.getTyped<PlainFile>()->getStream();
|
|
if (!fp) {
|
|
return false;
|
|
}
|
|
m_error_no = curl_easy_setopt(m_cp, (CURLoption)option, fp);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case CURLOPT_WRITEFUNCTION:
|
|
m_write.callback = value;
|
|
m_write.method = PHP_CURL_USER;
|
|
break;
|
|
case CURLOPT_READFUNCTION:
|
|
m_read.callback = value;
|
|
m_read.method = PHP_CURL_USER;
|
|
m_emptyPost = false;
|
|
break;
|
|
case CURLOPT_HEADERFUNCTION:
|
|
m_write_header.callback = value;
|
|
m_write_header.method = PHP_CURL_USER;
|
|
break;
|
|
case CURLOPT_POSTFIELDS:
|
|
m_emptyPost = false;
|
|
if (value.is(KindOfArray) || value.is(KindOfObject)) {
|
|
Array arr = value.toArray();
|
|
curl_httppost *first = NULL;
|
|
curl_httppost *last = NULL;
|
|
for (ArrayIter iter(arr); iter; ++iter) {
|
|
String key = iter.first().toString();
|
|
String val = iter.second().toString();
|
|
const char *postval = val.data();
|
|
|
|
/* The arguments after _NAMELENGTH and _CONTENTSLENGTH
|
|
* must be explicitly cast to long in curl_formadd
|
|
* use since curl needs a long not an int. */
|
|
if (*postval == '@') {
|
|
++postval;
|
|
m_error_no = (CURLcode)curl_formadd
|
|
(&first, &last,
|
|
CURLFORM_COPYNAME, key.data(),
|
|
CURLFORM_NAMELENGTH, (long)key.size(),
|
|
CURLFORM_FILE, postval,
|
|
CURLFORM_END);
|
|
} else {
|
|
m_error_no = (CURLcode)curl_formadd
|
|
(&first, &last,
|
|
CURLFORM_COPYNAME, key.data(),
|
|
CURLFORM_NAMELENGTH, (long)key.size(),
|
|
CURLFORM_COPYCONTENTS, postval,
|
|
CURLFORM_CONTENTSLENGTH,(long)val.size(),
|
|
CURLFORM_END);
|
|
}
|
|
}
|
|
|
|
if (m_error_no != CURLE_OK) {
|
|
return false;
|
|
}
|
|
|
|
m_to_free->post.push_back(first);
|
|
m_error_no = curl_easy_setopt(m_cp, CURLOPT_HTTPPOST, first);
|
|
|
|
} else {
|
|
String svalue = value.toString();
|
|
#if LIBCURL_VERSION_NUM >= 0x071100
|
|
/* with curl 7.17.0 and later, we can use COPYPOSTFIELDS,
|
|
but we have to provide size before */
|
|
m_error_no = curl_easy_setopt(m_cp, CURLOPT_POSTFIELDSIZE,
|
|
svalue.size());
|
|
m_error_no = curl_easy_setopt(m_cp, CURLOPT_COPYPOSTFIELDS,
|
|
svalue.c_str());
|
|
#else
|
|
char *post = strndup(svalue.data(), svalue.size());
|
|
m_to_free->str.push_back(post);
|
|
|
|
m_error_no = curl_easy_setopt(m_cp, CURLOPT_POSTFIELDS, post);
|
|
m_error_no = curl_easy_setopt(m_cp, CURLOPT_POSTFIELDSIZE,
|
|
svalue.size());
|
|
#endif
|
|
}
|
|
break;
|
|
case CURLOPT_HTTPHEADER:
|
|
case CURLOPT_QUOTE:
|
|
case CURLOPT_HTTP200ALIASES:
|
|
case CURLOPT_POSTQUOTE:
|
|
if (value.is(KindOfArray) || value.is(KindOfObject)) {
|
|
Array arr = value.toArray();
|
|
curl_slist *slist = NULL;
|
|
for (ArrayIter iter(arr); iter; ++iter) {
|
|
String key = iter.first().toString();
|
|
String val = iter.second().toString();
|
|
|
|
slist = curl_slist_append(slist, val.c_str());
|
|
if (!slist) {
|
|
raise_warning("Could not build curl_slist");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
m_to_free->slist.push_back(slist);
|
|
m_error_no = curl_easy_setopt(m_cp, (CURLoption)option, slist);
|
|
|
|
} else {
|
|
raise_warning("You must pass either an object or an array with "
|
|
"the CURLOPT_HTTPHEADER, CURLOPT_QUOTE, "
|
|
"CURLOPT_HTTP200ALIASES and CURLOPT_POSTQUOTE "
|
|
"arguments");
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case CURLINFO_HEADER_OUT:
|
|
if (value.toInt64() == 1) {
|
|
curl_easy_setopt(m_cp, CURLOPT_DEBUGFUNCTION, curl_debug);
|
|
curl_easy_setopt(m_cp, CURLOPT_DEBUGDATA, (void *)this);
|
|
curl_easy_setopt(m_cp, CURLOPT_VERBOSE, 1);
|
|
} else {
|
|
curl_easy_setopt(m_cp, CURLOPT_DEBUGFUNCTION, NULL);
|
|
curl_easy_setopt(m_cp, CURLOPT_DEBUGDATA, NULL);
|
|
curl_easy_setopt(m_cp, CURLOPT_VERBOSE, 0);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
m_error_no = CURLE_FAILED_INIT;
|
|
throw_invalid_argument("option: %d", option);
|
|
break;
|
|
}
|
|
|
|
m_opts.set(int64_t(option), value);
|
|
|
|
return m_error_no == CURLE_OK;
|
|
}
|
|
|
|
Variant getOption(long option) {
|
|
|
|
if (option != 0) {
|
|
if (!m_opts.exists(int64_t(option))) {
|
|
return false;
|
|
}
|
|
return m_opts[int64_t(option)];
|
|
}
|
|
|
|
return m_opts;
|
|
}
|
|
|
|
static int curl_debug(CURL *cp, curl_infotype type, char *buf,
|
|
size_t buf_len, void *ctx) {
|
|
CurlResource *ch = (CurlResource *)ctx;
|
|
if (type == CURLINFO_HEADER_OUT && buf_len > 0) {
|
|
ch->m_header = String(buf, buf_len, CopyString);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
Variant do_callback(CVarRef cb, CArrRef args) {
|
|
assert(!m_exception);
|
|
try {
|
|
return vm_call_user_func(cb, args);
|
|
} catch (Object &e) {
|
|
ObjectData *od = e.get();
|
|
od->incRefCount();
|
|
m_exception = od;
|
|
m_phpException = true;
|
|
} catch (Exception &e) {
|
|
m_exception = e.clone();
|
|
m_phpException = false;
|
|
}
|
|
return uninit_null();
|
|
}
|
|
|
|
static size_t curl_read(char *data, size_t size, size_t nmemb, void *ctx) {
|
|
CurlResource *ch = (CurlResource *)ctx;
|
|
ReadHandler *t = &ch->m_read;
|
|
|
|
int length = -1;
|
|
switch (t->method) {
|
|
case PHP_CURL_DIRECT:
|
|
if (!t->fp.isNull()) {
|
|
int data_size = size * nmemb;
|
|
String ret = t->fp->read(data_size);
|
|
length = ret.size();
|
|
if (length) {
|
|
memcpy(data, ret.data(), length);
|
|
}
|
|
}
|
|
break;
|
|
case PHP_CURL_USER:
|
|
{
|
|
int data_size = size * nmemb;
|
|
Variant ret = ch->do_callback(
|
|
t->callback, CREATE_VECTOR3(Object(ch), t->fp->fd(), data_size));
|
|
if (ret.isString()) {
|
|
String sret = ret.toString();
|
|
length = data_size < sret.size() ? data_size : sret.size();
|
|
memcpy(data, sret.data(), length);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return length;
|
|
}
|
|
|
|
static size_t curl_write(char *data, size_t size, size_t nmemb, void *ctx) {
|
|
CurlResource *ch = (CurlResource *)ctx;
|
|
WriteHandler *t = &ch->m_write;
|
|
size_t length = size * nmemb;
|
|
|
|
switch (t->method) {
|
|
case PHP_CURL_STDOUT:
|
|
echo(String(data, length, CopyString));
|
|
break;
|
|
case PHP_CURL_FILE:
|
|
return t->fp->write(String(data, length, CopyString), length);
|
|
case PHP_CURL_RETURN:
|
|
if (length > 0) {
|
|
t->buf.append(data, (int)length);
|
|
}
|
|
break;
|
|
case PHP_CURL_USER:
|
|
{
|
|
Variant ret = ch->do_callback(
|
|
t->callback,
|
|
CREATE_VECTOR2(Object(ch), String(data, length, CopyString)));
|
|
length = ret.toInt64();
|
|
}
|
|
break;
|
|
}
|
|
|
|
return length;
|
|
}
|
|
|
|
static size_t curl_write_header(char *data, size_t size, size_t nmemb,
|
|
void *ctx) {
|
|
CurlResource *ch = (CurlResource *)ctx;
|
|
WriteHandler *t = &ch->m_write_header;
|
|
size_t length = size * nmemb;
|
|
|
|
switch (t->method) {
|
|
case PHP_CURL_STDOUT:
|
|
// Handle special case write when we're returning the entire transfer
|
|
if (ch->m_write.method == PHP_CURL_RETURN && length > 0) {
|
|
ch->m_write.buf.append(data, (int)length);
|
|
} else {
|
|
echo(String(data, length, CopyString));
|
|
}
|
|
break;
|
|
case PHP_CURL_FILE:
|
|
return t->fp->write(String(data, length, CopyString), length);
|
|
case PHP_CURL_USER:
|
|
{
|
|
Variant ret = ch->do_callback(
|
|
t->callback,
|
|
CREATE_VECTOR2(Object(ch), String(data, length, CopyString)));
|
|
length = ret.toInt64();
|
|
}
|
|
break;
|
|
case PHP_CURL_IGNORE:
|
|
return length;
|
|
default:
|
|
return (size_t)-1;
|
|
}
|
|
|
|
return length;
|
|
}
|
|
|
|
CURL *get(bool nullOkay = false) {
|
|
if (m_cp == NULL && !nullOkay) {
|
|
throw NullPointerException();
|
|
}
|
|
return m_cp;
|
|
}
|
|
|
|
int getError() {
|
|
return m_error_no;
|
|
}
|
|
|
|
String getErrorString() {
|
|
return String(m_error_str, CopyString);
|
|
}
|
|
|
|
private:
|
|
CURL *m_cp;
|
|
void *m_exception;
|
|
|
|
char m_error_str[CURL_ERROR_SIZE + 1];
|
|
CURLcode m_error_no;
|
|
|
|
ToFreePtr m_to_free;
|
|
|
|
String m_url;
|
|
String m_header;
|
|
Array m_opts;
|
|
|
|
WriteHandler m_write;
|
|
WriteHandler m_write_header;
|
|
ReadHandler m_read;
|
|
|
|
bool m_phpException;
|
|
bool m_emptyPost;
|
|
};
|
|
IMPLEMENT_OBJECT_ALLOCATION_NO_DEFAULT_SWEEP(CurlResource);
|
|
void CurlResource::sweep() {
|
|
m_write.buf.release();
|
|
m_write_header.buf.release();
|
|
closeForSweep();
|
|
}
|
|
|
|
StaticString CurlResource::s_class_name("cURL handle");
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
#define CHECK_RESOURCE(curl) \
|
|
CurlResource *curl = ch.getTyped<CurlResource>(true, true); \
|
|
if (curl == NULL) { \
|
|
raise_warning("supplied argument is not a valid cURL handle resource"); \
|
|
return false; \
|
|
} \
|
|
|
|
Variant f_curl_init(CStrRef url /* = null_string */) {
|
|
return NEWOBJ(CurlResource)(url);
|
|
}
|
|
|
|
Variant f_curl_copy_handle(CObjRef ch) {
|
|
CHECK_RESOURCE(curl);
|
|
return NEWOBJ(CurlResource)(curl);
|
|
}
|
|
|
|
static const StaticString s_version_number("version_number");
|
|
static const StaticString s_age("age");
|
|
static const StaticString s_features("features");
|
|
static const StaticString s_ssl_version_number("ssl_version_number");
|
|
static const StaticString s_version("version");
|
|
static const StaticString s_host("host");
|
|
static const StaticString s_ssl_version("ssl_version");
|
|
static const StaticString s_libz_version("libz_version");
|
|
static const StaticString s_protocols("protocols");
|
|
|
|
Variant f_curl_version(int uversion /* = k_CURLVERSION_NOW */) {
|
|
curl_version_info_data *d = curl_version_info((CURLversion)uversion);
|
|
if (d == NULL) {
|
|
return false;
|
|
}
|
|
|
|
ArrayInit ret(9);
|
|
ret.set(s_version_number, (int)d->version_num);
|
|
ret.set(s_age, d->age);
|
|
ret.set(s_features, d->features);
|
|
ret.set(s_ssl_version_number, d->ssl_version_num);
|
|
ret.set(s_version, d->version);
|
|
ret.set(s_host, d->host);
|
|
ret.set(s_ssl_version, d->ssl_version);
|
|
ret.set(s_libz_version, d->libz_version);
|
|
|
|
// Add an array of protocols
|
|
char **p = (char **) d->protocols;
|
|
Array protocol_list;
|
|
while (*p != NULL) {
|
|
protocol_list.append(String(*p++, CopyString));
|
|
}
|
|
ret.set(s_protocols, protocol_list);
|
|
return ret.create();
|
|
}
|
|
|
|
bool f_curl_setopt(CObjRef ch, int option, CVarRef value) {
|
|
CHECK_RESOURCE(curl);
|
|
return curl->setOption(option, value);
|
|
}
|
|
|
|
bool f_curl_setopt_array(CObjRef ch, CArrRef options) {
|
|
CHECK_RESOURCE(curl);
|
|
for (ArrayIter iter(options); iter; ++iter) {
|
|
if (!curl->setOption(iter.first().toInt32(), iter.second())) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
Variant f_fb_curl_getopt(CObjRef ch, int opt /* = 0 */) {
|
|
CHECK_RESOURCE(curl);
|
|
return curl->getOption(opt);
|
|
}
|
|
|
|
Variant f_curl_exec(CObjRef ch) {
|
|
CHECK_RESOURCE(curl);
|
|
return curl->execute();
|
|
}
|
|
|
|
static const StaticString s_url("url");
|
|
static const StaticString s_content_type("content_type");
|
|
static const StaticString s_http_code("http_code");
|
|
static const StaticString s_header_size("header_size");
|
|
static const StaticString s_request_size("request_size");
|
|
static const StaticString s_filetime("filetime");
|
|
static const StaticString s_ssl_verify_result("ssl_verify_result");
|
|
static const StaticString s_redirect_count("redirect_count");
|
|
static const StaticString s_local_port("local_port");
|
|
static const StaticString s_total_time("total_time");
|
|
static const StaticString s_namelookup_time("namelookup_time");
|
|
static const StaticString s_connect_time("connect_time");
|
|
static const StaticString s_pretransfer_time("pretransfer_time");
|
|
static const StaticString s_size_upload("size_upload");
|
|
static const StaticString s_size_download("size_download");
|
|
static const StaticString s_speed_download("speed_download");
|
|
static const StaticString s_speed_upload("speed_upload");
|
|
static const StaticString s_download_content_length("download_content_length");
|
|
static const StaticString s_upload_content_length("upload_content_length");
|
|
static const StaticString s_starttransfer_time("starttransfer_time");
|
|
static const StaticString s_redirect_time("redirect_time");
|
|
static const StaticString s_request_header("request_header");
|
|
|
|
Variant f_curl_getinfo(CObjRef ch, int opt /* = 0 */) {
|
|
CHECK_RESOURCE(curl);
|
|
CURL *cp = curl->get();
|
|
|
|
if (opt == 0) {
|
|
char *s_code;
|
|
long l_code;
|
|
double d_code;
|
|
|
|
Array ret;
|
|
if (curl_easy_getinfo(cp, CURLINFO_EFFECTIVE_URL, &s_code) == CURLE_OK) {
|
|
ret.set(s_url, String(s_code, CopyString));
|
|
}
|
|
if (curl_easy_getinfo(cp, CURLINFO_CONTENT_TYPE, &s_code) == CURLE_OK) {
|
|
if (s_code != NULL) {
|
|
ret.set(s_content_type, String(s_code, CopyString));
|
|
} else {
|
|
ret.set(s_content_type, uninit_null());
|
|
}
|
|
}
|
|
if (curl_easy_getinfo(cp, CURLINFO_HTTP_CODE, &l_code) == CURLE_OK) {
|
|
ret.set(s_http_code, l_code);
|
|
}
|
|
if (curl_easy_getinfo(cp, CURLINFO_HEADER_SIZE, &l_code) == CURLE_OK) {
|
|
ret.set(s_header_size, l_code);
|
|
}
|
|
if (curl_easy_getinfo(cp, CURLINFO_REQUEST_SIZE, &l_code) == CURLE_OK) {
|
|
ret.set(s_request_size, l_code);
|
|
}
|
|
if (curl_easy_getinfo(cp, CURLINFO_FILETIME, &l_code) == CURLE_OK) {
|
|
ret.set(s_filetime, l_code);
|
|
}
|
|
if (curl_easy_getinfo(cp, CURLINFO_SSL_VERIFYRESULT, &l_code) ==
|
|
CURLE_OK) {
|
|
ret.set(s_ssl_verify_result, l_code);
|
|
}
|
|
if (curl_easy_getinfo(cp, CURLINFO_REDIRECT_COUNT, &l_code) == CURLE_OK) {
|
|
ret.set(s_redirect_count, l_code);
|
|
}
|
|
#if LIBCURL_VERSION_NUM >= 0x071500
|
|
if (curl_easy_getinfo(cp, CURLINFO_LOCAL_PORT, &l_code) == CURLE_OK) {
|
|
ret.set(s_local_port, l_code);
|
|
}
|
|
#endif
|
|
if (curl_easy_getinfo(cp, CURLINFO_TOTAL_TIME, &d_code) == CURLE_OK) {
|
|
ret.set(s_total_time, d_code);
|
|
}
|
|
if (curl_easy_getinfo(cp, CURLINFO_NAMELOOKUP_TIME, &d_code) == CURLE_OK) {
|
|
ret.set(s_namelookup_time, d_code);
|
|
}
|
|
if (curl_easy_getinfo(cp, CURLINFO_CONNECT_TIME, &d_code) == CURLE_OK) {
|
|
ret.set(s_connect_time, d_code);
|
|
}
|
|
if (curl_easy_getinfo(cp, CURLINFO_PRETRANSFER_TIME, &d_code) ==
|
|
CURLE_OK) {
|
|
ret.set(s_pretransfer_time, d_code);
|
|
}
|
|
if (curl_easy_getinfo(cp, CURLINFO_SIZE_UPLOAD, &d_code) == CURLE_OK) {
|
|
ret.set(s_size_upload, d_code);
|
|
}
|
|
if (curl_easy_getinfo(cp, CURLINFO_SIZE_DOWNLOAD, &d_code) == CURLE_OK) {
|
|
ret.set(s_size_download, d_code);
|
|
}
|
|
if (curl_easy_getinfo(cp, CURLINFO_SPEED_DOWNLOAD, &d_code) == CURLE_OK) {
|
|
ret.set(s_speed_download, d_code);
|
|
}
|
|
if (curl_easy_getinfo(cp, CURLINFO_SPEED_UPLOAD, &d_code) == CURLE_OK) {
|
|
ret.set(s_speed_upload, d_code);
|
|
}
|
|
if (curl_easy_getinfo(cp, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &d_code) ==
|
|
CURLE_OK) {
|
|
ret.set(s_download_content_length, d_code);
|
|
}
|
|
if (curl_easy_getinfo(cp, CURLINFO_CONTENT_LENGTH_UPLOAD, &d_code) ==
|
|
CURLE_OK) {
|
|
ret.set(s_upload_content_length, d_code);
|
|
}
|
|
if (curl_easy_getinfo(cp, CURLINFO_STARTTRANSFER_TIME, &d_code) ==
|
|
CURLE_OK) {
|
|
ret.set(s_starttransfer_time, d_code);
|
|
}
|
|
if (curl_easy_getinfo(cp, CURLINFO_REDIRECT_TIME, &d_code) == CURLE_OK) {
|
|
ret.set(s_redirect_time, d_code);
|
|
}
|
|
String header = curl->getHeader();
|
|
if (!header.empty()) {
|
|
ret.set(s_request_header, header);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
switch (opt) {
|
|
case CURLINFO_PRIVATE:
|
|
case CURLINFO_EFFECTIVE_URL:
|
|
case CURLINFO_CONTENT_TYPE: {
|
|
char *s_code = NULL;
|
|
if (curl_easy_getinfo(cp, (CURLINFO)opt, &s_code) == CURLE_OK &&
|
|
s_code) {
|
|
return String(s_code, CopyString);
|
|
}
|
|
return false;
|
|
}
|
|
case CURLINFO_HTTP_CODE:
|
|
case CURLINFO_HEADER_SIZE:
|
|
case CURLINFO_REQUEST_SIZE:
|
|
case CURLINFO_FILETIME:
|
|
case CURLINFO_SSL_VERIFYRESULT:
|
|
#if LIBCURL_VERSION_NUM >= 0x071500
|
|
case CURLINFO_LOCAL_PORT:
|
|
#endif
|
|
case CURLINFO_REDIRECT_COUNT: {
|
|
long code = 0;
|
|
if (curl_easy_getinfo(cp, (CURLINFO)opt, &code) == CURLE_OK) {
|
|
return code;
|
|
}
|
|
return false;
|
|
}
|
|
case CURLINFO_TOTAL_TIME:
|
|
case CURLINFO_NAMELOOKUP_TIME:
|
|
case CURLINFO_CONNECT_TIME:
|
|
case CURLINFO_PRETRANSFER_TIME:
|
|
case CURLINFO_SIZE_UPLOAD:
|
|
case CURLINFO_SIZE_DOWNLOAD:
|
|
case CURLINFO_SPEED_DOWNLOAD:
|
|
case CURLINFO_SPEED_UPLOAD:
|
|
case CURLINFO_CONTENT_LENGTH_DOWNLOAD:
|
|
case CURLINFO_CONTENT_LENGTH_UPLOAD:
|
|
case CURLINFO_STARTTRANSFER_TIME:
|
|
case CURLINFO_REDIRECT_TIME: {
|
|
double code = 0.0;
|
|
if (curl_easy_getinfo(cp, (CURLINFO)opt, &code) == CURLE_OK) {
|
|
return code;
|
|
}
|
|
return false;
|
|
}
|
|
case CURLINFO_HEADER_OUT:
|
|
{
|
|
String header = curl->getHeader();
|
|
if (!header.empty()) {
|
|
return header;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return uninit_null();
|
|
}
|
|
|
|
Variant f_curl_errno(CObjRef ch) {
|
|
CHECK_RESOURCE(curl);
|
|
return curl->getError();
|
|
}
|
|
|
|
Variant f_curl_error(CObjRef ch) {
|
|
CHECK_RESOURCE(curl);
|
|
return curl->getErrorString();
|
|
}
|
|
|
|
Variant f_curl_close(CObjRef ch) {
|
|
CHECK_RESOURCE(curl);
|
|
curl->close();
|
|
return uninit_null();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
class CurlMultiResource : public SweepableResourceData {
|
|
public:
|
|
DECLARE_OBJECT_ALLOCATION(CurlMultiResource)
|
|
|
|
static StaticString s_class_name;
|
|
// overriding ResourceData
|
|
CStrRef o_getClassNameHook() const { return s_class_name; }
|
|
|
|
CurlMultiResource() {
|
|
m_multi = curl_multi_init();
|
|
}
|
|
|
|
~CurlMultiResource() {
|
|
close();
|
|
}
|
|
|
|
void close() {
|
|
if (m_multi) {
|
|
curl_multi_cleanup(m_multi);
|
|
m_easyh.clear();
|
|
m_multi = NULL;
|
|
}
|
|
}
|
|
|
|
void add(CObjRef ch) {
|
|
m_easyh.append(ch);
|
|
}
|
|
|
|
void remove(CurlResource *curle) {
|
|
for (ArrayIter iter(m_easyh); iter; ++iter) {
|
|
if (toObject(iter.second()).getTyped<CurlResource>()->get(true) ==
|
|
curle->get()) {
|
|
m_easyh.remove(iter.first());
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
Object find(CURL *cp) {
|
|
for (ArrayIter iter(m_easyh); iter; ++iter) {
|
|
if (toObject(iter.second()).getTyped<CurlResource>()->get(true) == cp) {
|
|
return iter.second();
|
|
}
|
|
}
|
|
return Object();
|
|
}
|
|
|
|
void check_exceptions() {
|
|
ObjectData* phpException = 0;
|
|
Exception* cppException = 0;
|
|
for (ArrayIter iter(m_easyh); iter; ++iter) {
|
|
CurlResource* curl = iter.second().toCObjRef(). getTyped<CurlResource>();
|
|
if (ObjectData* e = curl->getAndClearPhpException()) {
|
|
if (phpException) {
|
|
e->o_set(s_previous, Variant(phpException), s_exception);
|
|
phpException->decRefCount();
|
|
}
|
|
phpException = e;
|
|
} else if (Exception *e = curl->getAndClearCppException()) {
|
|
delete cppException;
|
|
cppException = e;
|
|
}
|
|
}
|
|
if (cppException) {
|
|
if (phpException) decRefObj(phpException);
|
|
cppException->throwException();
|
|
}
|
|
if (phpException) {
|
|
Object e(phpException);
|
|
phpException->decRefCount();
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
CURLM *get() {
|
|
if (m_multi == NULL) {
|
|
throw NullPointerException();
|
|
}
|
|
return m_multi;
|
|
}
|
|
|
|
private:
|
|
int m_still_running;
|
|
CURLM *m_multi;
|
|
Array m_easyh;
|
|
};
|
|
IMPLEMENT_OBJECT_ALLOCATION_NO_DEFAULT_SWEEP(CurlMultiResource);
|
|
void CurlMultiResource::sweep() {
|
|
if (m_multi) {
|
|
curl_multi_cleanup(m_multi);
|
|
}
|
|
}
|
|
|
|
StaticString CurlMultiResource::s_class_name("cURL Multi Handle");
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
#define CHECK_MULTI_RESOURCE(curlm) \
|
|
CurlMultiResource *curlm = mh.getTyped<CurlMultiResource>(true, true); \
|
|
if (curlm == NULL) { \
|
|
raise_warning("expects parameter 1 to be cURL multi resource"); \
|
|
return uninit_null(); \
|
|
} \
|
|
|
|
Object f_curl_multi_init() {
|
|
return NEWOBJ(CurlMultiResource)();
|
|
}
|
|
|
|
Variant f_curl_multi_add_handle(CObjRef mh, CObjRef ch) {
|
|
CHECK_MULTI_RESOURCE(curlm);
|
|
CurlResource *curle = ch.getTyped<CurlResource>();
|
|
curlm->add(ch);
|
|
return curl_multi_add_handle(curlm->get(), curle->get());
|
|
}
|
|
|
|
Variant f_curl_multi_remove_handle(CObjRef mh, CObjRef ch) {
|
|
CHECK_MULTI_RESOURCE(curlm);
|
|
CurlResource *curle = ch.getTyped<CurlResource>();
|
|
curlm->remove(curle);
|
|
return curl_multi_remove_handle(curlm->get(), curle->get());
|
|
}
|
|
|
|
Variant f_curl_multi_exec(CObjRef mh, VRefParam still_running) {
|
|
CHECK_MULTI_RESOURCE(curlm);
|
|
int running = still_running;
|
|
IOStatusHelper io("curl_multi_exec");
|
|
SYNC_VM_REGS_SCOPED();
|
|
int result = curl_multi_perform(curlm->get(), &running);
|
|
curlm->check_exceptions();
|
|
still_running = running;
|
|
return result;
|
|
}
|
|
|
|
/* Fallback implementation of curl_multi_select() for
|
|
* libcurl < 7.28.0 without FB's curl_multi_select() patch
|
|
*
|
|
* This allows the OSS build to work with older package
|
|
* versions of libcurl, but will fail with file descriptors
|
|
* over 1024.
|
|
*/
|
|
UNUSED
|
|
static void hphp_curl_multi_select(CURLM *mh, int timeout_ms, int *ret) {
|
|
fd_set read_fds, write_fds, except_fds;
|
|
int maxfds, nfds = -1;
|
|
struct timeval tv;
|
|
|
|
FD_ZERO(&read_fds);
|
|
FD_ZERO(&write_fds);
|
|
FD_ZERO(&except_fds);
|
|
|
|
tv.tv_sec = timeout_ms / 1000;
|
|
tv.tv_usec = (timeout_ms * 1000) % 1000000;
|
|
|
|
curl_multi_fdset(mh, &read_fds, &write_fds, &except_fds, &maxfds);
|
|
if (maxfds < 1024) {
|
|
nfds = select(maxfds + 1, &read_fds, &write_fds, &except_fds, &tv);
|
|
} else {
|
|
/* fd_set can only hold sockets from 0 to 1023,
|
|
* anything higher is ignored by FD_SET()
|
|
* avoid "unexplained" behavior by failing outright
|
|
*/
|
|
raise_warning("libcurl versions < 7.28.0 do not support selecting on "
|
|
"file descriptors of 1024 or higher.");
|
|
}
|
|
if (ret) {
|
|
*ret = nfds;
|
|
}
|
|
}
|
|
|
|
#ifndef HAVE_CURL_MULTI_SELECT
|
|
# ifdef HAVE_CURL_MULTI_WAIT
|
|
# define curl_multi_select(mh, tm, ret) curl_multi_wait((mh), NULL, 0, (tm), (ret))
|
|
# else
|
|
# define curl_multi_select hphp_curl_multi_select
|
|
# endif
|
|
#endif
|
|
|
|
Variant f_curl_multi_select(CObjRef mh, double timeout /* = 1.0 */) {
|
|
CHECK_MULTI_RESOURCE(curlm);
|
|
int ret;
|
|
unsigned long timeout_ms = (unsigned long)(timeout * 1000.0);
|
|
IOStatusHelper io("curl_multi_select");
|
|
curl_multi_select(curlm->get(), timeout_ms, &ret);
|
|
return ret;
|
|
}
|
|
|
|
Variant f_curl_multi_getcontent(CObjRef ch) {
|
|
CHECK_RESOURCE(curl);
|
|
return curl->getContents();
|
|
}
|
|
|
|
Array f_curl_convert_fd_to_stream(fd_set *fd, int max_fd) {
|
|
Array ret = Array::Create();
|
|
for (int i=0; i<=max_fd; i++) {
|
|
if (FD_ISSET(i, fd)) {
|
|
BuiltinFile *file = NEWOBJ(BuiltinFile)(i);
|
|
ret.append(file);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
Variant f_fb_curl_multi_fdset(CObjRef mh,
|
|
VRefParam read_fd_set,
|
|
VRefParam write_fd_set,
|
|
VRefParam exc_fd_set,
|
|
VRefParam max_fd /* = null_object */) {
|
|
CHECK_MULTI_RESOURCE(curlm);
|
|
|
|
fd_set read_set;
|
|
fd_set write_set;
|
|
fd_set exc_set;
|
|
int max = 0;
|
|
|
|
FD_ZERO(&read_set);
|
|
FD_ZERO(&write_set);
|
|
FD_ZERO(&exc_set);
|
|
|
|
int r = curl_multi_fdset(curlm->get(), &read_set, &write_set, &exc_set, &max);
|
|
read_fd_set = f_curl_convert_fd_to_stream(&read_set, max);
|
|
write_fd_set = f_curl_convert_fd_to_stream(&write_set, max);
|
|
exc_fd_set = f_curl_convert_fd_to_stream(&exc_set, max);
|
|
max_fd = max;
|
|
|
|
return r;
|
|
}
|
|
|
|
static const StaticString s_msg("msg");
|
|
static const StaticString s_result("result");
|
|
static const StaticString s_handle("handle");
|
|
static const StaticString s_headers("headers");
|
|
static const StaticString s_requests("requests");
|
|
|
|
Variant f_curl_multi_info_read(CObjRef mh,
|
|
VRefParam msgs_in_queue /* = null */) {
|
|
CHECK_MULTI_RESOURCE(curlm);
|
|
|
|
int queued_msgs;
|
|
CURLMsg *tmp_msg = curl_multi_info_read(curlm->get(), &queued_msgs);
|
|
curlm->check_exceptions();
|
|
if (tmp_msg == NULL) {
|
|
return false;
|
|
}
|
|
msgs_in_queue = queued_msgs;
|
|
|
|
Array ret;
|
|
ret.set(s_msg, tmp_msg->msg);
|
|
ret.set(s_result, tmp_msg->data.result);
|
|
Object curle = curlm->find(tmp_msg->easy_handle);
|
|
if (!curle.isNull()) {
|
|
ret.set(s_handle, curle);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
Variant f_curl_multi_close(CObjRef mh) {
|
|
CHECK_MULTI_RESOURCE(curlm);
|
|
curlm->close();
|
|
return uninit_null();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// evhttp functions
|
|
|
|
class LibEventHttpHandle : public SweepableResourceData {
|
|
public:
|
|
DECLARE_OBJECT_ALLOCATION(LibEventHttpHandle)
|
|
|
|
static StaticString s_class_name;
|
|
// overriding ResourceData
|
|
virtual CStrRef o_getClassNameHook() const { return s_class_name; }
|
|
|
|
explicit LibEventHttpHandle(LibEventHttpClientPtr client) : m_client(client) {
|
|
}
|
|
|
|
~LibEventHttpHandle() {
|
|
if (m_client) {
|
|
m_client->release();
|
|
}
|
|
}
|
|
|
|
LibEventHttpClientPtr m_client;
|
|
};
|
|
IMPLEMENT_OBJECT_ALLOCATION(LibEventHttpHandle)
|
|
|
|
StaticString LibEventHttpHandle::s_class_name("LibEventHttp");
|
|
|
|
static LibEventHttpClientPtr prepare_client
|
|
(CStrRef url, CStrRef data, CArrRef headers, int timeout,
|
|
bool async, bool post) {
|
|
string sUrl = url.data();
|
|
if (sUrl.size() < 7 || sUrl.substr(0, 7) != "http://") {
|
|
raise_warning("Invalid URL: %s", sUrl.c_str());
|
|
return LibEventHttpClientPtr();
|
|
}
|
|
|
|
// parsing server address
|
|
size_t pos = sUrl.find('/', 7);
|
|
string path;
|
|
if (pos == string::npos) {
|
|
pos = sUrl.length();
|
|
path = "/";
|
|
} else if (pos == 7) {
|
|
raise_warning("Invalid URL: %s", sUrl.c_str());
|
|
return LibEventHttpClientPtr();
|
|
} else {
|
|
path = sUrl.substr(pos);
|
|
}
|
|
string address = sUrl.substr(7, pos - 7);
|
|
|
|
// parsing server port
|
|
pos = address.find(':');
|
|
int port = 80;
|
|
if (pos != string::npos) {
|
|
if (pos < address.length() - 1) {
|
|
string sport = address.substr(pos + 1, address.length() - pos - 1);
|
|
port = atoi(sport.c_str());
|
|
}
|
|
address = address.substr(0, pos);
|
|
}
|
|
|
|
LibEventHttpClientPtr client = LibEventHttpClient::Get(address, port);
|
|
if (!client) {
|
|
return client;
|
|
}
|
|
|
|
vector<string> sheaders;
|
|
for (ArrayIter iter(headers); iter; ++iter) {
|
|
sheaders.push_back(iter.second().toString().data());
|
|
}
|
|
if (!client->send(path.c_str(), sheaders, timeout, async,
|
|
post ? (void*)data.data() : NULL,
|
|
post ? data.size() : 0)) {
|
|
return LibEventHttpClientPtr();
|
|
}
|
|
return client;
|
|
}
|
|
|
|
static const StaticString s_code("code");
|
|
static const StaticString s_response("response");
|
|
|
|
static Array prepare_response(LibEventHttpClientPtr client) {
|
|
int len = 0;
|
|
char *res = client->recv(len); // block on return
|
|
|
|
ArrayInit ret(4);
|
|
ret.set(s_code, client->getCode());
|
|
ret.set(s_response, String(res, len, AttachString));
|
|
|
|
Array headers = Array::Create();
|
|
const vector<string> &responseHeaders = client->getResponseHeaders();
|
|
for (unsigned int i = 0; i < responseHeaders.size(); i++) {
|
|
headers.append(String(responseHeaders[i]));
|
|
}
|
|
ret.set(s_headers, headers);
|
|
ret.set(s_requests, client->getRequests());
|
|
return ret.create();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
void f_evhttp_set_cache(CStrRef address, int max_conn, int port /* = 80 */) {
|
|
if (RuntimeOption::ServerHttpSafeMode) {
|
|
throw_fatal("evhttp_set_cache is disabled");
|
|
}
|
|
LibEventHttpClient::SetCache(address.data(), port, max_conn);
|
|
}
|
|
|
|
Variant f_evhttp_get(CStrRef url, CArrRef headers /* = null_array */,
|
|
int timeout /* = 5 */) {
|
|
if (RuntimeOption::ServerHttpSafeMode) {
|
|
throw_fatal("evhttp_set_cache is disabled");
|
|
}
|
|
LibEventHttpClientPtr client = prepare_client(url, "", headers, timeout,
|
|
false, false);
|
|
if (client) {
|
|
Variant ret = prepare_response(client);
|
|
client->release();
|
|
return ret;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Variant f_evhttp_post(CStrRef url, CStrRef data,
|
|
CArrRef headers /* = null_array */,
|
|
int timeout /* = 5 */) {
|
|
if (RuntimeOption::ServerHttpSafeMode) {
|
|
throw_fatal("evhttp_post is disabled");
|
|
}
|
|
LibEventHttpClientPtr client = prepare_client(url, data, headers, timeout,
|
|
false, true);
|
|
if (client) {
|
|
Variant ret = prepare_response(client);
|
|
client->release();
|
|
return ret;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Variant f_evhttp_async_get(CStrRef url, CArrRef headers /* = null_array */,
|
|
int timeout /* = 5 */) {
|
|
if (RuntimeOption::ServerHttpSafeMode) {
|
|
throw_fatal("evhttp_async_get is disabled");
|
|
}
|
|
LibEventHttpClientPtr client = prepare_client(url, "", headers, timeout,
|
|
true, false);
|
|
if (client) {
|
|
return Object(NEWOBJ(LibEventHttpHandle)(client));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Variant f_evhttp_async_post(CStrRef url, CStrRef data,
|
|
CArrRef headers /* = null_array */,
|
|
int timeout /* = 5 */) {
|
|
if (RuntimeOption::ServerHttpSafeMode) {
|
|
throw_fatal("evhttp_async_post is disabled");
|
|
}
|
|
LibEventHttpClientPtr client = prepare_client(url, data, headers, timeout,
|
|
true, true);
|
|
if (client) {
|
|
return Object(NEWOBJ(LibEventHttpHandle)(client));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Variant f_evhttp_recv(CObjRef handle) {
|
|
if (RuntimeOption::ServerHttpSafeMode) {
|
|
throw_fatal("evhttp_recv is disabled");
|
|
}
|
|
LibEventHttpHandle *obj = handle.getTyped<LibEventHttpHandle>();
|
|
if (obj->m_client) {
|
|
return prepare_response(obj->m_client);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#if LIBCURL_VERSION_NUM >= 0x071500
|
|
const int64_t k_CURLINFO_LOCAL_PORT = CURLINFO_LOCAL_PORT;
|
|
#else
|
|
const int64_t k_CURLINFO_LOCAL_PORT = CURLINFO_NONE;
|
|
#endif
|
|
|
|
#if LIBCURL_VERSION_NUM >= 0x071002
|
|
const int64_t k_CURLOPT_TIMEOUT_MS = CURLOPT_TIMEOUT_MS;
|
|
const int64_t k_CURLOPT_CONNECTTIMEOUT_MS = CURLOPT_CONNECTTIMEOUT_MS;
|
|
#else
|
|
const int64_t k_CURLOPT_TIMEOUT_MS = CURLOPT_LASTENTRY;
|
|
const int64_t k_CURLOPT_CONNECTTIMEOUT_MS = CURLOPT_LASTENTRY;
|
|
#endif
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
}
|