Arquivos
hhvm/hphp/runtime/ext/ext_curl.cpp
T
Drew Paroski 84b9d9a3a2 Separate resources from objects, part 1
In HHVM (and HPHPc before it) we've been piggybacking resources on the
KindOfObject machinery. At the language level, resource is considered to
be a different type than object, and there are a number of differences
in behavior between objects and resources (ex. resources don't allow for
dynamic properties, resources don't work with the clone operator, the
"(object)" cast behaves differently for resources vs. objects, etc).

Piggybacking resources on the KindOfObject machinery has some downsides.
Code that deals with KindOfObject values often needs to check if the value
is a resource and go down a different code path. This makes things harder
to maintain and harder to keep parity with Zend. Also, these extra branches
hurt performance a little, and they make it harder for the JIT to do a good
job in some cases when its generating machine code that operates on objects.

This diff prepares the code base for a new KindOfResource type by adding a
new "Resource" smart pointer type (currently a typedef for the Object smart
pointer type) and it updates the C++ code and the idl files appropriately.
This diff is essentially a cosmetic change and should not impact run time
behavior. In the next diff (part 2) we'll actually add a new KindOfResource
type, detach ResourceData from the ObjectData inheritence hierarchy, and
provide a real implementation for the Resource smart pointer type (instead
of just aliasing the Object smart pointer type).
2013-07-10 11:16:33 -07:00

1474 linhas
42 KiB
C++

/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010-2013 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 "hphp/runtime/ext/ext_curl.h"
#include "hphp/runtime/ext/ext_function.h"
#include "hphp/runtime/base/util/string_buffer.h"
#include "hphp/runtime/base/util/libevent_http_client.h"
#include "hphp/runtime/base/util/curl_tls_workarounds.h"
#include "hphp/runtime/base/runtime_option.h"
#include "hphp/runtime/base/server/server_stats.h"
#include "hphp/runtime/vm/jit/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;
SmartResource<File> fp;
StringBuffer buf;
String content;
int type;
};
class ReadHandler {
public:
ReadHandler() : method(0) {}
int method;
Variant callback;
SmartResource<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.isResource()) {
return false;
}
Resource obj = value.toResource();
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: %ld", 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(Resource(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:
g_context->write(data, length);
break;
case PHP_CURL_FILE:
return t->fp->write(String(data, length, AttachLiteral), 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(Resource(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 {
g_context->write(data, length);
}
break;
case PHP_CURL_FILE:
return t->fp->write(String(data, length, AttachLiteral), length);
case PHP_CURL_USER:
{
Variant ret = ch->do_callback(
t->callback,
CREATE_VECTOR2(Resource(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(CResRef ch) {
CHECK_RESOURCE(curl);
return NEWOBJ(CurlResource)(curl);
}
const StaticString
s_version_number("version_number"),
s_age("age"),
s_features("features"),
s_ssl_version_number("ssl_version_number"),
s_version("version"),
s_host("host"),
s_ssl_version("ssl_version"),
s_libz_version("libz_version"),
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(CResRef ch, int option, CVarRef value) {
CHECK_RESOURCE(curl);
return curl->setOption(option, value);
}
bool f_curl_setopt_array(CResRef 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(CResRef ch, int opt /* = 0 */) {
CHECK_RESOURCE(curl);
return curl->getOption(opt);
}
Variant f_curl_exec(CResRef ch) {
CHECK_RESOURCE(curl);
return curl->execute();
}
const StaticString
s_url("url"),
s_content_type("content_type"),
s_http_code("http_code"),
s_header_size("header_size"),
s_request_size("request_size"),
s_filetime("filetime"),
s_ssl_verify_result("ssl_verify_result"),
s_redirect_count("redirect_count"),
s_local_port("local_port"),
s_total_time("total_time"),
s_namelookup_time("namelookup_time"),
s_connect_time("connect_time"),
s_pretransfer_time("pretransfer_time"),
s_size_upload("size_upload"),
s_size_download("size_download"),
s_speed_download("speed_download"),
s_speed_upload("speed_upload"),
s_download_content_length("download_content_length"),
s_upload_content_length("upload_content_length"),
s_starttransfer_time("starttransfer_time"),
s_redirect_time("redirect_time"),
s_request_header("request_header");
Variant f_curl_getinfo(CResRef 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(CResRef ch) {
CHECK_RESOURCE(curl);
return curl->getError();
}
Variant f_curl_error(CResRef ch) {
CHECK_RESOURCE(curl);
return curl->getErrorString();
}
Variant f_curl_close(CResRef 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(CResRef ch) {
m_easyh.append(ch);
}
void remove(CurlResource *curle) {
for (ArrayIter iter(m_easyh); iter; ++iter) {
if (iter.second().toResource().getTyped<CurlResource>()->get(true) ==
curle->get()) {
m_easyh.remove(iter.first());
return;
}
}
}
Resource find(CURL *cp) {
for (ArrayIter iter(m_easyh); iter; ++iter) {
if (iter.second().toResource().
getTyped<CurlResource>()->get(true) == cp) {
return iter.second().toResource();
}
}
return Resource();
}
void check_exceptions() {
ObjectData* phpException = 0;
Exception* cppException = 0;
for (ArrayIter iter(m_easyh); iter; ++iter) {
CurlResource* curl = iter.second().toResource().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:
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(); \
} \
Resource f_curl_multi_init() {
return NEWOBJ(CurlMultiResource)();
}
Variant f_curl_multi_add_handle(CResRef mh, CResRef 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(CResRef mh, CResRef 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(CResRef 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(CResRef 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(CResRef 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(CResRef 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;
}
const StaticString
s_msg("msg"),
s_result("result"),
s_handle("handle"),
s_headers("headers"),
s_requests("requests");
Variant f_curl_multi_info_read(CResRef 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);
Resource curle = curlm->find(tmp_msg->easy_handle);
if (!curle.isNull()) {
ret.set(s_handle, curle);
}
return ret;
}
Variant f_curl_multi_close(CResRef 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;
}
const StaticString
s_code("code"),
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 Resource(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 Resource(NEWOBJ(LibEventHttpHandle)(client));
}
return false;
}
Variant f_evhttp_recv(CResRef 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
///////////////////////////////////////////////////////////////////////////////
}