#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 "fy-utf8.h"
#include "fy-ctype.h"
#include "fy-utils.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 (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;
}