#include "config.h"
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#include <stdio.h>
#include <ctype.h>
#include "ne_string.h"
#include "ne_alloc.h"
#include "ne_uri.h"
#define PS (0x0001)
#define PC (0x0002)
#define DS (0x0004)
#define DT (0x0008)
#define US (0x0010)
#define TD (0x0020)
#define FS (0x0040)
#define CL (0x0080)
#define AT (0x0100)
#define QU (0x0200)
#define DG (0x0400)
#define AL (0x0800)
#define GD (0x1000)
#define SD (0x2000)
#define OT (0x4000)
#define URI_ALPHA (AL)
#define URI_DIGIT (DG)
#define URI_UNRESERVED (AL | DG | DS | DT | US | TD)
#define URI_SCHEME (AL | DG | PS | DS | DT)
#define URI_SUBDELIM (PS | SD)
#define URI_GENDELIM (GD | CL | FS | AT | QU)
#define URI_USERINFO (URI_UNRESERVED | PC | URI_SUBDELIM | CL)
#define URI_PCHAR (URI_UNRESERVED | PC | URI_SUBDELIM | CL | AT)
#define URI_SEGCHAR (URI_PCHAR | FS)
#define URI_QUERY (URI_PCHAR | FS | QU)
#define URI_FRAGMENT URI_QUERY
#define URI_ESCAPE ((URI_GENDELIM & ~(FS)) | URI_SUBDELIM | OT | PC)
static const unsigned int uri_chars[256] = {
OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT,
OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT,
OT, SD, OT, GD, SD, PC, SD, SD, SD, SD, SD, PS, SD, DS, DT, FS,
DG, DG, DG, DG, DG, DG, DG, DG, DG, DG, CL, SD, OT, SD, OT, QU,
AT, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL,
AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, GD, OT, GD, OT, US,
OT, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL,
AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, OT, OT, OT, TD, OT,
OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT,
OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT,
OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT,
OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT,
OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT,
OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT,
OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT,
OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT
};
#define uri_lookup(ch) (uri_chars[(unsigned char)ch])
char *ne_path_parent(const char *uri)
{
size_t len = strlen(uri);
const char *pnt = uri + len - 1;
if (pnt >= uri && *pnt == '/')
pnt--;
while (pnt > uri && *pnt != '/')
pnt--;
if (pnt < uri || (pnt == uri && *pnt != '/'))
return NULL;
return ne_strndup(uri, pnt - uri + 1);
}
int ne_path_has_trailing_slash(const char *uri)
{
size_t len = strlen(uri);
return ((len > 0) &&
(uri[len-1] == '/'));
}
unsigned int ne_uri_defaultport(const char *scheme)
{
if (ne_strcasecmp(scheme, "http") == 0)
return 80;
else if (ne_strcasecmp(scheme, "https") == 0)
return 443;
else
return 0;
}
int ne_uri_parse(const char *uri, ne_uri *parsed)
{
const char *p, *s;
memset(parsed, 0, sizeof *parsed);
p = s = uri;
if (uri_lookup(*p) & URI_ALPHA) {
while (uri_lookup(*p) & URI_SCHEME)
p++;
if (*p == ':') {
parsed->scheme = ne_strndup(uri, p - s);
s = p + 1;
}
}
if (s[0] == '/' && s[1] == '/') {
const char *pa;
s = pa = s + 2;
while (*pa != '/' && *pa != '\0')
pa++;
p = s;
while (p < pa && uri_lookup(*p) & URI_USERINFO)
p++;
if (*p == '@') {
parsed->userinfo = ne_strndup(s, p - s);
s = p + 1;
}
if (s[0] == '[') {
p = s + 1;
while (*p != ']' && p < pa)
p++;
if (p == pa || (p + 1 != pa && p[1] != ':')) {
return -1;
}
p++;
} else {
p = pa;
while (*p != ':' && p > s)
p--;
}
if (p == s) {
p = pa;
} else if (p + 1 != pa) {
parsed->port = atoi(p + 1);
}
parsed->host = ne_strndup(s, p - s);
s = pa;
if (*s == '\0') {
s = "/";
}
}
p = s;
while (uri_lookup(*p) & URI_SEGCHAR)
p++;
parsed->path = ne_strndup(s, p - s);
if (*p != '\0') {
s = p++;
while (uri_lookup(*p) & URI_QUERY)
p++;
if (*s == '?') {
parsed->query = ne_strndup(s + 1, p - s - 1);
if (*p != '\0') {
s = p++;
while (uri_lookup(*p) & URI_FRAGMENT)
p++;
}
}
if (*s == '#') {
parsed->fragment = ne_strndup(s + 1, p - s - 1);
}
else if (*p || *s != '?') {
return -1;
}
}
return 0;
}
static char *merge_paths(const ne_uri *base, const char *path)
{
const char *p;
if (base->host && base->path[0] == '\0') {
return ne_concat("/", path, NULL);
}
p = strrchr(base->path, '/');
if (p == NULL) {
return ne_strdup(path);
} else {
size_t len = p - base->path + 1;
char *ret = ne_malloc(strlen(path) + len + 1);
memcpy(ret, base->path, len);
memcpy(ret + len, path, strlen(path) + 1);
return ret;
}
}
static char *remove_dot_segments(const char *path)
{
char *in, *inc, *out;
inc = in = ne_strdup(path);
out = ne_malloc(strlen(path) + 1);
out[0] = '\0';
while (in[0]) {
if (strncmp(in, "./", 2) == 0) {
in += 2;
}
else if (strncmp(in, "../", 3) == 0) {
in += 3;
}
else if (strncmp(in, "/./", 3) == 0) {
in += 2;
}
else if (strcmp(in, "/.") == 0) {
in[1] = '\0';
}
else if (strncmp(in, "/../", 4) == 0 || strcmp(in, "/..") == 0) {
char *p;
if (in[3] == '\0') {
in += 2;
in[0] = '/';
} else {
in += 3;
}
p = strrchr(out, '/');
if (p) {
*p = '\0';
} else {
out[0] = '\0';
}
}
else if (strcmp(in, ".") == 0 || strcmp(in, "..") == 0) {
in[0] = '\0';
}
else {
char *p;
p = strchr(in + (in[0] == '/'), '/');
if (p == NULL) p = strchr(in, '\0');
strncat(out, in, p - in);
in = p;
}
}
ne_free(inc);
return out;
}
static void copy_authority(ne_uri *dest, const ne_uri *src)
{
if (src->host) dest->host = ne_strdup(src->host);
dest->port = src->port;
if (src->userinfo) dest->userinfo = ne_strdup(src->userinfo);
}
ne_uri *ne_uri_resolve(const ne_uri *base, const ne_uri *relative,
ne_uri *target)
{
memset(target, 0, sizeof *target);
if (relative->scheme) {
target->scheme = ne_strdup(relative->scheme);
copy_authority(target, relative);
target->path = remove_dot_segments(relative->path);
if (relative->query) target->query = ne_strdup(relative->query);
} else {
if (relative->host) {
copy_authority(target, relative);
target->path = remove_dot_segments(relative->path);
if (relative->query) target->query = ne_strdup(relative->query);
} else {
if (relative->path[0] == '\0') {
target->path = ne_strdup(base->path);
if (relative->query) {
target->query = ne_strdup(relative->query);
} else if (base->query) {
target->query = ne_strdup(base->query);
}
} else {
if (relative->path[0] == '/') {
target->path = remove_dot_segments(relative->path);
} else {
char *merged = merge_paths(base, relative->path);
target->path = remove_dot_segments(merged);
ne_free(merged);
}
if (relative->query) target->query = ne_strdup(relative->query);
}
copy_authority(target, base);
}
if (base->scheme) target->scheme = ne_strdup(base->scheme);
}
if (relative->fragment) target->fragment = ne_strdup(relative->fragment);
return target;
}
ne_uri *ne_uri_copy(ne_uri *dest, const ne_uri *src)
{
memset(dest, 0, sizeof *dest);
if (src->scheme) dest->scheme = ne_strdup(src->scheme);
copy_authority(dest, src);
if (src->path) dest->path = ne_strdup(src->path);
if (src->query) dest->query = ne_strdup(src->query);
if (src->fragment) dest->fragment = ne_strdup(src->fragment);
return dest;
}
void ne_uri_free(ne_uri *u)
{
if (u->host) ne_free(u->host);
if (u->path) ne_free(u->path);
if (u->scheme) ne_free(u->scheme);
if (u->userinfo) ne_free(u->userinfo);
if (u->fragment) ne_free(u->fragment);
if (u->query) ne_free(u->query);
memset(u, 0, sizeof *u);
}
char *ne_path_unescape(const char *uri)
{
const char *pnt;
char *ret, *retpos, buf[5] = { "0x00" };
retpos = ret = ne_malloc(strlen(uri) + 1);
for (pnt = uri; *pnt != '\0'; pnt++) {
if (*pnt == '%') {
if (!isxdigit((unsigned char) pnt[1]) ||
!isxdigit((unsigned char) pnt[2])) {
ne_free(ret);
return NULL;
}
buf[2] = *++pnt; buf[3] = *++pnt;
*retpos++ = (char)strtol(buf, NULL, 16);
} else {
*retpos++ = *pnt;
}
}
*retpos = '\0';
return ret;
}
#define path_escape_ch(ch) (uri_lookup(ch) & URI_ESCAPE)
char *ne_path_escape(const char *path)
{
const unsigned char *pnt;
char *ret, *p;
size_t count = 0;
for (pnt = (const unsigned char *)path; *pnt != '\0'; pnt++) {
count += path_escape_ch(*pnt);
}
if (count == 0) {
return ne_strdup(path);
}
p = ret = ne_malloc(strlen(path) + 2 * count + 1);
for (pnt = (const unsigned char *)path; *pnt != '\0'; pnt++) {
if (path_escape_ch(*pnt)) {
sprintf(p, "%%%02x", (unsigned char) *pnt);
p += 3;
} else {
*p++ = *pnt;
}
}
*p = '\0';
return ret;
}
#undef path_escape_ch
#define CMPWITH(field, func) \
do { \
if (u1->field) { \
if (!u2->field) return -1; \
n = func(u1->field, u2->field); \
if (n) return n; \
} else if (u2->field) { \
return 1; \
} \
} while (0)
#define CMP(field) CMPWITH(field, strcmp)
#define CASECMP(field) CMPWITH(field, ne_strcasecmp)
int ne_uri_cmp(const ne_uri *u1, const ne_uri *u2)
{
int n;
CMP(path);
CASECMP(host);
CASECMP(scheme);
CMP(query);
CMP(fragment);
CMP(userinfo);
return u2->port - u1->port;
}
#undef CMP
#undef CASECMP
#undef CMPWITH
int ne_path_compare(const char *a, const char *b)
{
int ret = ne_strcasecmp(a, b);
if (ret) {
int traila = ne_path_has_trailing_slash(a),
trailb = ne_path_has_trailing_slash(b),
lena = strlen(a), lenb = strlen(b);
if (traila != trailb && abs(lena - lenb) == 1 &&
((traila && lena > lenb) || (trailb && lenb > lena))) {
if (strncasecmp(a, b, lena < lenb ? lena : lenb) == 0)
ret = 0;
}
}
return ret;
}
char *ne_uri_unparse(const ne_uri *uri)
{
ne_buffer *buf = ne_buffer_create();
if (uri->scheme) {
ne_buffer_concat(buf, uri->scheme, ":", NULL);
}
if (uri->host) {
ne_buffer_czappend(buf, "//");
if (uri->userinfo) {
ne_buffer_concat(buf, uri->userinfo, "@", NULL);
}
ne_buffer_zappend(buf, uri->host);
if (uri->port > 0
&& (!uri->scheme
|| ne_uri_defaultport(uri->scheme) != uri->port)) {
char str[20];
ne_snprintf(str, 20, ":%d", uri->port);
ne_buffer_zappend(buf, str);
}
}
ne_buffer_zappend(buf, uri->path);
if (uri->query) {
ne_buffer_concat(buf, "?", uri->query, NULL);
}
if (uri->fragment) {
ne_buffer_concat(buf, "#", uri->fragment, NULL);
}
return ne_buffer_finish(buf);
}
int ne_path_childof(const char *parent, const char *child)
{
char *root = ne_strdup(child);
int ret;
if (strlen(parent) >= strlen(child)) {
ret = 0;
} else {
root[strlen(parent)] = '\0';
ret = (ne_path_compare(parent, root) == 0);
}
ne_free(root);
return ret;
}