/*
 * MySQL module interface
 *
 * Copyright (C) 2001-2003 FhG FOKUS
 * Copyright (C) 2006-2007 iptelorg GmbH
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * Kamailio 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
 *
 * Kamailio 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
 */

#include "my_uri.h"

#include "../../core/dprint.h"
#include "../../core/mem/mem.h"
#include "../../core/ut.h"
#include "../../lib/srdb2/db_gen.h"

#include <stdlib.h>
#include <string.h>


/* compare s1 & s2  with a function f (which should return 0 if ==);
 * s1 & s2 can be null
 * return 0 if match, 1 if not */
#define cmpstr(s1, s2, f) \
	((s1) != (s2)) && ((s1) == 0 || (s2) == 0 || (f)((s1), (s2)) != 0)

/*
 * Compare two connection identifiers
 */
static unsigned char my_uri_cmp(db_uri_t *uri1, db_uri_t *uri2)
{
	struct my_uri *muri1, *muri2;

	if(!uri1 || !uri2)
		return 0;

	muri1 = DB_GET_PAYLOAD(uri1);
	muri2 = DB_GET_PAYLOAD(uri2);
	if(muri1->port != muri2->port)
		return 0;

	if(cmpstr(muri1->username, muri2->username, strcmp))
		return 0;
	if(cmpstr(muri1->password, muri2->password, strcmp))
		return 0;
	if(cmpstr(muri1->host, muri2->host, strcasecmp))
		return 0;
	if(cmpstr(muri1->database, muri2->database, strcmp))
		return 0;
	return 1;
}


/*
 * Duplicate a string
 */
static int dupl_string(char **dst, const char *begin, const char *end)
{
	if(*dst)
		pkg_free(*dst);

	*dst = pkg_malloc(end - begin + 1);
	if((*dst) == NULL) {
		PKG_MEM_ERROR;
		return -1;
	}

	memcpy(*dst, begin, end - begin);
	(*dst)[end - begin] = '\0';
	return 0;
}


/*
 * Parse mysql URI of form
 * //[username[:password]@]hostname[:port]/database
 *
 * Returns 0 if parsing was successful and -1 otherwise
 */
