summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoan Touzet <wohali@users.noreply.github.com>2020-04-27 12:47:22 -0400
committerGitHub <noreply@github.com>2020-04-27 12:47:22 -0400
commit10ba83aa10d674e2d247d343db0e1b5d114d86dc (patch)
tree32a6425b652ee3935853b3750c344e44bbcb2705
parent34eefa395c8ff9b7c06dd12c0dd4122e283742e2 (diff)
parent55deba0509038b9d892e72ae9ed029aa8905afe5 (diff)
downloadcouchdb-robust-black.tar.gz
Merge branch 'master' into robust-blackrobust-black
-rw-r--r--.gitignore2
-rw-r--r--rebar.config.script16
-rw-r--r--src/couch/priv/couch_js/68/help.h86
-rw-r--r--src/couch/priv/couch_js/68/http.cpp650
-rw-r--r--src/couch/priv/couch_js/68/http.h27
-rw-r--r--src/couch/priv/couch_js/68/main.cpp535
-rw-r--r--src/couch/priv/couch_js/68/util.cpp358
-rw-r--r--src/couch/priv/couch_js/68/util.h41
-rw-r--r--src/couch/rebar.config.script68
-rw-r--r--src/couch/src/couch_query_servers.erl107
-rw-r--r--src/couch/test/eunit/couch_js_tests.erl1
-rw-r--r--src/mango/test/21-empty-selector-tests.py10
-rw-r--r--support/build_js.escript6
13 files changed, 1859 insertions, 48 deletions
diff --git a/.gitignore b/.gitignore
index 3cfa3721e..645817b76 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,6 +13,7 @@
.rebar/
.eunit/
cover/
+core
log
apache-couchdb-*/
bin/
@@ -116,6 +117,7 @@ src/mango/ebin/
src/mango/test/*.pyc
src/mango/nosetests.xml
src/mango/venv/
+src/jwtf/.rebar3/
test/javascript/junit.xml
/_build/
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..20a609701
--- /dev/null
+++ b/src/couch/priv/couch_js/68/http.cpp
@@ -0,0 +1,650 @@
+// 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 "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
+#endif
+
+
+typedef struct curl_slist CurlHeaders;
+
+
+typedef struct {
+ int method;
+ std::string 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, std::string& body);
+
+
+bool
+http_ctor(JSContext* cx, JSObject* req)
+{
+ HTTPData* http = new HTTPData();
+ bool ret = false;
+
+ if(!http)
+ {
+ JS_ReportErrorUTF8(cx, "Failed to create CouchHTTP instance.");
+ goto error;
+ }
+
+ http->method = -1;
+ http->req_headers = NULL;
+ http->last_status = -1;
+
+ JS_SetPrivate(req, http);
+
+ ret = true;
+ goto success;
+
+error:
+ if(http) delete http;
+
+success:
+ return ret;
+}
+
+
+void
+http_dtor(JSFreeOp* fop, JSObject* obj)
+{
+ HTTPData* http = (HTTPData*) JS_GetPrivate(obj);
+ if(http) {
+ if(http->req_headers) curl_slist_free_all(http->req_headers);
+ delete http;
+ }
+}
+
+
+bool
+http_open(JSContext* cx, JSObject* req, JS::Value mth, JS::Value url, JS::Value snc)
+{
+ HTTPData* http = (HTTPData*) JS_GetPrivate(req);
+ int methid;
+
+ if(!http) {
+ JS_ReportErrorUTF8(cx, "Invalid CouchHTTP instance.");
+ return false;
+ }
+
+ if(!mth.isString()) {
+ JS_ReportErrorUTF8(cx, "Method must be a string.");
+ return false;
+ }
+
+ std::string method;
+ if(!js_to_string(cx, JS::RootedValue(cx, mth), method)) {
+ JS_ReportErrorUTF8(cx, "Failed to encode method.");
+ return false;
+ }
+
+ for(methid = 0; METHODS[methid] != NULL; methid++) {
+ if(strcasecmp(METHODS[methid], method.c_str()) == 0) break;
+ }
+
+ if(methid > OPTIONS) {
+ JS_ReportErrorUTF8(cx, "Invalid method specified.");
+ return false;
+ }
+
+ http->method = methid;
+
+ if(!url.isString()) {
+ JS_ReportErrorUTF8(cx, "URL must be a string");
+ return false;
+ }
+
+ std::string urlstr;
+ if(!js_to_string(cx, JS::RootedValue(cx, url), urlstr)) {
+ JS_ReportErrorUTF8(cx, "Failed to encode URL.");
+ return false;
+ }
+ http->url = urlstr;
+
+ if(snc.isBoolean() && snc.isTrue()) {
+ JS_ReportErrorUTF8(cx, "Synchronous flag must be false.");
+ return false;
+ }
+
+ 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:");
+
+ return true;
+}
+
+
+bool
+http_set_hdr(JSContext* cx, JSObject* req, JS::Value name, JS::Value val)
+{
+ HTTPData* http = (HTTPData*) JS_GetPrivate(req);
+
+ if(!http) {
+ JS_ReportErrorUTF8(cx, "Invalid CouchHTTP instance.");
+ return false;
+ }
+
+ if(!name.isString())
+ {
+ JS_ReportErrorUTF8(cx, "Header names must be strings.");
+ return false;
+ }
+
+ std::string keystr;
+ if(!js_to_string(cx, JS::RootedValue(cx, name), keystr))
+ {
+ JS_ReportErrorUTF8(cx, "Failed to encode header name.");
+ return false;
+ }
+
+ if(!val.isString())
+ {
+ JS_ReportErrorUTF8(cx, "Header values must be strings.");
+ return false;
+ }
+
+ std::string valstr;
+ if(!js_to_string(cx, JS::RootedValue(cx, val), valstr)) {
+ JS_ReportErrorUTF8(cx, "Failed to encode header value.");
+ return false;
+ }
+
+ std::string header = keystr + ": " + valstr;
+ http->req_headers = curl_slist_append(http->req_headers, header.c_str());
+
+ return true;
+}
+
+bool
+http_send(JSContext* cx, JSObject* req, JS::Value body)
+{
+ HTTPData* http = (HTTPData*) JS_GetPrivate(req);
+
+ if(!http) {
+ JS_ReportErrorUTF8(cx, "Invalid CouchHTTP instance.");
+ return false;
+ }
+
+ std::string bodystr;
+ if(!js_to_string(cx, JS::RootedValue(cx, body), bodystr)) {
+ JS_ReportErrorUTF8(cx, "Failed to encode body.");
+ return false;
+ }
+
+ return go(cx, req, http, bodystr);
+}
+
+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;
+ const 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, std::string& body)
+{
+ CurlState state;
+ 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.c_str();;
+ state.sendlen = body.size();
+ 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);
+
+ std::string referer;
+ if(!js_to_string(cx, JS::RootedValue(cx, tmp), referer)) {
+ 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.c_str());
+
+ 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.size() > 0) {
+ curl_easy_setopt(HTTP_HANDLE, CURLOPT_INFILESIZE, body.size());
+ } 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.c_str());
+ 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';
+ std::string bodystr(state.recvbuf, state.read);
+ jsbody = string_to_js(cx, bodystr);
+ 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 = static_cast<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 = static_cast<CurlState*>(ptr);
+ if(origin != SEEK_SET) return -1;
+
+ state->sent = static_cast<size_t>(offset);
+ return static_cast<int>(state->sent);
+}
+
+static size_t
+recv_header(void *ptr, size_t size, size_t nmem, void *data)
+{
+ CurlState* state = static_cast<CurlState*>(data);
+ char code[4];
+ char* header = static_cast<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.
+ std::string hdrstr(header, length);
+ hdr = string_to_js(state->cx, hdrstr);
+ 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 = static_cast<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;
+}
+
+#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..2c95f6129
--- /dev/null
+++ b/src/couch/priv/couch_js/68/main.cpp
@@ -0,0 +1,535 @@
+// 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 "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)
+{
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ JS::RootedObject obj(cx);
+ if (!args.computeThis(cx, &obj))
+ return false;
+ 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)
+{
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ JS::RootedObject obj(cx);
+ if (!args.computeThis(cx, &obj))
+ return false;
+ 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)
+{
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ JS::RootedObject obj(cx);
+ if (!args.computeThis(cx, &obj))
+ return false;
+ 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)
+{
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ JS::RootedObject obj(cx);
+ if (!args.computeThis(cx, &obj))
+ return false;
+
+ 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)
+{
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ JS::RootedObject obj(cx);
+ if (!args.computeThis(cx, &obj))
+ return false;
+
+ couch_args *cargs = static_cast<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);
+
+ bool use_stderr = false;
+ if(argc > 1 && args[1].isTrue()) {
+ use_stderr = true;
+ }
+
+ if(!args[0].isString()) {
+ JS_ReportErrorUTF8(cx, "Unable to print non-string value.");
+ return false;
+ }
+
+ couch_print(cx, args[0], use_stderr);
+
+ 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 = static_cast<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) {
+ JS::RootedValue exc(cx);
+ if(!JS_GetPendingException(cx, &exc)) {
+ fprintf(stderr, "Failed to compile file: %s\n", filename);
+ } else {
+ JS::RootedObject exc_obj(cx, &exc.toObject());
+ JSErrorReport* report = JS_ErrorFromException(cx, exc_obj);
+ couch_error(cx, report);
+ }
+ return 1;
+ }
+
+ JS::RootedValue result(cx);
+ if(JS_ExecuteScript(cx, script, &result) != true) {
+ JS::RootedValue exc(cx);
+ if(!JS_GetPendingException(cx, &exc)) {
+ fprintf(stderr, "Failed to execute script.\n");
+ } else {
+ JS::RootedObject exc_obj(cx, &exc.toObject());
+ JSErrorReport* report = JS_ErrorFromException(cx, exc_obj);
+ couch_error(cx, report);
+ }
+ }
+
+ // Give the GC a chance to run.
+ JS_MaybeGC(cx);
+ }
+
+ return 0;
+}
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..7717f1185
--- /dev/null
+++ b/src/couch/priv/couch_js/68/util.cpp
@@ -0,0 +1,358 @@
+// 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 <sstream>
+
+#include <jsapi.h>
+#include <jsfriendapi.h>
+#include <js/CharacterEncoding.h>
+#include <js/Conversions.h>
+#include <js/Initialization.h>
+#include <js/MemoryFunctions.h>
+#include <js/RegExp.h>
+
+#include "help.h"
+#include "util.h"
+
+std::string
+js_to_string(JSContext* cx, JS::HandleValue val)
+{
+ JS::AutoSaveExceptionState exc_state(cx);
+ JS::RootedString sval(cx);
+ sval = val.toString();
+
+ JS::UniqueChars chars(JS_EncodeStringToUTF8(cx, sval));
+ if(!chars) {
+ JS_ClearPendingException(cx);
+ return std::string();
+ }
+
+ return chars.get();
+}
+
+bool
+js_to_string(JSContext* cx, JS::HandleValue val, std::string& str)
+{
+ if(!val.isString()) {
+ return false;
+ }
+
+ if(JS_GetStringLength(val.toString()) == 0) {
+ str = "";
+ return true;
+ }
+
+ std::string conv = js_to_string(cx, val);
+ if(!conv.size()) {
+ return false;
+ }
+
+ str = conv;
+ return true;
+}
+
+JSString*
+string_to_js(JSContext* cx, const std::string& raw)
+{
+ JS::UTF8Chars utf8(raw.c_str(), raw.size());
+ JS::UniqueTwoByteChars utf16;
+ size_t len;
+
+ utf16.reset(JS::UTF8CharsToNewTwoByteCharsZ(cx, utf8, &len, js::MallocArena).get());
+ if(!utf16) {
+ return nullptr;
+ }
+
+ return JS_NewUCString(cx, std::move(utf16), len);
+}
+
+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 = new char[nread + 1];
+ if(buf == NULL) {
+ fprintf(stderr, "Out of memory.\n");
+ exit(3);
+ }
+ memcpy(buf, fbuf, nread);
+ } else {
+ tmp = new char[buflen + nread + 1];
+ if(tmp == NULL) {
+ fprintf(stderr, "Out of memory.\n");
+ exit(3);
+ }
+ memcpy(tmp, buf, buflen);
+ memcpy(tmp+buflen, fbuf, nread);
+ delete 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 = new couch_args();
+ if(args == NULL)
+ return NULL;
+
+ args->eval = 0;
+ args->use_http = 0;
+ args->use_test_funs = 0;
+ args->stack_size = 64L * 1024L * 1024L;
+ args->scripts = nullptr;
+ args->uri_file = nullptr;
+ args->uri = nullptr;
+
+ 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;
+
+ 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';
+ 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));
+ JS_free(cx, bytes);
+ return str;
+}
+
+
+void
+couch_print(JSContext* cx, JS::HandleValue obj, bool use_stderr)
+{
+ FILE *stream = stdout;
+
+ if (use_stderr) {
+ stream = stderr;
+ }
+ std::string val = js_to_string(cx, obj);
+ fprintf(stream, "%s\n", val.c_str());
+ fflush(stream);
+}
+
+
+void
+couch_error(JSContext* cx, JSErrorReport* report)
+{
+ if(!report) {
+ return;
+ }
+
+ if(JSREPORT_IS_WARNING(report->flags)) {
+ return;
+ }
+
+ std::ostringstream msg;
+ msg << "error: " << report->message().c_str();
+
+ mozilla::Maybe<JSAutoRealm> ar;
+ JS::RootedValue exc(cx);
+ JS::RootedObject exc_obj(cx);
+ JS::RootedObject stack_obj(cx);
+ JS::RootedString stack_str(cx);
+ JS::RootedValue stack_val(cx);
+ JSPrincipals* principals = GetRealmPrincipals(js::GetContextRealm(cx));
+
+ if(!JS_GetPendingException(cx, &exc)) {
+ goto done;
+ }
+
+ // Clear the exception before an JS method calls or the result is
+ // infinite, recursive error report generation.
+ JS_ClearPendingException(cx);
+
+ exc_obj.set(exc.toObjectOrNull());
+ stack_obj.set(JS::ExceptionStackOrNull(exc_obj));
+
+ if(!stack_obj) {
+ // Compilation errors don't have a stack
+
+ msg << " at ";
+
+ if(report->filename) {
+ msg << report->filename;
+ } else {
+ msg << "<unknown>";
+ }
+
+ if(report->lineno) {
+ msg << ':' << report->lineno << ':' << report->column;
+ }
+
+ goto done;
+ }
+
+ if(!JS::BuildStackString(cx, principals, stack_obj, &stack_str, 2)) {
+ goto done;
+ }
+
+ stack_val.set(JS::StringValue(stack_str));
+ msg << std::endl << std::endl << js_to_string(cx, stack_val).c_str();
+
+done:
+ msg << std::endl;
+ fprintf(stderr, "%s", msg.str().c_str());
+}
+
+
+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..bd7843eb9
--- /dev/null
+++ b/src/couch/priv/couch_js/68/util.h
@@ -0,0 +1,41 @@
+// 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);
+bool js_to_string(JSContext* cx, JS::HandleValue val, std::string& 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, JS::HandleValue str, bool use_stderr);
+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);
+
+#endif // Included util.h
diff --git a/src/couch/rebar.config.script b/src/couch/rebar.config.script
index 91e24d99e..ad897e8e3 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
+ ""; % 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/src/couch/src/couch_query_servers.erl b/src/couch/src/couch_query_servers.erl
index c6d255f17..447daea61 100644
--- a/src/couch/src/couch_query_servers.erl
+++ b/src/couch/src/couch_query_servers.erl
@@ -519,7 +519,7 @@ with_ddoc_proc(#doc{id=DDocId,revs={Start, [DiskRev|_]}}=DDoc, Fun) ->
proc_prompt(Proc, Args) ->
case proc_prompt_raw(Proc, Args) of
{json, Json} ->
- ?JSON_DECODE(Json);
+ raw_to_ejson({json, Json});
EJson ->
EJson
end.
@@ -528,10 +528,76 @@ proc_prompt_raw(#proc{prompt_fun = {Mod, Func}} = Proc, Args) ->
apply(Mod, Func, [Proc#proc.pid, Args]).
raw_to_ejson({json, Json}) ->
- ?JSON_DECODE(Json);
+ try
+ ?JSON_DECODE(Json)
+ catch throw:{invalid_json, {_, invalid_string}} ->
+ Forced = try
+ force_utf8(Json)
+ catch _:_ ->
+ Json
+ end,
+ ?JSON_DECODE(Forced)
+ end;
raw_to_ejson(EJson) ->
EJson.
+force_utf8(Bin) ->
+ case binary:match(Bin, <<"\\u">>) of
+ {Start, 2} ->
+ <<Prefix:Start/binary, Rest1/binary>> = Bin,
+ {Insert, Rest3} = case check_uescape(Rest1) of
+ {ok, Skip} ->
+ <<Skipped:Skip/binary, Rest2/binary>> = Rest1,
+ {Skipped, Rest2};
+ {error, Skip} ->
+ <<_:Skip/binary, Rest2/binary>> = Rest1,
+ {<<16#EF, 16#BF, 16#BD>>, Rest2}
+ end,
+ RestForced = force_utf8(Rest3),
+ <<Prefix/binary, Insert/binary, RestForced/binary>>;
+ nomatch ->
+ Bin
+ end.
+
+check_uescape(Data) ->
+ case extract_uescape(Data) of
+ {Hi, Rest} when Hi >= 16#D800, Hi < 16#DC00 ->
+ case extract_uescape(Rest) of
+ {Lo, _} when Lo >= 16#DC00, Lo =< 16#DFFF ->
+ % A low surrogate pair
+ UTF16 = <<
+ Hi:16/big-unsigned-integer,
+ Lo:16/big-unsigned-integer
+ >>,
+ try
+ [_] = xmerl_ucs:from_utf16be(UTF16),
+ {ok, 12}
+ catch _:_ ->
+ {error, 6}
+ end;
+ {_, _} ->
+ % Found a uescape that's not a low half
+ {error, 6};
+ false ->
+ % No hex escape found
+ {error, 6}
+ end;
+ {Hi, _} when Hi >= 16#DC00, Hi =< 16#DFFF ->
+ % Found a low surrogate half without a high half
+ {error, 6};
+ {_, _} ->
+ % Found a uescape we don't care about
+ {ok, 6};
+ false ->
+ % Incomplete uescape which we don't care about
+ {ok, 2}
+ end.
+
+extract_uescape(<<"\\u", Code:4/binary, Rest/binary>>) ->
+ {binary_to_integer(Code, 16), Rest};
+extract_uescape(_) ->
+ false.
+
proc_stop(Proc) ->
{Mod, Func} = Proc#proc.stop_fun,
apply(Mod, Func, [Proc#proc.pid]).
@@ -680,4 +746,41 @@ test_reduce(Reducer, KVs) ->
{ok, Finalized} = finalize(Reducer, Reduced),
Finalized.
+force_utf8_test() ->
+ % "\uDCA5\uD83D"
+ Ok = [
+ <<"foo">>,
+ <<"\\u00A0">>,
+ <<"\\u0032">>,
+ <<"\\uD83D\\uDCA5">>,
+ <<"foo\\uD83D\\uDCA5bar">>,
+ % Truncated but we doesn't break replacements
+ <<"\\u0FA">>
+ ],
+ lists:foreach(fun(Case) ->
+ ?assertEqual(Case, force_utf8(Case))
+ end, Ok),
+
+ NotOk = [
+ <<"\\uDCA5">>,
+ <<"\\uD83D">>,
+ <<"fo\\uDCA5bar">>,
+ <<"foo\\uD83Dbar">>,
+ <<"\\uDCA5\\uD83D">>,
+ <<"\\uD83Df\\uDCA5">>,
+ <<"\\uDCA5\\u00A0">>,
+ <<"\\uD83D\\u00A0">>
+ ],
+ ToJSON = fun(Bin) -> <<34, Bin/binary, 34>> end,
+ lists:foreach(fun(Case) ->
+ try
+ ?assertNotEqual(Case, force_utf8(Case)),
+ ?assertThrow(_, ?JSON_DECODE(ToJSON(Case))),
+ ?assertMatch(<<_/binary>>, ?JSON_DECODE(ToJSON(force_utf8(Case))))
+ catch
+ T:R ->
+ io:format(standard_error, "~p~n~p~n", [T, R])
+ end
+ end, NotOk).
+
-endif.
diff --git a/src/couch/test/eunit/couch_js_tests.erl b/src/couch/test/eunit/couch_js_tests.erl
index c2c62463b..693cd9772 100644
--- a/src/couch/test/eunit/couch_js_tests.erl
+++ b/src/couch/test/eunit/couch_js_tests.erl
@@ -137,7 +137,6 @@ should_allow_js_string_mutations() ->
true = couch_query_servers:proc_prompt(Proc, [<<"add_fun">>, Src3]),
Doc = {[{<<"value">>, MomWashedTheFrame}]},
Result = couch_query_servers:proc_prompt(Proc, [<<"map_doc">>, Doc]),
- io:format(standard_error, "~w~n~w~n", [MomWashedTheFrame, Result]),
Expect = [
[[<<"length">>, 14]],
[[<<"substring">>, Washed]],
diff --git a/src/mango/test/21-empty-selector-tests.py b/src/mango/test/21-empty-selector-tests.py
index 31ad8e645..8fd76fcd5 100644
--- a/src/mango/test/21-empty-selector-tests.py
+++ b/src/mango/test/21-empty-selector-tests.py
@@ -42,17 +42,13 @@ def make_empty_selector_suite(klass):
assert len(docs) == 0
def test_empty_array_and_with_age(self):
- resp = self.db.find(
- {"age": 22, "$and": []}, explain=True
- )
+ resp = self.db.find({"age": 22, "$and": []}, explain=True)
self.assertEqual(resp["index"]["type"], klass.INDEX_TYPE)
docs = self.db.find({"age": 22, "$and": []})
assert len(docs) == 1
def test_empty_array_all_age(self):
- resp = self.db.find(
- {"age": 22, "company": {"$all": []}}, explain=True
- )
+ resp = self.db.find({"age": 22, "company": {"$all": []}}, explain=True)
self.assertEqual(resp["index"]["type"], klass.INDEX_TYPE)
docs = self.db.find({"age": 22, "company": {"$all": []}})
assert len(docs) == 0
@@ -62,7 +58,7 @@ def make_empty_selector_suite(klass):
{"age": 22, "$and": [{"company": {"$all": []}}]}, explain=True
)
self.assertEqual(resp["index"]["type"], klass.INDEX_TYPE)
- docs = self.db.find( {"age": 22, "$and": [{"company": {"$all": []}}]})
+ docs = self.db.find({"age": 22, "$and": [{"company": {"$all": []}}]})
assert len(docs) == 0
def test_empty_arrays_complex(self):
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,