/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
#include "lib.h"
#include "buffer.h"
#include "hex-dec.h"
#include "unichar.h"
#include "istream-private.h"
#include "istream-jsonstr.h"
#define MAX_UTF8_LEN 6
struct jsonstr_istream {
struct istream_private istream;
/* The end '"' was found */
bool str_end:1;
};
static int
i_stream_jsonstr_read_parent(struct jsonstr_istream *jstream,
unsigned int min_bytes)
{
struct istream_private *stream = &jstream->istream;
size_t size, avail;
ssize_t ret;
size = i_stream_get_data_size(stream->parent);
while (size < min_bytes) {
ret = i_stream_read_memarea(stream->parent);
if (ret <= 0) {
if (ret == -2) {
/* tiny parent buffer size - shouldn't happen */
return -2;
}
stream->istream.stream_errno =
stream->parent->stream_errno;
stream->istream.eof = stream->parent->eof;
if (ret == -1 && stream->istream.stream_errno == 0) {
io_stream_set_error(&stream->iostream,
"EOF before trailing <\"> was seen");
stream->istream.stream_errno = EPIPE;
}
return ret;
}
size = i_stream_get_data_size(stream->parent);
}
if (!i_stream_try_alloc(stream, size, &avail))
return -2;
return 1;
}
static int
i_stream_json_unescape(const unsigned char *src, size_t len,
unsigned char *dest,
unsigned int *src_size_r, unsigned int *dest_size_r)
{
switch (*src) {
case '"':
case '\\':
case '/':
*dest = *src;
break;
case 'b':
*dest = '\b';
break;
case 'f':
*dest = '\f';
break;
case 'n':
*dest = '\n';
break;
case 'r':
*dest = '\r';
break;
case 't':
*dest = '\t';
break;
case 'u': {
char chbuf[5] = {0};
unichar_t chr,chr2 = 0;
buffer_t buf;
if (len < 5)
return 5;
buffer_create_from_data(&buf, dest, MAX_UTF8_LEN);
memcpy(chbuf, src+1, 4);
if (str_to_uint32_hex(chbuf, &chr)<0)
return -1;
if (UTF16_VALID_LOW_SURROGATE(chr))
return -1;
/* if we encounter surrogate, we need another \\uxxxx */
if (UTF16_VALID_HIGH_SURROGATE(chr)) {
if (len < 5+2+4)
return 5+2+4;
if (src[5] != '\\' && src[6] != 'u')
return -1;
memcpy(chbuf, src+7, 4);
if (str_to_uint32_hex(chbuf, &chr2)<0)
return -1;
if (!UTF16_VALID_LOW_SURROGATE(chr2))
return -1;
chr = uni_join_surrogate(chr, chr2);
}
if (!uni_is_valid_ucs4(chr))
return -1;
uni_ucs4_to_utf8_c(chr, &buf);
*src_size_r = 5 + (chr2>0?6:0);
*dest_size_r = buf.used;
return 0;
}
default:
return -1;
}
*src_size_r = 1;
*dest_size_r = 1;
return 0;
}
static ssize_t i_stream_jsonstr_read(struct istream_private *stream)
{
struct jsonstr_istream *jstream = (struct jsonstr_istream *)stream;
const unsigned char *data;
unsigned int srcskip, destskip, extra;
size_t i, dest, size;
ssize_t ret, ret2;
if (jstream->str_end) {
stream->istream.eof = TRUE;
return -1;
}
ret = i_stream_jsonstr_read_parent(jstream, 1);
if (ret <= 0)
return ret;
/* @UNSAFE */
dest = stream->pos;
extra = 0;
data = i_stream_get_data(stream->parent, &size);
for (i = 0; i < size && dest < stream->buffer_size; ) {
if (data[i] == '"') {
jstream->str_end = TRUE;
if (dest == stream->pos) {
stream->istream.eof = TRUE;
return -1;
}
break;
} else if (data[i] == '\\') {
if (i+1 == size) {
/* not enough input for \x */
extra = 1;
break;
}
if (data[i+1] == 'u' && stream->buffer_size - dest < MAX_UTF8_LEN) {
/* UTF8 output is max. 6 chars */
if (dest == stream->pos)
return -2;
break;
}
i++;
if ((ret2 = i_stream_json_unescape(data + i, size - i,
stream->w_buffer + dest,
&srcskip, &destskip)) < 0) {
/* invalid string */
io_stream_set_error(&stream->iostream,
"Invalid JSON string");
stream->istream.stream_errno = EINVAL;
return -1;
} else if (ret2 > 0) {
/* we need to get more bytes, do not consume
escape slash */
i--;
extra = ret2;
break;
}
i += srcskip;
i_assert(i <= size);
dest += destskip;
i_assert(dest <= stream->buffer_size);
} else {
stream->w_buffer[dest++] = data[i];
i++;
}
}
i_stream_skip(stream->parent, i);
ret = dest - stream->pos;
if (ret == 0) {
/* not enough input */
i_assert(i == 0);
i_assert(extra > 0);
ret = i_stream_jsonstr_read_parent(jstream, extra+1);
if (ret <= 0)
return ret;
return i_stream_jsonstr_read(stream);
}
i_assert(ret > 0);
stream->pos = dest;
return ret;
}
struct istream *i_stream_create_jsonstr(struct istream *input)
{
struct jsonstr_istream *dstream;
dstream = i_new(struct jsonstr_istream, 1);
dstream->istream.max_buffer_size = input->real_stream->max_buffer_size;
dstream->istream.read = i_stream_jsonstr_read;
dstream->istream.istream.readable_fd = FALSE;
dstream->istream.istream.blocking = input->blocking;
dstream->istream.istream.seekable = FALSE;
return i_stream_create(&dstream->istream, input,
i_stream_get_fd(input), 0);
}