2015-07-28 19:24:06 +00:00
|
|
|
#include <sys/types.h>
|
|
|
|
|
2014-11-11 18:56:05 +00:00
|
|
|
#include <ctype.h>
|
2015-05-24 20:46:47 +00:00
|
|
|
#include <err.h>
|
2015-01-03 11:44:32 +00:00
|
|
|
#include <errno.h>
|
2015-06-20 22:19:22 +00:00
|
|
|
#include <libgen.h>
|
2015-01-03 11:44:32 +00:00
|
|
|
#include <limits.h>
|
2015-07-28 19:24:06 +00:00
|
|
|
#include <stdarg.h>
|
2012-08-03 10:03:17 +00:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
2014-11-11 18:56:05 +00:00
|
|
|
#include <string.h>
|
|
|
|
#include <time.h>
|
2013-01-20 18:14:34 +00:00
|
|
|
|
2014-03-31 20:46:58 +00:00
|
|
|
#include "util.h"
|
|
|
|
|
2015-07-28 19:24:06 +00:00
|
|
|
static void
|
|
|
|
encodehex(unsigned char c, char *s)
|
2015-06-20 22:20:12 +00:00
|
|
|
{
|
2015-07-28 19:24:06 +00:00
|
|
|
static const char *table = "0123456789ABCDEF";
|
|
|
|
|
|
|
|
s[0] = table[((c - (c % 16)) / 16) % 16];
|
|
|
|
s[1] = table[c % 16];
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
parseuri(const char *s, struct uri *u, int rel)
|
|
|
|
{
|
|
|
|
const char *p = s;
|
2015-06-20 22:20:12 +00:00
|
|
|
size_t i;
|
|
|
|
|
2015-07-28 19:24:06 +00:00
|
|
|
memset(u, 0, sizeof(struct uri));
|
|
|
|
if (!*s)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* prefix is "//", don't read protocol, skip to domain parsing */
|
|
|
|
if (!strncmp(p, "//", 2)) {
|
|
|
|
p += 2; /* skip "//" */
|
|
|
|
} else {
|
|
|
|
/* protocol part */
|
|
|
|
for (p = s; *p && (isalpha((int)*p) || isdigit((int)*p) ||
|
|
|
|
*p == '+' || *p == '-' || *p == '.'); p++)
|
|
|
|
;
|
|
|
|
if (!strncmp(p, "://", 3)) {
|
|
|
|
if (p - s + 1 >= (ssize_t)sizeof(u->proto))
|
|
|
|
return -1; /* protocol too long */
|
|
|
|
memcpy(u->proto, s, p - s);
|
|
|
|
p += 3; /* skip "://" */
|
|
|
|
} else {
|
|
|
|
p = s; /* no protocol format, set to start */
|
|
|
|
/* relative url: read rest as path, else as domain */
|
|
|
|
if (rel)
|
|
|
|
goto readpath;
|
|
|
|
}
|
2015-06-20 22:20:12 +00:00
|
|
|
}
|
2015-07-28 19:24:06 +00:00
|
|
|
/* domain / host part, skip until "/" or end. */
|
|
|
|
i = strcspn(p, "/");
|
|
|
|
if (i + 1 >= sizeof(u->host))
|
|
|
|
return -1; /* host too long */
|
|
|
|
memcpy(u->host, p, i);
|
|
|
|
p = &p[i];
|
|
|
|
|
|
|
|
readpath:
|
|
|
|
if (u->host[0]) {
|
|
|
|
p = &p[strspn(p, "/")];
|
|
|
|
strlcpy(u->path, "/", sizeof(u->path));
|
|
|
|
} else {
|
|
|
|
/* having no host is an error in this case */
|
|
|
|
if (!rel)
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
/* treat truncation as an error */
|
|
|
|
return strlcat(u->path, p, sizeof(u->path)) >= sizeof(u->path) ? -1 : 0;
|
2015-06-20 22:20:12 +00:00
|
|
|
}
|
|
|
|
|
2015-07-31 20:29:34 +00:00
|
|
|
/* Get absolute uri; if `link` is relative use `base` to make it absolute.
|
|
|
|
* the returned string in `buf` is uri encoded, see: encodeuri(). */
|
2015-07-28 19:24:06 +00:00
|
|
|
int
|
|
|
|
absuri(const char *link, const char *base, char *buf, size_t bufsiz)
|
2014-11-11 18:56:05 +00:00
|
|
|
{
|
2015-07-28 19:24:06 +00:00
|
|
|
struct uri ulink, ubase;
|
|
|
|
char tmp[4096] = "", *p;
|
|
|
|
int r = -1, c;
|
2015-07-28 19:49:24 +00:00
|
|
|
size_t i;
|
2015-07-28 19:24:06 +00:00
|
|
|
|
|
|
|
buf[0] = '\0';
|
|
|
|
if (parseuri(base, &ubase, 0) == -1 ||
|
|
|
|
parseuri(link, &ulink, 1) == -1)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (!ulink.host[0] && !ubase.host[0])
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
r = snprintf(tmp, sizeof(tmp), "%s://%s",
|
|
|
|
ulink.proto[0] ?
|
|
|
|
ulink.proto :
|
|
|
|
(ubase.proto[0] ? ubase.proto : "http"),
|
|
|
|
!strncmp(link, "//", 2) ?
|
|
|
|
ulink.host :
|
|
|
|
(ulink.host[0] ? ulink.host : ubase.host));
|
|
|
|
if (r == -1 || (size_t)r >= sizeof(tmp))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
/* relative to root */
|
|
|
|
if (!ulink.host[0] && ulink.path[0] != '/') {
|
|
|
|
/* relative to base url path */
|
|
|
|
if (ulink.path[0]) {
|
|
|
|
if ((p = strrchr(ubase.path, '/'))) {
|
|
|
|
/* temporary null-terminate */
|
|
|
|
c = *(++p);
|
|
|
|
*p = '\0';
|
2015-07-28 19:49:24 +00:00
|
|
|
i = strlcat(tmp, ubase.path, sizeof(tmp));
|
2015-07-28 19:24:06 +00:00
|
|
|
*p = c; /* restore */
|
2015-07-28 19:49:24 +00:00
|
|
|
if (i >= sizeof(tmp))
|
|
|
|
return -1;
|
2015-07-28 19:24:06 +00:00
|
|
|
}
|
2012-08-03 10:03:17 +00:00
|
|
|
} else {
|
2015-07-28 19:49:24 +00:00
|
|
|
if (strlcat(tmp, ubase.path, sizeof(tmp)) >= sizeof(tmp))
|
|
|
|
return -1;
|
2012-08-03 10:03:17 +00:00
|
|
|
}
|
2015-07-28 19:24:06 +00:00
|
|
|
}
|
|
|
|
if (strlcat(tmp, ulink.path, sizeof(tmp)) >= sizeof(tmp))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return encodeuri(tmp, buf, bufsiz);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
encodeuri(const char *s, char *buf, size_t bufsiz)
|
|
|
|
{
|
|
|
|
size_t i, b;
|
|
|
|
|
|
|
|
if (!bufsiz)
|
|
|
|
return -1;
|
|
|
|
for (i = 0, b = 0; s[i]; i++) {
|
|
|
|
if ((int)s[i] == ' ' ||
|
|
|
|
(unsigned char)s[i] > 127 ||
|
|
|
|
iscntrl((int)s[i])) {
|
|
|
|
if (b + 3 >= bufsiz)
|
|
|
|
return -1;
|
|
|
|
buf[b++] = '%';
|
|
|
|
encodehex(s[i], &buf[b]);
|
|
|
|
b += 2;
|
2014-04-08 22:00:13 +00:00
|
|
|
} else {
|
2015-07-28 19:24:06 +00:00
|
|
|
if (b >= bufsiz)
|
|
|
|
return -1;
|
|
|
|
buf[b++] = s[i];
|
2012-08-03 10:03:17 +00:00
|
|
|
}
|
|
|
|
}
|
2015-07-28 19:24:06 +00:00
|
|
|
if (b >= bufsiz)
|
|
|
|
return -1;
|
|
|
|
buf[b] = '\0';
|
|
|
|
|
|
|
|
return 0;
|
2012-08-03 10:03:17 +00:00
|
|
|
}
|
|
|
|
|
2015-07-31 20:29:34 +00:00
|
|
|
/* Read a field-separated line from 'fp',
|
2014-06-27 13:42:53 +00:00
|
|
|
* separated by a character 'separator',
|
2015-08-02 11:45:43 +00:00
|
|
|
* 'fields' is a list of pointers with a size of FieldLast (must be >0).
|
|
|
|
* 'line' buffer is allocated using malloc, 'size' will contain the allocated
|
|
|
|
* buffer size.
|
|
|
|
* returns: amount of fields read (>0) or -1 on error. */
|
|
|
|
ssize_t
|
|
|
|
parseline(char **line, size_t *size, char *fields[FieldLast], FILE *fp)
|
2014-04-08 22:00:13 +00:00
|
|
|
{
|
2012-08-03 10:03:17 +00:00
|
|
|
char *prev, *s;
|
2015-08-02 11:45:43 +00:00
|
|
|
size_t i;
|
2012-08-03 10:03:17 +00:00
|
|
|
|
2015-07-28 19:56:46 +00:00
|
|
|
if (getline(line, size, fp) <= 0)
|
2015-05-16 13:56:00 +00:00
|
|
|
return -1;
|
|
|
|
|
2015-07-28 19:56:46 +00:00
|
|
|
for (prev = *line, i = 0;
|
2015-08-02 11:45:43 +00:00
|
|
|
(s = strchr(prev, '\t')) && i < FieldLast - 1;
|
2015-05-16 13:56:00 +00:00
|
|
|
i++) {
|
|
|
|
*s = '\0';
|
2012-08-03 10:03:17 +00:00
|
|
|
fields[i] = prev;
|
2015-05-16 13:56:00 +00:00
|
|
|
prev = s + 1;
|
2012-08-03 10:03:17 +00:00
|
|
|
}
|
2015-05-16 13:56:00 +00:00
|
|
|
fields[i++] = prev;
|
|
|
|
/* make non-parsed fields empty. */
|
2015-08-02 11:45:43 +00:00
|
|
|
for (; i < FieldLast; i++)
|
2015-05-16 13:56:00 +00:00
|
|
|
fields[i] = "";
|
|
|
|
|
2015-08-02 11:45:43 +00:00
|
|
|
return (ssize_t)i;
|
2012-08-03 10:03:17 +00:00
|
|
|
}
|
|
|
|
|
2015-07-31 20:29:34 +00:00
|
|
|
/* Parse time to time_t, assumes time_t is signed. */
|
2015-01-03 11:44:32 +00:00
|
|
|
int
|
|
|
|
strtotime(const char *s, time_t *t)
|
|
|
|
{
|
|
|
|
long l;
|
|
|
|
|
|
|
|
errno = 0;
|
|
|
|
l = strtol(s, NULL, 10);
|
2015-07-28 19:56:46 +00:00
|
|
|
if (errno != 0)
|
2015-01-03 11:44:32 +00:00
|
|
|
return -1;
|
2015-08-02 11:45:43 +00:00
|
|
|
if (t)
|
|
|
|
*t = (time_t)l;
|
2015-01-03 11:44:32 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2015-05-24 20:46:47 +00:00
|
|
|
|
2015-08-02 11:08:12 +00:00
|
|
|
/* Escape characters below as HTML 2.0 / XML 1.0. */
|
2015-05-24 20:46:47 +00:00
|
|
|
void
|
2015-08-02 11:08:12 +00:00
|
|
|
xmlencode(const char *s, FILE *fp)
|
2015-07-31 19:06:52 +00:00
|
|
|
{
|
|
|
|
for (; *s; s++) {
|
2015-08-02 11:08:12 +00:00
|
|
|
switch(*s) {
|
|
|
|
case '<': fputs("<", fp); break;
|
|
|
|
case '>': fputs(">", fp); break;
|
|
|
|
case '\'': fputs("'", fp); break;
|
|
|
|
case '&': fputs("&", fp); break;
|
|
|
|
case '"': fputs(""", fp); break;
|
|
|
|
default: fputc(*s, fp);
|
2015-05-24 20:46:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-06-20 22:19:22 +00:00
|
|
|
|
|
|
|
/* Some implementations of basename(3) return a pointer to a static
|
|
|
|
* internal buffer (OpenBSD). Others modify the contents of `path` (POSIX).
|
|
|
|
* This is a wrapper function that is compatible with both versions.
|
|
|
|
* The program will error out if basename(3) failed, this can only happen
|
2015-07-31 20:29:34 +00:00
|
|
|
* with the OpenBSD version. */
|
2015-06-20 22:19:22 +00:00
|
|
|
char *
|
|
|
|
xbasename(const char *path)
|
|
|
|
{
|
|
|
|
char *p, *b;
|
|
|
|
|
2015-07-28 19:56:46 +00:00
|
|
|
if (!(p = strdup(path)))
|
2015-06-20 22:19:22 +00:00
|
|
|
err(1, "strdup");
|
2015-07-28 19:56:46 +00:00
|
|
|
if (!(b = basename(p)))
|
2015-06-20 22:19:22 +00:00
|
|
|
err(1, "basename");
|
2015-07-28 19:56:46 +00:00
|
|
|
if (!(b = strdup(b)))
|
2015-06-20 22:19:22 +00:00
|
|
|
err(1, "strdup");
|
|
|
|
free(p);
|
|
|
|
return b;
|
|
|
|
}
|