You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by wo...@apache.org on 2020/04/27 17:06:45 UTC

[couchdb] branch 3.x-sm68 created (now cf39e2f)

This is an automated email from the ASF dual-hosted git repository.

wohali pushed a change to branch 3.x-sm68
in repository https://gitbox.apache.org/repos/asf/couchdb.git.


      at cf39e2f  python black cleanup

This branch includes the following new commits:

     new 7ffabb2  First pass at SpiderMonkey 68 support
     new e51ff02  Incorporate changes from #2786
     new 2c7429a  Replace broken u-escape sequences
     new 7458ab3  Fix new JS test case
     new cf39e2f  python black cleanup

The 5 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[couchdb] 01/05: First pass at SpiderMonkey 68 support

Posted by wo...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

wohali pushed a commit to branch 3.x-sm68
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit 7ffabb2e6218ea7a8a1b3a8735dc33ca8b061cb8
Author: Joan Touzet <wo...@apache.org>
AuthorDate: Wed Apr 15 00:28:27 2020 +0000

    First pass at SpiderMonkey 68 support
---
 .gitignore                          |   1 +
 rebar.config.script                 |  16 +-
 src/couch/priv/couch_js/68/help.h   |  86 +++++
 src/couch/priv/couch_js/68/http.cpp | 710 ++++++++++++++++++++++++++++++++++++
 src/couch/priv/couch_js/68/http.h   |  27 ++
 src/couch/priv/couch_js/68/main.cpp | 494 +++++++++++++++++++++++++
 src/couch/priv/couch_js/68/utf8.cpp | 309 ++++++++++++++++
 src/couch/priv/couch_js/68/utf8.h   |  19 +
 src/couch/priv/couch_js/68/util.cpp | 350 ++++++++++++++++++
 src/couch/priv/couch_js/68/util.h   |  60 +++
 src/couch/rebar.config.script       |  66 ++--
 support/build_js.escript            |   6 +
 12 files changed, 2107 insertions(+), 37 deletions(-)

diff --git a/.gitignore b/.gitignore
index 60e6d14..551a6a6 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 bfca5c8..0e9c978 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 0000000..678651f
--- /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 0000000..a0c73bd
--- /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 0000000..797b3c0
--- /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 0000000..3860a01
--- /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 0000000..c28e026
--- /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 0000000..c8b1f4d
--- /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 0000000..f941e7d
--- /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 0000000..dc8a3a7
--- /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 91e24d9..89c652a 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 90ad316..2d9de61 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,
 


[couchdb] 05/05: python black cleanup

Posted by wo...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

wohali pushed a commit to branch 3.x-sm68
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit cf39e2f5946933883ccf5388eb8ea9eec09a4490
Author: Joan Touzet <jo...@atypical.net>
AuthorDate: Mon Apr 27 12:15:51 2020 -0400

    python black cleanup
---
 src/mango/test/21-empty-selector-tests.py | 10 +++-------
 1 file changed, 3 insertions(+), 7 deletions(-)

diff --git a/src/mango/test/21-empty-selector-tests.py b/src/mango/test/21-empty-selector-tests.py
index 31ad8e6..8fd76fc 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):


[couchdb] 02/05: Incorporate changes from #2786

Posted by wo...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

wohali pushed a commit to branch 3.x-sm68
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit e51ff02f9a03f3ce42ac1a30fae37a912154a787
Author: Joan Touzet <wo...@apache.org>
AuthorDate: Sat Apr 18 03:22:28 2020 +0000

    Incorporate changes from #2786
---
 .gitignore                              |   1 +
 src/couch/priv/couch_js/68/http.cpp     | 214 ++++++++--------------
 src/couch/priv/couch_js/68/main.cpp     |  65 +++++--
 src/couch/priv/couch_js/68/utf8.cpp     | 309 --------------------------------
 src/couch/priv/couch_js/68/utf8.h       |  19 --
 src/couch/priv/couch_js/68/util.cpp     | 202 +++++++++++----------
 src/couch/priv/couch_js/68/util.h       |  23 +--
 src/couch/rebar.config.script           |   2 +-
 src/couch/test/eunit/couch_js_tests.erl | 112 ++++++++++++
 9 files changed, 351 insertions(+), 596 deletions(-)

