diff options
author | Paul Joseph Davis <davisp@apache.org> | 2009-11-26 19:31:00 +0000 |
---|---|---|
committer | Paul Joseph Davis <davisp@apache.org> | 2009-11-26 19:31:00 +0000 |
commit | 95ee619df135a4c8b3ecefe65503c6d1cc7c36da (patch) | |
tree | 16fe8cecc8237e55fe4eec53198403a114b702b4 /src/couchdb | |
parent | c452048cae6ff8b275e72a2f4b590ccaeebc35ba (diff) |
Complete refactoring of couch_js.
In particular, the cURL bindings have been rewritten to be more useful
and easily applied in command line scripts.
git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@884672 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src/couchdb')
-rw-r--r-- | src/couchdb/priv/Makefile.am | 32 | ||||
-rw-r--r-- | src/couchdb/priv/couch_js/couch_js.c | 1286 | ||||
-rw-r--r-- | src/couchdb/priv/couch_js/curlhelper.c | 255 | ||||
-rw-r--r-- | src/couchdb/priv/couch_js/curlhelper.h | 49 | ||||
-rw-r--r-- | src/couchdb/priv/couch_js/http.c | 647 | ||||
-rw-r--r-- | src/couchdb/priv/couch_js/http.h | 18 | ||||
-rw-r--r-- | src/couchdb/priv/couch_js/main.c | 324 | ||||
-rw-r--r-- | src/couchdb/priv/couch_js/utf8.c | 286 | ||||
-rw-r--r-- | src/couchdb/priv/couch_js/utf8.h | 19 |
9 files changed, 1325 insertions, 1591 deletions
diff --git a/src/couchdb/priv/Makefile.am b/src/couchdb/priv/Makefile.am index d4c759c1..8c4383dc 100644 --- a/src/couchdb/priv/Makefile.am +++ b/src/couchdb/priv/Makefile.am @@ -15,11 +15,41 @@ couchprivdir = $(couchlibdir)/priv couchprivlibdir = $(couchlibdir)/priv/lib EXTRA_DIST = \ - couchspawnkillable.sh \ + spawnkillable/couchspawnkillable.sh \ stat_descriptions.cfg.in CLEANFILES = stat_descriptions.cfg +ICU_LOCAL_FLAGS = $(ICU_LOCAL_CFLAGS) $(ICU_LOCAL_LDFLAGS) +if WINDOWS +ICU_LOCAL_LIBS=-licuuc -licudt -licuin +else +ICU_LOCAL_LIBS=-licuuc -licudata -licui18n +endif + +couchprivlib_LTLIBRARIES = couch_icu_driver.la +couch_icu_driver_la_SOURCES = icu_driver/couch_icu_driver.c +couch_icu_driver_la_LDFLAGS = -module -avoid-version $(ICU_LOCAL_FLAGS) +couch_icu_driver_la_CFLAGS = $(ICU_LOCAL_FLAGS) +couch_icu_driver_la_LIBADD = $(ICU_LOCAL_LIBS) + +if WINDOWS +couch_icu_driver_la_LDFLAGS += -no-undefined +endif + +COUCHJS_SRCS = \ + couch_js/http.c \ + couch_js/http.h \ + couch_js/main.c \ + couch_js/utf8.c \ + couch_js/utf8.h + +locallibbin_PROGRAMS = couchjs +couchjs_SOURCES = $(COUCHJS_SRCS) +couchjs_LDFLAGS = $(CURL_LDFLAGS) +couchjs_CFLAGS = $(CURL_CFLAGS) +couchjs_LDADD = $(CURL_LDFLAGS) @JSLIB@ + couchpriv_DATA = stat_descriptions.cfg couchpriv_PROGRAMS = couchspawnkillable diff --git a/src/couchdb/priv/couch_js/couch_js.c b/src/couchdb/priv/couch_js/couch_js.c deleted file mode 100644 index 0acc5b55..00000000 --- a/src/couchdb/priv/couch_js/couch_js.c +++ /dev/null @@ -1,1286 +0,0 @@ -/* - -Licensed under the Apache License, Version 2.0 (the "License"); you may not use -this file except in compliance with the License. You may obtain a copy of the -License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software distributed -under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -CONDITIONS OF ANY KIND, either express or implied. See the License for the -specific language governing permissions and limitations under the License. - -*/ - -#include <stdlib.h> -#include <stdio.h> -#include <string.h> -#include "curlhelper.h" -#include <jsapi.h> -#include <curl/curl.h> -#include "config.h" - -#ifndef CURLOPT_COPYPOSTFIELDS - #define CURLOPT_COPYPOSTFIELDS 10165 -#endif - -int gExitCode = 0; -int gStackChunkSize = 8L * 1024L; - -int -EncodeChar(uint8 *utf8Buffer, uint32 ucs4Char) { - int utf8Length = 1; - - if (ucs4Char < 0x80) { - *utf8Buffer = (uint8)ucs4Char; - } else { - int i; - uint32 a = ucs4Char >> 11; - utf8Length = 2; - while (a) { - a >>= 5; - utf8Length++; - } - i = utf8Length; - while (--i) { - utf8Buffer[i] = (uint8)((ucs4Char & 0x3F) | 0x80); - ucs4Char >>= 6; - } - *utf8Buffer = (uint8)(0x100 - (1 << (8-utf8Length)) + ucs4Char); - } - return utf8Length; -} - -JSBool -EncodeString(const jschar *src, size_t srclen, char *dst, size_t *dstlenp) { - size_t i, utf8Len, dstlen = *dstlenp, origDstlen = dstlen; - jschar c, c2; - uint32 v; - uint8 utf8buf[6]; - - if (!dst) - dstlen = origDstlen = (size_t) -1; - - while (srclen) { - c = *src++; - srclen--; - if ((c >= 0xDC00) && (c <= 0xDFFF)) - goto badSurrogate; - if (c < 0xD800 || c > 0xDBFF) { - v = c; - } else { - if (srclen < 1) - goto bufferTooSmall; - c2 = *src++; - srclen--; - if ((c2 < 0xDC00) || (c2 > 0xDFFF)) { - c = c2; - goto badSurrogate; - } - v = ((c - 0xD800) << 10) + (c2 - 0xDC00) + 0x10000; - } - if (v < 0x0080) { - /* no encoding necessary - performance hack */ - if (!dstlen) - goto bufferTooSmall; - if (dst) - *dst++ = (char) v; - utf8Len = 1; - } else { - utf8Len = EncodeChar(utf8buf, v); - if (utf8Len > dstlen) - goto bufferTooSmall; - if (dst) { - for (i = 0; i < utf8Len; i++) - *dst++ = (char) utf8buf[i]; - } - } - dstlen -= utf8Len; - } - *dstlenp = (origDstlen - dstlen); - return JS_TRUE; - -badSurrogate: - *dstlenp = (origDstlen - dstlen); - return JS_FALSE; - -bufferTooSmall: - *dstlenp = (origDstlen - dstlen); - return JS_FALSE; -} - -static uint32 -DecodeChar(const uint8 *utf8Buffer, int utf8Length) { - uint32 ucs4Char; - uint32 minucs4Char; - /* from Unicode 3.1, non-shortest form is illegal */ - static const uint32 minucs4Table[] = { - 0x00000080, 0x00000800, 0x0001000, 0x0020000, 0x0400000 - }; - - if (utf8Length == 1) { - ucs4Char = *utf8Buffer; - } else { - ucs4Char = *utf8Buffer++ & ((1<<(7-utf8Length))-1); - minucs4Char = minucs4Table[utf8Length-2]; - while (--utf8Length) { - ucs4Char = ucs4Char<<6 | (*utf8Buffer++ & 0x3F); - } - if (ucs4Char < minucs4Char || - ucs4Char == 0xFFFE || ucs4Char == 0xFFFF) { - ucs4Char = 0xFFFD; - } - } - return ucs4Char; -} - -JSBool -DecodeString(const char *src, size_t srclen, jschar *dst, size_t *dstlenp) { - uint32 v; - size_t offset = 0, j, n, dstlen = *dstlenp, origDstlen = dstlen; - - if (!dst) - dstlen = origDstlen = (size_t) -1; - - while (srclen) { - v = (uint8) *src; - n = 1; - if (v & 0x80) { - while (v & (0x80 >> n)) - n++; - if (n > srclen) - goto bufferTooSmall; - if (n == 1 || n > 6) - goto badCharacter; - for (j = 1; j < n; j++) { - if ((src[j] & 0xC0) != 0x80) - goto badCharacter; - } - v = DecodeChar((const uint8 *) src, n); - if (v >= 0x10000) { - v -= 0x10000; - if (v > 0xFFFFF || dstlen < 2) { - *dstlenp = (origDstlen - dstlen); - return JS_FALSE; - } - if (dstlen < 2) - goto bufferTooSmall; - if (dst) { - *dst++ = (jschar)((v >> 10) + 0xD800); - v = (jschar)((v & 0x3FF) + 0xDC00); - } - dstlen--; - } - } - if (!dstlen) - goto bufferTooSmall; - if (dst) - *dst++ = (jschar) v; - dstlen--; - offset += n; - src += n; - srclen -= n; - } - *dstlenp = (origDstlen - dstlen); - return JS_TRUE; - -badCharacter: - *dstlenp = (origDstlen - dstlen); - return JS_FALSE; - -bufferTooSmall: - *dstlenp = (origDstlen - dstlen); - return JS_FALSE; -} - -static JSBool -EvalInContext(JSContext *context, JSObject *obj, uintN argc, jsval *argv, - jsval *rval) { - JSString *str; - JSObject *sandbox; - JSContext *sub_context; - const jschar *src; - size_t srclen; - JSBool ok; - jsval v; - - sandbox = NULL; - if (!JS_ConvertArguments(context, argc, argv, "S / o", &str, &sandbox)) - return JS_FALSE; - - sub_context = JS_NewContext(JS_GetRuntime(context), gStackChunkSize); - if (!sub_context) { - JS_ReportOutOfMemory(context); - return JS_FALSE; - } - -#ifdef USE_JS_SETOPCB - JS_SetContextThread(sub_context); - JS_BeginRequest(sub_context); -#endif - - src = JS_GetStringChars(str); - srclen = JS_GetStringLength(str); - - if (!sandbox) { - sandbox = JS_NewObject(sub_context, NULL, NULL, NULL); - if (!sandbox || !JS_InitStandardClasses(sub_context, sandbox)) { - ok = JS_FALSE; - goto out; - } - } - - if (srclen == 0) { - *rval = OBJECT_TO_JSVAL(sandbox); - ok = JS_TRUE; - } else { - ok = JS_EvaluateUCScript(sub_context, sandbox, src, srclen, NULL, 0, - rval); - ok = JS_TRUE; - } - -out: -#ifdef USE_JS_SETOPCB - JS_EndRequest(sub_context); - JS_ClearContextThread(sub_context); -#endif - - JS_DestroyContext(sub_context); - return ok; -} - -static JSBool -GC(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { - JS_GC(context); - return JS_TRUE; -} - -static JSBool -Print(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { - uintN i; - size_t cl, bl; - JSString *str; - jschar *chars; - char *bytes; - - for (i = 0; i < argc; i++) { - str = JS_ValueToString(context, argv[i]); - if (!str) - return JS_FALSE; - chars = JS_GetStringChars(str); - cl = JS_GetStringLength(str); - if (!EncodeString(chars, cl, NULL, &bl)) - return JS_FALSE; - bytes = JS_malloc(context, bl + 1); - bytes[bl] = '\0'; - if (!EncodeString(chars, cl, bytes, &bl)) { - JS_free(context, bytes); - return JS_FALSE; - } - fprintf(stdout, "%s%s", i ? " " : "", bytes); - JS_free(context, bytes); - } - - fputc('\n', stdout); - fflush(stdout); - return JS_TRUE; -} - -static JSBool -Quit(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { - JS_ConvertArguments(context, argc, argv, "/ i", &gExitCode); - return JS_FALSE; -} - -static JSBool -ReadLine(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { - char *bytes, *tmp; - jschar *chars; - size_t bufsize, byteslen, charslen, readlen; - JSString *str; - - JS_MaybeGC(context); - - byteslen = 0; - bufsize = 256; - bytes = JS_malloc(context, bufsize); - if (!bytes) - return JS_FALSE; - - while ((readlen = js_fgets(bytes + byteslen, bufsize - byteslen, stdin)) > 0) { - byteslen += readlen; - - /* Are we done? */ - if (bytes[byteslen - 1] == '\n') { - bytes[byteslen - 1] = '\0'; - break; - } - - /* Else, grow our buffer for another pass */ - tmp = JS_realloc(context, bytes, bufsize * 2); - if (!tmp) { - JS_free(context, bytes); - return JS_FALSE; - } - - bufsize *= 2; - bytes = tmp; - } - - /* Treat the empty string specially */ - if (byteslen == 0) { - *rval = JS_GetEmptyStringValue(context); - JS_free(context, bytes); - return JS_TRUE; - } - - /* Shrink the buffer to the real size */ - tmp = JS_realloc(context, bytes, byteslen); - if (!tmp) { - JS_free(context, bytes); - return JS_FALSE; - } - bytes = tmp; - - /* Decode the string from UTF-8 */ - if (!DecodeString(bytes, byteslen, NULL, &charslen)) { - JS_free(context, bytes); - return JS_FALSE; - } - chars = JS_malloc(context, (charslen + 1) * sizeof(jschar)); - if (!DecodeString(bytes, byteslen, chars, &charslen)) { - JS_free(context, bytes); - JS_free(context, chars); - return JS_FALSE; - } - JS_free(context, bytes); - chars[charslen] = '\0'; - - /* Initialize a JSString object */ - str = JS_NewUCString(context, chars, charslen - 1); - if (!str) { - JS_free(context, chars); - return JS_FALSE; - } - - *rval = STRING_TO_JSVAL(str); - return JS_TRUE; -} - -static JSBool -Seal(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { - JSObject *target; - JSBool deep = JS_FALSE; - - if (!JS_ConvertArguments(context, argc, argv, "o/b", &target, &deep)) - return JS_FALSE; - if (!target) - return JS_TRUE; - return JS_SealObject(context, target, deep); -} - -static void -ExecuteScript(JSContext *context, JSObject *obj, const char *filename) { - FILE *file; - JSScript *script; - jsval result; - - if (!filename || strcmp(filename, "-") == 0) { - file = stdin; - } else { - file = fopen(filename, "r"); - if (!file) { - fprintf(stderr, "could not open script file %s\n", filename); - gExitCode = 1; - return; - } - } - - script = JS_CompileFileHandle(context, obj, filename, file); - if (script) { - JS_ExecuteScript(context, obj, script, &result); - JS_DestroyScript(context, script); - } -} - -static uint32 gBranchCount = 0; - -#ifdef USE_JS_SETOPCB -static JSBool -OperationCallback(JSContext *context) -{ - if ((++gBranchCount & 0x3fff) == 1) { - JS_MaybeGC(context); - } - return JS_TRUE; -} -#else -static JSBool -BranchCallback(JSContext *context, JSScript *script) { - if ((++gBranchCount & 0x3fff) == 1) { - JS_MaybeGC(context); - } - return JS_TRUE; -} -#endif - -static void -PrintError(JSContext *context, const char *message, JSErrorReport *report) { - if (!report || !JSREPORT_IS_WARNING(report->flags)) - fprintf(stderr, "%s\n", message); -} - -JSBool ThrowError(JSContext *cx, const char *message) -{ - void *mark; - jsval *args; - jsval exc; - - printf("%s\n",message); - - args = JS_PushArguments(cx, &mark, "s", message); - if (args) { - if (JS_CallFunctionName(cx, JS_GetGlobalObject(cx), - "Error", 1, args, &exc)) - JS_SetPendingException(cx, exc); - JS_PopArguments(cx, mark); - } - - return JS_FALSE; -} - -typedef struct buffer_counter { - Buffer buffer; - int pos; -}* BufferCount; - -size_t curl_read(void *ptr, size_t size, size_t nmemb, void *stream) { - int readlength, spaceleft, i; - char* databuffer = (char*)ptr; - Buffer b = ((BufferCount)stream)->buffer; - int* pos = &(((BufferCount)stream)->pos); - - if( size == 0 || nmemb == 0) { - return 0; - } - - if((b->count - *pos) == 0) { - return 0; - } - - readlength = size*nmemb; - spaceleft = b->count - *pos; - - if(readlength < spaceleft) { - copy_Buffer(b,databuffer,*pos,readlength); - *(pos) += readlength; - return readlength; - } else { - copy_Buffer(b,databuffer,*pos,spaceleft); - *(pos) += spaceleft; - return spaceleft; - } -} - -size_t curl_write(void *ptr, size_t size, size_t nmemb, void *stream) { - char *data, *tmp; - Buffer b; - if( size == 0 || nmemb == 0 ) - return 0; - - data = (char *)ptr; - b = (Buffer)stream; - - append_Buffer(b,data,size*nmemb); - - return size*nmemb; -} - -// This uses MALLOC dont forget to free -char* JSValToChar(JSContext* context, jsval* arg) { - char *c, *tmp; - JSString *jsmsg; - size_t len; - int i; - if(!JSVAL_IS_STRING(*arg)) { - return NULL; - } - - jsmsg = JS_ValueToString(context,*arg); - len = JS_GetStringLength(jsmsg); - tmp = JS_GetStringBytes(jsmsg); - - c = (char*)malloc(len+1); - c[len] = '\0'; - - for(i = 0;i < len;i++) { - c[i] = tmp[i]; - } - - return c; -} - -JSBool BufferToJSVal(JSContext *context, Buffer b, jsval *rval) { - char* c; - JSString *str; - - // Important for char* to be JS_malloced, otherwise js wont let you use it in the NewString method - c = JS_malloc(context, b->count * sizeof(char)); - copy_Buffer(b,c,0,b->count); - - - /* Initialize a JSString object */ - str = JS_NewString(context, c, b->count); - - if (!str) { - JS_free(context, c); - return JS_FALSE; - } - - // Set Return Value - *rval = STRING_TO_JSVAL(str); - if(rval == NULL) { - return JS_FALSE; - } - return JS_TRUE; -} - -struct curl_slist* generateCurlHeaders(JSContext* context,jsval* arg) { - // If arg is an object then we go the header-hash route else return NULL - - if(!JSVAL_IS_NULL(*arg)) { - - struct curl_slist *slist = NULL; - JSObject* header_obj; - JSObject* iterator; - jsval *jsProperty; - jsval *jsValue; - jsid *jsId; - Buffer bTmp; - char* jsPropertyName, *jsPropertyValue; - - // If we fail to convert arg2 to an object. Error! - if(!JS_ValueToObject(context,*arg,&header_obj)) { - return NULL; - } - - iterator = JS_NewPropertyIterator(context,header_obj); - - jsProperty = JS_malloc(context,sizeof(jsval)); - jsValue = JS_malloc(context,sizeof(jsval)); - jsId = JS_malloc(context,sizeof(jsid)); - - while(JS_NextProperty(context,iterator,jsId) == JS_TRUE) { - - if(*jsId == JSVAL_VOID) { - break; - } - - // TODO: Refactor this maybe make a JSValAppendBuffer method b/c that is what you really want to do. - - bTmp = init_Buffer(); - JS_IdToValue(context,*jsId,jsProperty); - jsPropertyName = JSValToChar(context,jsProperty); - - // TODO: Remove strlen =/ - append_Buffer(bTmp,jsPropertyName,strlen(jsPropertyName)); - append_Buffer(bTmp,": ",2); - - JS_GetProperty(context,header_obj,jsPropertyName,jsValue); - jsPropertyValue = JSValToChar(context,jsValue); - // TODO: Remove strlen =/ - append_Buffer(bTmp,jsPropertyValue,strlen(jsPropertyValue)); - append_Buffer(bTmp,"",1); - - slist = curl_slist_append(slist,bTmp->data); - - free_Buffer(bTmp); - free(jsPropertyValue); - free(jsPropertyName); - } - - JS_free(context,jsProperty); - JS_free(context,jsValue); - JS_free(context,jsId); - - return slist; - - } else { - return NULL; - } -} - -static JSBool -GetHttp(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { - CURL* handle; - Buffer b; - char *url; - size_t charslen, readlen; - struct curl_slist *slist; - int exitcode; - - // Run GC - JS_MaybeGC(context); - - // Init Curl - if((handle = curl_easy_init()) == NULL) { - return JS_FALSE; - } - - // Get URL - url = JSValToChar(context,argv); - if( url == NULL ) { - return ThrowError(context,"Unable to convert url (argument 0) to a string"); - } - - b = init_Buffer(); // Allocate buffer that will store the get resultant - - // Configuration - curl_easy_setopt(handle,CURLOPT_WRITEFUNCTION,curl_write); - curl_easy_setopt(handle,CURLOPT_WRITEDATA,b); - curl_easy_setopt(handle,CURLOPT_HEADERFUNCTION,curl_write); - curl_easy_setopt(handle,CURLOPT_WRITEHEADER,b); - curl_easy_setopt(handle,CURLOPT_URL,url); - curl_easy_setopt(handle,CURLOPT_HTTPGET,1); - curl_easy_setopt(handle,CURLOPT_FOLLOWLOCATION,1); - curl_easy_setopt(handle,CURLOPT_NOPROGRESS,1); - curl_easy_setopt(handle,CURLOPT_IPRESOLVE,CURL_IPRESOLVE_V4); - - slist = generateCurlHeaders(context,argv+1); - if(slist != NULL) { - curl_easy_setopt(handle,CURLOPT_HTTPHEADER,slist); - } - - // Perform - if((exitcode = curl_easy_perform(handle)) != 0) { - if(slist != NULL) { - curl_slist_free_all(slist); - } - curl_easy_cleanup(handle); - free(url); - free_Buffer(b); - return JS_FALSE; - } - - free(url); - if(slist != NULL) { - curl_slist_free_all(slist); - } - - /* Treat the empty string specially */ - if (b->count == 0) { - free_Buffer(b); - *rval = JS_GetEmptyStringValue(context); - curl_easy_cleanup(handle); - return JS_TRUE; - } - - /* Shrink the buffer to the real size and store its value in rval */ - shrink_Buffer(b); - BufferToJSVal(context,b,rval); - - // Free Buffer - free_Buffer(b); - - if(rval == NULL) { - curl_easy_cleanup(handle); - return JS_FALSE; - } - - JS_MaybeGC(context); - - curl_easy_cleanup(handle); - - return JS_TRUE; -} - -static JSBool -HeadHttp(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { - CURL* handle; - Buffer b; - char *url; - size_t charslen, readlen; - struct curl_slist *slist; - int exitcode; - - // Run GC - JS_MaybeGC(context); - - // Init Curl - if((handle = curl_easy_init()) == NULL) { - return JS_FALSE; - } - - // Get URL - url = JSValToChar(context,argv); - if( url == NULL ) { - return ThrowError(context,"Unable to convert url (argument 0) to a string"); - } - - b = init_Buffer(); // Allocate buffer that will store the get resultant - - // Configuration - // curl_easy_setopt(handle,CURLOPT_WRITEFUNCTION,curl_write); - // curl_easy_setopt(handle,CURLOPT_WRITEDATA,b); - curl_easy_setopt(handle,CURLOPT_HEADERFUNCTION,curl_write); - curl_easy_setopt(handle,CURLOPT_WRITEHEADER,b); - curl_easy_setopt(handle,CURLOPT_URL,url); - curl_easy_setopt(handle,CURLOPT_HTTPGET,0); - curl_easy_setopt(handle,CURLOPT_NOBODY,1); - curl_easy_setopt(handle,CURLOPT_NOPROGRESS,1); - curl_easy_setopt(handle,CURLOPT_IPRESOLVE,CURL_IPRESOLVE_V4); - - slist = generateCurlHeaders(context,argv+1); - if(slist != NULL) { - curl_easy_setopt(handle,CURLOPT_HTTPHEADER,slist); - } - - // fprintf(stderr, "about to run HEAD request\n"); - - // Perform - if((exitcode = curl_easy_perform(handle)) != 0) { - if(slist != NULL) { - curl_slist_free_all(slist); - } - curl_easy_cleanup(handle); - free(url); - free_Buffer(b); - return JS_FALSE; - } - // fprintf(stderr, "ran ok HEAD request\n"); - - free(url); - if(slist != NULL) { - curl_slist_free_all(slist); - } - - /* Treat the empty string specially */ - if (b->count == 0) { - free_Buffer(b); - *rval = JS_GetEmptyStringValue(context); - curl_easy_cleanup(handle); - return JS_TRUE; - } - - /* Shrink the buffer to the real size and store its value in rval */ - shrink_Buffer(b); - BufferToJSVal(context,b,rval); - - // Free Buffer - free_Buffer(b); - - if(rval == NULL) { - curl_easy_cleanup(handle); - return JS_FALSE; - } - - JS_MaybeGC(context); - - curl_easy_cleanup(handle); - - return JS_TRUE; -} - - -static JSBool -PostHttp(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { - CURL* handle; - Buffer b; - char *url, *body; - size_t charslen, readlen; - struct curl_slist *slist; - int exitcode; - - // Run GC - JS_MaybeGC(context); - - // Init Curl - if((handle = curl_easy_init()) == NULL) { - return JS_FALSE; - } - - // Get URL - if((url = JSValToChar(context,argv)) == NULL) { - curl_easy_cleanup(handle); - return JS_FALSE; - } - - // Initialize buffer - b = init_Buffer(); - - curl_easy_setopt(handle,CURLOPT_WRITEFUNCTION,curl_write); // function that recieves data - curl_easy_setopt(handle,CURLOPT_WRITEDATA,b); // buffer to write the data to - curl_easy_setopt(handle,CURLOPT_HEADERFUNCTION,curl_write); - curl_easy_setopt(handle,CURLOPT_WRITEHEADER,b); - curl_easy_setopt(handle,CURLOPT_URL,url); // url - curl_easy_setopt(handle,CURLOPT_POST,1); // Set Op. to post - curl_easy_setopt(handle,CURLOPT_NOPROGRESS,1); // No Progress Meter - curl_easy_setopt(handle,CURLOPT_IPRESOLVE,CURL_IPRESOLVE_V4); // only ipv4 - - if((body = JSValToChar(context,argv+1)) == NULL) { // Convert arg1 to a string - free(url); - free_Buffer(b); - curl_easy_cleanup(handle); - return JS_FALSE; - } - - curl_easy_setopt(handle,CURLOPT_POSTFIELDSIZE,strlen(body)); - curl_easy_setopt(handle,CURLOPT_POSTFIELDS,body); // Curl wants '\0' terminated, we oblige - - slist = generateCurlHeaders(context,argv+2); // Initialize Headers - if(slist != NULL) { - curl_easy_setopt(handle,CURLOPT_HTTPHEADER,slist); - } - - if((exitcode = curl_easy_perform(handle)) != 0) { // Perform - curl_slist_free_all(slist); - free(body); - free(url); - free_Buffer(b); - curl_easy_cleanup(handle); - return JS_FALSE; - } - - free(body); - free(url); - curl_slist_free_all(slist); - - // Convert response back to javascript value and then clean - BufferToJSVal(context,b,rval); - free_Buffer(b); - curl_easy_cleanup(handle); - - JS_MaybeGC(context); - - if( rval == NULL ) { - return JS_FALSE; - } - - return JS_TRUE; -} - -#define CLEAN \ - free_Buffer(b); \ - free_Buffer(b_data->buffer); \ - free(b_data); \ - free(url) - -static JSBool -PutHttp(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ - - Buffer b; - BufferCount b_data; - char *url, *data; - size_t charslen, readlen; - JSObject* header_obj; - CURL* handle; - struct curl_slist *slist; - int exitcode; - - // Run GC - JS_MaybeGC(context); - - // Get URL - url = JSValToChar(context,argv); - - // Allocate buffer that will store the get resultant - b = init_Buffer(); - - // Allocate data buffer and move data into them - b_data = (BufferCount)malloc(sizeof(Buffer) + sizeof(int)); - b_data->buffer = init_Buffer(); - b_data->pos = 0; - - data = JSValToChar(context,(argv+1)); - readlen = strlen(data); - - - - // TODO: remove strlen - append_Buffer(b_data->buffer,data,readlen); - - free(data); - - // Init Curl - - if((handle = curl_easy_init()) == NULL) { - CLEAN; - return JS_FALSE; - } - - // Configuration - curl_easy_setopt(handle,CURLOPT_WRITEFUNCTION,curl_write); - curl_easy_setopt(handle,CURLOPT_WRITEDATA,b); - curl_easy_setopt(handle,CURLOPT_HEADERFUNCTION,curl_write); - curl_easy_setopt(handle,CURLOPT_WRITEHEADER,b); - curl_easy_setopt(handle,CURLOPT_READFUNCTION,curl_read); - curl_easy_setopt(handle,CURLOPT_READDATA,b_data); - curl_easy_setopt(handle,CURLOPT_URL,url); - curl_easy_setopt(handle,CURLOPT_UPLOAD,1); - curl_easy_setopt(handle,CURLOPT_INFILESIZE,readlen); - - - - // Curl structure - slist = generateCurlHeaders(context,argv+2); - if(slist != NULL) { - curl_easy_setopt(handle,CURLOPT_HTTPHEADER,slist); - } - - // Little Things - // No progress meter - curl_easy_setopt(handle,CURLOPT_NOPROGRESS,1); - // Use only ipv4 - curl_easy_setopt(handle,CURLOPT_IPRESOLVE,CURL_IPRESOLVE_V4); - - - - // Perform - if((exitcode = curl_easy_perform(handle)) != 0) { - if(slist != NULL) - curl_slist_free_all(slist); - curl_easy_cleanup(handle); - CLEAN; - return JS_FALSE; - } - - if(slist != NULL) - curl_slist_free_all(slist); - free_Buffer(b_data->buffer); - free(b_data); - free(url); - - /* Treat the empty string specially */ - if (b->count == 0) { - *rval = JS_GetEmptyStringValue(context); - curl_easy_cleanup(handle); - free_Buffer(b); - return JS_TRUE; - } - - /* Shrink the buffer to the real size */ - shrink_Buffer(b); - - BufferToJSVal(context,b,rval); - - free_Buffer(b); - - if(rval == NULL) { - curl_easy_cleanup(handle); - return JS_FALSE; - } - - JS_MaybeGC(context); - - curl_easy_cleanup(handle); - - return JS_TRUE; -} - -static JSBool -DelHttp(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { - Buffer b; - char *url; - size_t charslen, readlen; - char header_name[7]; - CURL* handle; - int exitcode; - struct curl_slist *slist = NULL; - - strcpy(header_name,"DELETE"); - - // Run GC - JS_MaybeGC(context); - - // Get URL - url = JSValToChar(context,argv); - - // Allocate buffer that will store the del resultant - b = init_Buffer(); - - // Init Curl - if((handle = curl_easy_init()) == NULL) { - free_Buffer(b); - return JS_FALSE; - } - - // Configuration - curl_easy_setopt(handle,CURLOPT_WRITEFUNCTION,curl_write); - curl_easy_setopt(handle,CURLOPT_WRITEDATA,b); - curl_easy_setopt(handle,CURLOPT_HEADERFUNCTION,curl_write); - curl_easy_setopt(handle,CURLOPT_WRITEHEADER,b); - curl_easy_setopt(handle,CURLOPT_URL,url); - curl_easy_setopt(handle,CURLOPT_CUSTOMREQUEST,header_name); - curl_easy_setopt(handle,CURLOPT_NOPROGRESS,1); - curl_easy_setopt(handle,CURLOPT_IPRESOLVE,CURL_IPRESOLVE_V4); - - // Curl structure - if((slist = generateCurlHeaders(context,argv+1)) != NULL) { - curl_easy_setopt(handle,CURLOPT_HTTPHEADER,slist); - } - - // Perform - if((exitcode = curl_easy_perform(handle)) != 0) { - if(slist != NULL) - curl_slist_free_all(slist); - curl_easy_cleanup(handle); - free(url); - free_Buffer(b); - return JS_FALSE; - } - - if(slist != NULL) - curl_slist_free_all(slist); - free(url); - - /* Treat the empty string specially */ - if (b->count == 0) { - *rval = JS_GetEmptyStringValue(context); - curl_easy_cleanup(handle); - free_Buffer(b); - return JS_TRUE; - } - - /* Shrink the buffer to the real size */ - shrink_Buffer(b); - - BufferToJSVal(context,b,rval); - - if(rval == NULL) { - curl_easy_cleanup(handle); - return JS_FALSE; - } - - JS_MaybeGC(context); - - curl_easy_cleanup(handle); - - return JS_TRUE; -} - -static JSBool -CopyHttp(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { - Buffer b; - char *url; - size_t charslen, readlen; - char header_name[5]; - CURL* handle; - int exitcode; - struct curl_slist *slist = NULL; - - strcpy(header_name,"COPY"); - - // Run GC - JS_MaybeGC(context); - - // Get URL - url = JSValToChar(context,argv); - - // Allocate buffer that will store the del resultant - b = init_Buffer(); - - // Init Curl - if((handle = curl_easy_init()) == NULL) { - free_Buffer(b); - return JS_FALSE; - } - - // Configuration - curl_easy_setopt(handle,CURLOPT_WRITEFUNCTION,curl_write); - curl_easy_setopt(handle,CURLOPT_WRITEDATA,b); - curl_easy_setopt(handle,CURLOPT_HEADERFUNCTION,curl_write); - curl_easy_setopt(handle,CURLOPT_WRITEHEADER,b); - curl_easy_setopt(handle,CURLOPT_URL,url); - curl_easy_setopt(handle,CURLOPT_CUSTOMREQUEST,header_name); - curl_easy_setopt(handle,CURLOPT_NOPROGRESS,1); - curl_easy_setopt(handle,CURLOPT_IPRESOLVE,CURL_IPRESOLVE_V4); - - // Curl structure - if((slist = generateCurlHeaders(context,argv+1)) != NULL) { - curl_easy_setopt(handle,CURLOPT_HTTPHEADER,slist); - } - - // Perform - if((exitcode = curl_easy_perform(handle)) != 0) { - if(slist != NULL) - curl_slist_free_all(slist); - curl_easy_cleanup(handle); - free(url); - free_Buffer(b); - return JS_FALSE; - } - - if(slist != NULL) - curl_slist_free_all(slist); - free(url); - - /* Treat the empty string specially */ - if (b->count == 0) { - *rval = JS_GetEmptyStringValue(context); - curl_easy_cleanup(handle); - free_Buffer(b); - return JS_TRUE; - } - - /* Shrink the buffer to the real size */ - shrink_Buffer(b); - - BufferToJSVal(context,b,rval); - - if(rval == NULL) { - curl_easy_cleanup(handle); - return JS_FALSE; - } - - JS_MaybeGC(context); - - curl_easy_cleanup(handle); - - return JS_TRUE; -} - -static JSBool -MoveHttp(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { - Buffer b; - char *url; - size_t charslen, readlen; - char header_name[5]; - CURL* handle; - struct curl_slist *slist = NULL; - int exitcode; - - strcpy(header_name,"MOVE"); - - // Run GC - JS_MaybeGC(context); - - // Get URL - url = JSValToChar(context,argv); - - // Allocate buffer that will store the del resultant - b = init_Buffer(); - - // Init Curl - if((handle = curl_easy_init()) == NULL) { - free_Buffer(b); - return JS_FALSE; - } - - // Configuration - curl_easy_setopt(handle,CURLOPT_WRITEFUNCTION,curl_write); - curl_easy_setopt(handle,CURLOPT_WRITEDATA,b); - curl_easy_setopt(handle,CURLOPT_HEADERFUNCTION,curl_write); - curl_easy_setopt(handle,CURLOPT_WRITEHEADER,b); - curl_easy_setopt(handle,CURLOPT_URL,url); - curl_easy_setopt(handle,CURLOPT_CUSTOMREQUEST,header_name); - curl_easy_setopt(handle,CURLOPT_NOPROGRESS,1); - curl_easy_setopt(handle,CURLOPT_IPRESOLVE,CURL_IPRESOLVE_V4); - - // Curl structure - if((slist = generateCurlHeaders(context,argv+1)) != NULL) { - curl_easy_setopt(handle,CURLOPT_HTTPHEADER,slist); - } - - // Perform - if((exitcode = curl_easy_perform(handle)) != 0) { - if(slist != NULL) - curl_slist_free_all(slist); - curl_easy_cleanup(handle); - free(url); - free_Buffer(b); - return JS_FALSE; - } - - if(slist != NULL) - curl_slist_free_all(slist); - free(url); - - /* Treat the empty string specially */ - if (b->count == 0) { - *rval = JS_GetEmptyStringValue(context); - curl_easy_cleanup(handle); - free_Buffer(b); - return JS_TRUE; - } - - /* Shrink the buffer to the real size */ - shrink_Buffer(b); - - BufferToJSVal(context,b,rval); - - if(rval == NULL) { - curl_easy_cleanup(handle); - return JS_FALSE; - } - - JS_MaybeGC(context); - - curl_easy_cleanup(handle); - - return JS_TRUE; -} - -int -main(int argc, const char * argv[]) { - JSRuntime *runtime; - JSContext *context; - JSObject *global; - - runtime = JS_NewRuntime(64L * 1024L * 1024L); - if (!runtime) - return 1; - context = JS_NewContext(runtime, gStackChunkSize); - if (!context) - return 1; - /* FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=477187 */ - JS_SetErrorReporter(context, PrintError); -#ifdef USE_JS_SETOPCB - JS_SetContextThread(context); - JS_BeginRequest(context); - JS_SetOperationCallback(context, OperationCallback); -#else - JS_SetBranchCallback(context, BranchCallback); - JS_ToggleOptions(context, JSOPTION_NATIVE_BRANCH_CALLBACK); -#endif - JS_ToggleOptions(context, JSOPTION_XML); - - global = JS_NewObject(context, NULL, NULL, NULL); - if (!global) - return 1; - if (!JS_InitStandardClasses(context, global)) - return 1; - if (!JS_DefineFunction(context, global, "evalcx", EvalInContext, 0, 0) - || !JS_DefineFunction(context, global, "gc", GC, 0, 0) - || !JS_DefineFunction(context, global, "print", Print, 0, 0) - || !JS_DefineFunction(context, global, "quit", Quit, 0, 0) - || !JS_DefineFunction(context, global, "readline", ReadLine, 0, 0) - || !JS_DefineFunction(context, global, "seal", Seal, 0, 0) - || !JS_DefineFunction(context, global, "gethttp", GetHttp, 1, 0) - || !JS_DefineFunction(context, global, "headhttp", HeadHttp, 1, 0) - || !JS_DefineFunction(context, global, "posthttp", PostHttp, 2, 0) - || !JS_DefineFunction(context, global, "puthttp", PutHttp, 2, 0) - || !JS_DefineFunction(context, global, "delhttp", DelHttp, 1, 0) - || !JS_DefineFunction(context, global, "movehttp", MoveHttp, 1, 0) - || !JS_DefineFunction(context, global, "copyhttp", CopyHttp, 1, 0)) - return 1; - - if (argc != 2) { - fprintf(stderr, "incorrect number of arguments\n\n"); - fprintf(stderr, "usage: %s <scriptfile>\n", argv[0]); - return 2; - } - - ExecuteScript(context, global, argv[1]); - -#ifdef USE_JS_SETOPCB - JS_EndRequest(context); - JS_ClearContextThread(context); -#endif - - JS_DestroyContext(context); - JS_DestroyRuntime(runtime); - JS_ShutDown(); - - return gExitCode; -} diff --git a/src/couchdb/priv/couch_js/curlhelper.c b/src/couchdb/priv/couch_js/curlhelper.c deleted file mode 100644 index 738ac64a..00000000 --- a/src/couchdb/priv/couch_js/curlhelper.c +++ /dev/null @@ -1,255 +0,0 @@ -/* - -Licensed under the Apache License, Version 2.0 (the "License"); you may not use -this file except in compliance with the License. You may obtain a copy of the -License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software distributed -under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -CONDITIONS OF ANY KIND, either express or implied. See the License for the -specific language governing permissions and limitations under the License. - -*/ - -#include <stdlib.h> -#include <stdio.h> -#include "curlhelper.h" - -#define TRUE 1 -#define FALSE 0 - -Buffer init_Buffer() { - Buffer b; - - if((b = (Buffer)malloc(sizeof(char*) + sizeof(int)*2)) == NULL) { - return NULL; - } - - b->count = 0; - b->capacity = 50; - - if(b->data = (char*)malloc(sizeof(char)*b->capacity)) { - return b; - } else { - return NULL; - } -} - -void free_Buffer(Buffer b) { - if(b == NULL) - return; - if(b->data != NULL) - free(b->data); - free(b); -} - -int append_Buffer(Buffer b, char* c, int length) { - int capacity_changed; - int new_count; - int i; - - capacity_changed = FALSE; - new_count = b->count + length; - - if(new_count > b->capacity) { - capacity_changed = TRUE; - b->capacity *= 2; - } - - while(new_count > b->capacity) { - b->capacity *= 2; - } - - if(capacity_changed) { - if((b->data = (char*)realloc(b->data,b->capacity)) == NULL) { - return FALSE; - } - } - - for(i = 0;i < length;i++) { - *(b->data + b->count + i) = *(c + i); - } - - b->count = new_count; - - return TRUE; -} - -char* toString_Buffer(Buffer b) { - char* result; - int i; - - if((result = (char*)malloc(sizeof(char)*(b->count+1))) == NULL) { - return NULL; - } - - result[b->count] = '\0'; - - for(i = 0;i < b->count;i++) { - result[i] = b->data[i]; - } - - return result; -} - -int print_Buffer(Buffer b) { - char* c; - - if((c = toString_Buffer(b)) == NULL) { - return FALSE; - } - - printf("%s\n",c); - - free(c); - - return TRUE; -} - - -int shrink_Buffer(Buffer b) { - b->capacity = b->count; - - if((b->data = realloc(b->data,sizeof(char)*b->capacity)) == NULL) { - return FALSE; - } else { - return TRUE; - } -} - -void copy_Buffer(Buffer b, char* c, int pos, int length) { - int i; - if((pos + length) > b->count) - return; - - for(i = 0; i < length;i++) { - *(c + i) = *(b->data + pos + i); - } -} - - -List init_List(int capacity) { - List l; - if(capacity < 5) - capacity = 5; - - if((l = (List)malloc(sizeof(void**)+sizeof(int)*2)) == NULL) { - return NULL; - } - - l->count = 0; - l->capacity = capacity; - - if((l->elements = (void**)malloc(sizeof(void*)*l->capacity)) == NULL) { - return NULL; - } - - return l; -} - -void free_List(List l) { - if(l == NULL) - return; - if(l->elements != NULL) - free(l->elements); - free(l); -} - -void* get_List(List l, int pos) { - if(pos > (l->count - 1)) { - return NULL; - } - return *(l->elements + pos); -} - -void* pull_List(List l) { - void* r = *(l->elements); - - int i; - - for(i = 1; i < (l->count-1);i++) { - l->elements[i] = l->elements[i+1]; - } - l->count -= 1; - return r; -} - -int set_List(List l, int pos, void* ptr) { - if(pos > (l->count - 1)) { - return FALSE; - } - - *(l->elements + pos) = ptr; - - return TRUE; -} - -int append_List(List l, void* ptr, int length) { - int capacity_changed; - int new_count; - int i; - - capacity_changed = FALSE; - new_count = l->count + length; - - if(new_count > l->capacity) { - capacity_changed = TRUE; - l->capacity *= 2; - } - - while(new_count > l->capacity) { - l->capacity *= 2; - } - - if(capacity_changed) { - if((l->elements = (void*)realloc(l->elements,l->capacity)) == NULL) { - return FALSE; - } - } - - for(i = 0;i < length;i++) { - *(l->elements + l->count + i) = (void *)((char *)ptr + i); - } - - l->count = new_count; - - return TRUE; -} - -int push_List(List l, void* ptr, int length) { - int capacity_changed; - int new_count; - int i; - - capacity_changed = FALSE; - new_count = l->count + length; - - if(new_count > l->capacity) { - capacity_changed = TRUE; - l->capacity *= 2; - } - - while(new_count > l->capacity) { - l->capacity *= 2; - } - - if(capacity_changed) { - if((l->elements = (void*)realloc(l->elements,l->capacity)) == NULL) { - return FALSE; - } - } - - for(i = 0;i < length;i++) { - *(l->elements + l->count + i) = *(l->elements + i); - } - - for(i = 0;i < length;i++) { - *(l->elements + i) = (void *)((char *)ptr+i); - } - - l->count = new_count; - - return TRUE; -} diff --git a/src/couchdb/priv/couch_js/curlhelper.h b/src/couchdb/priv/couch_js/curlhelper.h deleted file mode 100644 index 098bdf02..00000000 --- a/src/couchdb/priv/couch_js/curlhelper.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - -Licensed under the Apache License, Version 2.0 (the "License"); you may not use -this file except in compliance with the License. You may obtain a copy of the -License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software distributed -under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -CONDITIONS OF ANY KIND, either express or implied. See the License for the -specific language governing permissions and limitations under the License. - -*/ - -#ifndef CURLHELPER_H -#define CURLHELPER_H - -typedef struct { - char* data; - int count; - int capacity; -}* Buffer; - -Buffer init_Buffer(); -void free_Buffer(Buffer b); -int append_Buffer(Buffer b,char* c,int length); -// WARNING USES MALLOC DONT FORGET TO FREE -char* toString_Buffer(Buffer b); -int print_Buffer(Buffer b); -int shrink_Buffer(Buffer b); -void copy_Buffer(Buffer b, char* c, int pos, int length); - - -typedef struct { - void** elements; - int count; - int capacity; -}* List; - -List init_List(int capacity); -void free_List(List l); -void* get_List(List l, int pos); -void* pull_List(List l); -int set_List(List l, int pos, void* ptr); -int append_List(List l, void* ptr, int length); -int push_List(List l, void* ptr, int length); - -#endif diff --git a/src/couchdb/priv/couch_js/http.c b/src/couchdb/priv/couch_js/http.c new file mode 100644 index 00000000..6ee337af --- /dev/null +++ b/src/couchdb/priv/couch_js/http.c @@ -0,0 +1,647 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +#include <stdlib.h> +#include <string.h> +#include <jsapi.h> +#include <curl/curl.h> + +#include "utf8.h" + +typedef struct curl_slist CurlHeaders; + +typedef struct { + int method; + char* url; + CurlHeaders* req_headers; + jsint last_status; +} HTTPData; + +char* METHODS[] = {"GET", "HEAD", "POST", "PUT", "DELETE", "COPY", NULL}; + +#define GET 0 +#define HEAD 1 +#define POST 2 +#define PUT 3 +#define DELETE 4 +#define COPY 5 + +static JSBool +go(JSContext* cx, JSObject* obj, HTTPData* http, char* body, size_t blen); + +static JSBool +constructor(JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval) +{ + HTTPData* http = NULL; + JSBool ret = JS_FALSE; + + http = (HTTPData*) malloc(sizeof(HTTPData)); + if(!http) + { + JS_ReportError(cx, "Failed to create CouchHTTP instance."); + goto error; + } + + http->method = -1; + http->url = NULL; + http->req_headers = NULL; + http->last_status = -1; + + if(!JS_SetPrivate(cx, obj, http)) + { + JS_ReportError(cx, "Failed to set private CouchHTTP data."); + goto error; + } + + ret = JS_TRUE; + goto success; + +error: + if(http) free(http); + +success: + return ret; +} + +static void +destructor(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->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) +{ + HTTPData* http = (HTTPData*) JS_GetPrivate(cx, obj); + char* method = NULL; + char* url = NULL; + JSBool ret = JS_FALSE; + int methid; + + if(!http) + { + JS_ReportError(cx, "Invalid CouchHTTP instance."); + goto done; + } + + if(argv[0] == JSVAL_VOID) + { + JS_ReportError(cx, "You must specify a method."); + goto done; + } + + method = enc_string(cx, argv[0], NULL); + if(!method) + { + JS_ReportError(cx, "Failed to encode method."); + goto done; + } + + for(methid = 0; METHODS[methid] != NULL; methid++) + { + if(strcasecmp(METHODS[methid], method) == 0) break; + } + + if(methid > COPY) + { + JS_ReportError(cx, "Invalid method specified."); + goto done; + } + + http->method = methid; + + if(argv[1] == JSVAL_VOID) + { + JS_ReportError(cx, "You must specify a URL."); + goto done; + } + + if(http->url) + { + free(http->url); + http->url = NULL; + } + + http->url = enc_string(cx, argv[1], NULL); + if(!http->url) + { + 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."); + goto done; + } + + if(http->req_headers) + { + curl_slist_free_all(http->req_headers); + http->req_headers = NULL; + } + + // Disable Expect: 100-continue + http->req_headers = curl_slist_append(http->req_headers, "Expect:"); + + ret = JS_TRUE; + +done: + if(method) free(method); + return ret; +} + +static JSBool +setheader(JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval) +{ + HTTPData* http = (HTTPData*) JS_GetPrivate(cx, obj); + char* keystr = NULL; + char* valstr = NULL; + char* hdrbuf = NULL; + size_t hdrlen = -1; + JSBool ret = JS_FALSE; + + if(!http) + { + JS_ReportError(cx, "Invalid CouchHTTP instance."); + goto done; + } + + if(argv[0] == JSVAL_VOID) + { + JS_ReportError(cx, "You must speciy a header name."); + goto done; + } + + keystr = enc_string(cx, argv[0], NULL); + if(!keystr) + { + JS_ReportError(cx, "Failed to encode header name."); + goto done; + } + + if(argv[1] == JSVAL_VOID) + { + JS_ReportError(cx, "You must specify a header value."); + goto done; + } + + valstr = enc_string(cx, argv[1], NULL); + if(!valstr) + { + JS_ReportError(cx, "Failed to encode header value."); + goto done; + } + + hdrlen = strlen(keystr) + strlen(valstr) + 3; + hdrbuf = (char*) malloc(hdrlen * sizeof(char)); + if(!hdrbuf) + { + JS_ReportError(cx, "Failed to allocate header buffer."); + goto done; + } + + snprintf(hdrbuf, hdrlen, "%s: %s", keystr, valstr); + http->req_headers = curl_slist_append(http->req_headers, hdrbuf); + + ret = JS_TRUE; + +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) +{ + HTTPData* http = (HTTPData*) JS_GetPrivate(cx, obj); + char* body = NULL; + size_t bodylen = 0; + JSBool ret = JS_FALSE; + + 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) + { + JS_ReportError(cx, "Failed to encode body."); + goto done; + } + } + + ret = go(cx, obj, http, body, bodylen); + +done: + if(body) free(body); + return ret; +} + +static JSBool +status(JSContext* cx, JSObject* obj, jsval idval, jsval* vp) +{ + HTTPData* http = (HTTPData*) JS_GetPrivate(cx, obj); + + 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; +} + + +// Curl Helpers + +typedef struct { + HTTPData* http; + JSContext* cx; + JSObject* resp_headers; + char* sendbuf; + size_t sendlen; + size_t sent; + char* recvbuf; + size_t recvlen; + size_t read; +} CurlState; + +/* + * I really hate doing this but this doesn't have to be + * uber awesome, it just has to work. + */ +CURL* HANDLE = NULL; +char ERRBUF[CURL_ERROR_SIZE]; + +static size_t send_body(void *ptr, size_t size, size_t nmem, void *data); +static int seek_body(void *ptr, curl_off_t offset, int origin); +static size_t recv_body(void *ptr, size_t size, size_t nmem, void *data); +static size_t recv_header(void *ptr, size_t size, size_t nmem, void *data); + +static JSBool +go(JSContext* cx, JSObject* obj, HTTPData* http, char* body, size_t bodylen) +{ + CurlState state; + JSString* jsbody; + JSBool ret = JS_FALSE; + jsval tmp; + + state.cx = cx; + state.http = http; + + state.sendbuf = body; + state.sendlen = bodylen; + state.sent = 0; + + state.recvbuf = NULL; + state.recvlen = 0; + state.read = 0; + + if(HANDLE == NULL) + { + HANDLE = curl_easy_init(); + curl_easy_setopt(HANDLE, CURLOPT_READFUNCTION, send_body); + curl_easy_setopt(HANDLE, CURLOPT_SEEKFUNCTION, seek_body); + curl_easy_setopt(HANDLE, CURLOPT_HEADERFUNCTION, recv_header); + curl_easy_setopt(HANDLE, CURLOPT_WRITEFUNCTION, recv_body); + curl_easy_setopt(HANDLE, CURLOPT_NOPROGRESS, 1); + curl_easy_setopt(HANDLE, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); + curl_easy_setopt(HANDLE, CURLOPT_ERRORBUFFER, ERRBUF); + curl_easy_setopt(HANDLE, CURLOPT_COOKIEFILE, ""); + curl_easy_setopt(HANDLE, CURLOPT_USERAGENT, "CouchHTTP Client - Relax"); + } + + if(!HANDLE) + { + JS_ReportError(cx, "Failed to initialize cURL handle."); + goto error; + } + + if(http->method < 0 || http->method > COPY) + { + JS_ReportError(cx, "INTERNAL: Unknown method."); + goto error; + } + + curl_easy_setopt(HANDLE, CURLOPT_CUSTOMREQUEST, METHODS[http->method]); + curl_easy_setopt(HANDLE, CURLOPT_NOBODY, 0); + curl_easy_setopt(HANDLE, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(HANDLE, CURLOPT_UPLOAD, 0); + + if(http->method == HEAD) + { + curl_easy_setopt(HANDLE, CURLOPT_NOBODY, 1); + curl_easy_setopt(HANDLE, CURLOPT_FOLLOWLOCATION, 0); + } + else if(http->method == POST || http->method == PUT) + { + curl_easy_setopt(HANDLE, CURLOPT_UPLOAD, 1); + curl_easy_setopt(HANDLE, CURLOPT_FOLLOWLOCATION, 0); + } + + if(body && bodylen) + { + curl_easy_setopt(HANDLE, CURLOPT_INFILESIZE, bodylen); + } + else + { + curl_easy_setopt(HANDLE, CURLOPT_INFILESIZE, 0); + } + + //curl_easy_setopt(HANDLE, CURLOPT_VERBOSE, 1); + + curl_easy_setopt(HANDLE, CURLOPT_URL, http->url); + curl_easy_setopt(HANDLE, CURLOPT_HTTPHEADER, http->req_headers); + curl_easy_setopt(HANDLE, CURLOPT_READDATA, &state); + curl_easy_setopt(HANDLE, CURLOPT_SEEKDATA, &state); + curl_easy_setopt(HANDLE, CURLOPT_WRITEHEADER, &state); + curl_easy_setopt(HANDLE, CURLOPT_WRITEDATA, &state); + + if(curl_easy_perform(HANDLE) != 0) + { + JS_ReportError(cx, "Failed to execute HTTP request: %s", ERRBUF); + goto error; + } + + if(!state.resp_headers) + { + JS_ReportError(cx, "Failed to recieve HTTP headers."); + goto error; + } + + tmp = OBJECT_TO_JSVAL(state.resp_headers); + if(!JS_DefineProperty( + cx, + obj, + "_headers", + tmp, + NULL, + NULL, + JSPROP_READONLY + )) + { + JS_ReportError(cx, "INTERNAL: Failed to set response headers."); + goto error; + } + + if(state.recvbuf) // Is good enough? + { + state.recvbuf[state.read] = '\0'; + jsbody = dec_string(cx, state.recvbuf, state.read+1); + if(!jsbody) + { + // This is so dirty its not even almost funny. I'm ignoring + // all sorts of content-types and character sets and just falling + // back to doing a chop job when something doesn't decode as UTF-8 + // which is pretty sad. But, if you hate me for it, then feel free + // to write a patch that does the proper content-type parsing and + // actually respects charsets as returned in headers. + jsbody = JS_NewString(cx, state.recvbuf, state.read); + if(!jsbody) { + JS_ReportError(cx, "INTERNAL: Failed to decode body."); + goto error; + } + } + tmp = STRING_TO_JSVAL(jsbody); + } + else + { + tmp = JS_GetEmptyStringValue(cx); + } + + if(!JS_DefineProperty( + cx, + obj, + "responseText", + tmp, + NULL, + NULL, + JSPROP_READONLY + )) + { + JS_ReportError(cx, "INTERNAL: Failed to set responseText."); + goto error; + } + + ret = JS_TRUE; + goto success; + +error: + if(state.recvbuf) JS_free(cx, state.recvbuf); + +success: + return ret; +} + +static size_t +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) + { + return 0; + } + + 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; + + return towrite; +} + +static int +seek_body(void* ptr, curl_off_t offset, int origin) +{ + CurlState* state = (CurlState*) ptr; + if(origin != SEEK_SET) return -1; + + state->sent = (size_t) offset; + return (int) state->sent; +} + +static size_t +recv_header(void *ptr, size_t size, size_t nmem, void *data) +{ + CurlState* state = (CurlState*) 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) + { + return CURLE_WRITE_ERROR; + } + + memcpy(code, header+9, 3*sizeof(char)); + code[3] = '\0'; + state->http->last_status = atoi(code); + + state->resp_headers = JS_NewArrayObject(state->cx, 0, NULL); + if(!state->resp_headers) + { + return CURLE_WRITE_ERROR; + } + + return length; + } + + // We get a notice at the \r\n\r\n after headers. + if(length <= 2) + { + return length; + } + + // Append the new header to our array. + hdr = dec_string(state->cx, header, length); + if(!hdr) + { + return CURLE_WRITE_ERROR; + } + + 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)) + { + return CURLE_WRITE_ERROR; + } + + return length; +} + +static size_t +recv_body(void *ptr, size_t size, size_t nmem, void *data) +{ + CurlState* state = (CurlState*) data; + size_t length = size * nmem; + char* tmp = NULL; + + if(!state->recvbuf) + { + state->recvlen = 4096; + state->read = 0; + state->recvbuf = JS_malloc(state->cx, state->recvlen); + } + + 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); + if(!tmp) return CURLE_WRITE_ERROR; + state->recvbuf = tmp; + + memcpy(state->recvbuf + state->read, ptr, length); + state->read += length; + return length; +} + diff --git a/src/couchdb/priv/couch_js/http.h b/src/couchdb/priv/couch_js/http.h new file mode 100644 index 00000000..b5f8c70f --- /dev/null +++ b/src/couchdb/priv/couch_js/http.h @@ -0,0 +1,18 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +#ifndef COUCH_JS_HTTP_H +#define COUCH_JS_HTTP_H + +JSObject* install_http(JSContext* cx, JSObject* global); + +#endif
\ No newline at end of file diff --git a/src/couchdb/priv/couch_js/main.c b/src/couchdb/priv/couch_js/main.c new file mode 100644 index 00000000..ee0e42c9 --- /dev/null +++ b/src/couchdb/priv/couch_js/main.c @@ -0,0 +1,324 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <jsapi.h> +#include "config.h" + +#include "utf8.h" +#include "http.h" + +int gExitCode = 0; + +#ifdef JS_THREADSAFE +#define SETUP_REQUEST(cx) \ + JS_SetContextThread(cx); \ + JS_BeginRequest(cx); +#define FINISH_REQUEST(cx) \ + JS_EndRequest(cx); \ + JS_ClearContextThread(cx); +#else +#define SETUP_REQUEST(cx) +#define FINISH_REQUEST(cx) +#endif + +static JSBool +evalcx(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSString *str; + JSObject *sandbox; + JSContext *subcx; + const jschar *src; + size_t srclen; + JSBool ret = JS_FALSE; + jsval v; + + sandbox = NULL; + if(!JS_ConvertArguments(cx, argc, argv, "S / o", &str, &sandbox)) + { + return JS_FALSE; + } + + subcx = JS_NewContext(JS_GetRuntime(cx), 8L * 1024L); + if(!subcx) + { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + + SETUP_REQUEST(subcx); + + src = JS_GetStringChars(str); + srclen = JS_GetStringLength(str); + + if(!sandbox) + { + sandbox = JS_NewObject(subcx, NULL, NULL, NULL); + if(!sandbox || !JS_InitStandardClasses(subcx, sandbox)) goto done; + } + + if(srclen == 0) + { + *rval = OBJECT_TO_JSVAL(sandbox); + } + else + { + JS_EvaluateUCScript(subcx, sandbox, src, srclen, NULL, 0, rval); + } + + ret = JS_TRUE; + +done: + FINISH_REQUEST(subcx); + JS_DestroyContext(subcx); + return ret; +} + +static JSBool +gc(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JS_GC(cx); + return JS_TRUE; +} + +static JSBool +print(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + uintN i; + char *bytes; + + for(i = 0; i < argc; i++) + { + bytes = enc_string(cx, argv[i], NULL); + if(!bytes) return JS_FALSE; + + fprintf(stdout, "%s%s", i ? " " : "", bytes); + JS_free(cx, bytes); + } + + fputc('\n', stdout); + fflush(stdout); + return JS_TRUE; +} + +static JSBool +quit(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JS_ConvertArguments(cx, argc, argv, "/ i", &gExitCode); + return JS_FALSE; +} + +static char* +readfp(JSContext* cx, FILE* fp, size_t* buflen) +{ + char* bytes = NULL; + char* tmp = NULL; + size_t used = 0; + size_t byteslen = 256; + size_t readlen = 0; + + bytes = JS_malloc(cx, byteslen); + if(bytes == NULL) return NULL; + + while((readlen = js_fgets(bytes+used, byteslen-used, stdin)) > 0) + { + used += readlen; + + if(bytes[used-1] == '\n') + { + bytes[used-1] = '\0'; + break; + } + + // Double our buffer and read more. + byteslen *= 2; + tmp = JS_realloc(cx, bytes, byteslen); + if(!tmp) + { + JS_free(cx, bytes); + return NULL; + } + bytes = tmp; + } + + *buflen = used; + return bytes; +} + +static JSBool +readline(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { + jschar *chars; + JSString *str; + char* bytes; + char* tmp; + size_t byteslen; + + /* GC Occasionally */ + JS_MaybeGC(cx); + + bytes = readfp(cx, stdin, &byteslen); + if(!bytes) return JS_FALSE; + + /* Treat the empty string specially */ + if(byteslen == 0) + { + *rval = JS_GetEmptyStringValue(cx); + JS_free(cx, bytes); + return JS_TRUE; + } + + /* Shrink the buffer to the real size */ + tmp = JS_realloc(cx, bytes, byteslen); + if(!tmp) + { + JS_free(cx, bytes); + return JS_FALSE; + } + bytes = tmp; + + str = dec_string(cx, bytes, byteslen); + JS_free(cx, bytes); + + if(!str) return JS_FALSE; + + *rval = STRING_TO_JSVAL(str); + + return JS_TRUE; +} + +static JSBool +seal(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { + JSObject *target; + JSBool deep = JS_FALSE; + + if (!JS_ConvertArguments(cx, argc, argv, "o/b", &target, &deep)) + return JS_FALSE; + if (!target) + return JS_TRUE; + return JS_SealObject(cx, target, deep); +} + +static void +execute_script(JSContext *cx, JSObject *obj, const char *filename) { + FILE *file; + JSScript *script; + jsval result; + + if(!filename || strcmp(filename, "-") == 0) + { + file = stdin; + } + else + { + file = fopen(filename, "r"); + if (!file) + { + fprintf(stderr, "could not open script file %s\n", filename); + gExitCode = 1; + return; + } + } + + script = JS_CompileFileHandle(cx, obj, filename, file); + if(script) + { + JS_ExecuteScript(cx, obj, script, &result); + JS_DestroyScript(cx, script); + } +} + +static void +printerror(JSContext *cx, const char *mesg, JSErrorReport *report) +{ + if(!report || !JSREPORT_IS_WARNING(report->flags)) + { + fprintf(stderr, "%s\n", mesg); + } +} + +static JSFunctionSpec global_functions[] = { + {"evalcx", evalcx, 0, 0, 0}, + {"gc", gc, 0, 0, 0}, + {"print", print, 0, 0, 0}, + {"quit", quit, 0, 0, 0}, + {"readline", readline, 0, 0, 0}, + {"seal", seal, 0, 0, 0}, + {0, 0, 0, 0, 0} +}; + +static JSClass global_class = { + "GlobalClass", + JSCLASS_GLOBAL_FLAGS, + JS_PropertyStub, + JS_PropertyStub, + JS_PropertyStub, + JS_PropertyStub, + JS_EnumerateStub, + JS_ResolveStub, + JS_ConvertStub, + JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS +}; + +int +main(int argc, const char * argv[]) +{ + JSRuntime* rt = NULL; + JSContext* cx = NULL; + JSObject *global = NULL; + int i = 0; + + rt = JS_NewRuntime(64L * 1024L * 1024L); + if (!rt) return 1; + + cx = JS_NewContext(rt, 8L * 1024L); + if (!cx) return 1; + + JS_SetErrorReporter(cx, printerror); + JS_ToggleOptions(cx, JSOPTION_XML); + + SETUP_REQUEST(cx); + + global = JS_NewObject(cx, &global_class, NULL, NULL); + if (!global) return 1; + if (!JS_InitStandardClasses(cx, global)) return 1; + + if(!JS_DefineFunctions(cx, global, global_functions)) + { + return 1; + } + + if(!install_http(cx, global)) + { + return 1; + } + + JS_SetGlobalObject(cx, global); + + if(argc != 2) { + fprintf(stderr, "incorrect number of arguments\n\n"); + fprintf(stderr, "usage: %s <scriptfile>\n", argv[0]); + return 2; + } + + execute_script(cx, global, argv[1]); + + FINISH_REQUEST(cx); + + JS_DestroyContext(cx); + JS_DestroyRuntime(rt); + JS_ShutDown(); + + return gExitCode; +} diff --git a/src/couchdb/priv/couch_js/utf8.c b/src/couchdb/priv/couch_js/utf8.c new file mode 100644 index 00000000..d6d6dd2b --- /dev/null +++ b/src/couchdb/priv/couch_js/utf8.c @@ -0,0 +1,286 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +#include <jsapi.h> + +static inline int +enc_char(uint8 *utf8Buffer, uint32 ucs4Char) +{ + int utf8Length = 1; + + if (ucs4Char < 0x80) + { + *utf8Buffer = (uint8)ucs4Char; + } + else + { + int i; + uint32 a = ucs4Char >> 11; + utf8Length = 2; + while(a) + { + a >>= 5; + utf8Length++; + } + i = utf8Length; + while(--i) + { + utf8Buffer[i] = (uint8)((ucs4Char & 0x3F) | 0x80); + ucs4Char >>= 6; + } + *utf8Buffer = (uint8)(0x100 - (1 << (8-utf8Length)) + ucs4Char); + } + + return utf8Length; +} + +static JSBool +enc_charbuf(const jschar* src, size_t srclen, char* dst, size_t* dstlenp) +{ + size_t i; + size_t utf8Len; + size_t dstlen = *dstlenp; + size_t origDstlen = dstlen; + jschar c; + jschar c2; + uint32 v; + uint8 utf8buf[6]; + + if(!dst) + { + dstlen = origDstlen = (size_t) -1; + } + + while(srclen) + { + c = *src++; + srclen--; + + if((c >= 0xDC00) && (c <= 0xDFFF)) goto bad_surrogate; + + if(c < 0xD800 || c > 0xDBFF) + { + v = c; + } + else + { + if(srclen < 1) goto buffer_too_small; + c2 = *src++; + srclen--; + if ((c2 < 0xDC00) || (c2 > 0xDFFF)) + { + c = c2; + goto bad_surrogate; + } + v = ((c - 0xD800) << 10) + (c2 - 0xDC00) + 0x10000; + } + if(v < 0x0080) + { + /* no encoding necessary - performance hack */ + if(!dstlen) goto buffer_too_small; + if(dst) *dst++ = (char) v; + utf8Len = 1; + } + else + { + utf8Len = enc_char(utf8buf, v); + if(utf8Len > dstlen) goto buffer_too_small; + if(dst) + { + for (i = 0; i < utf8Len; i++) + { + *dst++ = (char) utf8buf[i]; + } + } + } + dstlen -= utf8Len; + } + + *dstlenp = (origDstlen - dstlen); + return JS_TRUE; + +bad_surrogate: + *dstlenp = (origDstlen - dstlen); + return JS_FALSE; + +buffer_too_small: + *dstlenp = (origDstlen - dstlen); + return JS_FALSE; +} + +char* +enc_string(JSContext* cx, jsval arg, size_t* buflen) +{ + JSString* str = NULL; + jschar* src = NULL; + char* bytes = NULL; + size_t srclen = 0; + size_t byteslen = 0; + + str = JS_ValueToString(cx, arg); + if(!str) goto error; + + src = JS_GetStringChars(str); + srclen = JS_GetStringLength(str); + + if(!enc_charbuf(src, srclen, NULL, &byteslen)) goto error; + + bytes = JS_malloc(cx, (byteslen) + 1); + bytes[byteslen] = 0; + + if(!enc_charbuf(src, srclen, bytes, &byteslen)) goto error; + + if(buflen) *buflen = byteslen; + goto success; + +error: + if(bytes != NULL) JS_free(cx, bytes); + bytes = NULL; + +success: + return bytes; +} + +static inline uint32 +dec_char(const uint8 *utf8Buffer, int utf8Length) +{ + uint32 ucs4Char; + uint32 minucs4Char; + + /* from Unicode 3.1, non-shortest form is illegal */ + static const uint32 minucs4Table[] = { + 0x00000080, 0x00000800, 0x0001000, 0x0020000, 0x0400000 + }; + + if (utf8Length == 1) + { + ucs4Char = *utf8Buffer; + } + else + { + ucs4Char = *utf8Buffer++ & ((1<<(7-utf8Length))-1); + minucs4Char = minucs4Table[utf8Length-2]; + while(--utf8Length) + { + ucs4Char = ucs4Char<<6 | (*utf8Buffer++ & 0x3F); + } + if(ucs4Char < minucs4Char || ucs4Char == 0xFFFE || ucs4Char == 0xFFFF) + { + ucs4Char = 0xFFFD; + } + } + + return ucs4Char; +} + +static JSBool +dec_charbuf(const char *src, size_t srclen, jschar *dst, size_t *dstlenp) +{ + uint32 v; + size_t offset = 0; + size_t j; + size_t n; + size_t dstlen = *dstlenp; + size_t origDstlen = dstlen; + + if(!dst) dstlen = origDstlen = (size_t) -1; + + while(srclen) + { + v = (uint8) *src; + n = 1; + + if(v & 0x80) + { + while(v & (0x80 >> n)) + { + n++; + } + + if(n > srclen) goto buffer_too_small; + if(n == 1 || n > 6) goto bad_character; + + for(j = 1; j < n; j++) + { + if((src[j] & 0xC0) != 0x80) goto bad_character; + } + + v = dec_char((const uint8 *) src, n); + if(v >= 0x10000) + { + v -= 0x10000; + + if(v > 0xFFFFF || dstlen < 2) + { + *dstlenp = (origDstlen - dstlen); + return JS_FALSE; + } + + if(dstlen < 2) goto buffer_too_small; + + if(dst) + { + *dst++ = (jschar)((v >> 10) + 0xD800); + v = (jschar)((v & 0x3FF) + 0xDC00); + } + dstlen--; + } + } + + if(!dstlen) goto buffer_too_small; + if(dst) *dst++ = (jschar) v; + + dstlen--; + offset += n; + src += n; + srclen -= n; + } + + *dstlenp = (origDstlen - dstlen); + return JS_TRUE; + +bad_character: + *dstlenp = (origDstlen - dstlen); + return JS_FALSE; + +buffer_too_small: + *dstlenp = (origDstlen - dstlen); + return JS_FALSE; +} + +JSString* +dec_string(JSContext* cx, const char* bytes, size_t byteslen) +{ + JSString* str = NULL; + jschar* chars = NULL; + size_t charslen; + + if(!dec_charbuf(bytes, byteslen, NULL, &charslen)) goto error; + + chars = JS_malloc(cx, (charslen + 1) * sizeof(jschar)); + if(!chars) return NULL; + chars[charslen] = 0; + + if(!dec_charbuf(bytes, byteslen, chars, &charslen)) goto error; + + str = JS_NewUCString(cx, chars, charslen - 1); + if(!str) goto error; + + goto success; + +error: + if(chars != NULL) JS_free(cx, chars); + str = NULL; + +success: + return str; +}
\ No newline at end of file diff --git a/src/couchdb/priv/couch_js/utf8.h b/src/couchdb/priv/couch_js/utf8.h new file mode 100644 index 00000000..00f6b736 --- /dev/null +++ b/src/couchdb/priv/couch_js/utf8.h @@ -0,0 +1,19 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +#ifndef COUCH_JS_UTF_8_H +#define COUCH_JS_UTF_8_H + +char* enc_string(JSContext* cx, jsval arg, size_t* buflen); +JSString* dec_string(JSContext* cx, const char* buf, size_t buflen); + +#endif
\ No newline at end of file |