static int parse_mysql_uri(struct my_uri *res, str *uri)
{
#define SHORTEST_DB_URL "//a/b"
#define SHORTEST_DB_URL_LEN (sizeof(SHORTEST_DB_URL) - 1)

	enum state
	{
		ST_SLASH1,	  /* First slash */
		ST_SLASH2,	  /* Second slash */
		ST_USER_HOST, /* Username or hostname */
		ST_PASS_PORT, /* Password or port part */
		ST_HOST,	  /* Hostname part */
		ST_HOST6,	  /* Hostname part IPv6 */
		ST_PORT,	  /* Port part */
		ST_DB		  /* Database part */
	};

	enum state st;
	int i, ipv6_flag = 0;
	const char *begin;
	char *prev_token;

	prev_token = 0;

	if(!res || !uri) {
		goto err;
	}

	if(uri->len < SHORTEST_DB_URL_LEN) {
		goto err;
	}

	st = ST_SLASH1;
	begin = uri->s;

	for(i = 0; i < uri->len; i++) {
		switch(st) {
			case ST_SLASH1:
				switch(uri->s[i]) {
					case '/':
						st = ST_SLASH2;
						break;

					default:
						goto err;
				}
				break;

			case ST_SLASH2:
				switch(uri->s[i]) {
					case '/':
						st = ST_USER_HOST;
						begin = uri->s + i + 1;
						break;

					default:
						goto err;
				}
				break;

			case ST_USER_HOST:
				switch(uri->s[i]) {
					case '@':
						st = ST_HOST;
						if(dupl_string(&res->username, begin, uri->s + i) < 0)
							goto err;
						begin = uri->s + i + 1;
						break;

					case ':':
						st = ST_PASS_PORT;
						if(dupl_string(&prev_token, begin, uri->s + i) < 0)
							goto err;
						begin = uri->s + i + 1;
						break;

					case '[':
						st = ST_HOST6;
						begin = uri->s + i + 1;
						break;

					case '/':
						if(dupl_string(&res->host, begin, uri->s + i) < 0)
							goto err;
						if(dupl_string(&res->database, uri->s + i + 1,
								   uri->s + uri->len)
								< 0)
							goto err;
						return 0;
				}
				break;

			case ST_PASS_PORT:
				switch(uri->s[i]) {
					case '@':
						st = ST_HOST;
						res->username = prev_token;
						prev_token = 0;
						if(dupl_string(&res->password, begin, uri->s + i) < 0)
							goto err;
						begin = uri->s + i + 1;
						break;

					case '/':
						res->host = prev_token;
						prev_token = 0;
						res->port = str2s(begin, uri->s + i - begin, 0);
						if(dupl_string(&res->database, uri->s + i + 1,
								   uri->s + uri->len)
								< 0)
							goto err;
						return 0;
				}
				break;

			case ST_HOST:
				switch(uri->s[i]) {
					case '[':
						st = ST_HOST6;
						begin = uri->s + i + 1;
						break;

					case ':':
						st = ST_PORT;
						if(dupl_string(
								   &res->host, begin, uri->s + i - ipv6_flag)
								< 0)
							goto err;
						begin = uri->s + i + 1;
						break;

					case '/':
						if(dupl_string(
								   &res->host, begin, uri->s + i - ipv6_flag)
								< 0)
							goto err;
						if(dupl_string(&res->database, uri->s + i + 1,
								   uri->s + uri->len)
								< 0)
							goto err;
						return 0;
				}
				break;

			case ST_HOST6:
				switch(uri->s[i]) {
					case ']':
						ipv6_flag = 1;
						st = ST_HOST;
						break;
				}
				break;

			case ST_PORT:
				switch(uri->s[i]) {
					case '/':
						res->port = str2s(begin, uri->s + i - begin, 0);
						if(dupl_string(&res->database, uri->s + i + 1,
								   uri->s + uri->len)
								< 0)
							goto err;
						return 0;
				}
				break;

			case ST_DB:
				break;
		}
	}

	if(st != ST_DB)
		goto err;
	return 0;

err:
	if(prev_token)
		pkg_free(prev_token);
	if(res == NULL)
		return -1;
	if(res->username) {
		pkg_free(res->username);
		res->username = NULL;
	}
	if(res->password) {
		pkg_free(res->password);
		res->password = NULL;
	}
	if(res->host) {
		pkg_free(res->host);
		res->host = NULL;
	}
	if(res->database) {
		pkg_free(res->database);
		res->database = NULL;
	}
	return -1;
}


static void my_uri_free(db_uri_t *uri, struct my_uri *payload)
{
	if(payload == NULL)
		return;
	db_drv_free(&payload->drv);
	if(payload->username)
		pkg_free(payload->username);
	if(payload->password)
		pkg_free(payload->password);
	if(payload->host)
		pkg_free(payload->host);
	if(payload->database)
		pkg_free(payload->database);
	pkg_free(payload);
}


int my_uri(db_uri_t *uri)
{
	struct my_uri *res;

	res = (struct my_uri *)pkg_malloc(sizeof(struct my_uri));
	if(res == NULL) {
		PKG_MEM_ERROR;
		goto error;
	}
	memset(res, '\0', sizeof(struct my_uri));
	if(db_drv_init(&res->drv, my_uri_free) < 0)
		goto error;
	if(parse_mysql_uri(res, &uri->body) < 0)
		goto error;

	DB_SET_PAYLOAD(uri, res);
	uri->cmp = my_uri_cmp;
	return 0;

error:
	if(res) {
		db_drv_free(&res->drv);
		if(res)
			pkg_free(res);
	}
	return -1;
}
