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 16:45:43 UTC

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

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

wohali pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit 27d1405ce8379db7ce34c0b8abf9cf1eb757e8aa
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 3cfa372..8a4a6f0 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,