Modules: introduced Buffer alternatives for object properties.

Buffer variant returns the property bytes as is, whereas the string
version may convert bytes invalid in UTF-8 encoding into replacement
character.

HTTP
new request object properties:
r.reqBody (r.requestBody),
r.resBody (r.responseBody),
r.vars (r.variables).

Stream
new stream object properties:
s.vars (s.variables).

new events:
The events' callbacks are identical to the string counterparts, except the data
argument:
upstream (upload),
downstream (download).
diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c
index bf849e8..437f6be 100644
--- a/nginx/ngx_http_js_module.c
+++ b/nginx/ngx_http_js_module.c
@@ -43,6 +43,7 @@
     ngx_int_t              status;
     njs_opaque_value_t     request;
     njs_opaque_value_t     request_body;
+    njs_opaque_value_t     response_body;
     ngx_str_t              redirect_uri;
     ngx_array_t            promise_callbacks;
 } ngx_http_js_ctx_t;
@@ -319,9 +320,19 @@
     {
         .flags = NJS_EXTERN_PROPERTY,
         .name.string = njs_str("requestBody"),
+        .u.property = {
+            .handler = ngx_http_js_ext_get_request_body,
+            .magic32 = NGX_JS_STRING,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_PROPERTY,
+        .name.string = njs_str("reqBody"),
         .enumerable = 1,
         .u.property = {
             .handler = ngx_http_js_ext_get_request_body,
+            .magic32 = NGX_JS_BUFFER,
         }
     },
 
@@ -336,9 +347,19 @@
     {
         .flags = NJS_EXTERN_PROPERTY,
         .name.string = njs_str("responseBody"),
+        .u.property = {
+            .handler = ngx_http_js_ext_get_response_body,
+            .magic32 = NGX_JS_STRING,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_PROPERTY,
+        .name.string = njs_str("resBody"),
         .enumerable = 1,
         .u.property = {
             .handler = ngx_http_js_ext_get_response_body,
+            .magic32 = NGX_JS_BUFFER,
         }
     },
 
@@ -379,6 +400,17 @@
         .u.object = {
             .writable = 1,
             .prop_handler = ngx_http_js_ext_variables,
+            .magic32 = NGX_JS_STRING,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_OBJECT,
+        .name.string = njs_str("vars"),
+        .u.object = {
+            .writable = 1,
+            .prop_handler = ngx_http_js_ext_variables,
+            .magic32 = NGX_JS_BUFFER,
         }
     },
 
@@ -1891,8 +1923,12 @@
     request_body = (njs_value_t *) &ctx->request_body;
 
     if (!njs_value_is_null(request_body)) {
-        njs_value_assign(retval, request_body);
-        return NJS_OK;
+        if ((njs_vm_prop_magic32(prop) == NGX_JS_BUFFER)
+            == (uint32_t) njs_value_is_buffer(request_body))
+        {
+            njs_value_assign(retval, request_body);
+            return NJS_OK;
+        }
     }
 
     if (r->request_body == NULL || r->request_body->bufs == NULL) {
@@ -1939,8 +1975,7 @@
 
 done:
 
-    ret = njs_vm_value_string_set(vm, request_body, body, len);
-
+    ret = ngx_js_prop(vm, njs_vm_prop_magic32(prop), request_body, body, len);
     if (ret != NJS_OK) {
         return NJS_ERROR;
     }
@@ -2220,7 +2255,8 @@
             return NJS_DECLINED;
         }
 
-        return njs_vm_value_string_set(vm, retval, vv->data, vv->len);
+        return ngx_js_prop(vm, njs_vm_prop_magic32(prop), retval, vv->data,
+                           vv->len);
     }
 
     cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
@@ -2743,7 +2779,10 @@
 {
     size_t               len;
     u_char              *p;
+    njs_int_t            ret;
     ngx_buf_t           *b;
+    njs_value_t         *response_body;
+    ngx_http_js_ctx_t   *ctx;
     ngx_http_request_t  *r;
 
     r = njs_vm_external(vm, value);
@@ -2752,6 +2791,18 @@
         return NJS_DECLINED;
     }
 
+    ctx = ngx_http_get_module_ctx(r, ngx_http_js_module);
+    response_body = (njs_value_t *) &ctx->response_body;
+
+    if (!njs_value_is_null(response_body)) {
+        if ((njs_vm_prop_magic32(prop) == NGX_JS_BUFFER)
+            == (uint32_t) njs_value_is_buffer(response_body))
+        {
+            njs_value_assign(retval, response_body);
+            return NJS_OK;
+        }
+    }
+
     b = r->out ? r->out->buf : NULL;
 
     if (b == NULL) {
@@ -2761,8 +2812,9 @@
 
     len = b->last - b->pos;
 
-    p = njs_vm_value_string_alloc(vm, retval, len);
+    p = ngx_pnalloc(r->pool, len);
     if (p == NULL) {
+        njs_vm_memory_error(vm);
         return NJS_ERROR;
     }
 
@@ -2770,6 +2822,13 @@
         ngx_memcpy(p, b->pos, len);
     }
 
+    ret = ngx_js_prop(vm, njs_vm_prop_magic32(prop), response_body, p, len);
+    if (ret != NJS_OK) {
+        return NJS_ERROR;
+    }
+
+    njs_value_assign(retval, response_body);
+
     return NJS_OK;
 }
 
