- /*
- * Copyright (C) Nginx, Inc.
- */
- /*
- * JSON string unescape. Decodes escape sequences defined by
- * RFC 8259, Section 7, including \uXXXX and UTF-16 surrogate pairs.
- */
- #include <ngx_config.h>
- #include <ngx_core.h>
- #include <ngx_json_unescape.h>
- static ngx_inline u_char *ngx_json_utf8_encode(u_char *dst,
- uint32_t codepoint);
- ngx_int_t
- ngx_json_unescape_string(ngx_str_t *str)
- {
- size_t size;
- u_char *d, *s, *start, ch;
- uint32_t codepoint, high_surrogate;
- ngx_int_t n;
- ngx_uint_t hex_left;
- enum {
- sw_usual = 0,
- sw_quoted,
- sw_hex,
- sw_surrogate_start, /* expect '\' of \uDCxx after high surrogate */
- sw_surrogate_u /* expect 'u' */
- } state;
- /*
- * RFC 8259, Section 7:
- *
- * string = quotation-mark *char quotation-mark
- *
- * char = unescaped /
- * escape (
- * %x22 / ; " quotation mark U+0022
- * %x5C / ; \ reverse solidus U+005C
- * %x2F / ; / solidus U+002F
- * %x62 / ; b backspace U+0008
- * %x66 / ; f form feed U+000C
- * %x6E / ; n line feed U+000A
- * %x72 / ; r carriage return U+000D
- * %x74 / ; t tab U+0009
- * %x75 4HEXDIG ) ; uXXXX U+XXXX
- *
- * Non-BMP code points are encoded as a UTF-16 surrogate pair:
- * \uD800..\uDBFF high surrogate
- * \uDC00..\uDFFF low surrogate
- * The pair decodes to U+10000 + (high - 0xD800) * 0x400
- * + (low - 0xDC00).
- * The input is an unquoted JSON string body (the bytes between the
- * surrounding double quotes); it is decoded in place.
- */
- if (str->len == 0) {
- return NGX_OK;
- }
- if (str->data == NULL) {
- return NGX_ERROR;
- }
- start = str->data;
- size = str->len;
- d = s = start;
- state = sw_usual;
- codepoint = 0;
- high_surrogate = 0;
- hex_left = 0;
- while (size--) {
- ch = *s++;
- switch (state) {
- case sw_usual:
- if (ch == '"') {
- return NGX_ERROR;
- }
- if (ch == '\\') {
- state = sw_quoted;
- break;
- }
- if (ch < 0x20) {
- /* RFC 8259: control characters must be escaped */
- return NGX_ERROR;
- }
- *d++ = ch;
- break;
- case sw_quoted:
- switch (ch) {
- case 'u':
- codepoint = 0;
- hex_left = 4;
- state = sw_hex;
- break;
- case '"':
- case '/':
- case '\\':
- *d++ = ch;
- state = sw_usual;
- break;
- case 'b':
- *d++ = '\b';
- state = sw_usual;
- break;
- case 'f':
- *d++ = '\f';
- state = sw_usual;
- break;
- case 'n':
- *d++ = '\n';
- state = sw_usual;
- break;
- case 'r':
- *d++ = '\r';
- state = sw_usual;
- break;
- case 't':
- *d++ = '\t';
- state = sw_usual;
- break;
- default:
- return NGX_ERROR;
- }
- break;
- case sw_hex:
- n = ngx_json_hex_digit(ch);
- if (n == NGX_ERROR) {
- return NGX_ERROR;
- }
- codepoint = (codepoint << 4) | (uint32_t) n;
- if (--hex_left > 0) {
- break;
- }
- if (high_surrogate) {
- if (codepoint < 0xDC00 || codepoint > 0xDFFF) {
- return NGX_ERROR;
- }
- codepoint = 0x10000
- + ((high_surrogate - 0xD800) << 10)
- + (codepoint - 0xDC00);
- d = ngx_json_utf8_encode(d, codepoint);
- high_surrogate = 0;
- state = sw_usual;
- break;
- }
- if (codepoint >= 0xD800 && codepoint <= 0xDBFF) {
- /* high surrogate - wait for the low surrogate */
- high_surrogate = codepoint;
- state = sw_surrogate_start;
- break;
- }
- if (codepoint >= 0xDC00 && codepoint <= 0xDFFF) {
- /* lone low surrogate */
- return NGX_ERROR;
- }
- d = ngx_json_utf8_encode(d, codepoint);
- state = sw_usual;
- break;
- case sw_surrogate_start:
- /*
- * Expect '\' to begin the low-surrogate escape.
- * Lone surrogates are not valid Unicode scalar values
- * (RFC 8259, Section 8.2).
- */
- if (ch == '\\') {
- state = sw_surrogate_u;
- break;
- }
- /* lone high surrogate */
- return NGX_ERROR;
- case sw_surrogate_u:
- if (ch == 'u') {
- codepoint = 0;
- hex_left = 4;
- state = sw_hex;
- break;
- }
- /* lone high surrogate */
- return NGX_ERROR;
- }
- }
- if (state != sw_usual) {
- /* truncated escape sequence or unpaired high surrogate */
- return NGX_ERROR;
- }
- str->data = start;
- str->len = d - start;
- return NGX_OK;
- }
- static ngx_inline u_char *
- ngx_json_utf8_encode(u_char *dst, uint32_t codepoint)
- {
- if (codepoint <= 0x7F) {
- *dst++ = (u_char) codepoint;
- } else if (codepoint <= 0x7FF) {
- *dst++ = (u_char) (0xC0 | (codepoint >> 6));
- *dst++ = (u_char) (0x80 | (codepoint & 0x3F));
- } else if (codepoint <= 0xFFFF) {
- *dst++ = (u_char) (0xE0 | (codepoint >> 12));
- *dst++ = (u_char) (0x80 | ((codepoint >> 6) & 0x3F));
- *dst++ = (u_char) (0x80 | (codepoint & 0x3F));
- } else {
- *dst++ = (u_char) (0xF0 | (codepoint >> 18));
- *dst++ = (u_char) (0x80 | ((codepoint >> 12) & 0x3F));
- *dst++ = (u_char) (0x80 | ((codepoint >> 6) & 0x3F));
- *dst++ = (u_char) (0x80 | (codepoint & 0x3F));
- }
- return dst;
- }
- ngx_int_t
- ngx_json_hex_digit(u_char ch)
- {
- if (ch >= '0' && ch <= '9') {
- return ch - '0';
- }
- ch = (u_char) (ch | 0x20);
- if (ch >= 'a' && ch <= 'f') {
- return ch - 'a' + 10;
- }
- return NGX_ERROR;
- }