#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <termios.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <ctype.h>
#include "fy-utf8.h"
#include "fy-ctype.h"
#include "fy-utils.h"
#include "xxhash.h"
#if defined(__APPLE__) && (_POSIX_C_SOURCE < 200809L)
#ifndef min
#define min(X, Y) (((X) < (Y)) ? (X) : (Y))
#endif
struct memstream {
size_t position;
size_t size;
size_t capacity;
char *contents;
char **ptr;
size_t *sizeloc;
};
static int memstream_grow(struct memstream *ms, size_t minsize)
{
size_t newcap;
char *newcontents;
newcap = ms->capacity * 2;
while (newcap <= minsize + 1)
newcap *= 2;
newcontents = realloc(ms->contents, newcap);
if (!newcontents)
return -1;
ms->contents = newcontents;
memset(ms->contents + ms->capacity, 0, newcap - ms->capacity);
ms->capacity = newcap;
*ms->ptr = ms->contents;
return 0;
}
static int memstream_read(void *cookie, char *buf, int count)
{
struct memstream *ms = cookie;
size_t n;
n = min(ms->size - ms->position, (size_t)count);
if (n < 1)
return 0;
memcpy(buf, ms->contents, n);
ms->position += n;
return n;
}
static int memstream_write(void *cookie, const char *buf, int count)
{
struct memstream *ms = cookie;
if (ms->capacity <= ms->position + (size_t)count &&
memstream_grow(ms, ms->position + (size_t)count) < 0)
return -1;
memcpy(ms->contents + ms->position, buf, count);
ms->position += count;
ms->contents[ms->position] = '\0';
if (ms->size < ms->position)
*ms->sizeloc = ms->size = ms->position;
return count;
}
static fpos_t memstream_seek(void *cookie, fpos_t offset, int whence)
{
struct memstream *ms = cookie;
fpos_t pos= 0;
switch (whence) {
case SEEK_SET:
pos = offset;
break;
case SEEK_CUR:
pos = ms->position + offset;
break;
case SEEK_END:
pos = ms->size + offset;
break;
default:
errno= EINVAL;
return -1;
}
if (pos >= (fpos_t)ms->capacity && memstream_grow(ms, pos) < 0)
return -1;
ms->position = pos;
if (ms->size < ms->position)
*ms->sizeloc = ms->size = ms->position;
return pos;
}
static int memstream_close(void *cookie)
{
struct memstream *ms = cookie;
ms->size = min(ms->size, ms->position);
*ms->ptr = ms->contents;
*ms->sizeloc = ms->size;
ms->contents[ms->size]= 0;
free(ms);
return 0;
}
FILE *open_memstream(char **ptr, size_t *sizeloc)
{
struct memstream *ms;
FILE *fp;
if (!ptr || !sizeloc) {
errno= EINVAL;
goto err_out;
}
ms = calloc(1, sizeof(struct memstream));
if (!ms)
goto err_out;
ms->position = ms->size= 0;
ms->capacity = 4096;
ms->contents = calloc(ms->capacity, 1);
if (!ms->contents)
goto err_free_ms;
ms->ptr = ptr;
ms->sizeloc = sizeloc;
fp= funopen(ms, memstream_read, memstream_write,
memstream_seek, memstream_close);
if (!fp)
goto err_free_all;
*ptr = ms->contents;
*sizeloc = ms->size;
return fp;
err_free_all:
free(ms->contents);
err_free_ms:
free(ms);
err_out:
return NULL;
}
#endif
bool fy_tag_uri_is_valid(const char *data, size_t len)
{
const char *s, *e;
int w, j, k, width, c;
uint8_t octet, esc_octets[4];
s = data;
e = s + len;
while ((c = fy_utf8_get(s, e - s, &w)) >= 0) {
if (c != '%') {
s += w;
continue;
}
width = 0;
k = 0;
do {
if ((e - s) < 3)
return false;
if (width > 0) {
c = fy_utf8_get(s, e - s, &w);
if (c != '%')
return false;
}
s += w;
octet = 0;
for (j = 0; j < 2; j++) {
c = fy_utf8_get(s, e - s, &w);
if (!fy_is_hex(c))
return false;
s += w;
octet <<= 4;
if (c >= '0' && c <= '9')
octet |= c - '0';
else if (c >= 'a' && c <= 'f')
octet |= 10 + c - 'a';
else
octet |= 10 + c - 'A';
}
if (!width) {
width = fy_utf8_width_by_first_octet(octet);
if (width < 1 || width > 4)
return false;
k = 0;
}
esc_octets[k++] = octet;
} while (--width > 0);
c = fy_utf8_get(esc_octets, k, &w);
if (c < 0)
return false;
}
return true;
}
int fy_tag_handle_length(const char *data, size_t len)
{
const char *s, *e;
int c, w;
s = data;
e = s + len;
c = fy_utf8_get(s, e - s, &w);
if (c != '!')
return -1;
s += w;
c = fy_utf8_get(s, e - s, &w);
if (c == -1)
return len;
if (fy_is_ws(c))
return s - data;
if (c == '!') {
s += w;
return s - data;
}
if (!fy_is_first_alpha(c))
return -1;
s += w;
while (fy_is_alnum(c = fy_utf8_get(s, e - s, &w)))
s += w;
if (c == '!')
s += w;
return s - data;
}
int fy_tag_uri_length(const char *data, size_t len)
{
const char *s, *e;
int c, w, cn, wn, uri_length;
s = data;
e = s + len;
while (fy_is_uri(c = fy_utf8_get(s, e - s, &w))) {
cn = fy_utf8_get(s + w, e - (s + w), &wn);
if ((fy_is_z(cn) || fy_is_blank(cn) || fy_is_any_lb(cn)) && fy_utf8_strchr(",}]", c))
break;
s += w;
}
uri_length = s - data;
if (!fy_tag_uri_is_valid(data, uri_length))
return -1;
return uri_length;
}
int fy_tag_scan(const char *data, size_t len, struct fy_tag_scan_info *info)
{
const char *s, *e;
int total_length, handle_length, uri_length, prefix_length, suffix_length;
int c, cn, w, wn;
s = data;
e = s + len;
prefix_length = 0;
c = fy_utf8_get(s, e - s, &w);
if (c != '!')
return -1;
cn = fy_utf8_get(s + w, e - (s + w), &wn);
if (cn == '<') {
prefix_length = 2;
suffix_length = 1;
} else
prefix_length = suffix_length = 0;
if (prefix_length) {
handle_length = 0;
s += prefix_length;
} else {
handle_length = fy_tag_handle_length(s, e - s);
if (handle_length < 0)
return -1;
s += handle_length;
}
uri_length = fy_tag_uri_length(s, e - s);
if (uri_length < 0)
return -1;
if (!prefix_length && (handle_length == 0 || data[handle_length - 1] != '!')) {
if (handle_length == 1 && uri_length == 0) {
handle_length = 0;
uri_length = 1;
} else {
uri_length = handle_length - 1 + uri_length;
handle_length = 1;
}
}
total_length = prefix_length + handle_length + uri_length + suffix_length;
if (total_length != (int)len)
return -1;
info->total_length = total_length;
info->handle_length = handle_length;
info->uri_length = uri_length;
info->prefix_length = prefix_length;
info->suffix_length = suffix_length;
return 0;
}
int fy_term_set_raw(int fd, struct termios *oldt)
{
struct termios newt, t;
int ret;
if (!isatty(fd))
return -1;
ret = tcgetattr(fd, &t);
if (ret != 0)
return ret;
newt = t;
cfmakeraw(&newt);
ret = tcsetattr(fd, TCSANOW, &newt);
if (ret != 0)
return ret;
if (oldt)
*oldt = t;
return 0;
}
int fy_term_restore(int fd, const struct termios *oldt)
{
if (!isatty(fd))
return -1;
return tcsetattr(fd, TCSANOW, oldt);
}
ssize_t fy_term_write(int fd, const void *data, size_t count)
{
ssize_t wrn, r;
if (!isatty(fd))
return -1;
r = 0;
wrn = 0;
while (count > 0) {
do {
r = write(fd, data, count);
} while (r == -1 && errno == EAGAIN);
if (r < 0)
break;
wrn += r;
data += r;
count -= r;
}
return wrn > 0 ? wrn : r;
}
int fy_term_safe_write(int fd, const void *data, size_t count)
{
if (!isatty(fd))
return -1;
return fy_term_write(fd, data, count) == (ssize_t)count ? 0 : -1;
}
ssize_t fy_term_read(int fd, void *data, size_t count, int timeout_us)
{
ssize_t rdn, r;
struct timeval tv, tvto, *tvp;
fd_set rdfds;
if (!isatty(fd))
return -1;
FD_ZERO(&rdfds);
memset(&tvto, 0, sizeof(tvto));
memset(&tv, 0, sizeof(tv));
if (timeout_us >= 0) {
tvto.tv_sec = timeout_us / 1000000;
tvto.tv_usec = timeout_us % 1000000;
tvp = &tv;
} else {
tvp = NULL;
}
r = 0;
rdn = 0;
while (count > 0) {
do {
FD_SET(fd, &rdfds);
if (tvp)
*tvp = tvto;
r = select(fd + 1, &rdfds, NULL, NULL, tvp);
} while (r == -1 && errno == EAGAIN);
if (r <= 0 || !FD_ISSET(fd, &rdfds))
break;
do {
r = read(fd, data, count);
} while (r == -1 && errno == EAGAIN);
if (r < 0)
break;
rdn += r;
data += r;
count -= r;
}
return rdn > 0 ? rdn : r;
}
ssize_t fy_term_read_escape(int fd, void *buf, size_t count)
{
char *p;
int r, rdn;
char c;
if (count < 3)
return -1;
p = buf;
rdn = 0;
r = fy_term_read(fd, &c, 1, 100 * 1000);
if (r != 1 || c != '\x1b')
return -1;
*p++ = c;
count--;
rdn++;
r = fy_term_read(fd, &c, 1, 100 * 1000);
if (r != 1 || c != '[')
return rdn;
*p++ = c;
count--;
rdn++;
r = -1;
while (count > 0) {
r = fy_term_read(fd, &c, 1, 100 * 1000);
if (r != 1)
r = -1;
if (r != 1)
break;
*p++ = c;
count--;
rdn++;
if (c >= 0x40 && c <= 0x7e)
break;
}
return rdn;
}
int fy_term_query_size_raw(int fd, int *rows, int *cols)
{
char buf[32];
char *s, *e;
ssize_t r;
if (!isatty(fd))
return -1;
*rows = *cols = 0;
r = fy_term_safe_write(fd, "\x1b[18t", 5);
if (r != 0)
return r;
r = fy_term_read_escape(fd, buf, sizeof(buf));
if (r < 8 || r >= (int)sizeof(buf) - 2)
return -1;
s = buf;
e = s + r;
if (s[0] != '\x1b' || s[1] != '[' || s[2] != '8' || s[3] != ';')
return -1;
s += 4;
if (e[-1] != 't')
return -1;
*--e = '\0';
r = sscanf(s, "%d;%d", rows, cols);
if (r != 2)
return -1;
return 0;
}
int fy_term_query_size(int fd, int *rows, int *cols)
{
struct termios old_term;
int ret, r;
if (!isatty(fd))
return -1;
r = fy_term_set_raw(fd, &old_term);
if (r != 0)
return -1;
ret = fy_term_query_size_raw(fd, rows, cols);
r = fy_term_restore(fd, &old_term);
if (r != 0)
return -1;
return ret;
}
int fy_comment_iter_begin(const char *comment, size_t size, struct fy_comment_iter *iter)
{
if (!comment || !iter)
return -1;
memset(iter, 0, sizeof(*iter));
iter->start = comment;
iter->size = size == (size_t)-1 ? strlen(comment) : size;
iter->end = iter->start + iter->size;
if (!iter->size)
return -1;
iter->next = iter->start;
iter->line = 0;
return 0;
}
const char *fy_comment_iter_next_line(struct fy_comment_iter *iter, size_t *lenp)
{
const char *s, *e, *le, *t;
if (!iter || !lenp || !iter->start)
return NULL;
if (iter->next >= iter->end)
return NULL;
again:
*lenp = 0;
s = iter->next;
e = iter->end;
while (s < e && isblank((unsigned char)*s))
s++;
if (s >= e)
return NULL;
le = memchr(s, '\n', e - s);
if (!le) {
le = e;
iter->next = e;
} else {
iter->next = le + 1;
}
if (le >= e && (le - s) > 2 && le[-2] == '*' && le[-1] == '/')
le -= 2;
while (le > s && isblank((unsigned char)le[-1]))
le--;
t = s;
while (t < le && ispunct((unsigned char)*t))
t++;
if (t > s && t >= le) {
if (((le - s) == 2 && s[0] == '/' && s[1] == '/') ||
((le - s) == 1 && s[0] == '*')) {
iter->line++;
return "";
}
goto again;
}
if ((le - s) > 3 && s[0] == '/' && s[1] == '/' && isblank((unsigned char)s[2]))
s += 3;
else if ((le - s) > 2 && s[0] == '*' && isblank((unsigned char)s[1]))
s += 2;
else if (iter->line == 0 && (le - s) > 3 && s[0] == '/' && s[1] == '*' && isblank((unsigned char)s[2]))
s += 3;
iter->line++;
*lenp = le - s;
return s;
}
void fy_comment_iter_end(struct fy_comment_iter *iter)
{
}
char *fy_get_cooked_comment(const char *raw_comment, size_t size)
{
struct fy_comment_iter iter;
FILE *fp;
char *buf;
const char *line;
size_t len, line_len;
int ret;
if (!raw_comment)
return NULL;
fp = open_memstream(&buf, &len);
if (!fp)
return NULL;
ret = 0;
fy_comment_iter_begin(raw_comment, size, &iter);
while ((line = fy_comment_iter_next_line(&iter, &line_len)) != NULL) {
ret = fprintf(fp, "%.*s\n", (int)line_len, line);
if (ret < 0)
break;
}
fy_comment_iter_end(&iter);
fclose(fp);
if (ret < 0) {
if (buf)
free(buf);
return NULL;
}
return buf;
}
int fy_keyword_iter_begin(const char *text, size_t size, const char *keyword, struct fy_keyword_iter *iter)
{
if (!text || !size || !keyword || !iter)
return -1;
memset(iter, 0, sizeof(*iter));
iter->keyword = keyword;
iter->keyword_len = strlen(keyword);
iter->start = text;
iter->size = size == (size_t)-1 ? strlen(text) : size;
iter->end = iter->start + iter->size;
iter->pc = '\n';
if (!iter->size)
return -1;
iter->next = iter->start;
return 0;
}
const char *fy_keyword_iter_next(struct fy_keyword_iter *iter)
{
const char *keyword;
size_t keyword_len;
const char *s, *e;
int c, w, pc, mode;
if (!iter || !iter->start)
return NULL;
if (iter->next >= iter->end)
return NULL;
s = iter->next;
e = iter->end;
keyword = iter->keyword;
keyword_len = iter->keyword_len;
mode = 0;
pc = iter->pc;
while ((c = fy_utf8_get_s(s, e, &w)) >= 0) {
switch (mode) {
case 0:
if (c == keyword[0] && (fy_is_any_lb(pc) || fy_is_ws(pc)) &&
(size_t)(e - s) > (keyword_len + 1) && !memcmp(s, keyword, keyword_len) &&
(fy_is_ws(s[keyword_len]) || fy_is_any_lb(s[keyword_len]))) {
iter->next = s;
iter->pc = pc;
return s;
}
if (c == '\'')
mode = 1;
else if (c == '"')
mode = 3;
break;
case 1:
if (c == '\\')
mode = 2;
else if (c == '\'')
mode = 0;
break;
case 2:
mode = 1;
break;
case 3:
if (c == '\\')
mode = 4;
else if (c == '"')
mode = 0;
break;
case 4:
mode = 3;
break;
}
s += w;
pc = c;
}
return NULL;
}
void fy_keyword_iter_advance(struct fy_keyword_iter *iter, size_t advance)
{
int w;
const char *prev;
if (!iter || !iter->next)
return;
prev = iter->next;
iter->next += advance;
if (iter->next >= iter->end)
iter->next = iter->end;
iter->pc = fy_utf8_get_right(prev, (size_t)(iter->next - prev), &w);
if (iter->pc < 0)
iter->pc = '\n';
}
void fy_keyword_iter_end(struct fy_keyword_iter *iter)
{
}
#define FY_XXHASH64_SEED ((uint64_t)0x1973198120142019U)
uint64_t fy_iovec_xxhash64(const struct iovec *iov, int iovcnt)
{
XXH64_state_t xxstate;
uint64_t hash;
int i;
XXH64_reset(&xxstate, FY_XXHASH64_SEED);
for (i = 0; i < iovcnt; i++)
XXH64_update(&xxstate, iov[i].iov_base, iov[i].iov_len);
hash = XXH64_digest(&xxstate);
if (!hash)
hash++;
return hash;
}