diff --git a/.gitignore b/.gitignore
index 551a6a6..3b81634 100644
--- a/.gitignore
+++ b/.gitignore
@@ -116,6 +116,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/src/couch/priv/couch_js/68/http.cpp b/src/couch/priv/couch_js/68/http.cpp
index a0c73bd..20a6097 100644
--- a/src/couch/priv/couch_js/68/http.cpp
+++ b/src/couch/priv/couch_js/68/http.cpp
@@ -19,7 +19,6 @@
 #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
@@ -101,7 +100,6 @@ http_check_enabled()
 #ifdef XP_WIN
 #define strcasecmp _strcmpi
 #define strncasecmp _strnicmp
-#define snprintf _snprintf
 #endif
 
 
@@ -110,7 +108,7 @@ typedef struct curl_slist CurlHeaders;
 
 typedef struct {
     int             method;
-    char*           url;
+    std::string     url;
     CurlHeaders*    req_headers;
     int16_t          last_status;
 } HTTPData;
@@ -128,22 +126,15 @@ const char* METHODS[] = {"GET", "HEAD", "POST", "PUT", "DELETE", "COPY", "OPTION
 #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);
-*/
+static bool go(JSContext* cx, JSObject* obj, HTTPData* http, std::string& body);
 
 
 bool
 http_ctor(JSContext* cx, JSObject* req)
 {
-    HTTPData* http = NULL;
+    HTTPData* http = new HTTPData();
     bool ret = false;
 
-    http = (HTTPData*) malloc(sizeof(HTTPData));
     if(!http)
     {
         JS_ReportErrorUTF8(cx, "Failed to create CouchHTTP instance.");
@@ -151,7 +142,6 @@ http_ctor(JSContext* cx, JSObject* req)
     }
 
     http->method = -1;
-    http->url = NULL;
     http->req_headers = NULL;
     http->last_status = -1;
 
@@ -161,7 +151,7 @@ http_ctor(JSContext* cx, JSObject* req)
     goto success;
 
 error:
-    if(http) free(http);
+    if(http) delete http;
 
 success:
     return ret;
@@ -173,9 +163,8 @@ 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);
+        delete http;
     }
 }
 
@@ -184,56 +173,50 @@ 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;
+        return false;
     }
 