diff --git a/nginx/ngx_js.h b/nginx/ngx_js.h
index 2bd90e2..0e290d5 100644
--- a/nginx/ngx_js.h
+++ b/nginx/ngx_js.h
@@ -15,10 +15,20 @@
 #include <njs.h>
 
 
+#define NGX_JS_UNSET   0
+#define NGX_JS_STRING  1
+#define NGX_JS_BUFFER  2
+
+
 #define ngx_external_connection(vm, ext)                                    \
     (*((ngx_connection_t **) ((u_char *) ext + njs_vm_meta(vm, 0))))
 
 
+#define ngx_js_prop(vm, type, value, start, len)                              \
+    ((type == NGX_JS_STRING) ? njs_vm_value_string_set(vm, value, start, len) \
+                             : njs_vm_value_buffer_set(vm, value, start, len))
+
+
 ngx_int_t ngx_js_call(njs_vm_t *vm, ngx_str_t *s, njs_opaque_value_t *value,
     ngx_log_t *log);
 
diff --git a/nginx/ngx_stream_js_module.c b/nginx/ngx_stream_js_module.c
index c4cac7a..2900ac5 100644
--- a/nginx/ngx_stream_js_module.c
+++ b/nginx/ngx_stream_js_module.c
@@ -39,6 +39,12 @@
 
 
 typedef struct {
+    njs_vm_event_t          ev;
+    ngx_uint_t              data_type;
+} ngx_stream_js_ev_t;
+
+
+typedef struct {
     njs_vm_t               *vm;
     ngx_log_t              *log;
     njs_opaque_value_t      args[3];
@@ -51,7 +57,7 @@
 #define NGX_JS_EVENT_UPLOAD   0
 #define NGX_JS_EVENT_DOWNLOAD 1
 #define NGX_JS_EVENT_MAX      2
-    njs_vm_event_t          events[2];
+    ngx_stream_js_ev_t      events[2];
     unsigned                from_upstream:1;
     unsigned                filter:1;
     unsigned                in_progress:1;
@@ -79,7 +85,7 @@
 static void ngx_stream_js_cleanup_ctx(void *data);
 static void ngx_stream_js_cleanup_vm(void *data);
 static njs_int_t ngx_stream_js_buffer_arg(ngx_stream_session_t *s,
-    njs_value_t *buffer);
+    njs_value_t *buffer, ngx_uint_t data_type);
 static njs_int_t ngx_stream_js_flags_arg(ngx_stream_session_t *s,
     njs_value_t *flags);
 static njs_vm_event_t *ngx_stream_js_event(ngx_stream_session_t *s,
@@ -233,6 +239,17 @@
         .u.object = {
             .writable = 1,
             .prop_handler = ngx_stream_js_ext_variables,
+            .magic32 = NGX_JS_STRING,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_OBJECT,
+        .name.string = njs_str("vars"),
+        .u.object = {
+            .writable = 1,
+            .prop_handler = ngx_stream_js_ext_variables,
+            .magic32 = NGX_JS_BUFFER,
         }
     },
 
@@ -412,6 +429,7 @@
     njs_int_t             ret;
     ngx_int_t             rc;
     ngx_connection_t     *c;
+    ngx_stream_js_ev_t   *event;
     ngx_stream_js_ctx_t  *ctx;
 
     if (name->len == 0) {
@@ -444,8 +462,11 @@
         }
     }
 
