serval-dna/strbuf_helpers.c
2020-07-10 09:28:02 +09:30

1046 lines
27 KiB
C

/*
Serval string buffer helper functions.
Copyright (C) 2012 Serval Project Inc.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/*
Portions Copyright (C) 2013 Petter Reinholdtsen
Some rights reserved
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#include <ctype.h>
#include <string.h>
#include <time.h>
#include <assert.h>
#include <stdarg.h>
#include <inttypes.h>
#include <sys/wait.h>
#ifdef HAVE_POLL_H
#include <poll.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#include <sys/uio.h>
#include <sys/socket.h>
#include <netdb.h>
#include "lang.h" // for FALLTHROUGH
#include "overlay_address.h" // for REACHABLE_ flags
#include "http_server.h"
#include "strbuf_helpers.h"
#include "str.h"
#include "socket.h"
#include "whence.h"
static inline strbuf _toprint(strbuf sb, char c)
{
if (c == '\0')
strbuf_puts(sb, "\\0");
else if (c == '\n')
strbuf_puts(sb, "\\n");
else if (c == '\r')
strbuf_puts(sb, "\\r");
else if (c == '\t')
strbuf_puts(sb, "\\t");
else if (c == '\\')
strbuf_puts(sb, "\\\\");
else if (c >= ' ' && c <= '~')
strbuf_putc(sb, c);
else
strbuf_sprintf(sb, "\\x%02x", (unsigned char) c);
return sb;
}
inline static strbuf _overrun(strbuf sb, const char *suffix)
{
if (strbuf_overrun(sb)) {
strbuf_trunc(sb, -strlen(suffix));
strbuf_puts(sb, suffix);
}
return sb;
}
inline static strbuf _overrun_quote(strbuf sb, char quote, const char *suffix)
{
if (strbuf_overrun(sb)) {
strbuf_trunc(sb, -strlen(suffix) - (quote ? 1 : 0));
if (quote)
strbuf_putc(sb, quote);
strbuf_puts(sb, suffix);
}
return sb;
}
strbuf strbuf_toprint_len(strbuf sb, const char *buf, size_t len)
{
for (; len && !strbuf_overrun(sb); ++buf, --len)
_toprint(sb, *buf);
return _overrun(sb, "...");
}
strbuf strbuf_toprint(strbuf sb, const char *str)
{
for (; *str && !strbuf_overrun(sb); ++str)
_toprint(sb, *str);
return _overrun(sb, "...");
}
strbuf strbuf_toprint_quoted_len(strbuf sb, const char quotes[2], const char *buf, size_t len)
{
if (quotes && quotes[0])
strbuf_putc(sb, quotes[0]);
for (; len && !strbuf_overrun(sb); ++buf, --len)
if (quotes && *buf == quotes[1]) {
strbuf_putc(sb, '\\');
strbuf_putc(sb, *buf);
} else
_toprint(sb, *buf);
if (quotes && quotes[1])
strbuf_putc(sb, quotes[1]);
return _overrun_quote(sb, quotes ? quotes[1] : '\0', "...");
}
strbuf strbuf_toprint_quoted(strbuf sb, const char quotes[2], const char *str)
{
if (quotes && quotes[0])
strbuf_putc(sb, quotes[0]);
for (; *str && !strbuf_overrun(sb); ++str)
if (quotes && *str == quotes[1]) {
strbuf_putc(sb, '\\');
strbuf_putc(sb, *str);
} else
_toprint(sb, *str);
if (quotes && quotes[1])
strbuf_putc(sb, quotes[1]);
return _overrun_quote(sb, quotes ? quotes[1] : '\0', "...");
}
strbuf strbuf_append_sourceloc(strbuf sb, struct __sourceloc loc)
{
xprint_sourceloc(XPRINTF_STRBUF(sb), loc);
return sb;
}
strbuf strbuf_path_join(strbuf sb, ...)
{
va_list ap;
va_start(ap, sb);
const char *segment;
while ((segment = va_arg(ap, const char*))) {
if (segment[0] == '/')
strbuf_reset(sb);
else if (strbuf_len(sb) && *strbuf_substr(sb, -1) != '/')
strbuf_putc(sb, '/');
strbuf_puts(sb, segment);
}
va_end(ap);
return sb;
}
strbuf strbuf_append_poll_events(strbuf sb, short events)
{
static struct { short flags; const char *name; } symbols[] = {
{ POLLIN, "IN" },
{ POLLPRI, "PRI" },
{ POLLOUT, "OUT" },
{ POLLERR, "ERR" },
{ POLLHUP, "HUP" },
{ POLLNVAL, "NVAL" },
{ POLLRDNORM, "RDNORM" },
{ POLLRDBAND, "RDBAND" },
#ifdef POLLWRNORM
{ POLLWRNORM, "WRNORM" },
#endif
#ifdef POLLWRBAND
{ POLLWRBAND, "WRBAND" },
#endif
#ifdef POLLMSG
{ POLLMSG, "MSG" },
#endif
#ifdef POLLREMOVE
{ POLLREMOVE, "REMOVE" },
#endif
#ifdef POLLRDHUP
{ POLLRDHUP, "RDHUP" },
#endif
{ 0, NULL }
}, *sp;
int n = 0;
for (sp = symbols; sp->name; ++sp) {
if (events & sp->flags) {
if (n)
strbuf_putc(sb, '|');
strbuf_puts(sb, sp->name);
++n;
}
}
if (!n)
strbuf_putc(sb, '0');
return sb;
}
static int is_shellmeta(char c)
{
return !(isalnum(c) || c == '.' || c == '-' || c == '/' || c == ':' || c == '+' || c == '_' || c == ',');
}
strbuf strbuf_append_shell_quote(strbuf sb, const char *word)
{
strbuf_putc(sb, '\'');
const char *p;
for (p = word; *p; ++p)
if (*p == '\'')
strbuf_puts(sb, "'\\''");
else
strbuf_putc(sb, *p);
strbuf_putc(sb, '\'');
return sb;
}
strbuf strbuf_append_shell_quotemeta(strbuf sb, const char *word)
{
const char *p;
int hasmeta = 0;
for (p = word; *p && !hasmeta; ++p)
if (is_shellmeta(*p))
hasmeta = 1;
if (!word[0] || hasmeta)
strbuf_append_shell_quote(sb, word);
else
strbuf_puts(sb, word);
return sb;
}
strbuf strbuf_append_argv(strbuf sb, int argc, const char *const *argv)
{
int i;
for (i = 0; i < argc; ++i) {
if (i)
strbuf_putc(sb, ' ');
if (argv[i])
strbuf_toprint_quoted(sb, "\"\"", argv[i]);
else
strbuf_puts(sb, "NULL");
}
return sb;
}
strbuf strbuf_append_exit_status(strbuf sb, int status)
{
if (WIFEXITED(status))
strbuf_sprintf(sb, "exited normally with status %u", WEXITSTATUS(status));
else if (WIFSIGNALED(status)) {
strbuf_sprintf(sb, "terminated by signal %u (%s)", WTERMSIG(status), strsignal(WTERMSIG(status)));
#ifdef WCOREDUMP
if (WCOREDUMP(status))
strbuf_puts(sb, " and dumped core");
#endif
} else if (WIFSTOPPED(status))
strbuf_sprintf(sb, "stopped by signal %u (%s)", WSTOPSIG(status), strsignal(WSTOPSIG(status)));
#ifdef WIFCONTINUED
else if (WIFCONTINUED(status))
strbuf_sprintf(sb, "continued by signal %u (SIGCONT)", SIGCONT);
#endif
return sb;
}
strbuf strbuf_append_socket_domain(strbuf sb, int domain)
{
const char *fam = NULL;
switch (domain) {
case AF_UNSPEC: fam = "AF_UNSPEC"; break;
case AF_UNIX: fam = "AF_UNIX"; break;
case AF_INET: fam = "AF_INET"; break;
#if 0
// These values are not used in Serval, yet.
case AF_BLUETOOTH: fam = "AF_BLUETOOTH"; break;
case AF_INET6: fam = "AF_INET6"; break;
// These values will probably never be used in Serval.
case AF_AX25: fam = "AF_AX25"; break;
case AF_IPX: fam = "AF_IPX"; break;
case AF_APPLETALK: fam = "AF_APPLETALK"; break;
case AF_NETROM: fam = "AF_NETROM"; break;
case AF_BRIDGE: fam = "AF_BRIDGE"; break;
case AF_ATMPVC: fam = "AF_ATMPVC"; break;
case AF_X25: fam = "AF_X25"; break;
case AF_ROSE: fam = "AF_ROSE"; break;
case AF_DECnet: fam = "AF_DECnet"; break;
case AF_NETBEUI: fam = "AF_NETBEUI"; break;
case AF_SECURITY: fam = "AF_SECURITY"; break;
case AF_KEY: fam = "AF_KEY"; break;
case AF_NETLINK: fam = "AF_NETLINK"; break;
case AF_PACKET: fam = "AF_PACKET"; break;
case AF_ASH: fam = "AF_ASH"; break;
case AF_ECONET: fam = "AF_ECONET"; break;
case AF_ATMSVC: fam = "AF_ATMSVC"; break;
case AF_SNA: fam = "AF_SNA"; break;
case AF_IRDA: fam = "AF_IRDA"; break;
case AF_PPPOX: fam = "AF_PPPOX"; break;
case AF_WANPIPE: fam = "AF_WANPIPE"; break;
case AF_LLC: fam = "AF_LLC"; break;
case AF_TIPC: fam = "AF_TIPC"; break;
#endif
}
if (fam)
strbuf_puts(sb, fam);
else
strbuf_sprintf(sb, "[%d]", domain);
return sb;
}
strbuf strbuf_append_signal_name(strbuf sb, int signal)
{
const char *desc = "";
switch(signal) {
#ifdef SIGHUP
case SIGHUP: desc = "HUP"; break;
#endif
#ifdef SIGINT
case SIGINT: desc = "INT"; break;
#endif
#ifdef SIGQUIT
case SIGQUIT: desc = "QUIT"; break;
#endif
#ifdef SIGILL
case SIGILL: desc = "ILL (not reset when caught)"; break;
#endif
#ifdef SIGTRAP
case SIGTRAP: desc = "TRAP (not reset when caught)"; break;
#endif
#ifdef SIGABRT
case SIGABRT: desc = "ABRT"; break;
#endif
#ifdef SIGPOLL
case SIGPOLL: desc = "POLL ([XSR] generated, not supported)"; break;
#endif
#ifdef SIGEMT
case SIGEMT: desc = "EMT"; break;
#endif
#ifdef SIGFPE
case SIGFPE: desc = "FPE"; break;
#endif
#ifdef SIGKILL
case SIGKILL: desc = "KILL (cannot be caught or ignored)"; break;
#endif
#ifdef SIGBUS
case SIGBUS: desc = "BUS"; break;
#endif
#ifdef SIGSEGV
case SIGSEGV: desc = "SEGV"; break;
#endif
#ifdef SIGSYS
case SIGSYS: desc = "SYS"; break;
#endif
#ifdef SIGPIPE
case SIGPIPE: desc = "PIPE"; break;
#endif
#ifdef SIGALRM
case SIGALRM: desc = "ALRM"; break;
#endif
#ifdef SIGTERM
case SIGTERM: desc = "TERM"; break;
#endif
#ifdef SIGURG
case SIGURG: desc = "URG"; break;
#endif
#ifdef SIGSTOP
case SIGSTOP: desc = "STOP"; break;
#endif
#ifdef SIGTSTP
case SIGTSTP: desc = "TSTP"; break;
#endif
#ifdef SIGCONT
case SIGCONT: desc = "CONT"; break;
#endif
#ifdef SIGCHLD
case SIGCHLD: desc = "CHLD"; break;
#endif
#ifdef SIGTTIN
case SIGTTIN: desc = "TTIN"; break;
#endif
#ifdef SIGTTOU
case SIGTTOU: desc = "TTOU"; break;
#endif
#ifdef SIGIO
#if SIGIO != SIGPOLL
case SIGIO: desc = "IO"; break;
#endif
#endif
#ifdef SIGXCPU
case SIGXCPU: desc = "XCPU"; break;
#endif
#ifdef SIGXFSZ
case SIGXFSZ: desc = "XFSZ"; break;
#endif
#ifdef SIGVTALRM
case SIGVTALRM: desc = "VTALRM"; break;
#endif
#ifdef SIGPROF
case SIGPROF: desc = "PROF"; break;
#endif
#ifdef SIGWINCH
case SIGWINCH: desc = "WINCH"; break;
#endif
#ifdef SIGINFO
case SIGINFO: desc = "INFO"; break;
#endif
#ifdef SIGUSR1
case SIGUSR1: desc = "USR1"; break;
#endif
#ifdef SIGUSR2
case SIGUSR2: desc = "USR2"; break;
#endif
}
strbuf_sprintf(sb, "SIG%s (%d) %s", desc, signal, strsignal(signal));
return sb;
}
strbuf strbuf_append_socket_type(strbuf sb, int type)
{
const char *typ = NULL;
switch (type) {
case SOCK_STREAM: typ = "SOCK_STREAM"; break;
case SOCK_DGRAM: typ = "SOCK_DGRAM"; break;
#ifdef SOCK_RAW
case SOCK_RAW: typ = "SOCK_RAW"; break;
#endif
#ifdef SOCK_RDM
case SOCK_RDM: typ = "SOCK_RDM"; break;
#endif
#ifdef SOCK_SEQPACKET
case SOCK_SEQPACKET: typ = "SOCK_SEQPACKET"; break;
#endif
#ifdef SOCK_PACKET
case SOCK_PACKET: typ = "SOCK_PACKET"; break;
#endif
}
if (typ)
strbuf_puts(sb, typ);
else
strbuf_sprintf(sb, "[%d]", type);
return sb;
}
strbuf strbuf_append_in_addr(strbuf sb, const struct in_addr *addr)
{
strbuf_sprintf(sb, "%u.%u.%u.%u",
((unsigned char *) &addr->s_addr)[0],
((unsigned char *) &addr->s_addr)[1],
((unsigned char *) &addr->s_addr)[2],
((unsigned char *) &addr->s_addr)[3]);
return sb;
}
strbuf strbuf_append_sockaddr(strbuf sb, const struct sockaddr *addr, socklen_t addrlen)
{
switch (addr->sa_family) {
case AF_UNIX: {
struct sockaddr_un *addr_un = (struct sockaddr_un *)addr;
strbuf_puts(sb, "AF_UNIX:");
size_t len = (size_t)addrlen > sizeof addr->sa_family ? addrlen - sizeof addr->sa_family : 0;
if (addr_un->sun_path[0]) {
strbuf_toprint_quoted_len(sb, "\"\"", addr_un->sun_path, len);
if (len < 2)
strbuf_sprintf(sb, " (addrlen=%d too short)", (int)addrlen);
if (len == 0 || addr_un->sun_path[len - 1] != '\0')
strbuf_sprintf(sb, " (addrlen=%d, no nul terminator)", (int)addrlen);
} else {
strbuf_puts(sb, "abstract ");
strbuf_toprint_quoted_len(sb, "\"\"", addr_un->sun_path, len);
if (len == 0)
strbuf_sprintf(sb, " (addrlen=%d too short)", (int)addrlen);
}
}
break;
case AF_INET: FALLTHROUGH;
case AF_INET6:
{
char name[INET6_ADDRSTRLEN];
char service[6];
if (getnameinfo(addr, addrlen, name, sizeof name, service, sizeof service, NI_NUMERICHOST|NI_NUMERICSERV)==0){
strbuf_sprintf(sb, "%s:%s:%s",
addr->sa_family==AF_INET?"AF_INET":"AF_INET6",
name,
service);
break;
}
strbuf_append_socket_domain(sb, addr->sa_family);
size_t len = (size_t)addrlen > sizeof addr->sa_family ? addrlen - sizeof addr->sa_family : 0;
unsigned i;
for (i = 0; i < len; ++i) {
strbuf_putc(sb, i ? ',' : ':');
strbuf_sprintf(sb, "%02x", addr->sa_data[i]);
}
}
break;
default: {
strbuf_append_socket_domain(sb, addr->sa_family);
size_t len = (size_t)addrlen > sizeof addr->sa_family ? addrlen - sizeof addr->sa_family : 0;
unsigned i;
for (i = 0; i < len; ++i) {
strbuf_putc(sb, i ? ',' : ':');
strbuf_sprintf(sb, "%02x", addr->sa_data[i]);
}
}
break;
}
return sb;
}
strbuf strbuf_append_socket_address(strbuf sb, const struct socket_address *addr)
{
return strbuf_append_sockaddr(sb, &addr->addr, addr->addrlen);
}
strbuf strbuf_append_fragmented_data(strbuf sb, const struct fragmented_data *data)
{
return strbuf_append_iovec(sb, data->iov, data->fragment_count);
}
strbuf strbuf_append_strftime(strbuf sb, const char *format, const struct tm *tm)
{
// First, try calling strftime(3) directly on the buffer in the strbuf, if there is one and it
// looks to be long enough.
const size_t need = strlen(format); // heuristic, could be better
if (strbuf_str(sb)) {
size_t avail = strbuf_remaining(sb);
if (avail > need) {
size_t n = strftime(strbuf_end(sb), avail + 1, format, tm);
if (n) {
assert(n <= avail);
sb->current += n;
return sb;
}
}
}
// If that didn't work, then call strftime(3) on a temporary buffer and concatenate the result
// into the strbuf.
const size_t len = 500; // should be enough
char *buf = alloca(len + 1);
size_t n = strftime(buf, len + 1, format, tm);
strbuf_ncat(sb, buf, n);
return sb;
}
strbuf strbuf_append_iovec(strbuf sb, const struct iovec *iov, int iovcnt)
{
int i;
strbuf_putc(sb, '[');
for (i = 0; i < iovcnt; ++i) {
if (i)
strbuf_puts(sb, ", ");
strbuf_sprintf(sb, "%p#%zu", iov[i].iov_base, iov[i].iov_len);
}
strbuf_putc(sb, ']');
return sb;
}
strbuf strbuf_append_time_t(strbuf sb, time_t time)
{
struct tm tm;
localtime_r(&time, &tm);
strbuf_append_strftime(sb, "%Y/%m/%d %H:%M:%S %z", &tm);
return sb;
}
strbuf strbuf_append_time_ms_t(strbuf sb, time_ms_t ms)
{
struct tm tm;
time_t time = ms / 1000;
localtime_r(&time, &tm);
strbuf_append_strftime(sb, "%Y/%m/%d %H:%M:%S", &tm);
strbuf_sprintf(sb, ".%.03u", (unsigned)(ms % 1000));
strbuf_append_strftime(sb, " %z", &tm);
return sb;
}
strbuf strbuf_append_timeval(strbuf sb, const struct timeval *tv)
{
if (tv->tv_sec < 0 || tv->tv_usec < 0 || tv->tv_usec > 999999) {
strbuf_sprintf(sb, "INVALID{tv_sec=%ld,tv_usec=%ld}", (long)tv->tv_sec, (long)tv->tv_usec);
} else {
struct tm tm;
localtime_r(&tv->tv_sec, &tm);
strbuf_append_strftime(sb, "%Y/%m/%d %H:%M:%S", &tm);
strbuf_sprintf(sb, ".%.06lu", (long)tv->tv_usec);
strbuf_append_strftime(sb, " %z", &tm);
}
return sb;
}
strbuf strbuf_append_timespec(strbuf sb, const struct timespec *tv)
{
if (tv->tv_sec < 0 || tv->tv_nsec < 0 || tv->tv_nsec > 999999999) {
strbuf_sprintf(sb, "INVALID{tv_sec=%ld,tv_nsec=%ld}", (long)tv->tv_sec, tv->tv_nsec);
} else {
struct tm tm;
localtime_r(&tv->tv_sec, &tm);
strbuf_append_strftime(sb, "%Y/%m/%d %H:%M:%S", &tm);
strbuf_sprintf(sb, ".%.09lu", tv->tv_nsec);
strbuf_append_strftime(sb, " %z", &tm);
}
return sb;
}
strbuf strbuf_append_file_meta(strbuf sb, const struct file_meta *metap)
{
strbuf_puts(sb, "{ .mtime=");
strbuf_append_timespec(sb, &metap->mtime);
strbuf_sprintf(sb, ", .size=%ld }", (long)metap->size);
return sb;
}
strbuf strbuf_append_reachable_flags(strbuf sb, int flags) {
if (flags == REACHABLE_NONE)
strbuf_puts(sb, "NONE");
else {
const char *sep = "";
if (flags & REACHABLE_SELF) {
strbuf_puts(sb, sep);
strbuf_puts(sb, "SELF");
sep = "|";
}
if (flags & REACHABLE_BROADCAST) {
strbuf_puts(sb, sep);
strbuf_puts(sb, "BROADCAST");
sep = "|";
}
if (flags & REACHABLE_UNICAST) {
strbuf_puts(sb, sep);
strbuf_puts(sb, "UNICAST");
sep = "|";
}
if (flags & REACHABLE_INDIRECT) {
strbuf_puts(sb, sep);
strbuf_puts(sb, "INDIRECT");
sep = "|";
}
if (flags & REACHABLE_ASSUMED) {
strbuf_puts(sb, sep);
strbuf_puts(sb, "ASSUMED");
sep = "|";
}
}
return sb;
}
strbuf strbuf_append_quoted_string(strbuf sb, const char *str)
{
strbuf_putc(sb, '"');
for (; *str; ++str) {
if (*str == '"' || *str == '\\')
strbuf_putc(sb, '\\');
strbuf_putc(sb, *str);
}
strbuf_putc(sb, '"');
return sb;
}
strbuf strbuf_append_token_or_quoted_string(strbuf sb, const char *str)
{
const char *s;
for (s = str; *s; ++s)
if (!is_http_token(*s))
return strbuf_append_quoted_string(sb, str);
strbuf_puts(sb, str);
return sb;
}
static void _html_char(strbuf sb, char c)
{
if (c == '&')
strbuf_puts(sb, "&amp;");
else if (c == '<')
strbuf_puts(sb, "&lt;");
else if (c == '>')
strbuf_puts(sb, "&gt;");
else if (c == '"')
strbuf_puts(sb, "&quot;");
else if (c == '\'')
strbuf_puts(sb, "&apos;");
else if (iscntrl(c))
strbuf_sprintf(sb, "&#%u;", (unsigned char) c);
else
strbuf_putc(sb, c);
}
strbuf strbuf_html_escape(strbuf sb, const char *str, size_t strlen)
{
for (; strlen; --strlen, ++str)
_html_char(sb, *str);
return sb;
}
strbuf strbuf_json_null(strbuf sb)
{
strbuf_puts(sb, "null");
return sb;
}
strbuf strbuf_json_boolean(strbuf sb, int boolean)
{
strbuf_puts(sb, boolean ? "true" : "false");
return sb;
}
strbuf strbuf_json_integer(strbuf sb, int64_t integer)
{
strbuf_sprintf(sb, "%"PRId64, integer);
return sb;
}
static const uint32_t offsetsFromUTF8[6] = {
0x00000000UL, 0x00003080UL, 0x000E2080UL,
0x03C82080UL, 0xFA082080UL, 0x82082080UL
};
// is start of UTF sequence
static uint8_t isutf(char c) {
return (c & 0xC0) != 0x80;
}
static uint32_t u8_nextchar(const char *s, unsigned *i)
{
if (!s[*i])
return 0;
uint32_t ch = 0;
int sz = 0;
do {
ch <<= 6;
ch += (unsigned char)s[(*i)++];
sz++;
} while (s[*i] && !isutf(s[*i]));
ch -= offsetsFromUTF8[sz-1];
return ch;
}
static void _json_char(strbuf sb, uint32_t c)
{
if (c == '"' || c == '\\') {
strbuf_putc(sb, '\\');
strbuf_putc(sb, c);
}
else if (c == '\b')
strbuf_puts(sb, "\\b");
else if (c == '\f')
strbuf_puts(sb, "\\f");
else if (c == '\n')
strbuf_puts(sb, "\\n");
else if (c == '\r')
strbuf_puts(sb, "\\r");
else if (c == '\t')
strbuf_puts(sb, "\\t");
else if (c>=0x10000){
c-=0x10000;
strbuf_sprintf(sb, "\\u%04X\\u%04X", 0xD800 + ((c>>10) & 0x3FF), 0xDC00 + (c & 0x3FF));
}else if (c>0x7f || iscntrl(c))
strbuf_sprintf(sb, "\\u%04X", c);
else
strbuf_putc(sb, c);
}
strbuf strbuf_json_string(strbuf sb, const char *str)
{
if (str) {
strbuf_putc(sb, '"');
unsigned pos=0;
uint32_t c;
while((c = u8_nextchar(str, &pos)))
_json_char(sb, c);
strbuf_putc(sb, '"');
} else
strbuf_json_null(sb);
return sb;
}
strbuf strbuf_json_string_len(strbuf sb, const char *str, size_t strlen)
{
if (str && strlen){
strbuf_putc(sb, '"');
unsigned pos=0;
uint32_t c;
while(pos<strlen && (c = u8_nextchar(str, &pos)))
_json_char(sb, c);
strbuf_putc(sb, '"');
} else
strbuf_json_null(sb);
return sb;
}
strbuf strbuf_json_hex(strbuf sb, const unsigned char *buf, size_t len)
{
if (buf) {
strbuf_putc(sb, '"');
size_t i;
for (i = 0; i != len; ++i) {
strbuf_putc(sb, hexdigit_upper[*buf >> 4]);
strbuf_putc(sb, hexdigit_upper[*buf++ & 0xf]);
}
strbuf_putc(sb, '"');
} else
strbuf_json_null(sb);
return sb;
}
strbuf strbuf_json_atom(strbuf sb, const struct json_atom *atom)
{
switch (atom->type) {
case JSON_NULL:
return strbuf_json_null(sb);
case JSON_BOOLEAN:
return strbuf_json_boolean(sb, atom->u.boolean);
case JSON_INTEGER:
return strbuf_json_integer(sb, atom->u.integer);
case JSON_STRING_NULTERM:
return strbuf_json_string(sb, atom->u.string.content);
case JSON_STRING_LENGTH:
return strbuf_json_string_len(sb, atom->u.string.content, atom->u.string.length);
case JSON_OBJECT: {
strbuf_putc(sb, '{');
size_t i;
for (i = 0; i != atom->u.object.itemc; ++i) {
if (i)
strbuf_putc(sb, ',');
strbuf_json_string(sb, atom->u.object.itemv[i].key);
strbuf_putc(sb, ':');
strbuf_json_atom(sb, atom->u.object.itemv[i].value);
}
strbuf_putc(sb, '}');
return sb;
}
case JSON_ARRAY: {
strbuf_putc(sb, '[');
size_t i;
for (i = 0; i != atom->u.array.itemc; ++i) {
if (i)
strbuf_putc(sb, ',');
strbuf_json_atom(sb, atom->u.array.itemv[i]);
}
strbuf_putc(sb, ']');
return sb;
}
}
abort();
}
static strbuf strbuf_puts_repeat(strbuf sb, const char *text, size_t repeat)
{
size_t i;
for (i = 0; i != repeat; ++i)
strbuf_puts(sb, text);
return sb;
}
static strbuf strbuf_json_atom_as_text_indented(strbuf sb, const struct json_atom *atom, const char *eol, const char *tab, unsigned indent)
{
switch (atom->type) {
case JSON_NULL:
return strbuf_json_null(sb);
case JSON_BOOLEAN:
return strbuf_puts(sb, atom->u.boolean ? "True" : "False");
case JSON_INTEGER:
strbuf_sprintf(sb, "%"PRId64, atom->u.integer);
return sb;
case JSON_STRING_NULTERM:
return strbuf_puts(sb, atom->u.string.content);
case JSON_STRING_LENGTH:
return strbuf_ncat(sb, atom->u.string.content, atom->u.string.length);
case JSON_OBJECT: {
strbuf_puts(sb, "{");
size_t i;
for (i = 0; i != atom->u.object.itemc; ++i) {
strbuf_puts(sb, eol);
strbuf_puts_repeat(sb, tab, indent);
strbuf_puts(sb, atom->u.object.itemv[i].key);
strbuf_puts(sb, ": ");
strbuf_json_atom_as_text_indented(sb, atom->u.object.itemv[i].value, eol, tab, indent + 1);
}
if (i) {
strbuf_puts(sb, eol);
strbuf_puts_repeat(sb, tab, indent);
}
strbuf_puts(sb, "}");
return sb;
}
case JSON_ARRAY: {
strbuf_puts(sb, "[");
size_t i;
for (i = 0; i != atom->u.array.itemc; ++i) {
if (i)
strbuf_puts(sb, ", ");
strbuf_json_atom_as_text_indented(sb, atom->u.array.itemv[i], eol, tab, indent + 1);
}
strbuf_puts(sb, "]");
return sb;
}
}
abort();
}
strbuf strbuf_json_atom_as_text(strbuf sb, const struct json_atom *atom, const char *eol)
{
return strbuf_json_atom_as_text_indented(sb, atom, eol, " ", 0);
}
strbuf strbuf_json_atom_as_html(strbuf sb, const struct json_atom *atom)
{
switch (atom->type) {
case JSON_NULL:
return strbuf_json_null(sb);
case JSON_BOOLEAN:
return strbuf_json_boolean(sb, atom->u.boolean);
case JSON_INTEGER:
strbuf_sprintf(sb, "%"PRId64, atom->u.integer);
return sb;
case JSON_STRING_NULTERM:
return strbuf_html_escape(sb, atom->u.string.content, strlen(atom->u.string.content));
case JSON_STRING_LENGTH:
return strbuf_html_escape(sb, atom->u.string.content, atom->u.string.length);
case JSON_OBJECT: {
strbuf_puts(sb, "<dl>");
size_t i;
for (i = 0; i != atom->u.object.itemc; ++i) {
strbuf_puts(sb, "\n<dt>");
strbuf_html_escape(sb, atom->u.object.itemv[i].key, strlen(atom->u.object.itemv[i].key));
strbuf_puts(sb, "</dt><dd>");
strbuf_json_atom_as_html(sb, atom->u.object.itemv[i].value);
strbuf_puts(sb, "</dd>");
}
strbuf_puts(sb, "\n</dl>");
return sb;
}
case JSON_ARRAY: {
strbuf_puts(sb, "<ol>");
size_t i;
for (i = 0; i != atom->u.array.itemc; ++i) {
strbuf_puts(sb, "\n<li>");
strbuf_json_atom_as_html(sb, atom->u.array.itemv[i]);
strbuf_puts(sb, "</li>");
}
strbuf_puts(sb, "\n</ol>");
return sb;
}
}
abort();
}
strbuf strbuf_append_http_ranges(strbuf sb, const struct http_range *ranges, unsigned nels)
{
unsigned i;
int first = 1;
for (i = 0; i != nels; ++i) {
const struct http_range *r = &ranges[i];
switch (r->type) {
case NIL: break;
case CLOSED:
strbuf_sprintf(sb, "%s%"PRIhttp_size_t"-%"PRIhttp_size_t, first ? "" : ",", r->first, r->last);
first = 0;
break;
case OPEN:
strbuf_sprintf(sb, "%s%"PRIhttp_size_t"-", first ? "" : ",", r->first);
first = 0;
break;
case SUFFIX:
strbuf_sprintf(sb, "%s-%"PRIhttp_size_t, first ? "" : ",", r->last);
first = 0;
break;
}
}
return sb;
}
strbuf strbuf_append_mime_content_type(strbuf sb, const struct mime_content_type *ct)
{
strbuf_puts(sb, ct->type);
strbuf_putc(sb, '/');
strbuf_puts(sb, ct->subtype);
if (ct->charset[0]) {
strbuf_puts(sb, "; charset=");
strbuf_append_token_or_quoted_string(sb, ct->charset);
}
if (ct->multipart_boundary[0]) {
strbuf_puts(sb, "; boundary=");
strbuf_append_token_or_quoted_string(sb, ct->multipart_boundary);
}
if (ct->format[0]) {
strbuf_puts(sb, "; format=");
strbuf_append_token_or_quoted_string(sb, ct->format);
}
return sb;
}
strbuf strbuf_append_mime_content_disposition(strbuf sb, const struct mime_content_disposition *cd)
{
strbuf_puts(sb, cd->type);
if (cd->name[0]) {
strbuf_puts(sb, "; name=");
strbuf_append_quoted_string(sb, cd->name);
}
if (cd->filename[0]) {
strbuf_puts(sb, "; filename=");
strbuf_append_quoted_string(sb, cd->filename);
}
if (cd->size)
strbuf_sprintf(sb, "; size=%"PRIhttp_size_t, cd->size);
struct tm tm;
if (cd->creation_date) {
strbuf_puts(sb, " creation_date=");
strbuf_append_strftime(sb, "\"%a, %d %b %Y %T %z\"", gmtime_r(&cd->creation_date, &tm));
}
if (cd->modification_date) {
strbuf_puts(sb, " modification_date=");
strbuf_append_strftime(sb, "\"%a, %d %b %Y %T %z\"", gmtime_r(&cd->modification_date, &tm));
}
if (cd->read_date) {
strbuf_puts(sb, " read_date=");
strbuf_append_strftime(sb, "\"%a, %d %b %Y %T %z\"", gmtime_r(&cd->read_date, &tm));
}
return sb;
}