summaryrefslogtreecommitdiff
path: root/couchjs/c_src/http.c
diff options
context:
space:
mode:
Diffstat (limited to 'couchjs/c_src/http.c')
-rw-r--r--couchjs/c_src/http.c382
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 */