diff options
author | Joan Touzet <wohali@apache.org> | 2020-04-15 00:28:27 +0000 |
---|---|---|
committer | Joan Touzet <joant@atypical.net> | 2020-04-27 13:03:49 -0400 |
commit | 7ffabb2e6218ea7a8a1b3a8735dc33ca8b061cb8 (patch) | |
tree | b0fe30547f6abaa2f7d9c1c88f90c08f3286b4e7 | |
parent | db5905382c6aab8c6094d4711b711159aa3a69f8 (diff) | |
download | couchdb-7ffabb2e6218ea7a8a1b3a8735dc33ca8b061cb8.tar.gz |
First pass at SpiderMonkey 68 support
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | rebar.config.script | 16 | ||||
-rw-r--r-- | src/couch/priv/couch_js/68/help.h | 86 | ||||
-rw-r--r-- | src/couch/priv/couch_js/68/http.cpp | 710 | ||||
-rw-r--r-- | src/couch/priv/couch_js/68/http.h | 27 | ||||
-rw-r--r-- | src/couch/priv/couch_js/68/main.cpp | 494 | ||||
-rw-r--r-- | src/couch/priv/couch_js/68/utf8.cpp | 309 | ||||
-rw-r--r-- | src/couch/priv/couch_js/68/utf8.h | 19 | ||||
-rw-r--r-- | src/couch/priv/couch_js/68/util.cpp | 350 | ||||
-rw-r--r-- | src/couch/priv/couch_js/68/util.h | 60 | ||||
-rw-r--r-- | src/couch/rebar.config.script | 66 | ||||
-rw-r--r-- | support/build_js.escript | 6 |
12 files changed, 2107 insertions, 37 deletions
diff --git a/.gitignore b/.gitignore index 60e6d145a..551a6a61f 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ .rebar/ .eunit/ cover/ +core log apache-couchdb-*/ bin/ diff --git a/rebar.config.script b/rebar.config.script index bfca5c84e..0e9c9781c 100644 --- a/rebar.config.script +++ b/rebar.config.script @@ -139,7 +139,7 @@ SubDirs = [ "src/setup", "src/smoosh", "rel" -], +]. DepDescs = [ %% Independent Apps @@ -162,18 +162,18 @@ DepDescs = [ {mochiweb, "mochiweb", {tag, "v2.20.0"}}, {meck, "meck", {tag, "0.8.8"}}, {recon, "recon", {tag, "2.5.0"}} -], +]. -WithProper = lists:keyfind(with_proper, 1, CouchConfig) == {with_proper, true}, +WithProper = lists:keyfind(with_proper, 1, CouchConfig) == {with_proper, true}. OptionalDeps = case WithProper of true -> [{proper, {url, "https://github.com/proper-testing/proper"}, {tag, "v1.3"}}]; false -> [] -end, +end. -BaseUrl = "https://github.com/apache/", +BaseUrl = "https://github.com/apache/". MakeDep = fun ({AppName, {url, Url}, Version}) -> @@ -186,12 +186,12 @@ MakeDep = fun ({AppName, RepoName, Version, Options}) -> Url = BaseUrl ++ "couchdb-" ++ RepoName ++ ".git", {AppName, ".*", {git, Url, Version}, Options} -end, +end. ErlOpts = case os:getenv("ERL_OPTS") of false -> []; Opts -> [list_to_atom(O) || O <- string:tokens(Opts, ",")] -end, +end. AddConfig = [ {require_otp_vsn, "19|20|21|22"}, @@ -210,7 +210,7 @@ AddConfig = [ sasl, setup, ssl, stdlib, syntax_tools, xmerl]}, {warnings, [unmatched_returns, error_handling, race_conditions]}]}, {post_hooks, [{compile, "escript support/build_js.escript"}]} -], +]. C = lists:foldl(fun({K, V}, CfgAcc) -> lists:keystore(K, 1, CfgAcc, {K, V}) diff --git a/src/couch/priv/couch_js/68/help.h b/src/couch/priv/couch_js/68/help.h new file mode 100644 index 000000000..678651fd3 --- /dev/null +++ b/src/couch/priv/couch_js/68/help.h @@ -0,0 +1,86 @@ +// 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 COUCHJS_HELP_H +#define COUCHJS_HELP_H + +#include "config.h" + +static const char VERSION_TEMPLATE[] = + "%s - %s\n" + "\n" + "Licensed under the Apache License, Version 2.0 (the \"License\"); you may " + "not use\n" + "this file except in compliance with the License. You may obtain a copy of" + "the\n" + "License at\n" + "\n" + " http://www.apache.org/licenses/LICENSE-2.0\n" + "\n" + "Unless required by applicable law or agreed to in writing, software " + "distributed\n" + "under the License is distributed on an \"AS IS\" BASIS, WITHOUT " + "WARRANTIES OR\n" + "CONDITIONS OF ANY KIND, either express or implied. See the License " + "for the\n" + "specific language governing permissions and limitations under the " + "License.\n"; + +static const char USAGE_TEMPLATE[] = + "Usage: %s [FILE]\n" + "\n" + "The %s command runs the %s JavaScript interpreter.\n" + "\n" + "The exit status is 0 for success or 1 for failure.\n" + "\n" + "Options:\n" + "\n" + " -h display a short help message and exit\n" + " -V display version information and exit\n" + " -H enable %s cURL bindings (only avaiable\n" + " if package was built with cURL available)\n" + " -T enable test suite specific functions (these\n" + " should not be enabled for production systems)\n" + " -S SIZE specify that the runtime should allow at\n" + " most SIZE bytes of memory to be allocated\n" + " default is 64 MiB\n" + " -u FILE path to a .uri file containing the address\n" + " (or addresses) of one or more servers\n" + " --eval Enable runtime code evaluation (dangerous!)\n" + "\n" + "Report bugs at <%s>.\n"; + +#define BASENAME COUCHJS_NAME + +#define couch_version(basename) \ + fprintf( \ + stdout, \ + VERSION_TEMPLATE, \ + basename, \ + PACKAGE_STRING) + +#define DISPLAY_VERSION couch_version(BASENAME) + + +#define couch_usage(basename) \ + fprintf( \ + stdout, \ + USAGE_TEMPLATE, \ + basename, \ + basename, \ + PACKAGE_NAME, \ + basename, \ + PACKAGE_BUGREPORT) + +#define DISPLAY_USAGE couch_usage(BASENAME) + +#endif // Included help.h diff --git a/src/couch/priv/couch_js/68/http.cpp b/src/couch/priv/couch_js/68/http.cpp new file mode 100644 index 000000000..a0c73bdc6 --- /dev/null +++ b/src/couch/priv/couch_js/68/http.cpp @@ -0,0 +1,710 @@ +// 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <jsapi.h> +#include <js/Initialization.h> +#include <js/MemoryFunctions.h> +#include "config.h" +#include "utf8.h" +#include "util.h" + +// Soft dependency on cURL bindings because they're +// only used when running the JS tests from the +// command line which is rare. +#ifndef HAVE_CURL + +void +http_check_enabled() +{ + fprintf(stderr, "HTTP API was disabled at compile time.\n"); + exit(3); +} + + +bool +http_ctor(JSContext* cx, JSObject* req) +{ + return false; +} + + +void +http_dtor(JSFreeOp* fop, JSObject* req) +{ + return; +} + + +bool +http_open(JSContext* cx, JSObject* req, JS::Value mth, JS::Value url, JS::Value snc) +{ + return false; +} + + +bool +http_set_hdr(JSContext* cx, JSObject* req, JS::Value name, JS::Value val) +{ + return false; +} + + +bool +http_send(JSContext* cx, JSObject* req, JS::Value body) +{ + return false; +} + + +int +http_status(JSContext* cx, JSObject* req) +{ + return -1; +} + +bool +http_uri(JSContext* cx, JSObject* req, couch_args* args, JS::Value* uri_val) +{ + return false; +} + + +#else +#include <curl/curl.h> +#ifndef XP_WIN +#include <unistd.h> +#endif + + +void +http_check_enabled() +{ + return; +} + + +// Map some of the string function names to things which exist on Windows +#ifdef XP_WIN +#define strcasecmp _strcmpi +#define strncasecmp _strnicmp +#define snprintf _snprintf +#endif + + +typedef struct curl_slist CurlHeaders; + + +typedef struct { + int method; + char* url; + CurlHeaders* req_headers; + int16_t last_status; +} HTTPData; + + +const char* METHODS[] = {"GET", "HEAD", "POST", "PUT", "DELETE", "COPY", "OPTIONS", NULL}; + + +#define GET 0 +#define HEAD 1 +#define POST 2 +#define PUT 3 +#define DELETE 4 +#define COPY 5 +#define OPTIONS 6 + + +static bool +go(JSContext* cx, JSObject* obj, HTTPData* http, char* body, size_t blen); + + +/*static JSString* +str_from_binary(JSContext* cx, char* data, size_t length); +*/ + + +bool +http_ctor(JSContext* cx, JSObject* req) +{ + HTTPData* http = NULL; + bool ret = false; + + http = (HTTPData*) malloc(sizeof(HTTPData)); + if(!http) + { + JS_ReportErrorUTF8(cx, "Failed to create CouchHTTP instance."); + goto error; + } + + http->method = -1; + http->url = NULL; + http->req_headers = NULL; + http->last_status = -1; + + JS_SetPrivate(req, http); + + ret = true; + goto success; + +error: + if(http) free(http); + +success: + return ret; +} + + +void +http_dtor(JSFreeOp* fop, JSObject* obj) +{ + HTTPData* http = (HTTPData*) JS_GetPrivate(obj); + if(http) { + if(http->url) free(http->url); + if(http->req_headers) curl_slist_free_all(http->req_headers); + free(http); + } +} + + +bool +http_open(JSContext* cx, JSObject* req, JS::Value mth, JS::Value url, JS::Value snc) +{ + HTTPData* http = (HTTPData*) JS_GetPrivate(req); + char* method = NULL; + int methid; + bool ret = false; + + if(!http) { + JS_ReportErrorUTF8(cx, "Invalid CouchHTTP instance."); + goto done; + } + + if(mth.isUndefined()) { + JS_ReportErrorUTF8(cx, "You must specify a method."); + goto done; + } + + method = enc_string(cx, mth, NULL); + if(!method) { + JS_ReportErrorUTF8(cx, "Failed to encode method."); + goto done; + } + + for(methid = 0; METHODS[methid] != NULL; methid++) { + if(strcasecmp(METHODS[methid], method) == 0) break; + } + + if(methid > OPTIONS) { + JS_ReportErrorUTF8(cx, "Invalid method specified."); + goto done; + } + + http->method = methid; + + if(url.isUndefined()) { + JS_ReportErrorUTF8(cx, "You must specify a URL."); + goto done; + } + + if(http->url != NULL) { + free(http->url); + http->url = NULL; + } + + http->url = enc_string(cx, url, NULL); + if(http->url == NULL) { + JS_ReportErrorUTF8(cx, "Failed to encode URL."); + goto done; + } + + if(snc.isBoolean() && snc.isTrue()) { + JS_ReportErrorUTF8(cx, "Synchronous flag must be false."); + 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 = true; + +done: + if(method) free(method); + return ret; +} + + +bool +http_set_hdr(JSContext* cx, JSObject* req, JS::Value name, JS::Value val) +{ + HTTPData* http = (HTTPData*) JS_GetPrivate(req); + char* keystr = NULL; + char* valstr = NULL; + char* hdrbuf = NULL; + size_t hdrlen = -1; + bool ret = false; + + if(!http) { + JS_ReportErrorUTF8(cx, "Invalid CouchHTTP instance."); + goto done; + } + + if(name.isUndefined()) + { + JS_ReportErrorUTF8(cx, "You must speciy a header name."); + goto done; + } + + keystr = enc_string(cx, name, NULL); + if(!keystr) + { + JS_ReportErrorUTF8(cx, "Failed to encode header name."); + goto done; + } + + if(val.isUndefined()) + { + JS_ReportErrorUTF8(cx, "You must specify a header value."); + goto done; + } + + valstr = enc_string(cx, val, NULL); + if(!valstr) + { + JS_ReportErrorUTF8(cx, "Failed to encode header value."); + goto done; + } + + hdrlen = strlen(keystr) + strlen(valstr) + 3; + hdrbuf = (char*) malloc(hdrlen * sizeof(char)); + if(!hdrbuf) { + JS_ReportErrorUTF8(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 = true; + +done: + if(keystr) free(keystr); + if(valstr) free(valstr); + if(hdrbuf) free(hdrbuf); + return ret; +} + +bool +http_send(JSContext* cx, JSObject* req, JS::Value body) +{ + HTTPData* http = (HTTPData*) JS_GetPrivate(req); + char* bodystr = NULL; + size_t bodylen = 0; + bool ret = false; + + if(!http) { + JS_ReportErrorUTF8(cx, "Invalid CouchHTTP instance."); + goto done; + } + + if(!body.isUndefined()) { + bodystr = enc_string(cx, body, &bodylen); + if(!bodystr) { + JS_ReportErrorUTF8(cx, "Failed to encode body."); + goto done; + } + } + + ret = go(cx, req, http, bodystr, bodylen); + +done: + if(bodystr) free(bodystr); + return ret; +} + +int +http_status(JSContext* cx, JSObject* req) +{ + HTTPData* http = (HTTPData*) JS_GetPrivate(req); + + if(!http) { + JS_ReportErrorUTF8(cx, "Invalid CouchHTTP instance."); + return false; + } + + return http->last_status; +} + +bool +http_uri(JSContext* cx, JSObject* req, couch_args* args, JS::Value* uri_val) +{ + FILE* uri_fp = NULL; + JSString* uri_str; + + // Default is http://localhost:15986/ when no uri file is specified + if (!args->uri_file) { + uri_str = JS_NewStringCopyZ(cx, "http://localhost:15986/"); + *uri_val = JS::StringValue(uri_str); + JS_SetReservedSlot(req, 0, *uri_val); + return true; + } + + // Else check to see if the base url is cached in a reserved slot + *uri_val = JS_GetReservedSlot(req, 0); + if (!(*uri_val).isUndefined()) { + return true; + } + + // Read the first line of the couch.uri file. + if(!((uri_fp = fopen(args->uri_file, "r")) && + (uri_str = couch_readline(cx, uri_fp)))) { + JS_ReportErrorUTF8(cx, "Failed to read couch.uri file."); + goto error; + } + + fclose(uri_fp); + *uri_val = JS::StringValue(uri_str); + JS_SetReservedSlot(req, 0, *uri_val); + return true; + +error: + if(uri_fp) fclose(uri_fp); + return false; +} + + +// Curl Helpers + +typedef struct { + HTTPData* http; + JSContext* cx; + JSObject* resp_headers; + char* sendbuf; + size_t sendlen; + size_t sent; + int sent_once; + 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* HTTP_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 bool +go(JSContext* cx, JSObject* obj, HTTPData* http, char* body, size_t bodylen) +{ + CurlState state; + char* referer; + JSString* jsbody; + bool ret = false; + JS::Value tmp; + JS::RootedObject robj(cx, obj); + JS::RootedValue vobj(cx); + + + state.cx = cx; + state.http = http; + + state.sendbuf = body; + state.sendlen = bodylen; + state.sent = 0; + state.sent_once = 0; + + state.recvbuf = NULL; + state.recvlen = 0; + state.read = 0; + + if(HTTP_HANDLE == NULL) { + HTTP_HANDLE = curl_easy_init(); + curl_easy_setopt(HTTP_HANDLE, CURLOPT_READFUNCTION, send_body); + curl_easy_setopt(HTTP_HANDLE, CURLOPT_SEEKFUNCTION, + (curl_seek_callback) seek_body); + curl_easy_setopt(HTTP_HANDLE, CURLOPT_HEADERFUNCTION, recv_header); + curl_easy_setopt(HTTP_HANDLE, CURLOPT_WRITEFUNCTION, recv_body); + curl_easy_setopt(HTTP_HANDLE, CURLOPT_NOPROGRESS, 1); + curl_easy_setopt(HTTP_HANDLE, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); + curl_easy_setopt(HTTP_HANDLE, CURLOPT_ERRORBUFFER, ERRBUF); + curl_easy_setopt(HTTP_HANDLE, CURLOPT_COOKIEFILE, ""); + curl_easy_setopt(HTTP_HANDLE, CURLOPT_USERAGENT, + "CouchHTTP Client - Relax"); + } + + if(!HTTP_HANDLE) { + JS_ReportErrorUTF8(cx, "Failed to initialize cURL handle."); + if(state.recvbuf) JS_free(cx, state.recvbuf); + return ret; + } + + tmp = JS_GetReservedSlot(obj, 0); + + if(!(referer = enc_string(cx, tmp, NULL))) { + JS_ReportErrorUTF8(cx, "Failed to encode referer."); + if(state.recvbuf) JS_free(cx, state.recvbuf); + return ret; + } + curl_easy_setopt(HTTP_HANDLE, CURLOPT_REFERER, referer); + free(referer); + + if(http->method < 0 || http->method > OPTIONS) { + JS_ReportErrorUTF8(cx, "INTERNAL: Unknown method."); + if(state.recvbuf) JS_free(cx, state.recvbuf); + return ret; + } + + curl_easy_setopt(HTTP_HANDLE, CURLOPT_CUSTOMREQUEST, METHODS[http->method]); + curl_easy_setopt(HTTP_HANDLE, CURLOPT_NOBODY, 0); + curl_easy_setopt(HTTP_HANDLE, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(HTTP_HANDLE, CURLOPT_UPLOAD, 0); + + if(http->method == HEAD) { + curl_easy_setopt(HTTP_HANDLE, CURLOPT_NOBODY, 1); + curl_easy_setopt(HTTP_HANDLE, CURLOPT_FOLLOWLOCATION, 0); + } else if(http->method == POST || http->method == PUT) { + curl_easy_setopt(HTTP_HANDLE, CURLOPT_UPLOAD, 1); + curl_easy_setopt(HTTP_HANDLE, CURLOPT_FOLLOWLOCATION, 0); + } + + if(body && bodylen) { + curl_easy_setopt(HTTP_HANDLE, CURLOPT_INFILESIZE, bodylen); + } else { + curl_easy_setopt(HTTP_HANDLE, CURLOPT_INFILESIZE, 0); + } + + // curl_easy_setopt(HTTP_HANDLE, CURLOPT_VERBOSE, 1); + + curl_easy_setopt(HTTP_HANDLE, CURLOPT_URL, http->url); + curl_easy_setopt(HTTP_HANDLE, CURLOPT_HTTPHEADER, http->req_headers); + curl_easy_setopt(HTTP_HANDLE, CURLOPT_READDATA, &state); + curl_easy_setopt(HTTP_HANDLE, CURLOPT_SEEKDATA, &state); + curl_easy_setopt(HTTP_HANDLE, CURLOPT_WRITEHEADER, &state); + curl_easy_setopt(HTTP_HANDLE, CURLOPT_WRITEDATA, &state); + + if(curl_easy_perform(HTTP_HANDLE) != 0) { + JS_ReportErrorUTF8(cx, "Failed to execute HTTP request: %s", ERRBUF); + if(state.recvbuf) JS_free(cx, state.recvbuf); + return ret; + } + + if(!state.resp_headers) { + JS_ReportErrorUTF8(cx, "Failed to recieve HTTP headers."); + if(state.recvbuf) JS_free(cx, state.recvbuf); + return ret; + } + tmp = JS::ObjectValue(*state.resp_headers); + JS::RootedValue rtmp(cx, tmp); + + if(!JS_DefineProperty( + cx, robj, + "_headers", + rtmp, + JSPROP_READONLY + )) { + JS_ReportErrorUTF8(cx, "INTERNAL: Failed to set response headers."); + if(state.recvbuf) JS_free(cx, state.recvbuf); + return ret;; + } + + if(state.recvbuf) { + state.recvbuf[state.read] = '\0'; + jsbody = dec_string(cx, state.recvbuf, state.read+1); + if(!jsbody) { + // If we can't decode the body as UTF-8 we forcefully + // convert it to a string by just forcing each byte + // to a char16_t. + jsbody = JS_NewStringCopyN(cx, state.recvbuf, state.read); + if(!jsbody) { + if(!JS_IsExceptionPending(cx)) { + JS_ReportErrorUTF8(cx, "INTERNAL: Failed to decode body."); + } + if(state.recvbuf) JS_free(cx, state.recvbuf); + return ret; + } + } + tmp = JS::StringValue(jsbody); + } else { + tmp = JS_GetEmptyStringValue(cx); + } + + JS::RootedValue rtmp2(cx, tmp); + + if(!JS_DefineProperty( + cx, robj, + "responseText", + rtmp2, + JSPROP_READONLY + )) { + JS_ReportErrorUTF8(cx, "INTERNAL: Failed to set responseText."); + if(state.recvbuf) JS_free(cx, state.recvbuf); + return ret; + } + + ret = true; + if(state.recvbuf) JS_free(cx, state.recvbuf); + 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; + + // Assume this is cURL trying to resend a request that + // failed. + if(towrite == 0 && state->sent_once == 0) { + state->sent_once = 1; + return 0; + } else if(towrite == 0) { + state->sent = 0; + state->sent_once = 0; + towrite = state->sendlen; + } + + if(length < towrite) towrite = length; + + 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; + JSString* hdr = NULL; + uint32_t hdrlen; + + 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); + 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; + } + + JS::RootedObject obj(state->cx, state->resp_headers); + if(!JS_GetArrayLength(state->cx, obj, &hdrlen)) { + return CURLE_WRITE_ERROR; + } + + JS::RootedString hdrval(state->cx, hdr); + if(!JS_SetElement(state->cx, obj, 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 = static_cast<char *>(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. + size_t oldlen = state->recvlen; + while(length+1 > state->recvlen - state->read) state->recvlen *= 2; + tmp = static_cast<char *>(JS_realloc(state->cx, state->recvbuf, oldlen, state->recvlen)); + if(!tmp) return CURLE_WRITE_ERROR; + state->recvbuf = tmp; + + memcpy(state->recvbuf + state->read, ptr, length); + state->read += length; + return length; +} + +/*JSString* +str_from_binary(JSContext* cx, char* data, size_t length) +{ + char16_t* conv = static_cast<char16_t *>(JS_malloc(cx, length * sizeof(char16_t))); + JSString* ret = NULL; + size_t i; + + if(!conv) return NULL; + + for(i = 0; i < length; i++) { + conv[i] = (char16_t) data[i]; + } + + ret = JS_NewUCString(cx, conv, length); + if(!ret) JS_free(cx, conv); + + return ret; +} +*/ + +#endif /* HAVE_CURL */ diff --git a/src/couch/priv/couch_js/68/http.h b/src/couch/priv/couch_js/68/http.h new file mode 100644 index 000000000..797b3c060 --- /dev/null +++ b/src/couch/priv/couch_js/68/http.h @@ -0,0 +1,27 @@ +// 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 + +#include "util.h" + +void http_check_enabled(); +bool http_ctor(JSContext* cx, JSObject* req); +void http_dtor(JSFreeOp* fop, JSObject* req); +bool http_open(JSContext* cx, JSObject* req, JS::Value mth, JS::Value url, JS::Value snc); +bool http_set_hdr(JSContext* cx, JSObject* req, JS::Value name, JS::Value val); +bool http_send(JSContext* cx, JSObject* req, JS::Value body); +int http_status(JSContext* cx, JSObject* req); +bool http_uri(JSContext* cx, JSObject *req, couch_args* args, JS::Value* uri); + +#endif diff --git a/src/couch/priv/couch_js/68/main.cpp b/src/couch/priv/couch_js/68/main.cpp new file mode 100644 index 000000000..3860a01a8 --- /dev/null +++ b/src/couch/priv/couch_js/68/main.cpp @@ -0,0 +1,494 @@ +// 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> + +#ifdef XP_WIN +#define NOMINMAX +#include <windows.h> +#else +#include <unistd.h> +#endif + +#include <jsapi.h> +#include <js/CompilationAndEvaluation.h> +#include <js/Conversions.h> +#include <js/Initialization.h> +#include <js/SourceText.h> +#include <js/Warnings.h> +#include <js/Wrapper.h> + +#include "config.h" +#include "http.h" +#include "utf8.h" +#include "util.h" + +static bool enableSharedMemory = true; + +static JSClassOps global_ops = { + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + JS_GlobalObjectTraceHook +}; + +/* The class of the global object. */ +static JSClass global_class = { + "global", + JSCLASS_GLOBAL_FLAGS, + &global_ops +}; + + +static void +req_dtor(JSFreeOp* fop, JSObject* obj) +{ + http_dtor(fop, obj); +} + +// With JSClass.construct. +static const JSClassOps clsOps = { + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + req_dtor, + nullptr, + nullptr, + nullptr +}; + +static const JSClass CouchHTTPClass = { + "CouchHTTP", /* name */ + JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(2), /* flags */ + &clsOps +}; + +static bool +req_ctor(JSContext* cx, unsigned int argc, JS::Value* vp) +{ + bool ret; + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JSObject* obj = JS_NewObjectForConstructor(cx, &CouchHTTPClass, args); + if(!obj) { + JS_ReportErrorUTF8(cx, "Failed to create CouchHTTP instance"); + return false; + } + ret = http_ctor(cx, obj); + args.rval().setObject(*obj); + return ret; +} + +static bool +req_open(JSContext* cx, unsigned int argc, JS::Value* vp) +{ + GET_THIS(cx, argc, vp, args, obj) + bool ret = false; + + if(argc == 2) { + ret = http_open(cx, obj, args[0], args[1], JS::BooleanValue(false)); + } else if(argc == 3) { + ret = http_open(cx, obj, args[0], args[1], args[2]); + } else { + JS_ReportErrorUTF8(cx, "Invalid call to CouchHTTP.open"); + } + + args.rval().setUndefined(); + return ret; +} + + +static bool +req_set_hdr(JSContext* cx, unsigned int argc, JS::Value* vp) +{ + GET_THIS(cx, argc, vp, args, obj) + bool ret = false; + + if(argc == 2) { + ret = http_set_hdr(cx, obj, args[0], args[1]); + } else { + JS_ReportErrorUTF8(cx, "Invalid call to CouchHTTP.set_header"); + } + + args.rval().setUndefined(); + return ret; +} + + +static bool +req_send(JSContext* cx, unsigned int argc, JS::Value* vp) +{ + GET_THIS(cx, argc, vp, args, obj) + bool ret = false; + + if(argc == 1) { + ret = http_send(cx, obj, args[0]); + } else { + JS_ReportErrorUTF8(cx, "Invalid call to CouchHTTP.send"); + } + + args.rval().setUndefined(); + return ret; +} + +static bool +req_status(JSContext* cx, unsigned int argc, JS::Value* vp) +{ + GET_THIS(cx, argc, vp, args, obj) + int status = http_status(cx, obj); + + if(status < 0) + return false; + + args.rval().set(JS::Int32Value(status)); + return true; +} + +static bool +base_url(JSContext *cx, unsigned int argc, JS::Value* vp) +{ + GET_THIS(cx, argc, vp, args, obj) + couch_args *cargs = (couch_args*)JS_GetContextPrivate(cx); + JS::Value uri_val; + bool rc = http_uri(cx, obj, cargs, &uri_val); + args.rval().set(uri_val); + return rc; +} + +static JSObject* +NewSandbox(JSContext* cx, bool lazy) +{ + JS::RealmOptions options; + options.creationOptions().setSharedMemoryAndAtomicsEnabled(enableSharedMemory); + options.creationOptions().setNewCompartmentAndZone(); + JS::RootedObject obj(cx, JS_NewGlobalObject(cx, &global_class, nullptr, + JS::DontFireOnNewGlobalHook, options)); + if (!obj) + return nullptr; + + { + JSAutoRealm ac(cx, obj); + if (!lazy && !JS::InitRealmStandardClasses(cx)) + return nullptr; + + JS::RootedValue value(cx, JS::BooleanValue(lazy)); + if (!JS_DefineProperty(cx, obj, "lazy", value, JSPROP_PERMANENT | JSPROP_READONLY)) + return nullptr; + + JS_FireOnNewGlobalObject(cx, obj); + } + + if (!JS_WrapObject(cx, &obj)) + return nullptr; + return obj; +} + +static bool +evalcx(JSContext *cx, unsigned int argc, JS::Value* vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + bool ret = false; + + JS::RootedString str(cx, args[0].toString()); + if (!str) + return false; + + JS::RootedObject sandbox(cx); + if (args.hasDefined(1)) { + sandbox = JS::ToObject(cx, args[1]); + if (!sandbox) + return false; + } + + if (!sandbox) { + sandbox = NewSandbox(cx, false); + if (!sandbox) + return false; + } + + JS::AutoStableStringChars strChars(cx); + if (!strChars.initTwoByte(cx, str)) + return false; + + mozilla::Range<const char16_t> chars = strChars.twoByteRange(); + JS::SourceText<char16_t> srcBuf; + if (!srcBuf.init(cx, chars.begin().get(), chars.length(), + JS::SourceOwnership::Borrowed)) { + return false; + } + + if(srcBuf.length() == 0) { + args.rval().setObject(*sandbox); + } else { + mozilla::Maybe<JSAutoRealm> ar; + unsigned flags; + JSObject* unwrapped = UncheckedUnwrap(sandbox, true, &flags); + if (flags & js::Wrapper::CROSS_COMPARTMENT) { + sandbox = unwrapped; + ar.emplace(cx, sandbox); + } + + JS::CompileOptions opts(cx); + JS::RootedValue rval(cx); + opts.setFileAndLine("<unknown>", 1); + + if (!JS::Evaluate(cx, opts, srcBuf, args.rval())) { + return false; + } + } + ret = true; + if (!JS_WrapValue(cx, args.rval())) + return false; + + return ret; +} + + +static bool +gc(JSContext* cx, unsigned int argc, JS::Value* vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS_GC(cx); + args.rval().setUndefined(); + return true; +} + + +static bool +print(JSContext* cx, unsigned int argc, JS::Value* vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + couch_print(cx, argc, args); + args.rval().setUndefined(); + return true; +} + + +static bool +quit(JSContext* cx, unsigned int argc, JS::Value* vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + int exit_code = args[0].toInt32();; + exit(exit_code); +} + + +static bool +readline(JSContext* cx, unsigned int argc, JS::Value* vp) +{ + JSString* line; + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + /* GC Occasionally */ + JS_MaybeGC(cx); + + line = couch_readline(cx, stdin); + if(line == NULL) return false; + + // return with JSString* instead of JSValue in the past + args.rval().setString(line); + return true; +} + + +static bool +seal(JSContext* cx, unsigned int argc, JS::Value* vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::RootedObject target(cx); + target = JS::ToObject(cx, args[0]); + if (!target) { + args.rval().setUndefined(); + return true; + } + bool deep = false; + deep = args[1].toBoolean(); + bool ret = deep ? JS_DeepFreezeObject(cx, target) : JS_FreezeObject(cx, target); + args.rval().setUndefined(); + return ret; +} + + +static bool +js_sleep(JSContext* cx, unsigned int argc, JS::Value* vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + int duration = args[0].toInt32(); + +#ifdef XP_WIN + Sleep(duration); +#else + usleep(duration * 1000); +#endif + + return true; +} + +JSPropertySpec CouchHTTPProperties[] = { + JS_PSG("status", req_status, 0), + JS_PSG("base_url", base_url, 0), + JS_PS_END +}; + + +JSFunctionSpec CouchHTTPFunctions[] = { + JS_FN("_open", req_open, 3, 0), + JS_FN("_setRequestHeader", req_set_hdr, 2, 0), + JS_FN("_send", req_send, 1, 0), + JS_FS_END +}; + + +JSFunctionSpec TestSuiteFunctions[] = { + JS_FN("sleep", js_sleep, 1, 0), + JS_FS_END +}; + + +static JSFunctionSpec global_functions[] = { + JS_FN("evalcx", evalcx, 0, 0), + JS_FN("gc", gc, 0, 0), + JS_FN("print", print, 0, 0), + JS_FN("quit", quit, 0, 0), + JS_FN("readline", readline, 0, 0), + JS_FN("seal", seal, 0, 0), + JS_FS_END +}; + + +static bool +csp_allows(JSContext* cx, JS::HandleValue code) +{ + couch_args *args = (couch_args*)JS_GetContextPrivate(cx); + if(args->eval) { + return true; + } else { + return false; + } +} + + +static JSSecurityCallbacks security_callbacks = { + csp_allows, + nullptr +}; + + +int +main(int argc, const char* argv[]) +{ + JSContext* cx = NULL; + JSObject* klass = NULL; + int i; + + couch_args* args = couch_parse_args(argc, argv); + + JS_Init(); + cx = JS_NewContext(args->stack_size, 8L * 1024L); + if(cx == NULL) + return 1; + + JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_BASELINE_ENABLE, 0); + JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_ION_ENABLE, 0); + + if (!JS::InitSelfHostedCode(cx)) + return 1; + + JS::SetWarningReporter(cx, couch_error); + JS::SetOutOfMemoryCallback(cx, couch_oom, NULL); + JS_SetContextPrivate(cx, args); + JS_SetSecurityCallbacks(cx, &security_callbacks); + + JS::RealmOptions options; + JS::RootedObject global(cx, JS_NewGlobalObject(cx, &global_class, nullptr, + JS::FireOnNewGlobalHook, options)); + if (!global) + return 1; + + JSAutoRealm ar(cx, global); + + if(!JS::InitRealmStandardClasses(cx)) + return 1; + + if(couch_load_funcs(cx, global, global_functions) != true) + return 1; + + if(args->use_http) { + http_check_enabled(); + + klass = JS_InitClass( + cx, global, + NULL, + &CouchHTTPClass, req_ctor, + 0, + CouchHTTPProperties, CouchHTTPFunctions, + NULL, NULL + ); + + if(!klass) + { + fprintf(stderr, "Failed to initialize CouchHTTP class.\n"); + exit(2); + } + } + + if(args->use_test_funs) { + if(couch_load_funcs(cx, global, TestSuiteFunctions) != true) + return 1; + } + + for(i = 0 ; args->scripts[i] ; i++) { + const char* filename = args->scripts[i]; + + // Compile and run + JS::CompileOptions options(cx); + options.setFileAndLine(filename, 1); + JS::RootedScript script(cx); + FILE* fp; + + fp = fopen(args->scripts[i], "r"); + if(fp == NULL) { + fprintf(stderr, "Failed to read file: %s\n", filename); + return 3; + } + script = JS::CompileUtf8File(cx, options, fp); + fclose(fp); + if (!script) { + fprintf(stderr, "Failed to compile file: %s\n", filename); + return 1; + } + + JS::RootedValue result(cx); + if(JS_ExecuteScript(cx, script, &result) != true) { + fprintf(stderr, "Failed to execute script.\n"); + return 1; + } + + // Give the GC a chance to run. + JS_MaybeGC(cx); + } + + return 0; +} diff --git a/src/couch/priv/couch_js/68/utf8.cpp b/src/couch/priv/couch_js/68/utf8.cpp new file mode 100644 index 000000000..c28e026f7 --- /dev/null +++ b/src/couch/priv/couch_js/68/utf8.cpp @@ -0,0 +1,309 @@ +// 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> +#include <js/Initialization.h> +#include <js/Conversions.h> +#include <js/MemoryFunctions.h> +#include <js/Wrapper.h> +#include "config.h" +#include "util.h" + +static int +enc_char(uint8_t *utf8Buffer, uint32_t ucs4Char) +{ + int utf8Length = 1; + + if (ucs4Char < 0x80) + { + *utf8Buffer = (uint8_t)ucs4Char; + } + else + { + int i; + uint32_t a = ucs4Char >> 11; + utf8Length = 2; + while(a) + { + a >>= 5; + utf8Length++; + } + i = utf8Length; + while(--i) + { + utf8Buffer[i] = (uint8_t)((ucs4Char & 0x3F) | 0x80); + ucs4Char >>= 6; + } + *utf8Buffer = (uint8_t)(0x100 - (1 << (8-utf8Length)) + ucs4Char); + } + + return utf8Length; +} + +static bool +enc_charbuf(const char16_t* src, size_t srclen, char* dst, size_t* dstlenp) +{ + size_t i; + size_t utf8Len; + size_t dstlen = *dstlenp; + size_t origDstlen = dstlen; + char16_t c; + char16_t c2; + uint32_t v; + uint8_t utf8buf[6]; + + if(!dst) + { + dstlen = origDstlen = (size_t) -1; + } + + while(srclen) + { + c = *src++; + srclen--; + + if(c <= 0xD7FF || c >= 0xE000) + { + v = (uint32_t) c; + } + else if(c >= 0xD800 && c <= 0xDBFF) + { + if(srclen < 1) goto buffer_too_small; + c2 = *src++; + srclen--; + if(c2 >= 0xDC00 && c2 <= 0xDFFF) + { + v = (uint32_t) (((c - 0xD800) << 10) + (c2 - 0xDC00) + 0x10000); + } + else + { + // Invalid second half of surrogate pair + v = (uint32_t) 0xFFFD; + // Undo our character advancement + src--; + srclen++; + } + } + else + { + // Invalid first half surrogate pair + v = (uint32_t) 0xFFFD; + } + + 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 true; + +buffer_too_small: + *dstlenp = (origDstlen - dstlen); + return false; +} + +char* +enc_string(JSContext* cx, JS::Value arg, size_t* buflen) +{ + JSString* str = NULL; + const char16_t* src = NULL; + char* bytes = NULL; + size_t srclen = 0; + size_t byteslen = 0; + JS::AutoStableStringChars rawChars(cx); + + str = arg.toString(); + if(!str) goto error; + + if (!rawChars.initTwoByte(cx, str)) + return NULL; + + src = rawChars.twoByteRange().begin().get(); + srclen = JS_GetStringLength(str); + + if(!enc_charbuf(src, srclen, NULL, &byteslen)) goto error; + + bytes = js_pod_malloc<char>(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: +/* + JS::RootedString str(cx, arg.toString()); + JS::UniqueChars chars = JS_EncodeStringToUTF8(cx, str); + + if(buflen) *buflen = strlen(chars.get()); + + return JS_NewUCStringCopyN(cs, chars.get(), buflen); +*/ + return bytes; +} + +static uint32_t +dec_char(const uint8_t *utf8Buffer, int utf8Length) +{ + uint32_t ucs4Char; + uint32_t minucs4Char; + + // from Unicode 3.1, non-shortest form is illegal + static const uint32_t 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 bool +dec_charbuf(const char *src, size_t srclen, char16_t *dst, size_t *dstlenp) +{ + uint32_t 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_t) *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_t *) src, n); + if(v >= 0x10000) + { + v -= 0x10000; + + if(v > 0xFFFFF || dstlen < 2) + { + *dstlenp = (origDstlen - dstlen); + return false; + } + + if(dstlen < 2) goto buffer_too_small; + + if(dst) + { + *dst++ = (char16_t)((v >> 10) + 0xD800); + v = (char16_t)((v & 0x3FF) + 0xDC00); + } + dstlen--; + } + } + + if(!dstlen) goto buffer_too_small; + if(dst) *dst++ = (char16_t) v; + + dstlen--; + offset += n; + src += n; + srclen -= n; + } + + *dstlenp = (origDstlen - dstlen); + return true; + +bad_character: + *dstlenp = (origDstlen - dstlen); + return false; + +buffer_too_small: + *dstlenp = (origDstlen - dstlen); + return false; +} + +JSString* +dec_string(JSContext* cx, const char* bytes, size_t byteslen) +{ + JSString* str = NULL; + size_t charslen; + + if(!dec_charbuf(bytes, byteslen, NULL, &charslen)) return NULL; + + JS::UniqueTwoByteChars chars(js_pod_malloc<char16_t>(charslen + 1)); + if(!chars) return NULL; + chars.get()[charslen] = 0; + + if(!dec_charbuf(bytes, byteslen, chars.get(), &charslen)) goto error; + + str = JS_NewUCString(cx, std::move(chars), charslen - 1); + if(!str) goto error; + + goto success; + +error: + if(chars != NULL) JS_free(cx, chars.get()); + str = NULL; + +success: + return str; +} diff --git a/src/couch/priv/couch_js/68/utf8.h b/src/couch/priv/couch_js/68/utf8.h new file mode 100644 index 000000000..c8b1f4d82 --- /dev/null +++ b/src/couch/priv/couch_js/68/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, JS::Value arg, size_t* buflen); +JSString* dec_string(JSContext* cx, const char* buf, size_t buflen); + +#endif diff --git a/src/couch/priv/couch_js/68/util.cpp b/src/couch/priv/couch_js/68/util.cpp new file mode 100644 index 000000000..f941e7dd2 --- /dev/null +++ b/src/couch/priv/couch_js/68/util.cpp @@ -0,0 +1,350 @@ +// 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 <js/Conversions.h> +#include <js/Initialization.h> +#include <js/MemoryFunctions.h> +#include <js/RegExp.h> + +#include "help.h" +#include "util.h" +#include "utf8.h" + +/* +std::string +js_to_string(JSContext* cx, JS::HandleValue val) +{ + JS::RootedString sval(cx); + sval = val.toString(); + + JS::UniqueChars chars(JS_EncodeStringToUTF8(cx, sval)); + if(!chars) { + JS_ClearPendingException(cx); + fprintf(stderr, "Error converting value to string.\n"); + exit(3); + } + + return chars.get(); +} + +std::string +js_to_string(JSContext* cx, JSString *str) +{ + JS::UniqueChars chars(JS_EncodeString(cx, str)); + if(!chars) { + JS_ClearPendingException(cx); + fprintf(stderr, "Error converting to string.\n"); + exit(3); + } + + return chars.get(); +} +*/ + +JSString* +string_to_js(JSContext* cx, const std::string& s) +{ +/* + + JSString* ret = JS_NewStringCopyN(cx, s.c_str(), s.size()); + if(ret != nullptr) { + return ret; + } + + fprintf(stderr, "Unable to allocate string object.\n"); + exit(3); +*/ + return dec_string(cx, s.c_str(), s.size()); +} + +size_t +couch_readfile(const char* file, char** outbuf_p) +{ + FILE* fp; + char fbuf[16384]; + char *buf = NULL; + char* tmp; + size_t nread = 0; + size_t buflen = 0; + + if(strcmp(file, "-") == 0) { + fp = stdin; + } else { + fp = fopen(file, "r"); + if(fp == NULL) { + fprintf(stderr, "Failed to read file: %s\n", file); + exit(3); + } + } + + while((nread = fread(fbuf, 1, 16384, fp)) > 0) { + if(buf == NULL) { + buf = (char*) malloc(nread + 1); + if(buf == NULL) { + fprintf(stderr, "Out of memory.\n"); + exit(3); + } + memcpy(buf, fbuf, nread); + } else { + tmp = (char*) malloc(buflen + nread + 1); + if(tmp == NULL) { + fprintf(stderr, "Out of memory.\n"); + exit(3); + } + memcpy(tmp, buf, buflen); + memcpy(tmp+buflen, fbuf, nread); + free(buf); + buf = tmp; + } + buflen += nread; + buf[buflen] = '\0'; + } + *outbuf_p = buf; + return buflen ; +} + +couch_args* +couch_parse_args(int argc, const char* argv[]) +{ + couch_args* args; + int i = 1; + + args = (couch_args*) malloc(sizeof(couch_args)); + if(args == NULL) + return NULL; + + memset(args, '\0', sizeof(couch_args)); + args->stack_size = 64L * 1024L * 1024L; + + while(i < argc) { + if(strcmp("-h", argv[i]) == 0) { + DISPLAY_USAGE; + exit(0); + } else if(strcmp("-V", argv[i]) == 0) { + DISPLAY_VERSION; + exit(0); + } else if(strcmp("-H", argv[i]) == 0) { + args->use_http = 1; + } else if(strcmp("-T", argv[i]) == 0) { + args->use_test_funs = 1; + } else if(strcmp("-S", argv[i]) == 0) { + args->stack_size = atoi(argv[++i]); + if(args->stack_size <= 0) { + fprintf(stderr, "Invalid stack size.\n"); + exit(2); + } + } else if(strcmp("-u", argv[i]) == 0) { + args->uri_file = argv[++i]; + } else if(strcmp("--eval", argv[i]) == 0) { + args->eval = 1; + } else if(strcmp("--", argv[i]) == 0) { + i++; + break; + } else { + break; + } + i++; + } + + if(i >= argc) { + DISPLAY_USAGE; + exit(3); + } + args->scripts = argv + i; + + return args; +} + + +int +couch_fgets(char* buf, int size, FILE* fp) +{ + int n, i, c; + + if(size <= 0) return -1; + n = size - 1; + + for(i = 0; i < n && (c = getc(fp)) != EOF; i++) { + buf[i] = c; + if(c == '\n') { + i++; + break; + } + } + + buf[i] = '\0'; + return i; +} + + +JSString* +couch_readline(JSContext* cx, FILE* fp) +{ + JSString* str; + char* bytes = NULL; + char* tmp = NULL; + size_t used = 0; + size_t byteslen = 256; + size_t oldbyteslen = 256; + size_t readlen = 0; + bool sawNewline = false; + + bytes = static_cast<char *>(JS_malloc(cx, byteslen)); + if(bytes == NULL) return NULL; + + while((readlen = couch_fgets(bytes+used, byteslen-used, fp)) > 0) { + used += readlen; + + if(bytes[used-1] == '\n') { + bytes[used-1] = '\0'; + sawNewline = true; + break; + } + + // Double our buffer and read more. + oldbyteslen = byteslen; + byteslen *= 2; + tmp = static_cast<char *>(JS_realloc(cx, bytes, oldbyteslen, byteslen)); + if(!tmp) { + JS_free(cx, bytes); + return NULL; + } + + bytes = tmp; + } + + // Treat empty strings specially + if(used == 0) { + JS_free(cx, bytes); + return JS_NewStringCopyZ(cx, nullptr); + } + + // Shrink the buffer to the actual data size + tmp = static_cast<char *>(JS_realloc(cx, bytes, byteslen, used)); + if(!tmp) { + JS_free(cx, bytes); + return NULL; + } + bytes = tmp; + byteslen = used; + + str = string_to_js(cx, std::string(tmp, byteslen)); + JS_free(cx, bytes); + return str; +} + + +void +couch_print(JSContext* cx, unsigned int argc, JS::CallArgs argv) +{ + FILE *stream = stdout; + + if (argc) { + if (argc > 1 && argv[1].isTrue()) { + stream = stderr; + } + JS::AutoSaveExceptionState exc_state(cx); + JS::RootedString sval(cx, JS::ToString(cx, argv[0])); + if (!sval) { + fprintf(stream, "couch_print: <cannot convert value to string>\n"); + fflush(stream); + return; + } + JS::UniqueChars bytes(JS_EncodeStringToUTF8(cx, sval)); + if (!bytes) + return; + + fprintf(stream, "%s", bytes.get()); + exc_state.restore(); + } + + fputc('\n', stream); + fflush(stream); +} + + +void +couch_error(JSContext* cx, JSErrorReport* report) +{ + JS::RootedValue v(cx), stack(cx), replace(cx); + char* bytes; + JSObject* regexp; + + if(!report || !JSREPORT_IS_WARNING(report->flags)) + { + fprintf(stderr, "%s\n", report->message().c_str()); + + // Print a stack trace, if available. + if (JSREPORT_IS_EXCEPTION(report->flags) && + JS_GetPendingException(cx, &v)) + { + // Clear the exception before an JS method calls or the result is + // infinite, recursive error report generation. + JS_ClearPendingException(cx); + + // Use JS regexp to indent the stack trace. + // If the regexp can't be created, don't JS_ReportErrorUTF8 since it is + // probably not productive to wind up here again. + JS::RootedObject vobj(cx, v.toObjectOrNull()); + + if(JS_GetProperty(cx, vobj, "stack", &stack) && + (regexp = JS::NewRegExpObject( + cx, "^(?=.)", 6, JS::RegExpFlag::Global | JS::RegExpFlag::Multiline))) + + { + // Set up the arguments to ``String.replace()`` + JS::RootedValueVector re_args(cx); + JS::RootedValue arg0(cx, JS::ObjectValue(*regexp)); + auto arg1 = JS::StringValue(string_to_js(cx, "\t")); + + if (re_args.append(arg0) && re_args.append(arg1)) { + // Perform the replacement + JS::RootedObject sobj(cx, stack.toObjectOrNull()); + if(JS_GetProperty(cx, sobj, "replace", &replace) && + JS_CallFunctionValue(cx, sobj, replace, re_args, &v)) + { + // Print the result + bytes = enc_string(cx, v, NULL); + fprintf(stderr, "Stacktrace:\n%s", bytes); + JS_free(cx, bytes); + } + } + } + } + } +} + + +void +couch_oom(JSContext* cx, void* data) +{ + fprintf(stderr, "out of memory\n"); + exit(1); +} + + +bool +couch_load_funcs(JSContext* cx, JS::HandleObject obj, JSFunctionSpec* funcs) +{ + JSFunctionSpec* f; + for(f = funcs; f->name; f++) { + if(!JS_DefineFunction(cx, obj, f->name.string(), f->call.op, f->nargs, f->flags)) { + fprintf(stderr, "Failed to create function: %s\n", f->name.string()); + return false; + } + } + return true; +} diff --git a/src/couch/priv/couch_js/68/util.h b/src/couch/priv/couch_js/68/util.h new file mode 100644 index 000000000..dc8a3a7b4 --- /dev/null +++ b/src/couch/priv/couch_js/68/util.h @@ -0,0 +1,60 @@ +// 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 COUCHJS_UTIL_H +#define COUCHJS_UTIL_H + +#include <jsapi.h> + +typedef struct { + int eval; + int use_http; + int use_test_funs; + int stack_size; + const char** scripts; + const char* uri_file; + JSString* uri; +} couch_args; + +/* +std::string js_to_string(JSContext* cx, JS::HandleValue val); +std::string js_to_string(JSContext* cx, JSString *str); +JSString* string_to_js(JSContext* cx, const std::string& s); +*/ + +couch_args* couch_parse_args(int argc, const char* argv[]); +int couch_fgets(char* buf, int size, FILE* fp); +JSString* couch_readline(JSContext* cx, FILE* fp); +size_t couch_readfile(const char* file, char** outbuf_p); +void couch_print(JSContext* cx, unsigned int argc, JS::CallArgs argv); +void couch_error(JSContext* cx, JSErrorReport* report); +void couch_oom(JSContext* cx, void* data); +bool couch_load_funcs(JSContext* cx, JS::HandleObject obj, JSFunctionSpec* funcs); + +/* + * GET_THIS: + * @cx: JSContext pointer passed into JSNative function + * @argc: Number of arguments passed into JSNative function + * @vp: Argument value array passed into JSNative function + * @args: Name for JS::CallArgs variable defined by this code snippet + * @to: Name for JS::RootedObject variable referring to function's this + * + * A convenience macro for getting the 'this' object a function was called with. + * Use in any JSNative function. + */ +#define GET_THIS(cx, argc, vp, args, to) \ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); \ + JS::RootedObject to(cx); \ + if (!args.computeThis(cx, &to)) \ + return false; + +#endif // Included util.h diff --git a/src/couch/rebar.config.script b/src/couch/rebar.config.script index 91e24d99e..89c652a58 100644 --- a/src/couch/rebar.config.script +++ b/src/couch/rebar.config.script @@ -22,7 +22,7 @@ CopyIfDifferent = fun(Path, Contents) -> false -> file:write_file(Path, Contents) end -end, +end. CouchJSName = case os:type() of @@ -30,21 +30,21 @@ CouchJSName = case os:type() of "couchjs.exe"; _ -> "couchjs" -end, -CouchJSPath = filename:join(["priv", CouchJSName]), +end. +CouchJSPath = filename:join(["priv", CouchJSName]). Version = case os:getenv("COUCHDB_VERSION") of false -> string:strip(os:cmd("git describe --always"), right, $\n); Version0 -> string:strip(Version0, right) -end, +end. GitSha = case os:getenv("COUCHDB_GIT_SHA") of false -> ""; % release builds won’t get a fallback GitSha0 -> string:strip(GitSha0, right) -end, +end. CouchConfig = case filelib:is_file(os:getenv("COUCHDB_CONFIG")) of true -> @@ -59,6 +59,8 @@ SMVsn = case lists:keyfind(spidermonkey_version, 1, CouchConfig) of "1.8.5"; {_, "60"} -> "60"; + {_, "68"} -> + "68"; undefined -> "1.8.5"; {_, Unsupported} -> @@ -78,24 +80,24 @@ ConfigH = [ {"PACKAGE_NAME", "\"Apache CouchDB\""}, {"PACKAGE_STRING", "\"Apache CouchDB " ++ Version ++ "\""}, {"PACKAGE_VERSION", "\"" ++ Version ++ "\""} -], +]. -CouchJSConfig = "priv/couch_js/" ++ SMVsn ++ "/config.h", -ConfigSrc = [["#define ", K, " ", V, $\n] || {K, V} <- ConfigH], -ConfigBin = iolist_to_binary(ConfigSrc), -ok = CopyIfDifferent(CouchJSConfig, ConfigBin), +CouchJSConfig = "priv/couch_js/" ++ SMVsn ++ "/config.h". +ConfigSrc = [["#define ", K, " ", V, $\n] || {K, V} <- ConfigH]. +ConfigBin = iolist_to_binary(ConfigSrc). +ok = CopyIfDifferent(CouchJSConfig, ConfigBin). MD5Config = case lists:keyfind(erlang_md5, 1, CouchConfig) of {erlang_md5, true} -> [{d, 'ERLANG_MD5', true}]; _ -> [] -end, +end. ProperConfig = case code:lib_dir(proper) of {error, bad_name} -> []; _ -> [{d, 'WITH_PROPER'}] -end, +end. {JS_CFLAGS, JS_LDFLAGS} = case os:type() of {win32, _} when SMVsn == "1.8.5" -> @@ -122,6 +124,11 @@ end, { "-DXP_UNIX -I/usr/include/mozjs-60 -I/usr/local/include/mozjs-60 -std=c++14", "-L/usr/local/lib -std=c++14 -lmozjs-60 -lm" + }; + {unix, _} when SMVsn == "68" -> + { + "-DXP_UNIX -I/usr/include/mozjs-68 -I/usr/local/include/mozjs-68 -std=c++14 -Wno-invalid-offsetof", + "-L/usr/local/lib -std=c++14 -lmozjs-68 -lm" } end. @@ -146,11 +153,12 @@ end. end; _ -> {"", ""} -end, +end. CouchJSSrc = case SMVsn of "1.8.5" -> ["priv/couch_js/1.8.5/*.c"]; - "60" -> ["priv/couch_js/60/*.cpp"] + "60" -> ["priv/couch_js/60/*.cpp"]; + "68" -> ["priv/couch_js/68/*.cpp"] end. CouchJSEnv = case SMVsn of @@ -159,26 +167,26 @@ CouchJSEnv = case SMVsn of {"CFLAGS", JS_CFLAGS ++ " " ++ CURL_CFLAGS}, {"LDFLAGS", JS_LDFLAGS ++ " " ++ CURL_LDFLAGS} ]; - "60" -> + _ -> [ {"CXXFLAGS", JS_CFLAGS ++ " " ++ CURL_CFLAGS}, {"LDFLAGS", JS_LDFLAGS ++ " " ++ CURL_LDFLAGS} ] -end, +end. -IcuPath = "priv/couch_icu_driver.so", -IcuSrc = ["priv/icu_driver/*.c"], +IcuPath = "priv/couch_icu_driver.so". +IcuSrc = ["priv/icu_driver/*.c"]. IcuEnv = [{"DRV_CFLAGS", "$DRV_CFLAGS -DPIC -O2 -fno-common"}, - {"DRV_LDFLAGS", "$DRV_LDFLAGS -lm -licuuc -licudata -licui18n -lpthread"}], + {"DRV_LDFLAGS", "$DRV_LDFLAGS -lm -licuuc -licudata -licui18n -lpthread"}]. IcuDarwinEnv = [{"CFLAGS", "-DXP_UNIX -I/usr/local/opt/icu4c/include"}, - {"LDFLAGS", "-L/usr/local/opt/icu4c/lib"}], + {"LDFLAGS", "-L/usr/local/opt/icu4c/lib"}]. IcuBsdEnv = [{"CFLAGS", "-DXP_UNIX -I/usr/local/include"}, - {"LDFLAGS", "-L/usr/local/lib"}], + {"LDFLAGS", "-L/usr/local/lib"}]. IcuWinEnv = [{"CFLAGS", "$DRV_CFLAGS /DXP_WIN"}, - {"LDFLAGS", "icuin.lib icudt.lib icuuc.lib"}], + {"LDFLAGS", "icuin.lib icudt.lib icuuc.lib"}]. -ComparePath = "priv/couch_ejson_compare.so", -CompareSrc = ["priv/couch_ejson_compare/*.c"], +ComparePath = "priv/couch_ejson_compare.so". +CompareSrc = ["priv/couch_ejson_compare/*.c"]. BaseSpecs = [ %% couchjs @@ -193,17 +201,17 @@ BaseSpecs = [ {"linux", ComparePath, CompareSrc, [{env, IcuEnv}]}, {"bsd", ComparePath, CompareSrc, [{env, IcuEnv ++ IcuBsdEnv}]}, {"win32", ComparePath, CompareSrc, [{env, IcuWinEnv}]} -], +]. SpawnSpec = [ {"priv/couchspawnkillable", ["priv/spawnkillable/*.c"]} -], +]. %% hack required until switch to enc/rebar3 PortEnvOverrides = [ {"win32", "EXE_LINK_CXX_TEMPLATE", "$LINKER $PORT_IN_FILES $LDFLAGS $EXE_LDFLAGS /OUT:$PORT_OUT_FILE"} -], +]. PortSpecs = case os:type() of {win32, _} -> @@ -213,10 +221,10 @@ PortSpecs = case os:type() of ok = CopyIfDifferent("priv/couchspawnkillable", CSK), os:cmd("chmod +x priv/couchspawnkillable"), BaseSpecs -end, +end. PlatformDefines = [ {platform_define, "win32", 'WINDOWS'} -], +]. AddConfig = [ {port_specs, PortSpecs}, {erl_opts, PlatformDefines ++ [ diff --git a/support/build_js.escript b/support/build_js.escript index 90ad3168f..2d9de6112 100644 --- a/support/build_js.escript +++ b/support/build_js.escript @@ -70,6 +70,12 @@ main([]) -> "share/server/60/esprima.js", "share/server/60/escodegen.js", "share/server/60/rewrite_fun.js" + ]; + "68" -> + [ + "share/server/60/esprima.js", + "share/server/60/escodegen.js", + "share/server/60/rewrite_fun.js" ] end, |