summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--share/www/script/couch_tests.js7
-rw-r--r--src/couchdb/Makefile.am3
-rw-r--r--src/couchdb/couch_js.c796
-rw-r--r--src/couchdb/curlhelper.c261
-rw-r--r--src/couchdb/curlhelper.h49
-rwxr-xr-xtest/runner.sh2
-rw-r--r--test/test.js252
7 files changed, 1367 insertions, 3 deletions
diff --git a/share/www/script/couch_tests.js b/share/www/script/couch_tests.js
index 46e42464..e88c68d1 100644
--- a/share/www/script/couch_tests.js
+++ b/share/www/script/couch_tests.js
@@ -10,6 +10,11 @@
// License for the specific language governing permissions and limitations under
// the License.
+// Used by replication test
+CouchDB.host = (typeof window == 'undefined' || !window) ?
+ "127.0.0.1" : window.location.host;
+CouchDB.port = 5984;
+
var tests = {
// Do some basic tests.
@@ -1556,7 +1561,7 @@ var tests = {
replication: function(debug) {
if (debug) debugger;
- var host = window.location.host;
+ var host = CouchDB.host;
var dbPairs = [
{source:"test_suite_db_a",
target:"test_suite_db_b"},
diff --git a/src/couchdb/Makefile.am b/src/couchdb/Makefile.am
index 83220a57..28fd418f 100644
--- a/src/couchdb/Makefile.am
+++ b/src/couchdb/Makefile.am
@@ -24,7 +24,8 @@ couch_erl_driver_la_CFLAGS = $(ICU_LOCAL_FLAGS)
couch_erl_driver_la_LIBADD = -licuuc -licudata -licui18n
locallibbin_PROGRAMS = couchjs
-couchjs_SOURCES = couch_js.c
+couchjs_SOURCES = couch_js.c curlhelper.c
+couchjs_LDADD = -lcurl -lgssapi_krb5
couchinclude_DATA = couch_db.hrl
diff --git a/src/couchdb/couch_js.c b/src/couchdb/couch_js.c
index 8481bc50..21faac1c 100644
--- a/src/couchdb/couch_js.c
+++ b/src/couchdb/couch_js.c
@@ -13,8 +13,16 @@ 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>
+
+#ifndef CURLOPT_COPYPOSTFIELDS
+ #define CURLOPT_COPYPOSTFIELDS 10165
+#endif
int gExitCode = 0;
int gStackChunkSize = 8L * 1024L;
@@ -401,6 +409,785 @@ PrintError(JSContext *context, const char *message, JSErrorReport *report) {
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) {
+ if( size == 0 || nmemb == 0) {
+ return 0;
+ }
+
+ char* databuffer = (char*)ptr;
+ Buffer b = ((BufferCount)stream)->buffer;
+ int* pos = &(((BufferCount)stream)->pos);
+
+ if((b->count - *pos) == 0) {
+ return 0;
+ }
+
+ int readlength = size*nmemb;
+ int spaceleft = b->count - *pos;
+ int i;
+
+ 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) {
+ if( size == 0 || nmemb == 0 )
+ return 0;
+
+ char *data, *tmp;
+ Buffer b;
+
+ 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) {
+ if(!JSVAL_IS_STRING(*arg)) {
+ return NULL;
+ }
+
+ char *c, *tmp;
+ JSString *jsmsg;
+ size_t len;
+
+ jsmsg = JS_ValueToString(context,*arg);
+ len = JS_GetStringLength(jsmsg);
+ tmp = JS_GetStringBytes(jsmsg);
+
+ c = (char*)malloc(len+1);
+ c[len] = '\0';
+
+ int i;
+
+ 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;
+
+ // If we fail to convert arg2 to an object. Error!
+ if(!JS_ValueToObject(context,*arg,&header_obj)) {
+ return NULL;
+ }
+
+ JSObject* iterator = JS_NewPropertyIterator(context,header_obj);
+
+ jsval *jsProperty = JS_malloc(context,sizeof(jsval));
+ jsval *jsValue = JS_malloc(context,sizeof(jsval));
+ jsid *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.
+
+ Buffer bTmp = init_Buffer();
+ JS_IdToValue(context,*jsId,jsProperty);
+ char* jsPropertyName = JSValToChar(context,jsProperty);
+
+ // TODO: Remove strlen =/
+ append_Buffer(bTmp,jsPropertyName,strlen(jsPropertyName));
+ append_Buffer(bTmp,": ",2);
+
+ JS_GetProperty(context,header_obj,jsPropertyName,jsValue);
+ char* 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;
+
+ // 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_NOPROGRESS,1);
+ curl_easy_setopt(handle,CURLOPT_IPRESOLVE,CURL_IPRESOLVE_V4);
+
+ struct curl_slist *slist = generateCurlHeaders(context,argv+1);
+ if(slist != NULL) {
+ curl_easy_setopt(handle,CURLOPT_HTTPHEADER,slist);
+ }
+
+ // Perform
+ int exitcode;
+
+ 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;
+
+ // 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);
+
+ struct curl_slist *slist = generateCurlHeaders(context,argv+1);
+ if(slist != NULL) {
+ curl_easy_setopt(handle,CURLOPT_HTTPHEADER,slist);
+ }
+
+ // fprintf(stderr, "about to run HEAD request\n");
+
+ // Perform
+ int exitcode;
+
+ 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;
+
+ // 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_HTTPPOST,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_COPYPOSTFIELDS,body); // Curl wants '\0' terminated, we oblige
+ free(body);
+
+ struct curl_slist *slist = generateCurlHeaders(context,argv+2); // Initialize Headers
+ if(slist != NULL) {
+ curl_easy_setopt(handle,CURLOPT_HTTPHEADER,slist);
+ }
+
+ int exitcode;
+
+ if((exitcode = curl_easy_perform(handle)) != 0) { // Perform
+ curl_slist_free_all(slist);
+ free(url);
+ free_Buffer(b);
+ curl_easy_cleanup(handle);
+ return JS_FALSE;
+ }
+
+ 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;
+
+ // 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));
+ // TODO: remove strlen
+ append_Buffer(b_data->buffer,data,strlen(data));
+
+ free(data);
+
+ CURL* handle;
+
+ // 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 structure
+ struct curl_slist *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
+ int exitcode;
+
+ 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];
+ 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();
+
+ CURL* handle;
+
+ // 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
+ struct curl_slist *slist = NULL;
+ if((slist = generateCurlHeaders(context,argv+1)) != NULL) {
+ curl_easy_setopt(handle,CURLOPT_HTTPHEADER,slist);
+ }
+
+ // Perform
+ int exitcode;
+
+ 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];
+ 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();
+
+ CURL* handle;
+
+ // 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
+ struct curl_slist *slist = NULL;
+ if((slist = generateCurlHeaders(context,argv+1)) != NULL) {
+ curl_easy_setopt(handle,CURLOPT_HTTPHEADER,slist);
+ }
+
+ // Perform
+ int exitcode;
+
+ 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];
+ 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();
+
+ CURL* handle;
+
+ // 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
+ struct curl_slist *slist = NULL;
+ if((slist = generateCurlHeaders(context,argv+1)) != NULL) {
+ curl_easy_setopt(handle,CURLOPT_HTTPHEADER,slist);
+ }
+
+ // Perform
+ int exitcode;
+
+ 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;
@@ -428,7 +1215,14 @@ main(int argc, const char * argv[]) {
|| !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, "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) {
diff --git a/src/couchdb/curlhelper.c b/src/couchdb/curlhelper.c
new file mode 100644
index 00000000..99b2e6ab
--- /dev/null
+++ b/src/couchdb/curlhelper.c
@@ -0,0 +1,261 @@
+/*
+
+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;
+
+ 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;
+ }
+ }
+
+ int i;
+
+ 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;
+
+ if((result = (char*)malloc(sizeof(char)*(b->count+1))) == NULL) {
+ return NULL;
+ }
+
+ result[b->count] = '\0';
+
+ int i;
+
+ 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) {
+ if((pos + length) > b->count)
+ return;
+
+ int i;
+
+ for(i = 0; i < length;i++) {
+ *(c + i) = *(b->data + pos + i);
+ }
+}
+
+
+List init_List(int capacity) {
+ if(capacity < 5)
+ capacity = 5;
+
+ List l;
+
+ 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;
+
+ 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;
+ }
+ }
+
+ int i;
+
+ for(i = 0;i < length;i++) {
+ *(l->elements + l->count + i) = ptr + i;
+ }
+
+ l->count = new_count;
+
+ return TRUE;
+}
+
+int push_List(List l, void* ptr, int length) {
+ int capacity_changed;
+ int new_count;
+
+ 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;
+ }
+ }
+
+ int i;
+
+ for(i = 0;i < length;i++) {
+ *(l->elements + l->count + i) = *(l->elements + i);
+ }
+
+ for(i = 0;i < length;i++) {
+ *(l->elements + i) = ptr+i;
+ }
+
+ l->count = new_count;
+
+ return TRUE;
+}
diff --git a/src/couchdb/curlhelper.h b/src/couchdb/curlhelper.h
new file mode 100644
index 00000000..c04c2c92
--- /dev/null
+++ b/src/couchdb/curlhelper.h
@@ -0,0 +1,49 @@
+/*
+
+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/test/runner.sh b/test/runner.sh
index ae0f1ef8..e071ad63 100755
--- a/test/runner.sh
+++ b/test/runner.sh
@@ -1,3 +1,5 @@
#!/bin/sh -e
erl -noshell -pa ../src/couchdb -pa ../src/mochiweb -eval "runner:run()"
+
+cat ../share/www/script/couch.js ../share/www/script/couch_tests.js test.js | ../src/couchdb/couchjs -
diff --git a/test/test.js b/test/test.js
new file mode 100644
index 00000000..1d3e989b
--- /dev/null
+++ b/test/test.js
@@ -0,0 +1,252 @@
+// 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.
+
+// couch.js, with modifications
+
+// 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.
+
+// some monkeypatches
+var JSON = {
+ parse : function(string) {
+ return eval('('+string+')');
+ },
+ stringify : function(obj) {
+ return toJSON(obj||null);
+ }
+};
+
+RegExp.escape = function(text) {
+ if (!arguments.callee.sRE) {
+ var specials = [
+ '/', '.', '*', '+', '?', '|',
+ '(', ')', '[', ']', '{', '}', '\\'
+ ];
+ arguments.callee.sRE = new RegExp(
+ '(\\' + specials.join('|\\') + ')', 'g'
+ );
+ }
+ return text.replace(arguments.callee.sRE, '\\$1');
+}
+
+// This is a JS wrapper for the curl function made available in couch_js.c,
+// it should be used in other JavaScripts that would like to make HTTP calls.
+
+var HTTP = (function() {
+ function parseCurl(string) {
+ var parts = string.split(/\r\n\r\n/);
+ var body = parts.pop();
+ var header = parts.pop();
+ var headers = header.split(/\n/);
+
+ var status = /HTTP\/1.\d (\d*)/.exec(header)[1];
+ return {
+ responseText: body,
+ status: parseInt(status),
+ getResponseHeader: function(key) {
+ var keymatcher = new RegExp(RegExp.escape(key), "i");
+ for (var i in headers) {
+ var h = headers[i];
+ if (keymatcher.test(h)) {
+ var value = h.substr(key.length+2);
+ value = value.slice(0, value.length-1);
+ return value;
+ }
+ }
+ return "";
+ }
+ }
+ };
+ return {
+ GET : function(url, body, headers) {
+ var st, urx = url, hx = (headers || null);
+ st = gethttp(urx, hx);
+ return parseCurl(st);
+ },
+ HEAD : function(url, body, headers) {
+ var st, urx = url, hx = (headers || null);
+ st = headhttp(urx, hx);
+ return parseCurl(st);
+ },
+ DELETE : function(url, body, headers) {
+ var st, urx = url, hx = (headers || null);
+ st = delhttp(urx, hx);
+ return parseCurl(st);
+ },
+ MOVE : function(url, body, headers) {
+ var st, urx = url, hx = (headers || null);
+ st = movehttp(urx, hx);
+ return parseCurl(st);
+ },
+ COPY : function(url, body, headers) {
+ var st, urx = url, hx = (headers || null);
+ st = copyhttp(urx, hx);
+ return parseCurl(st);
+ },
+ POST : function(url, body, headers) {
+ var st, urx = url, bx = (body || ""), hx = (headers || {});
+ hx['Content-Type'] = hx['Content-Type'] || "application/json";
+ st = posthttp(urx, bx, hx);
+ return parseCurl(st);
+ },
+ PUT : function(url, body, headers) {
+ var st, urx = url, bx = (body || ""), hx = (headers || {});
+ hx['Content-Type'] = hx['Content-Type'] || "application/json";
+ st = puthttp(urx, bx, hx);
+ return parseCurl(st);
+ }
+ };
+})();
+
+// Monkeypatches to CouchDB client for use of curl.
+
+CouchDB.host = (typeof window == 'undefined' || !window) ? "127.0.0.1" : window;
+CouchDB.port = 5984;
+
+CouchDB.request = function(method, uri, options) {
+ var full_uri = "http://" + CouchDB.host + ":" + CouchDB.port + uri;
+ options = options || {};
+ var response = HTTP[method](full_uri, options.body, options.headers);
+ return response;
+}
+
+
+function toJSON(val) {
+ if (typeof(val) == "undefined") {
+ throw {error:"bad_value", reason:"Cannot encode 'undefined' value as JSON"};
+ }
+ var subs = {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f',
+ '\r': '\\r', '"' : '\\"', '\\': '\\\\'};
+ if (typeof(val) == "xml") { // E4X support
+ val = val.toXMLString();
+ }
+ return {
+ "Array": function(v) {
+ var buf = [];
+ for (var i = 0; i < v.length; i++) {
+ buf.push(toJSON(v[i]));
+ }
+ return "[" + buf.join(",") + "]";
+ },
+ "Boolean": function(v) {
+ return v.toString();
+ },
+ "Date": function(v) {
+ var f = function(n) { return n < 10 ? '0' + n : n }
+ return '"' + v.getUTCFullYear() + '-' +
+ f(v.getUTCMonth() + 1) + '-' +
+ f(v.getUTCDate()) + 'T' +
+ f(v.getUTCHours()) + ':' +
+ f(v.getUTCMinutes()) + ':' +
+ f(v.getUTCSeconds()) + 'Z"';
+ },
+ "Number": function(v) {
+ return isFinite(v) ? v.toString() : "null";
+ },
+ "Object": function(v) {
+ if (v === null) return "null";
+ var buf = [];
+ for (var k in v) {
+ if (!v.hasOwnProperty(k) || typeof(k) !== "string" || v[k] === undefined) {
+ continue;
+ }
+ buf.push(toJSON(k, val) + ": " + toJSON(v[k]));
+ }
+ return "{" + buf.join(",") + "}";
+ },
+ "String": function(v) {
+ if (/["\\\x00-\x1f]/.test(v)) {
+ v = v.replace(/([\x00-\x1f\\"])/g, function(a, b) {
+ var c = subs[b];
+ if (c) return c;
+ c = b.charCodeAt();
+ return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16);
+ });
+ }
+ return '"' + v + '"';
+ }
+ }[val != null ? val.constructor.name : "Object"](val);
+}
+
+
+
+// *************** Test Framework Console Adapter ****************** //
+
+var p = print;
+var numFailures = 0;
+
+function runAllTestsConsole() {
+ var numTests = 0;
+ var debug = false;
+ for (var t in tests) {
+ p(t);
+ if (t == "utf8") {
+ p("We skip the utf8 test because it fails due to problems in couch_js.c");
+ p("Run the in-browser tests to verify utf8.\n");
+ } else {
+ numTests += 1;
+ var testFun = tests[t];
+ runTestConsole(testFun, debug);
+ }
+ }
+ p("Results: "+numFailures.toString() + " failures in "+numTests+" tests.")
+};
+
+function runTestConsole(testFun, debug) {
+ var start = new Date().getTime();
+ try {
+ if (!debug) testFun = patchTest(testFun) || testFun;
+ testFun();
+ p("PASS");
+ } catch(e) {
+ p("ERROR");
+ p("Exception raised: "+e.toString());
+ p("Backtrace: "+e.stack);
+ }
+ var duration = new Date().getTime() - start;
+ p(duration+"ms\n");
+};
+
+
+// Use T to perform a test that returns false on failure and if the test fails,
+// display the line that failed.
+// Example:
+// T(MyValue==1);
+function T(arg1, arg2) {
+ if (!arg1) {
+ p("Assertion failed: "+(arg2 != null ? arg2 : arg1).toString());
+ numFailures += 1
+ }
+}
+
+p("Running CouchDB Test Suite\n");
+p("Host: "+CouchDB.host);
+p("Port: "+CouchDB.port);
+
+try {
+ p("Version: "+CouchDB.getVersion()+"\n");
+ runAllTestsConsole();
+ // runTestConsole(tests.attachments);
+} catch (e) {
+ p(e.toString());
+}
+
+p("\nFinished");