1
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
2
// use this file except in compliance with the License. You may obtain a copy of
5
// http://www.apache.org/licenses/LICENSE-2.0
7
// Unless required by applicable law or agreed to in writing, software
8
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
// License for the specific language governing permissions and limitations under
16
#include <curl/curl.h>
21
// Map some of the string function names to things which exist on Windows
22
#define strcasecmp _strcmpi
23
#define strncasecmp _strnicmp
24
#define snprintf _snprintf
27
typedef struct curl_slist CurlHeaders;
32
CurlHeaders* req_headers;
36
char* METHODS[] = {"GET", "HEAD", "POST", "PUT", "DELETE", "COPY", NULL};
46
go(JSContext* cx, JSObject* obj, HTTPData* http, char* body, size_t blen);
49
str_from_binary(JSContext* cx, char* data, size_t length);
52
constructor(JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval)
54
HTTPData* http = NULL;
55
JSBool ret = JS_FALSE;
57
http = (HTTPData*) malloc(sizeof(HTTPData));
60
JS_ReportError(cx, "Failed to create CouchHTTP instance.");
66
http->req_headers = NULL;
67
http->last_status = -1;
69
if(!JS_SetPrivate(cx, obj, http))
71
JS_ReportError(cx, "Failed to set private CouchHTTP data.");
86
destructor(JSContext* cx, JSObject* obj)
88
HTTPData* http = (HTTPData*) JS_GetPrivate(cx, obj);
91
fprintf(stderr, "Unable to destroy invalid CouchHTTP instance.\n");
95
if(http->url) free(http->url);
96
if(http->req_headers) curl_slist_free_all(http->req_headers);
102
open(JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval)
104
HTTPData* http = (HTTPData*) JS_GetPrivate(cx, obj);
107
JSBool ret = JS_FALSE;
112
JS_ReportError(cx, "Invalid CouchHTTP instance.");
116
if(argv[0] == JSVAL_VOID)
118
JS_ReportError(cx, "You must specify a method.");
122
method = enc_string(cx, argv[0], NULL);
125
JS_ReportError(cx, "Failed to encode method.");
129
for(methid = 0; METHODS[methid] != NULL; methid++)
131
if(strcasecmp(METHODS[methid], method) == 0) break;
136
JS_ReportError(cx, "Invalid method specified.");
140
http->method = methid;
142
if(argv[1] == JSVAL_VOID)
144
JS_ReportError(cx, "You must specify a URL.");
154
http->url = enc_string(cx, argv[1], NULL);
157
JS_ReportError(cx, "Failed to encode URL.");
161
if(argv[2] != JSVAL_VOID && argv[2] != JSVAL_FALSE)
163
JS_ReportError(cx, "Synchronous flag must be false if specified.");
167
if(http->req_headers)
169
curl_slist_free_all(http->req_headers);
170
http->req_headers = NULL;
173
// Disable Expect: 100-continue
174
http->req_headers = curl_slist_append(http->req_headers, "Expect:");
179
if(method) free(method);
184
setheader(JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval)
186
HTTPData* http = (HTTPData*) JS_GetPrivate(cx, obj);
191
JSBool ret = JS_FALSE;
195
JS_ReportError(cx, "Invalid CouchHTTP instance.");
199
if(argv[0] == JSVAL_VOID)
201
JS_ReportError(cx, "You must speciy a header name.");
205
keystr = enc_string(cx, argv[0], NULL);
208
JS_ReportError(cx, "Failed to encode header name.");
212
if(argv[1] == JSVAL_VOID)
214
JS_ReportError(cx, "You must specify a header value.");
218
valstr = enc_string(cx, argv[1], NULL);
221
JS_ReportError(cx, "Failed to encode header value.");
225
hdrlen = strlen(keystr) + strlen(valstr) + 3;
226
hdrbuf = (char*) malloc(hdrlen * sizeof(char));
229
JS_ReportError(cx, "Failed to allocate header buffer.");
233
snprintf(hdrbuf, hdrlen, "%s: %s", keystr, valstr);
234
http->req_headers = curl_slist_append(http->req_headers, hdrbuf);
239
if(keystr) free(keystr);
240
if(valstr) free(valstr);
241
if(hdrbuf) free(hdrbuf);
247
sendreq(JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval)
249
HTTPData* http = (HTTPData*) JS_GetPrivate(cx, obj);
252
JSBool ret = JS_FALSE;
256
JS_ReportError(cx, "Invalid CouchHTTP instance.");
260
if(argv[0] != JSVAL_VOID && argv[0] != JS_GetEmptyStringValue(cx))
262
body = enc_string(cx, argv[0], &bodylen);
265
JS_ReportError(cx, "Failed to encode body.");
270
ret = go(cx, obj, http, body, bodylen);
278
status(JSContext* cx, JSObject* obj, jsval idval, jsval* vp)
280
HTTPData* http = (HTTPData*) JS_GetPrivate(cx, obj);
284
JS_ReportError(cx, "Invalid CouchHTTP instance.");
288
if(INT_FITS_IN_JSVAL(http->last_status))
290
*vp = INT_TO_JSVAL(http->last_status);
295
JS_ReportError(cx, "INTERNAL: Invalid last_status");
300
JSClass CouchHTTPClass = {
303
| JSCLASS_CONSTRUCT_PROTOTYPE
304
| JSCLASS_HAS_RESERVED_SLOTS(2),
313
JSCLASS_NO_OPTIONAL_MEMBERS
316
JSPropertySpec CouchHTTPProperties[] = {
317
{"status", 0, JSPROP_READONLY, status, NULL},
321
JSFunctionSpec CouchHTTPFunctions[] = {
322
{"_open", open, 3, 0, 0},
323
{"_setRequestHeader", setheader, 2, 0, 0},
324
{"_send", sendreq, 1, 0, 0},
329
install_http(JSContext* cx, JSObject* glbl)
331
JSObject* klass = NULL;
332
HTTPData* http = NULL;
334
klass = JS_InitClass(
349
fprintf(stderr, "Failed to initialize CouchHTTP class.\n");
362
JSObject* resp_headers;
372
* I really hate doing this but this doesn't have to be
373
* uber awesome, it just has to work.
375
CURL* HTTP_HANDLE = NULL;
376
char ERRBUF[CURL_ERROR_SIZE];
378
static size_t send_body(void *ptr, size_t size, size_t nmem, void *data);
379
static int seek_body(void *ptr, curl_off_t offset, int origin);
380
static size_t recv_body(void *ptr, size_t size, size_t nmem, void *data);
381
static size_t recv_header(void *ptr, size_t size, size_t nmem, void *data);
384
go(JSContext* cx, JSObject* obj, HTTPData* http, char* body, size_t bodylen)
388
JSBool ret = JS_FALSE;
394
state.sendbuf = body;
395
state.sendlen = bodylen;
398
state.recvbuf = NULL;
402
if(HTTP_HANDLE == NULL)
404
HTTP_HANDLE = curl_easy_init();
405
curl_easy_setopt(HTTP_HANDLE, CURLOPT_READFUNCTION, send_body);
406
curl_easy_setopt(HTTP_HANDLE, CURLOPT_SEEKFUNCTION, seek_body);
407
curl_easy_setopt(HTTP_HANDLE, CURLOPT_HEADERFUNCTION, recv_header);
408
curl_easy_setopt(HTTP_HANDLE, CURLOPT_WRITEFUNCTION, recv_body);
409
curl_easy_setopt(HTTP_HANDLE, CURLOPT_NOPROGRESS, 1);
410
curl_easy_setopt(HTTP_HANDLE, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
411
curl_easy_setopt(HTTP_HANDLE, CURLOPT_ERRORBUFFER, ERRBUF);
412
curl_easy_setopt(HTTP_HANDLE, CURLOPT_COOKIEFILE, "");
413
curl_easy_setopt(HTTP_HANDLE, CURLOPT_USERAGENT,
414
"CouchHTTP Client - Relax");
419
JS_ReportError(cx, "Failed to initialize cURL handle.");
423
if(http->method < 0 || http->method > COPY)
425
JS_ReportError(cx, "INTERNAL: Unknown method.");
429
curl_easy_setopt(HTTP_HANDLE, CURLOPT_CUSTOMREQUEST, METHODS[http->method]);
430
curl_easy_setopt(HTTP_HANDLE, CURLOPT_NOBODY, 0);
431
curl_easy_setopt(HTTP_HANDLE, CURLOPT_FOLLOWLOCATION, 1);
432
curl_easy_setopt(HTTP_HANDLE, CURLOPT_UPLOAD, 0);
434
if(http->method == HEAD)
436
curl_easy_setopt(HTTP_HANDLE, CURLOPT_NOBODY, 1);
437
curl_easy_setopt(HTTP_HANDLE, CURLOPT_FOLLOWLOCATION, 0);
439
else if(http->method == POST || http->method == PUT)
441
curl_easy_setopt(HTTP_HANDLE, CURLOPT_UPLOAD, 1);
442
curl_easy_setopt(HTTP_HANDLE, CURLOPT_FOLLOWLOCATION, 0);
447
curl_easy_setopt(HTTP_HANDLE, CURLOPT_INFILESIZE, bodylen);
451
curl_easy_setopt(HTTP_HANDLE, CURLOPT_INFILESIZE, 0);
454
//curl_easy_setopt(HTTP_HANDLE, CURLOPT_VERBOSE, 1);
456
curl_easy_setopt(HTTP_HANDLE, CURLOPT_URL, http->url);
457
curl_easy_setopt(HTTP_HANDLE, CURLOPT_HTTPHEADER, http->req_headers);
458
curl_easy_setopt(HTTP_HANDLE, CURLOPT_READDATA, &state);
459
curl_easy_setopt(HTTP_HANDLE, CURLOPT_SEEKDATA, &state);
460
curl_easy_setopt(HTTP_HANDLE, CURLOPT_WRITEHEADER, &state);
461
curl_easy_setopt(HTTP_HANDLE, CURLOPT_WRITEDATA, &state);
463
if(curl_easy_perform(HTTP_HANDLE) != 0)
465
JS_ReportError(cx, "Failed to execute HTTP request: %s", ERRBUF);
469
if(!state.resp_headers)
471
JS_ReportError(cx, "Failed to recieve HTTP headers.");
475
tmp = OBJECT_TO_JSVAL(state.resp_headers);
476
if(!JS_DefineProperty(
486
JS_ReportError(cx, "INTERNAL: Failed to set response headers.");
490
if(state.recvbuf) // Is good enough?
492
state.recvbuf[state.read] = '\0';
493
jsbody = dec_string(cx, state.recvbuf, state.read+1);
496
// If we can't decode the body as UTF-8 we forcefully
497
// convert it to a string by just forcing each byte
499
jsbody = str_from_binary(cx, state.recvbuf, state.read);
501
if(!JS_IsExceptionPending(cx)) {
502
JS_ReportError(cx, "INTERNAL: Failed to decode body.");
507
tmp = STRING_TO_JSVAL(jsbody);
511
tmp = JS_GetEmptyStringValue(cx);
514
if(!JS_DefineProperty(
524
JS_ReportError(cx, "INTERNAL: Failed to set responseText.");
531
if(state.recvbuf) JS_free(cx, state.recvbuf);
536
send_body(void *ptr, size_t size, size_t nmem, void *data)
538
CurlState* state = (CurlState*) data;
539
size_t length = size * nmem;
540
size_t towrite = state->sendlen - state->sent;
546
if(length < towrite) towrite = length;
548
//fprintf(stderr, "%lu %lu %lu %lu\n", state->bodyused, state->bodyread, length, towrite);
550
memcpy(ptr, state->sendbuf + state->sent, towrite);
551
state->sent += towrite;
557
seek_body(void* ptr, curl_off_t offset, int origin)
559
CurlState* state = (CurlState*) ptr;
560
if(origin != SEEK_SET) return -1;
562
state->sent = (size_t) offset;
563
return (int) state->sent;
567
recv_header(void *ptr, size_t size, size_t nmem, void *data)
569
CurlState* state = (CurlState*) data;
571
char* header = (char*) ptr;
572
size_t length = size * nmem;
574
JSString* hdr = NULL;
578
if(length > 7 && strncasecmp(header, "HTTP/1.", 7) == 0)
582
return CURLE_WRITE_ERROR;
585
memcpy(code, header+9, 3*sizeof(char));
587
state->http->last_status = atoi(code);
589
state->resp_headers = JS_NewArrayObject(state->cx, 0, NULL);
590
if(!state->resp_headers)
592
return CURLE_WRITE_ERROR;
598
// We get a notice at the \r\n\r\n after headers.
604
// Append the new header to our array.
605
hdr = dec_string(state->cx, header, length);
608
return CURLE_WRITE_ERROR;
611
if(!JS_GetArrayLength(state->cx, state->resp_headers, &hdrlen))
613
return CURLE_WRITE_ERROR;
616
hdrval = STRING_TO_JSVAL(hdr);
617
if(!JS_SetElement(state->cx, state->resp_headers, hdrlen, &hdrval))
619
return CURLE_WRITE_ERROR;
626
recv_body(void *ptr, size_t size, size_t nmem, void *data)
628
CurlState* state = (CurlState*) data;
629
size_t length = size * nmem;
634
state->recvlen = 4096;
636
state->recvbuf = JS_malloc(state->cx, state->recvlen);
641
return CURLE_WRITE_ERROR;
644
// +1 so we can add '\0' back up in the go function.
645
while(length+1 > state->recvlen - state->read) state->recvlen *= 2;
646
tmp = JS_realloc(state->cx, state->recvbuf, state->recvlen);
647
if(!tmp) return CURLE_WRITE_ERROR;
648
state->recvbuf = tmp;
650
memcpy(state->recvbuf + state->read, ptr, length);
651
state->read += length;
656
str_from_binary(JSContext* cx, char* data, size_t length)
658
jschar* conv = (jschar*) JS_malloc(cx, length * sizeof(jschar));
659
JSString* ret = NULL;
662
if(!conv) return NULL;
664
for(i = 0; i < length; i++)
666
conv[i] = (jschar) data[i];
669
ret = JS_NewUCString(cx, conv, length);
670
if(!ret) JS_free(cx, conv);