diff options
Diffstat (limited to 'couchjs/c_src/http.c')
-rw-r--r-- | couchjs/c_src/http.c | 382 |
1 files changed, 168 insertions, 214 deletions
diff --git a/couchjs/c_src/http.c b/couchjs/c_src/http.c index b781f0ef..aa21515c 100644 --- a/couchjs/c_src/http.c +++ b/couchjs/c_src/http.c @@ -13,29 +13,87 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> - #include "config.h" -#ifdef HAVE_JS_JSAPI_H -#include <js/jsapi.h> -#elif HAVE_MOZJS_JSAPI_H -#include <mozjs/jsapi.h> -#else -#include <jsapi.h> -#endif +#include "sm.h" +#include "utf8.h" + +// Soft dependency on cURL bindings because they're +// only used when running the JS tests from the +// command line which is rare. +#ifndef HAVE_LIBCURL + +void +http_check_enabled() +{ + fprintf(stderr, "HTTP API was disabled at compile time.\n"); + exit(3); +} + + +JSBool +http_ctor(JSContext* cx, JSObject* req) +{ + return JS_FALSE; +} + + +JSBool +http_dtor(JSContext* cx, JSObject* req) +{ + return JS_FALSE; +} + + +JSBool +http_open(JSContext* cx, JSObject* req, jsval mth, jsval url, jsval snc) +{ + return JS_FALSE; +} + + +JSBool +http_set_hdr(JSContext* cx, JSObject* req, jsval name, jsval val) +{ + return JS_FALSE; +} + + +JSBool +http_send(JSContext* cx, JSObject* req, jsval body) +{ + return JS_FALSE; +} + + +int +http_status(JSContext* cx, JSObject* req) +{ + return -1; +} + +#else #include <curl/curl.h> -#include "utf8.h" -#ifdef XP_WIN +void +http_check_enabled() +{ + return; +} + + // Map some of the string function names to things which exist on Windows +#ifdef XP_WIN #define strcasecmp _strcmpi #define strncasecmp _strnicmp #define snprintf _snprintf #endif + typedef struct curl_slist CurlHeaders; + typedef struct { int method; char* url; @@ -43,7 +101,9 @@ typedef struct { jsint last_status; } HTTPData; -char* METHODS[] = {"GET", "HEAD", "POST", "PUT", "DELETE", "COPY", NULL}; + +const char* METHODS[] = {"GET", "HEAD", "POST", "PUT", "DELETE", "COPY", NULL}; + #define GET 0 #define HEAD 1 @@ -52,14 +112,17 @@ char* METHODS[] = {"GET", "HEAD", "POST", "PUT", "DELETE", "COPY", NULL}; #define DELETE 4 #define COPY 5 + static JSBool go(JSContext* cx, JSObject* obj, HTTPData* http, char* body, size_t blen); + static JSString* str_from_binary(JSContext* cx, char* data, size_t length); -static JSBool -constructor(JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval) + +JSBool +http_ctor(JSContext* cx, JSObject* req) { HTTPData* http = NULL; JSBool ret = JS_FALSE; @@ -76,7 +139,7 @@ constructor(JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval) http->req_headers = NULL; http->last_status = -1; - if(!JS_SetPrivate(cx, obj, http)) + if(!JS_SetPrivate(cx, req, http)) { JS_ReportError(cx, "Failed to set private CouchHTTP data."); goto error; @@ -92,90 +155,76 @@ success: return ret; } -static void -destructor(JSContext* cx, JSObject* obj) + +void +http_dtor(JSContext* cx, JSObject* obj) { HTTPData* http = (HTTPData*) JS_GetPrivate(cx, obj); - if(!http) - { - fprintf(stderr, "Unable to destroy invalid CouchHTTP instance.\n"); - } - else - { + if(http) { if(http->url) free(http->url); if(http->req_headers) curl_slist_free_all(http->req_headers); free(http); } } -static JSBool -open(JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval) + +JSBool +http_open(JSContext* cx, JSObject* req, jsval mth, jsval url, jsval snc) { - HTTPData* http = (HTTPData*) JS_GetPrivate(cx, obj); + HTTPData* http = (HTTPData*) JS_GetPrivate(cx, req); char* method = NULL; - char* url = NULL; - JSBool ret = JS_FALSE; int methid; + JSBool ret = JS_FALSE; - if(!http) - { + if(!http) { JS_ReportError(cx, "Invalid CouchHTTP instance."); goto done; } - if(argv[0] == JSVAL_VOID) - { + if(mth == JSVAL_VOID) { JS_ReportError(cx, "You must specify a method."); goto done; } - method = enc_string(cx, argv[0], NULL); - if(!method) - { + method = enc_string(cx, mth, NULL); + if(!method) { JS_ReportError(cx, "Failed to encode method."); goto done; } - for(methid = 0; METHODS[methid] != NULL; methid++) - { + for(methid = 0; METHODS[methid] != NULL; methid++) { if(strcasecmp(METHODS[methid], method) == 0) break; } - if(methid > COPY) - { + if(methid > COPY) { JS_ReportError(cx, "Invalid method specified."); goto done; } http->method = methid; - if(argv[1] == JSVAL_VOID) - { + if(url == JSVAL_VOID) { JS_ReportError(cx, "You must specify a URL."); goto done; } - if(http->url) - { + if(http->url != NULL) { free(http->url); http->url = NULL; } - http->url = enc_string(cx, argv[1], NULL); - if(!http->url) - { + http->url = enc_string(cx, url, NULL); + if(http->url == NULL) { JS_ReportError(cx, "Failed to encode URL."); goto done; } - if(argv[2] != JSVAL_VOID && argv[2] != JSVAL_FALSE) - { - JS_ReportError(cx, "Synchronous flag must be false if specified."); + if(snc != JSVAL_FALSE) { + JS_ReportError(cx, "Synchronous flag must be false."); goto done; } - if(http->req_headers) - { + if(http->req_headers) { curl_slist_free_all(http->req_headers); http->req_headers = NULL; } @@ -190,42 +239,42 @@ done: return ret; } -static JSBool -setheader(JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval) + +JSBool +http_set_hdr(JSContext* cx, JSObject* req, jsval name, jsval val) { - HTTPData* http = (HTTPData*) JS_GetPrivate(cx, obj); + HTTPData* http = (HTTPData*) JS_GetPrivate(cx, req); char* keystr = NULL; char* valstr = NULL; char* hdrbuf = NULL; size_t hdrlen = -1; JSBool ret = JS_FALSE; - if(!http) - { + if(!http) { JS_ReportError(cx, "Invalid CouchHTTP instance."); goto done; } - if(argv[0] == JSVAL_VOID) + if(name == JSVAL_VOID) { JS_ReportError(cx, "You must speciy a header name."); goto done; } - keystr = enc_string(cx, argv[0], NULL); + keystr = enc_string(cx, name, NULL); if(!keystr) { JS_ReportError(cx, "Failed to encode header name."); goto done; } - if(argv[1] == JSVAL_VOID) + if(val == JSVAL_VOID) { JS_ReportError(cx, "You must specify a header value."); goto done; } - valstr = enc_string(cx, argv[1], NULL); + valstr = enc_string(cx, val, NULL); if(!valstr) { JS_ReportError(cx, "Failed to encode header value."); @@ -234,8 +283,7 @@ setheader(JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval) hdrlen = strlen(keystr) + strlen(valstr) + 3; hdrbuf = (char*) malloc(hdrlen * sizeof(char)); - if(!hdrbuf) - { + if(!hdrbuf) { JS_ReportError(cx, "Failed to allocate header buffer."); goto done; } @@ -249,121 +297,50 @@ done: if(keystr) free(keystr); if(valstr) free(valstr); if(hdrbuf) free(hdrbuf); - return ret; } -static JSBool -sendreq(JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval) +JSBool +http_send(JSContext* cx, JSObject* req, jsval body) { - HTTPData* http = (HTTPData*) JS_GetPrivate(cx, obj); - char* body = NULL; + HTTPData* http = (HTTPData*) JS_GetPrivate(cx, req); + char* bodystr = NULL; size_t bodylen = 0; JSBool ret = JS_FALSE; - if(!http) - { + if(!http) { JS_ReportError(cx, "Invalid CouchHTTP instance."); goto done; } - if(argv[0] != JSVAL_VOID && argv[0] != JS_GetEmptyStringValue(cx)) - { - body = enc_string(cx, argv[0], &bodylen); - if(!body) - { + if(body != JSVAL_VOID && body != JS_GetEmptyStringValue(cx)) { + bodystr = enc_string(cx, body, &bodylen); + if(!bodystr) { JS_ReportError(cx, "Failed to encode body."); goto done; } } - ret = go(cx, obj, http, body, bodylen); + ret = go(cx, req, http, bodystr, bodylen); done: - if(body) free(body); + if(bodystr) free(bodystr); return ret; } -static JSBool -status(JSContext* cx, JSObject* obj, jsval idval, jsval* vp) +int +http_status(JSContext* cx, JSObject* req) { - HTTPData* http = (HTTPData*) JS_GetPrivate(cx, obj); + HTTPData* http = (HTTPData*) JS_GetPrivate(cx, req); - if(!http) - { + if(!http) { JS_ReportError(cx, "Invalid CouchHTTP instance."); return JS_FALSE; } - if(INT_FITS_IN_JSVAL(http->last_status)) - { - *vp = INT_TO_JSVAL(http->last_status); - return JS_TRUE; - } - else - { - JS_ReportError(cx, "INTERNAL: Invalid last_status"); - return JS_FALSE; - } -} - -JSClass CouchHTTPClass = { - "CouchHTTP", - JSCLASS_HAS_PRIVATE - | JSCLASS_CONSTRUCT_PROTOTYPE - | JSCLASS_HAS_RESERVED_SLOTS(2), - JS_PropertyStub, - JS_PropertyStub, - JS_PropertyStub, - JS_PropertyStub, - JS_EnumerateStub, - JS_ResolveStub, - JS_ConvertStub, - destructor, - JSCLASS_NO_OPTIONAL_MEMBERS -}; - -JSPropertySpec CouchHTTPProperties[] = { - {"status", 0, JSPROP_READONLY, status, NULL}, - {0, 0, 0, 0, 0} -}; - -JSFunctionSpec CouchHTTPFunctions[] = { - {"_open", open, 3, 0, 0}, - {"_setRequestHeader", setheader, 2, 0, 0}, - {"_send", sendreq, 1, 0, 0}, - {0, 0, 0, 0, 0} -}; - -JSObject* -install_http(JSContext* cx, JSObject* glbl) -{ - JSObject* klass = NULL; - HTTPData* http = NULL; - - klass = JS_InitClass( - cx, - glbl, - NULL, - &CouchHTTPClass, - constructor, - 0, - CouchHTTPProperties, - CouchHTTPFunctions, - NULL, - NULL - ); - - if(!klass) - { - fprintf(stderr, "Failed to initialize CouchHTTP class.\n"); - return NULL; - } - - return klass; + return http->last_status; } - // Curl Helpers typedef struct { @@ -373,6 +350,7 @@ typedef struct { char* sendbuf; size_t sendlen; size_t sent; + int sent_once; char* recvbuf; size_t recvlen; size_t read; @@ -404,13 +382,13 @@ go(JSContext* cx, JSObject* obj, HTTPData* http, char* body, size_t bodylen) state.sendbuf = body; state.sendlen = bodylen; state.sent = 0; + state.sent_once = 0; state.recvbuf = NULL; state.recvlen = 0; state.read = 0; - if(HTTP_HANDLE == NULL) - { + if(HTTP_HANDLE == NULL) { HTTP_HANDLE = curl_easy_init(); curl_easy_setopt(HTTP_HANDLE, CURLOPT_READFUNCTION, send_body); curl_easy_setopt(HTTP_HANDLE, CURLOPT_SEEKFUNCTION, @@ -425,14 +403,12 @@ go(JSContext* cx, JSObject* obj, HTTPData* http, char* body, size_t bodylen) "CouchHTTP Client - Relax"); } - if(!HTTP_HANDLE) - { + if(!HTTP_HANDLE) { JS_ReportError(cx, "Failed to initialize cURL handle."); goto done; } - if(http->method < 0 || http->method > COPY) - { + if(http->method < 0 || http->method > COPY) { JS_ReportError(cx, "INTERNAL: Unknown method."); goto done; } @@ -442,27 +418,21 @@ go(JSContext* cx, JSObject* obj, HTTPData* http, char* body, size_t bodylen) curl_easy_setopt(HTTP_HANDLE, CURLOPT_FOLLOWLOCATION, 1); curl_easy_setopt(HTTP_HANDLE, CURLOPT_UPLOAD, 0); - if(http->method == HEAD) - { + if(http->method == HEAD) { curl_easy_setopt(HTTP_HANDLE, CURLOPT_NOBODY, 1); curl_easy_setopt(HTTP_HANDLE, CURLOPT_FOLLOWLOCATION, 0); - } - else if(http->method == POST || http->method == PUT) - { + } else if(http->method == POST || http->method == PUT) { curl_easy_setopt(HTTP_HANDLE, CURLOPT_UPLOAD, 1); curl_easy_setopt(HTTP_HANDLE, CURLOPT_FOLLOWLOCATION, 0); } - if(body && bodylen) - { + if(body && bodylen) { curl_easy_setopt(HTTP_HANDLE, CURLOPT_INFILESIZE, bodylen); - } - else - { + } else { curl_easy_setopt(HTTP_HANDLE, CURLOPT_INFILESIZE, 0); } - //curl_easy_setopt(HTTP_HANDLE, CURLOPT_VERBOSE, 1); + // curl_easy_setopt(HTTP_HANDLE, CURLOPT_VERBOSE, 1); curl_easy_setopt(HTTP_HANDLE, CURLOPT_URL, http->url); curl_easy_setopt(HTTP_HANDLE, CURLOPT_HTTPHEADER, http->req_headers); @@ -471,39 +441,32 @@ go(JSContext* cx, JSObject* obj, HTTPData* http, char* body, size_t bodylen) curl_easy_setopt(HTTP_HANDLE, CURLOPT_WRITEHEADER, &state); curl_easy_setopt(HTTP_HANDLE, CURLOPT_WRITEDATA, &state); - if(curl_easy_perform(HTTP_HANDLE) != 0) - { + if(curl_easy_perform(HTTP_HANDLE) != 0) { JS_ReportError(cx, "Failed to execute HTTP request: %s", ERRBUF); goto done; } - if(!state.resp_headers) - { + if(!state.resp_headers) { JS_ReportError(cx, "Failed to recieve HTTP headers."); goto done; } tmp = OBJECT_TO_JSVAL(state.resp_headers); if(!JS_DefineProperty( - cx, - obj, + cx, obj, "_headers", tmp, - NULL, - NULL, + NULL, NULL, JSPROP_READONLY - )) - { + )) { JS_ReportError(cx, "INTERNAL: Failed to set response headers."); goto done; } - if(state.recvbuf) // Is good enough? - { + if(state.recvbuf) { state.recvbuf[state.read] = '\0'; jsbody = dec_string(cx, state.recvbuf, state.read+1); - if(!jsbody) - { + if(!jsbody) { // If we can't decode the body as UTF-8 we forcefully // convert it to a string by just forcing each byte // to a jschar. @@ -516,22 +479,17 @@ go(JSContext* cx, JSObject* obj, HTTPData* http, char* body, size_t bodylen) } } tmp = STRING_TO_JSVAL(jsbody); - } - else - { + } else { tmp = JS_GetEmptyStringValue(cx); } if(!JS_DefineProperty( - cx, - obj, + cx, obj, "responseText", tmp, - NULL, - NULL, + NULL, NULL, JSPROP_READONLY - )) - { + )) { JS_ReportError(cx, "INTERNAL: Failed to set responseText."); goto done; } @@ -549,15 +507,20 @@ send_body(void *ptr, size_t size, size_t nmem, void *data) CurlState* state = (CurlState*) data; size_t length = size * nmem; size_t towrite = state->sendlen - state->sent; - if(towrite == 0) - { + + // Assume this is cURL trying to resend a request that + // failed. + if(towrite == 0 && state->sent_once == 0) { + state->sent_once = 1; return 0; + } else if(towrite == 0) { + state->sent = 0; + state->sent_once = 0; + towrite = state->sendlen; } if(length < towrite) towrite = length; - //fprintf(stderr, "%lu %lu %lu %lu\n", state->bodyused, state->bodyread, length, towrite); - memcpy(ptr, state->sendbuf + state->sent, towrite); state->sent += towrite; @@ -581,15 +544,12 @@ recv_header(void *ptr, size_t size, size_t nmem, void *data) char code[4]; char* header = (char*) ptr; size_t length = size * nmem; - size_t index = 0; JSString* hdr = NULL; jsuint hdrlen; jsval hdrval; - if(length > 7 && strncasecmp(header, "HTTP/1.", 7) == 0) - { - if(length < 12) - { + if(length > 7 && strncasecmp(header, "HTTP/1.", 7) == 0) { + if(length < 12) { return CURLE_WRITE_ERROR; } @@ -598,8 +558,7 @@ recv_header(void *ptr, size_t size, size_t nmem, void *data) state->http->last_status = atoi(code); state->resp_headers = JS_NewArrayObject(state->cx, 0, NULL); - if(!state->resp_headers) - { + if(!state->resp_headers) { return CURLE_WRITE_ERROR; } @@ -607,26 +566,22 @@ recv_header(void *ptr, size_t size, size_t nmem, void *data) } // We get a notice at the \r\n\r\n after headers. - if(length <= 2) - { + if(length <= 2) { return length; } // Append the new header to our array. hdr = dec_string(state->cx, header, length); - if(!hdr) - { + if(!hdr) { return CURLE_WRITE_ERROR; } - if(!JS_GetArrayLength(state->cx, state->resp_headers, &hdrlen)) - { + if(!JS_GetArrayLength(state->cx, state->resp_headers, &hdrlen)) { return CURLE_WRITE_ERROR; } hdrval = STRING_TO_JSVAL(hdr); - if(!JS_SetElement(state->cx, state->resp_headers, hdrlen, &hdrval)) - { + if(!JS_SetElement(state->cx, state->resp_headers, hdrlen, &hdrval)) { return CURLE_WRITE_ERROR; } @@ -640,21 +595,19 @@ recv_body(void *ptr, size_t size, size_t nmem, void *data) size_t length = size * nmem; char* tmp = NULL; - if(!state->recvbuf) - { + if(!state->recvbuf) { state->recvlen = 4096; state->read = 0; - state->recvbuf = JS_malloc(state->cx, state->recvlen); + state->recvbuf = (char*) JS_malloc(state->cx, state->recvlen); } - if(!state->recvbuf) - { + if(!state->recvbuf) { return CURLE_WRITE_ERROR; } // +1 so we can add '\0' back up in the go function. while(length+1 > state->recvlen - state->read) state->recvlen *= 2; - tmp = JS_realloc(state->cx, state->recvbuf, state->recvlen); + tmp = (char*) JS_realloc(state->cx, state->recvbuf, state->recvlen); if(!tmp) return CURLE_WRITE_ERROR; state->recvbuf = tmp; @@ -672,8 +625,7 @@ str_from_binary(JSContext* cx, char* data, size_t length) if(!conv) return NULL; - for(i = 0; i < length; i++) - { + for(i = 0; i < length; i++) { conv[i] = (jschar) data[i]; } @@ -682,3 +634,5 @@ str_from_binary(JSContext* cx, char* data, size_t length) return ret; } + +#endif /* HAVE_CURL */ |