/*
   +----------------------------------------------------------------------+
   | Copyright © The PHP Group and Contributors.                          |
   +----------------------------------------------------------------------+
   | This source file is subject to the Modified BSD License that is      |
   | bundled with this package in the file LICENSE, and is available      |
   | through the World Wide Web at <https://www.php.net/license/>.        |
   |                                                                      |
   | SPDX-License-Identifier: BSD-3-Clause                                |
   +----------------------------------------------------------------------+
   | Authors: Máté Kocsis <kocsismate@php.net>                            |
   |          Tim Düsterhus <timwolla@php.net>                            |
   +----------------------------------------------------------------------+
*/

#include "php.h"
#include "uri_parser_php_parse_url.h"
#include "php_uri_common.h"
#include "Zend/zend_exceptions.h"
#include "ext/standard/url.h"

static zend_string *decode_component(zend_string *in, php_uri_component_read_mode read_mode)
{
	switch (read_mode) {
	case PHP_URI_COMPONENT_READ_MODE_RAW:
		return zend_string_copy(in);
	case PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII:
	case PHP_URI_COMPONENT_READ_MODE_NORMALIZED_UNICODE: {
		zend_string *out = zend_string_alloc(ZSTR_LEN(in), false);

		ZSTR_LEN(out) = php_raw_url_decode_ex(ZSTR_VAL(out), ZSTR_VAL(in), ZSTR_LEN(in));

		return out;
	}
	default: ZEND_UNREACHABLE();
	}
}

static zend_result uri_parser_php_parse_url_scheme_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
{
	const php_url *parse_url_uri = uri;

	if (parse_url_uri->scheme) {
		ZVAL_STR(retval, decode_component(parse_url_uri->scheme, read_mode));
	} else {
		ZVAL_NULL(retval);
	}

	return SUCCESS;
}

static zend_result uri_parser_php_parse_url_username_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
{
	const php_url *parse_url_uri = uri;

	if (parse_url_uri->user) {
		ZVAL_STR(retval, decode_component(parse_url_uri->user, read_mode));
	} else {
		ZVAL_NULL(retval);
	}

	return SUCCESS;
}

static zend_result uri_parser_php_parse_url_password_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
{
	const php_url *parse_url_uri = uri;

	if (parse_url_uri->pass) {
		ZVAL_STR(retval, decode_component(parse_url_uri->pass, read_mode));
	} else {
		ZVAL_NULL(retval);
	}

	return SUCCESS;
}

static zend_result uri_parser_php_parse_url_host_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
{
	const php_url *parse_url_uri = uri;

	if (parse_url_uri->host) {
		ZVAL_STR(retval, decode_component(parse_url_uri->host, read_mode));
	} else {
		ZVAL_NULL(retval);
	}

	return SUCCESS;
}

static zend_result uri_parser_php_parse_url_port_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
{
	const php_url *parse_url_uri = uri;

	if (parse_url_uri->port) {
		ZVAL_LONG(retval, parse_url_uri->port);
	} else {
		ZVAL_NULL(retval);
	}

	return SUCCESS;
}

static zend_result uri_parser_php_parse_url_path_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
{
	const php_url *parse_url_uri = uri;

	if (parse_url_uri->path) {
		ZVAL_STR(retval, decode_component(parse_url_uri->path, read_mode));
	} else {
		ZVAL_NULL(retval);
	}

	return SUCCESS;
}

static zend_result uri_parser_php_parse_url_query_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
{
	const php_url *parse_url_uri = uri;

	if (parse_url_uri->query) {
		ZVAL_STR(retval, decode_component(parse_url_uri->query, read_mode));
	} else {
		ZVAL_NULL(retval);
	}

	return SUCCESS;
}

static zend_result uri_parser_php_parse_url_fragment_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
{
	const php_url *parse_url_uri = uri;

	if (parse_url_uri->fragment) {
		ZVAL_STR(retval, decode_component(parse_url_uri->fragment, read_mode));
	} else {
		ZVAL_NULL(retval);
	}

	return SUCCESS;
}

static void *uri_parser_php_parse_url_parse(const char *uri_str, size_t uri_str_len, const void *base_url, zval *errors, bool silent)
{
	bool has_port;

	php_url *url = php_url_parse_ex2(uri_str, uri_str_len, &has_port);
	if (url == NULL && !silent) {
		zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified URI is malformed", 0);
	}

	return url;
}

static void uri_parser_php_parse_url_destroy(void *uri)
{
	php_url *parse_url_uri = uri;

	if (UNEXPECTED(parse_url_uri == NULL)) {
		return;
	}

	php_url_free(parse_url_uri);
}

PHPAPI const php_uri_parser php_uri_parser_php_parse_url = {
	.name = PHP_URI_PARSER_PHP_PARSE_URL,
	.parse = uri_parser_php_parse_url_parse,
	.clone = NULL,
	.to_string = NULL,
	.destroy = uri_parser_php_parse_url_destroy,
	{
		.scheme = {.read = uri_parser_php_parse_url_scheme_read, .write = NULL},
		.username = {.read = uri_parser_php_parse_url_username_read, .write = NULL},
		.password = {.read = uri_parser_php_parse_url_password_read, .write = NULL},
		.host = {.read = uri_parser_php_parse_url_host_read, .write = NULL},
		.port = {.read = uri_parser_php_parse_url_port_read, .write = NULL},
		.path = {.read = uri_parser_php_parse_url_path_read, .write = NULL},
		.query = {.read = uri_parser_php_parse_url_query_read, .write = NULL},
		.fragment = {.read = uri_parser_php_parse_url_fragment_read, .write = NULL},
	}
};