-    if (ctx->events[NGX_JS_EVENT_UPLOAD] != NULL) {
-        ret = ngx_stream_js_buffer_arg(s, njs_value_arg(&ctx->args[1]));
+    event = &ctx->events[NGX_JS_EVENT_UPLOAD];
+
+    if (event->ev != NULL) {
+        ret = ngx_stream_js_buffer_arg(s, njs_value_arg(&ctx->args[1]),
+                                       event->data_type);
         if (ret != NJS_OK) {
             goto exception;
         }
@@ -455,8 +476,7 @@
             goto exception;
         }
 
-        njs_vm_post_event(ctx->vm, ctx->events[NGX_JS_EVENT_UPLOAD],
-                          njs_value_arg(&ctx->args[1]), 2);
+        njs_vm_post_event(ctx->vm, event->ev, njs_value_arg(&ctx->args[1]), 2);
 
         rc = njs_vm_run(ctx->vm);
         if (rc == NJS_ERROR) {
@@ -466,7 +486,7 @@
 
     if (njs_vm_pending(ctx->vm)) {
         ctx->in_progress = 1;
-        rc = ctx->events[NGX_JS_EVENT_UPLOAD] ? NGX_AGAIN : NGX_DONE;
+        rc = ctx->events[NGX_JS_EVENT_UPLOAD].ev ? NGX_AGAIN : NGX_DONE;
 
     } else {
         ctx->in_progress = 0;
@@ -490,8 +510,8 @@
 
 
 #define ngx_stream_event(from_upstream)                                 \
-    (from_upstream ? ctx->events[NGX_JS_EVENT_DOWNLOAD]                 \
-                   : ctx->events[NGX_JS_EVENT_UPLOAD])
+    (from_upstream ? &ctx->events[NGX_JS_EVENT_DOWNLOAD]                \
+                   : &ctx->events[NGX_JS_EVENT_UPLOAD])
 
 
 static ngx_int_t
@@ -503,6 +523,7 @@
     ngx_int_t                  rc;
     ngx_chain_t               *out, *cl;
     ngx_connection_t          *c;
+    ngx_stream_js_ev_t        *event;
     ngx_stream_js_ctx_t       *ctx;
     ngx_stream_js_srv_conf_t  *jscf;
 
@@ -543,8 +564,11 @@
     while (in) {
         ctx->buf = in->buf;
 
-        if (ngx_stream_event(from_upstream) != NULL) {
-            ret = ngx_stream_js_buffer_arg(s, njs_value_arg(&ctx->args[1]));
+        event = ngx_stream_event(from_upstream);
+
+        if (event->ev != NULL) {
+            ret = ngx_stream_js_buffer_arg(s, njs_value_arg(&ctx->args[1]),
+                                           event->data_type);
             if (ret != NJS_OK) {
                 goto exception;
             }
@@ -554,7 +578,7 @@
                 goto exception;
             }
 
-            njs_vm_post_event(ctx->vm, ngx_stream_event(from_upstream),
+            njs_vm_post_event(ctx->vm, event->ev,
                               njs_value_arg(&ctx->args[1]), 2);
 
             rc = njs_vm_run(ctx->vm);
@@ -729,9 +753,9 @@
     ngx_uint_t  i;
 
     for (i = 0; i < NGX_JS_EVENT_MAX; i++) {
-        if (ctx->events[i] != NULL) {
-            njs_vm_del_event(ctx->vm, ctx->events[i]);
-            ctx->events[i] = NULL;
+        if (ctx->events[i].ev != NULL) {
+            njs_vm_del_event(ctx->vm, ctx->events[i].ev);
+            ctx->events[i].ev = NULL;
         }
     }
 }
@@ -762,7 +786,8 @@
 
 
 static njs_int_t
-ngx_stream_js_buffer_arg(ngx_stream_session_t *s, njs_value_t *buffer)
+ngx_stream_js_buffer_arg(ngx_stream_session_t *s, njs_value_t *buffer,
+    ngx_uint_t data_type)
 {
     size_t                 len;
     u_char                *p;
@@ -777,8 +802,9 @@
 
     len = b ? b->last - b->pos : 0;
 
-    p = njs_vm_value_string_alloc(ctx->vm, buffer, len);
+    p = ngx_pnalloc(c->pool, len);
     if (p == NULL) {
+        njs_vm_memory_error(ctx->vm);
         return NJS_ERROR;
     }
 
@@ -786,11 +812,10 @@
         ngx_memcpy(p, b->pos, len);
     }
 
-    return NJS_OK;
+    return ngx_js_prop(ctx->vm, data_type, buffer, p, len);
 }
 
 
-
 static njs_int_t
 ngx_stream_js_flags_arg(ngx_stream_session_t *s, njs_value_t *flags)
 {
@@ -821,12 +846,37 @@
 static njs_vm_event_t *
 ngx_stream_js_event(ngx_stream_session_t *s, njs_str_t *event)
 {
-    ngx_uint_t             i, n;
+    ngx_uint_t            i, n, type;
     ngx_stream_js_ctx_t  *ctx;
 
-    static const njs_str_t events[] = {
-        njs_str("upload"),
-        njs_str("download")
+    static const struct {
+        ngx_str_t   name;
+        ngx_uint_t  data_type;
+        ngx_uint_t  id;
+    } events[] = {
+        {
+            ngx_string("upload"),
+            NGX_JS_STRING,
+            NGX_JS_EVENT_UPLOAD,
+        },
+
+        {
+            ngx_string("download"),
+            NGX_JS_STRING,
+            NGX_JS_EVENT_DOWNLOAD,
+        },
+
+        {
+            ngx_string("upstream"),
+            NGX_JS_BUFFER,
+            NGX_JS_EVENT_UPLOAD,
+        },
+
+        {
+            ngx_string("downstream"),
+            NGX_JS_BUFFER,
+            NGX_JS_EVENT_DOWNLOAD,
+        },
     };
 
     ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module);
@@ -835,8 +885,9 @@
     n = sizeof(events) / sizeof(events[0]);
 
     while (i < n) {
-        if (event->length == events[i].length
-            && ngx_memcmp(event->start, events[i].start, event->length) == 0)
+        if (event->length == events[i].name.len
+            && ngx_memcmp(event->start, events[i].name.data, event->length)
+               == 0)
         {
             break;
         }
@@ -849,11 +900,18 @@
         return NULL;
     }
 
-    if (i == 0) {
-        return &ctx->events[NGX_JS_EVENT_UPLOAD];
+    ctx->events[events[i].id].data_type = events[i].data_type;
+
+    for (n = 0; n < NGX_JS_EVENT_MAX; n++) {
+        type = ctx->events[n].data_type;
+        if (type != NGX_JS_UNSET && type != events[i].data_type) {
+            njs_vm_error(ctx->vm, "mixing string and buffer events"
+                         " is not allowed");
+            return NULL;
+        }
     }
 
-    return &ctx->events[NGX_JS_EVENT_DOWNLOAD];
+    return &ctx->events[events[i].id].ev;
 }
 
 
@@ -1131,7 +1189,8 @@
             return NJS_DECLINED;
         }
 
-        return njs_vm_value_string_set(vm, retval, vv->data, vv->len);
+        return ngx_js_prop(vm, njs_vm_prop_magic32(prop), retval, vv->data,
+                           vv->len);
     }
 
     cmcf = ngx_stream_get_module_main_conf(s, ngx_stream_core_module);
diff --git a/src/njs.h b/src/njs.h
index cf63f21..1bfc915 100644
--- a/src/njs.h
+++ b/src/njs.h
@@ -144,6 +144,7 @@
             unsigned                configurable;
             unsigned                enumerable;
             njs_prop_handler_t      prop_handler;
+            uint32_t                magic32;
             njs_exotic_keys_t       keys;
         } object;
     } u;
@@ -389,6 +390,7 @@
 NJS_EXPORT njs_int_t njs_value_is_object(const njs_value_t *value);
 NJS_EXPORT njs_int_t njs_value_is_array(const njs_value_t *value);
 NJS_EXPORT njs_int_t njs_value_is_function(const njs_value_t *value);
+NJS_EXPORT njs_int_t njs_value_is_buffer(const njs_value_t *value);
 
 NJS_EXPORT njs_int_t njs_vm_object_alloc(njs_vm_t *vm, njs_value_t *retval,
     ...);
diff --git a/src/njs_extern.c b/src/njs_extern.c
index 8d0ddf5..a2dfd01 100644
--- a/src/njs_extern.c
+++ b/src/njs_extern.c
@@ -132,6 +132,7 @@
             next->configurable = external->u.object.configurable;
             next->enumerable = external->u.object.enumerable;
             next->prop_handler = external->u.object.prop_handler;
+            next->magic32 = external->u.object.magic32;
             next->keys = external->u.object.keys;
 
             break;
diff --git a/src/njs_value.c b/src/njs_value.c
index 365013f..d72116a 100644
--- a/src/njs_value.c
+++ b/src/njs_value.c
@@ -492,6 +492,13 @@
 }
 
 
+njs_int_t
+njs_value_is_buffer(const njs_value_t *value)
+{
+    return njs_is_typed_array(value);
+}
+
+
 /*
  * ES5.1, 8.12.1: [[GetOwnProperty]], [[GetProperty]].
  * The njs_property_query() returns values
@@ -932,6 +939,7 @@
      *   njs_set_null(&prop->setter);
      */
 
+    prop->value.data.magic32 = slots->magic32;
     prop->name = pq->key;
 
     pq->lhq.value = prop;
diff --git a/src/njs_value.h b/src/njs_value.h
index bd9c672..06e4fd7 100644
--- a/src/njs_value.h
+++ b/src/njs_value.h
@@ -189,6 +189,7 @@
 typedef struct {
     /* Get, also Set if writable, also Delete if configurable. */
     njs_prop_handler_t  prop_handler;
+    uint32_t            magic32;
     unsigned            writable:1;
     unsigned            configurable:1;
     unsigned            enumerable:1;