-    if(mth.isUndefined()) {
-        JS_ReportErrorUTF8(cx, "You must specify a method.");
-        goto done;
+    if(!mth.isString()) {
+        JS_ReportErrorUTF8(cx, "Method must be a string.");
+        return false;
     }
 
-    method = enc_string(cx, mth, NULL);
-    if(!method) {
+    std::string method;
+    if(!js_to_string(cx, JS::RootedValue(cx, mth), method)) {
         JS_ReportErrorUTF8(cx, "Failed to encode method.");
-        goto done;
+        return false;
     }
 
     for(methid = 0; METHODS[methid] != NULL; methid++) {
-        if(strcasecmp(METHODS[methid], method) == 0) break;
+        if(strcasecmp(METHODS[methid], method.c_str()) == 0) break;
     }
 
     if(methid > OPTIONS) {
         JS_ReportErrorUTF8(cx, "Invalid method specified.");
-        goto done;
+        return false;
     }
 
     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;
+    if(!url.isString()) {
+        JS_ReportErrorUTF8(cx, "URL must be a string");
+        return false;
     }
 
-    http->url = enc_string(cx, url, NULL);
-    if(http->url == NULL) {
+    std::string urlstr;
+    if(!js_to_string(cx, JS::RootedValue(cx, url), urlstr)) {
         JS_ReportErrorUTF8(cx, "Failed to encode URL.");
-        goto done;
+        return false;
     }
+    http->url = urlstr;
 
     if(snc.isBoolean() && snc.isTrue()) {
         JS_ReportErrorUTF8(cx, "Synchronous flag must be false.");
-        goto done;
+        return false;
     }
 
     if(http->req_headers) {
@@ -244,11 +227,7 @@ http_open(JSContext* cx, JSObject* req, JS::Value mth, JS::Value url, JS::Value
     // Disable Expect: 100-continue
     http->req_headers = curl_slist_append(http->req_headers, "Expect:");
 
-    ret = true;
-
-done:
-    if(method) free(method);
-    return ret;
+    return true;
 }
 
 
@@ -256,88 +235,60 @@ 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;
+        return false;
     }
 
-    if(name.isUndefined())
+    if(!name.isString())
     {
-        JS_ReportErrorUTF8(cx, "You must speciy a header name.");
-        goto done;
+        JS_ReportErrorUTF8(cx, "Header names must be strings.");
+        return false;
     }
 
-    keystr = enc_string(cx, name, NULL);
-    if(!keystr)
+    std::string keystr;
+    if(!js_to_string(cx, JS::RootedValue(cx, name), keystr))
     {
         JS_ReportErrorUTF8(cx, "Failed to encode header name.");
-        goto done;
+        return false;
     }
 
-    if(val.isUndefined())
+    if(!val.isString())
     {
-        JS_ReportErrorUTF8(cx, "You must specify a header value.");
-        goto done;
+        JS_ReportErrorUTF8(cx, "Header values must be strings.");
+        return false;
     }
 
-    valstr = enc_string(cx, val, NULL);
-    if(!valstr)
-    {
+    std::string valstr;
+    if(!js_to_string(cx, JS::RootedValue(cx, val), 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;
+        return false;
     }
 
-    snprintf(hdrbuf, hdrlen, "%s: %s", keystr, valstr);
-    http->req_headers = curl_slist_append(http->req_headers, hdrbuf);
-
-    ret = true;
+    std::string header = keystr + ": " + valstr;
+    http->req_headers = curl_slist_append(http->req_headers, header.c_str());
 
-done:
-    if(keystr) free(keystr);
-    if(valstr) free(valstr);
-    if(hdrbuf) free(hdrbuf);
-    return ret;
+    return true;
 }
 
 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;
+        return false;
     }
 
-    if(!body.isUndefined()) {
-        bodystr = enc_string(cx, body, &bodylen);
-        if(!bodystr) {
-            JS_ReportErrorUTF8(cx, "Failed to encode body.");
-            goto done;
-        }
+    std::string bodystr;
+    if(!js_to_string(cx, JS::RootedValue(cx, body), bodystr)) {
+        JS_ReportErrorUTF8(cx, "Failed to encode body.");
+        return false;
     }
 
-    ret = go(cx, req, http, bodystr, bodylen);
-
-done:
-    if(bodystr) free(bodystr);
-    return ret;
+    return go(cx, req, http, bodystr);
 }
 
 int
@@ -397,7 +348,7 @@ typedef struct {
     HTTPData*   http;
     JSContext*  cx;
     JSObject*   resp_headers;
-    char*       sendbuf;
+    const char* sendbuf;
     size_t      sendlen;
     size_t      sent;
     int         sent_once;
@@ -419,10 +370,9 @@ 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)
+go(JSContext* cx, JSObject* obj, HTTPData* http, std::string& body)
 {
     CurlState state;
-    char* referer;
     JSString* jsbody;
     bool ret = false;
     JS::Value tmp;
@@ -433,8 +383,8 @@ go(JSContext* cx, JSObject* obj, HTTPData* http, char* body, size_t bodylen)
     state.cx = cx;
     state.http = http;
 
-    state.sendbuf = body;
-    state.sendlen = bodylen;
+    state.sendbuf = body.c_str();;
+    state.sendlen = body.size();
     state.sent = 0;
     state.sent_once = 0;
 
@@ -465,13 +415,13 @@ go(JSContext* cx, JSObject* obj, HTTPData* http, char* body, size_t bodylen)
 
     tmp = JS_GetReservedSlot(obj, 0);
 
-    if(!(referer = enc_string(cx, tmp, NULL))) {
+    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;
+        return ret;
     }
-    curl_easy_setopt(HTTP_HANDLE, CURLOPT_REFERER, referer);
-    free(referer);
+    curl_easy_setopt(HTTP_HANDLE, CURLOPT_REFERER, referer.c_str());
 
     if(http->method < 0 || http->method > OPTIONS) {
         JS_ReportErrorUTF8(cx, "INTERNAL: Unknown method.");
@@ -492,15 +442,15 @@ go(JSContext* cx, JSObject* obj, HTTPData* http, char* body, size_t bodylen)
         curl_easy_setopt(HTTP_HANDLE, CURLOPT_FOLLOWLOCATION, 0);
     }
 
-    if(body && bodylen) {
-        curl_easy_setopt(HTTP_HANDLE, CURLOPT_INFILESIZE, bodylen);
+    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);
+    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);
@@ -534,7 +484,8 @@ go(JSContext* cx, JSObject* obj, HTTPData* http, char* body, size_t bodylen)
 
     if(state.recvbuf) {
         state.recvbuf[state.read] = '\0';
-        jsbody = dec_string(cx, state.recvbuf, state.read+1);
+        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
@@ -574,7 +525,7 @@ go(JSContext* cx, JSObject* obj, HTTPData* http, char* body, size_t bodylen)
 static size_t
 send_body(void *ptr, size_t size, size_t nmem, void *data)
 {
-    CurlState* state = (CurlState*) data;
+    CurlState* state = static_cast<CurlState*>(data);
     size_t length = size * nmem;
     size_t towrite = state->sendlen - state->sent;
 
@@ -600,19 +551,19 @@ send_body(void *ptr, size_t size, size_t nmem, void *data)
 static int
 seek_body(void* ptr, curl_off_t offset, int origin)
 {
-    CurlState* state = (CurlState*) ptr;
+    CurlState* state = static_cast<CurlState*>(ptr);
     if(origin != SEEK_SET) return -1;
 
-    state->sent = (size_t) offset;
-    return (int) state->sent;
+    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 = (CurlState*) data;
+    CurlState* state = static_cast<CurlState*>(data);
     char code[4];
-    char* header = (char*) ptr;
+    char* header = static_cast<char*>(ptr);
     size_t length = size * nmem;
     JSString* hdr = NULL;
     uint32_t hdrlen;
@@ -640,7 +591,8 @@ recv_header(void *ptr, size_t size, size_t nmem, void *data)
     }
 
     // Append the new header to our array.
-    hdr = dec_string(state->cx, header, length);
+    std::string hdrstr(header, length);
+    hdr = string_to_js(state->cx, hdrstr);
     if(!hdr) {
         return CURLE_WRITE_ERROR;
     }
@@ -661,14 +613,17 @@ recv_header(void *ptr, size_t size, size_t nmem, void *data)
 static size_t
 recv_body(void *ptr, size_t size, size_t nmem, void *data)
 {
-    CurlState* state = (CurlState*) 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));
+        state->recvbuf = static_cast<char*>(JS_malloc(
+                state->cx,
+                state->recvlen
+            ));
     }
 
     if(!state->recvbuf) {
@@ -678,7 +633,12 @@ recv_body(void *ptr, size_t size, size_t nmem, void *data)
     // +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));
+    tmp = static_cast<char*>(JS_realloc(
+            state->cx,
+            state->recvbuf,
+            oldlen,
+            state->recvlen
+        ));
     if(!tmp) return CURLE_WRITE_ERROR;
     state->recvbuf = tmp;
 
@@ -687,24 +647,4 @@ recv_body(void *ptr, size_t size, size_t nmem, void *data)
     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/main.cpp b/src/couch/priv/couch_js/68/main.cpp
index 3860a01..2c95f61 100644
--- a/src/couch/priv/couch_js/68/main.cpp
+++ b/src/couch/priv/couch_js/68/main.cpp
@@ -31,7 +31,6 @@
 
 #include "config.h"
 #include "http.h"
-#include "utf8.h"
 #include "util.h"
 
 static bool enableSharedMemory = true;
@@ -102,7 +101,10 @@ req_ctor(JSContext* cx, unsigned int argc, JS::Value* vp)
 static bool
 req_open(JSContext* cx, unsigned int argc, JS::Value* vp)
 {
-    GET_THIS(cx, argc, vp, args, obj)
+    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+    JS::RootedObject obj(cx);
+    if (!args.computeThis(cx, &obj))
+        return false;
     bool ret = false;
 
     if(argc == 2) {
@@ -121,7 +123,10 @@ req_open(JSContext* cx, unsigned int argc, JS::Value* vp)
 static bool
 req_set_hdr(JSContext* cx, unsigned int argc, JS::Value* vp)
 {
-    GET_THIS(cx, argc, vp, args, obj)
+    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+    JS::RootedObject obj(cx);
+    if (!args.computeThis(cx, &obj))
+        return false;
     bool ret = false;
 
     if(argc == 2) {
@@ -138,7 +143,10 @@ req_set_hdr(JSContext* cx, unsigned int argc, JS::Value* vp)
 static bool
 req_send(JSContext* cx, unsigned int argc, JS::Value* vp)
 {
-    GET_THIS(cx, argc, vp, args, obj)
+    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+    JS::RootedObject obj(cx);
+    if (!args.computeThis(cx, &obj))
+        return false;
     bool ret = false;
 
     if(argc == 1) {
@@ -154,7 +162,11 @@ req_send(JSContext* cx, unsigned int argc, JS::Value* vp)
 static bool
 req_status(JSContext* cx, unsigned int argc, JS::Value* vp)
 {
-    GET_THIS(cx, argc, vp, args, obj)
+    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)
@@ -167,8 +179,12 @@ req_status(JSContext* cx, unsigned int argc, JS::Value* vp)
 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::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);
@@ -278,7 +294,19 @@ static bool
 print(JSContext* cx, unsigned int argc, JS::Value* vp)
 {
     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
-    couch_print(cx, argc, args);
+
+    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;
 }
@@ -381,7 +409,7 @@ static JSFunctionSpec global_functions[] = {
 static bool
 csp_allows(JSContext* cx, JS::HandleValue code)
 {
-    couch_args *args = (couch_args*)JS_GetContextPrivate(cx);
+    couch_args* args = static_cast<couch_args*>(JS_GetContextPrivate(cx));
     if(args->eval) {
         return true;
     } else {
@@ -476,14 +504,27 @@ main(int argc, const char* argv[])
         script = JS::CompileUtf8File(cx, options, fp);
         fclose(fp);
         if (!script) {
-            fprintf(stderr, "Failed to compile file: %s\n", filename);
+            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) {
-            fprintf(stderr, "Failed to execute script.\n");
-            return 1;
+            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.
diff --git a/src/couch/priv/couch_js/68/utf8.cpp b/src/couch/priv/couch_js/68/utf8.cpp
deleted file mode 100644
index c28e026..0000000
--- a/src/couch/priv/couch_js/68/utf8.cpp
+++ /dev/null
@@ -1,309 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-#include <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
deleted file mode 100644
index c8b1f4d..0000000
--- a/src/couch/priv/couch_js/68/utf8.h
+++ /dev/null
@@ -1,19 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-#ifndef 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
index f941e7d..7717f11 100644
--- a/src/couch/priv/couch_js/68/util.cpp
+++ b/src/couch/priv/couch_js/68/util.cpp
@@ -13,7 +13,11 @@
 #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>
@@ -21,53 +25,57 @@
 
 #include "help.h"
 #include "util.h"
-#include "utf8.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);
-        fprintf(stderr, "Error converting value to string.\n");
-        exit(3);
+        return std::string();
     }
 
     return chars.get();
 }
 
-std::string
-js_to_string(JSContext* cx, JSString *str)
+bool
+js_to_string(JSContext* cx, JS::HandleValue val, std::string& str)
 {
-    JS::UniqueChars chars(JS_EncodeString(cx, str));
-    if(!chars) {
-        JS_ClearPendingException(cx);
-        fprintf(stderr, "Error converting  to string.\n");
-        exit(3);
+    if(!val.isString()) {
+        return false;
     }
 
-    return chars.get();
+    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& s)
+string_to_js(JSContext* cx, const std::string& raw)
 {
-/*
+    JS::UTF8Chars utf8(raw.c_str(), raw.size());
+    JS::UniqueTwoByteChars utf16;
+    size_t len;
 
-    JSString* ret = JS_NewStringCopyN(cx, s.c_str(), s.size());
-    if(ret != nullptr) {
-        return ret;
+    utf16.reset(JS::UTF8CharsToNewTwoByteCharsZ(cx, utf8, &len, js::MallocArena).get());
+    if(!utf16) {
+        return nullptr;
     }
 
-    fprintf(stderr, "Unable to allocate string object.\n");
-    exit(3);
-*/
-    return dec_string(cx, s.c_str(), s.size());
+    return JS_NewUCString(cx, std::move(utf16), len);
 }
 
 size_t
@@ -92,21 +100,21 @@ couch_readfile(const char* file, char** outbuf_p)
 
     while((nread = fread(fbuf, 1, 16384, fp)) > 0) {
         if(buf == NULL) {
-            buf = (char*) malloc(nread + 1);
+            buf = new char[nread + 1];
             if(buf == NULL) {
                 fprintf(stderr, "Out of memory.\n");
                 exit(3);
             }
             memcpy(buf, fbuf, nread);
         } else {
-            tmp = (char*) malloc(buflen + nread + 1);
+            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);
-            free(buf);
+            delete buf;
             buf = tmp;
         }
         buflen += nread;
@@ -122,12 +130,17 @@ couch_parse_args(int argc, const char* argv[])
     couch_args* args;
     int i = 1;
 
-    args = (couch_args*) malloc(sizeof(couch_args));
+    args = new couch_args();
     if(args == NULL)
         return NULL;
 
-    memset(args, '\0', sizeof(couch_args));
+    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) {
@@ -200,9 +213,8 @@ couch_readline(JSContext* cx, FILE* fp)
     size_t byteslen = 256;
     size_t oldbyteslen = 256;
     size_t readlen = 0;
-    bool sawNewline = false;
 
-    bytes = static_cast<char *>(JS_malloc(cx, byteslen));
+    bytes = static_cast<char*>(JS_malloc(cx, byteslen));
     if(bytes == NULL) return NULL;
 
     while((readlen = couch_fgets(bytes+used, byteslen-used, fp)) > 0) {
@@ -210,14 +222,13 @@ couch_readline(JSContext* cx, FILE* fp)
 
         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));
+        tmp = static_cast<char*>(JS_realloc(cx, bytes, oldbyteslen, byteslen));
         if(!tmp) {
             JS_free(cx, bytes);
             return NULL;
@@ -233,7 +244,7 @@ couch_readline(JSContext* cx, FILE* fp)
     }
 
     // Shrink the buffer to the actual data size
-    tmp = static_cast<char *>(JS_realloc(cx, bytes, byteslen, used));
+    tmp = static_cast<char*>(JS_realloc(cx, bytes, byteslen, used));
     if(!tmp) {
         JS_free(cx, bytes);
         return NULL;
@@ -241,37 +252,22 @@ couch_readline(JSContext* cx, FILE* fp)
     bytes = tmp;
     byteslen = used;
 
-    str = string_to_js(cx, std::string(tmp, byteslen));
+    str = string_to_js(cx, std::string(tmp));
     JS_free(cx, bytes);
     return str;
 }
 
 
 void
-couch_print(JSContext* cx, unsigned int argc, JS::CallArgs argv)
+couch_print(JSContext* cx, JS::HandleValue obj, bool use_stderr)
 {
     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();
+    if (use_stderr) {
+        stream = stderr;
     }
-
-    fputc('\n', stream);
+    std::string val = js_to_string(cx, obj);
+    fprintf(stream, "%s\n", val.c_str());
     fflush(stream);
 }
 
@@ -279,52 +275,64 @@ couch_print(JSContext* cx, unsigned int argc, JS::CallArgs argv)
 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);
-                    }
-                }
-            }
+    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());
 }
 
 
diff --git a/src/couch/priv/couch_js/68/util.h b/src/couch/priv/couch_js/68/util.h
index dc8a3a7..bd7843e 100644
--- a/src/couch/priv/couch_js/68/util.h
+++ b/src/couch/priv/couch_js/68/util.h
@@ -25,36 +25,17 @@ typedef struct {
     JSString*    uri;
 } couch_args;
 
-/*
 std::string js_to_string(JSContext* cx, JS::HandleValue val);
-std::string js_to_string(JSContext* cx, JSString *str);
+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, unsigned int argc, JS::CallArgs argv);
+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);
 
-/*
- * 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 89c652a..ad897e8 100644
--- a/src/couch/rebar.config.script
+++ b/src/couch/rebar.config.script
@@ -41,7 +41,7 @@ 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.
diff --git a/src/couch/test/eunit/couch_js_tests.erl b/src/couch/test/eunit/couch_js_tests.erl
index cd6452c..893fb64 100644
--- a/src/couch/test/eunit/couch_js_tests.erl
+++ b/src/couch/test/eunit/couch_js_tests.erl
@@ -39,6 +39,118 @@ couch_js_test_() ->
     }.
 
 
+should_create_sandbox() ->
+    % Try and detect whether we can see out of the
+    % sandbox or not.
+    Src = <<
+      "function(doc) {\n"
+      "  try {\n"
+      "    emit(false, typeof(Couch.compile_function));\n"
+      "  } catch (e) {\n"
+      "    emit(true, e.message);\n"
+      "  }\n"
+      "}\n"
+    >>,
+    Proc = couch_query_servers:get_os_process(<<"javascript">>),
+    true = couch_query_servers:proc_prompt(Proc, [<<"add_fun">>, Src]),
+    Result = couch_query_servers:proc_prompt(Proc, [<<"map_doc">>, <<"{}">>]),
+    ?assertEqual([[[true, <<"Couch is not defined">>]]], Result).
+
+
+should_roundtrip_utf8() ->
+    % Try round tripping UTF-8 both directions through
+    % couchjs. These tests use hex encoded values of
+    % Ä (C384) and Ü (C39C) so as to avoid odd editor/Erlang encoding
+    % strangeness.
+    Src = <<
+      "function(doc) {\n"
+      "  emit(doc.value, \"", 16#C3, 16#9C, "\");\n"
+      "}\n"
+    >>,
+    Proc = couch_query_servers:get_os_process(<<"javascript">>),
+    true = couch_query_servers:proc_prompt(Proc, [<<"add_fun">>, Src]),
+    Doc = {[
+        {<<"value">>, <<16#C3, 16#84>>}
+    ]},
+    Result = couch_query_servers:proc_prompt(Proc, [<<"map_doc">>, Doc]),
+    ?assertEqual([[[<<16#C3, 16#84>>, <<16#C3, 16#9C>>]]], Result).
+
+
+should_roundtrip_modified_utf8() ->
+    % Mimicing the test case from the mailing list
+    Src = <<
+      "function(doc) {\n"
+      "  emit(doc.value.toLowerCase(), \"", 16#C3, 16#9C, "\");\n"
+      "}\n"
+    >>,
+    Proc = couch_query_servers:get_os_process(<<"javascript">>),
+    true = couch_query_servers:proc_prompt(Proc, [<<"add_fun">>, Src]),
+    Doc = {[
+        {<<"value">>, <<16#C3, 16#84>>}
+    ]},
+    Result = couch_query_servers:proc_prompt(Proc, [<<"map_doc">>, Doc]),
+    ?assertEqual([[[<<16#C3, 16#A4>>, <<16#C3, 16#9C>>]]], Result).
+
+
+should_replace_broken_utf16() ->
+    % This test reverse the surrogate pair of
+    % the Boom emoji U+1F4A5
+    Src = <<
+      "function(doc) {\n"
+      "  emit(doc.value.split(\"\").reverse().join(\"\"), 1);\n"
+      "}\n"
+    >>,
+    Proc = couch_query_servers:get_os_process(<<"javascript">>),
+    true = couch_query_servers:proc_prompt(Proc, [<<"add_fun">>, Src]),
+    Doc = {[
+        {<<"value">>, list_to_binary(xmerl_ucs:to_utf8([16#1F4A5]))}
+    ]},
+    Result = couch_query_servers:proc_prompt(Proc, [<<"map_doc">>, Doc]),
+    % Invalid UTF-8 gets replaced with the 16#FFFD replacement
+    % marker
+    Markers = list_to_binary(xmerl_ucs:to_utf8([16#FFFD, 16#FFFD])),
+    ?assertEqual([[[Markers, 1]]], Result).
+
+
+should_allow_js_string_mutations() ->
+    % This binary corresponds to this string: мама мыла раму
+    % Which I'm told translates to: "mom was washing the frame"
+    MomWashedTheFrame = <<
+        16#D0, 16#BC, 16#D0, 16#B0, 16#D0, 16#BC, 16#D0, 16#B0, 16#20,
+        16#D0, 16#BC, 16#D1, 16#8B, 16#D0, 16#BB, 16#D0, 16#B0, 16#20,
+        16#D1, 16#80, 16#D0, 16#B0, 16#D0, 16#BC, 16#D1, 16#83
+    >>,
+    Mom = <<16#D0, 16#BC, 16#D0, 16#B0, 16#D0, 16#BC, 16#D0, 16#B0>>,
+    Washed = <<16#D0, 16#BC, 16#D1, 16#8B, 16#D0, 16#BB, 16#D0, 16#B0>>,
+    Src1 = <<
+      "function(doc) {\n"
+      "  emit(\"length\", doc.value.length);\n"
+      "}\n"
+    >>,
+    Src2 = <<
+      "function(doc) {\n"
+      "  emit(\"substring\", doc.value.substring(5, 9));\n"
+      "}\n"
+    >>,
+    Src3 = <<
+      "function(doc) {\n"
+      "  emit(\"slice\", doc.value.slice(0, 4));\n"
+      "}\n"
+    >>,
+    Proc = couch_query_servers:get_os_process(<<"javascript">>),
+    true = couch_query_servers:proc_prompt(Proc, [<<"add_fun">>, Src1]),
+    true = couch_query_servers:proc_prompt(Proc, [<<"add_fun">>, Src2]),
+    true = couch_query_servers:proc_prompt(Proc, [<<"add_fun">>, Src3]),
+    Doc = {[{<<"value">>, MomWashedTheFrame}]},
+    Result = couch_query_servers:proc_prompt(Proc, [<<"map_doc">>, Doc]),
+    Expect = [
+        [[<<"length">>, 14]],
+        [[<<"substring">>, Washed]],
+        [[<<"slice">>, Mom]]
+    ],
+    ?assertEqual(Expect, Result).
+
+
 should_exit_on_oom() ->
     Proc = couch_query_servers:get_os_process(<<"javascript">>),
     true = couch_query_servers:proc_prompt(Proc, [<<"add_fun">>, ?FUNC]),


[couchdb] 04/05: Fix new JS test case

Posted by wo...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

wohali pushed a commit to branch 3.x-sm68
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit 7458ab3a78c18b0d8a2034c512317782830be7d9
Author: Joan Touzet <wo...@apache.org>
AuthorDate: Mon Apr 27 15:15:42 2020 +0000

    Fix new JS test case
---
 src/couch/src/couch_query_servers.erl | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/couch/src/couch_query_servers.erl b/src/couch/src/couch_query_servers.erl
index 9842177..447daea 100644
--- a/src/couch/src/couch_query_servers.erl
+++ b/src/couch/src/couch_query_servers.erl
@@ -777,8 +777,9 @@ force_utf8_test() ->
             ?assertNotEqual(Case, force_utf8(Case)),
             ?assertThrow(_, ?JSON_DECODE(ToJSON(Case))),
             ?assertMatch(<<_/binary>>, ?JSON_DECODE(ToJSON(force_utf8(Case))))
-        catch T:R:S ->
-            io:format(standard_error, "~p~n~p~n~p~n", [T, R, S])
+        catch
+          T:R ->
+            io:format(standard_error, "~p~n~p~n", [T, R])
         end
     end, NotOk).
 


[couchdb] 03/05: Replace broken u-escape sequences

Posted by wo...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

wohali pushed a commit to branch 3.x-sm68
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit 2c7429a77c4a8e89a37423179b73eb078041a92f
Author: Paul J. Davis <pa...@gmail.com>
AuthorDate: Tue Apr 21 15:48:16 2020 -0500

    Replace broken u-escape sequences
---
 src/couch/src/couch_query_servers.erl | 106 +++++++++++++++++++++++++++++++++-
 1 file changed, 104 insertions(+), 2 deletions(-)

diff --git a/src/couch/src/couch_query_servers.erl b/src/couch/src/couch_query_servers.erl
index c6d255f..9842177 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,40 @@ 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:S ->
+            io:format(standard_error, "~p~n~p~n~p~n", [T, R, S])
+        end
+    end, NotOk).
+
 -endif.