/*	$NetBSD: uri.c,v 1.4 2026/07/09 16:54:11 mlelstv Exp $	*/

/*-
 * Copyright (c) 2026 The NetBSD Foundation, Inc.
 * All 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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 <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "ssl.h"
#include "ftp_var.h"
#include "uri.h"

typedef struct {
	const char *buf;
	size_t scheme;
	size_t authority;
	size_t path;
	size_t query;
	size_t fragment;
} uriparts_t;

#define SCHEME(p) ((p)->buf)
#define AUTHORITY(p) (SCHEME(p) + (p)->scheme)
#define PATH(p) (AUTHORITY(p) + (p)->authority)
#define QUERY(p) (PATH(p) + (p)->path)
#define FRAGMENT(p) (QUERY(p) + (p)->query)

static void
split_uri(const char *uri, uriparts_t *parts)
{
	char *colon, *slash, *query, *sharp;

	parts->buf = uri;
	parts->scheme = 0;
	parts->authority = 0;
	parts->path = 0;
	parts->query = 0;
	parts->fragment = 0;

	colon = strchr(uri, ':');
	slash = strchr(uri, '/');
	if (colon && (slash == NULL || (colon - uri) < (slash - uri))) {
		parts->scheme = colon + 1 - uri;
		uri = colon + 1;
	}       

	if (uri[0] == '/' && uri[1] == '/') {
		slash = strchr(uri + 2, '/');
		if (slash == NULL) {
			parts->authority = strlen(uri); 
			return;
		}
		parts->authority = slash - uri;
		uri = slash;
	}       

	query = strchr(uri, '?');
	sharp = strchr(uri, '#');
	if (query && (sharp == NULL || (query - uri) < (sharp - uri))) {
		parts->path = query - uri;
		uri = query;
		if (sharp != NULL) {
			parts->query = sharp - query;
			parts->fragment = strlen(sharp);
			return;
		}
		parts->query = strlen(query);
		return;
	}

	if (sharp != NULL) {
		parts->path = sharp - uri;
		parts->fragment = strlen(sharp);
		return;
	}

	parts->path = strlen(uri);
}

static char *
build_uri(uriparts_t *s, uriparts_t *a, uriparts_t *p,
    uriparts_t *q, uriparts_t *f)
{
	size_t len;
	char *buf, *out;

	len = s->scheme + a->authority + p->path + q->query + f->fragment;
	buf = ftp_malloc(len + 1);

	out = buf;
	memcpy(out, SCHEME(s), s->scheme);
	out += s->scheme;
	memcpy(out, AUTHORITY(a), a->authority);
	out += a->authority;
	memcpy(out, PATH(p), p->path);
	out += p->path;
	memcpy(out, QUERY(q), q->query);
	out += q->query;
	memcpy(out, FRAGMENT(f), f->fragment);
	out += f->fragment;
	*out = '\0';

	return buf;
}

static char *
remove_dot_segments(const char *path, size_t len)
{
	char *p;
	char *pbuf, *buf, *out, *seg;
	size_t n;

	p = pbuf = ftp_malloc(len + 1);
	memcpy(p, path, len);
	p[len] = '\0';

	buf = ftp_malloc(len + 1);
	out = buf;
	*out = '\0';

	while (len > 0) {
		/* A */
		if (p[0] == '.') {
			if (len > 1 && p[1] == '/') {
				p += 2;
				len -= 2;
				continue;
			} else if (len > 2 && p[1] == '.' && p[2] == '/') {
				p += 3;
				len -= 3;
				continue;
			}
		}

		/* B */
		if (p[0] == '/') {
			if (len > 2 && p[1] == '.' && p[2] == '/') {
				p += 2;
				len -= 2;
				continue;
			} else if (len == 2 && p[1] == '.') {
				p += 1;
				len -= 1;
				p[0] = '/';
				continue;
			}
		}

		/* C */
		if (len > 2 && p[0] == '/' && p[1] == '.' && p[2] == '.') {
			if (len > 3 && p[3] == '/') {
				p += 3;
				len -= 3;
				seg = strrchr(buf, '/');
				if (seg != NULL)
					*seg = '\0';
				continue;
			} else if (len == 3) {
				p += 2;
				len -= 2;
				p[0] = '/';
				seg = strrchr(buf, '/');
				if (seg != NULL)
					*seg = '\0';
				continue;
			}
		}

		/* D */
		if (p[0] == '.') {
			if (len == 1) {
				p += 1;
				len -= 1;
				continue;
			} else if (len == 2 && p[1] == '.') {
				p += 2;
				len -= 2;
				continue;
			}
		}

		/* E */
		if (p[0] == '/') {
			seg = strchr(p+1, '/');
		} else {
			seg = strchr(p, '/');
		}
		if (seg != NULL) {
			n = seg - p;
			if (n > len)
				n = len;
		} else
			n = len;

		memcpy(out, p, n);
		out += n;
		*out = '\0';
		p += n;
		len -= n;
	};

	FREEPTR(pbuf);

	return buf;
}

static char *
merge_paths(uriparts_t *base, uriparts_t *ref)
{
	char *buf;
	const char *path, *last;
	size_t len;

	if (base->authority && base->path == 0) {
		len = 1 + ref->path;
		buf = ftp_malloc(len + 1);
		buf[0] = '/';
		memcpy(buf + 1, PATH(ref), ref->path);
	} else {
		path = PATH(base);
		last = strrchr(path, '/');
		if (last == NULL || (size_t)(last - path) > base->path)
			len = 0;
		else
			len = (size_t)(last - path) + 1;
		buf = ftp_malloc(len + ref->path + 1);
		memcpy(buf, path, len);
		memcpy(buf + len, PATH(ref), ref->path);
		len += ref->path;
	}
	buf[len] = '\0';

	return buf;
}

char *
make_absurl(char *refurl, const char *baseurl)
{
	uriparts_t base;
	uriparts_t ref;
	uriparts_t p;
	char *buf, *tmp1, *tmp2;

	split_uri(baseurl, &base);
	split_uri(refurl, &ref);

	p.scheme = p.authority = p.path = p.query = p.fragment = 0;

	if (ref.scheme) {
		tmp1 = remove_dot_segments(PATH(&ref), ref.path);
		p.buf = tmp1;
		p.path = strlen(tmp1);
		buf = build_uri(&ref, &ref, &p, &ref, &ref);
		FREEPTR(tmp1);
	} else {
		if (ref.authority) {
			tmp1 = remove_dot_segments(PATH(&ref), ref.path);
			p.buf = tmp1;
			p.path = strlen(tmp1);
			buf = build_uri(&base, &ref, &p, &ref, &ref);
			FREEPTR(tmp1);
		} else {
			if (ref.path == 0) {
				if (ref.query) {
					buf = build_uri(&base, &base, &base, &ref, &ref);
				} else {
					buf = build_uri(&base, &base, &base, &base, &ref);
				}
			} else {
				if (PATH(&ref)[0] == '/') {
					tmp1 = remove_dot_segments(PATH(&ref), ref.path);
					p.buf = tmp1;
					p.path = strlen(tmp1);
					buf = build_uri(&base, &base, &p, &ref, &ref);
					FREEPTR(tmp1);
				} else {
					tmp2 = merge_paths(&base, &ref);
					tmp1 = remove_dot_segments(tmp2, strlen(tmp2));
					p.buf = tmp1;
					p.path = strlen(tmp1);
					FREEPTR(tmp2);

					buf = build_uri(&base, &base, &p, &ref, &ref);
					FREEPTR(tmp1);
				}
			}
		}
	}

	FREEPTR(refurl);

	return buf;
}
