// SPDX-FileCopyrightText: 2011-2020 pancake <pancake@nopcode.org>
// SPDX-License-Identifier: LGPL-3.0-only

#include <rz_socket.h>
#include <rz_util.h>

#if __WINDOWS__
#include <WinInet.h>
#endif

#define SOCKET_HTTP_MAX_HEADER_LENGTH 0x2000
#define SOCKET_HTTP_MAX_REDIRECTS     5

static size_t socket_slurp(RzSocket *s, RzBuffer *buf) {
	size_t i;
	if (rz_socket_ready(s, 1, 0) != 1) {
		return 0;
	}
	rz_socket_block_time(s, true, 0, 1000);
	for (i = 0; i < SOCKET_HTTP_MAX_HEADER_LENGTH; i += 1) {
		ut8 c;
		int olen = rz_socket_read_block(s, &c, 1);
		if (olen != 1) {
			rz_buf_append_bytes(buf, (ut8 *)"", 1);
			break;
		}
		rz_buf_append_bytes(buf, &c, 1);
	}
	return i;
}

static char *socket_http_get_recursive(const char *url, int *code, int *rlen, ut32 redirections);

static char *socket_http_answer(RzSocket *s, int *code, int *rlen, ut32 redirections) {
	rz_return_val_if_fail(s, NULL);
	const char *p;
	int ret, len = 0, delta = 0;
	char *dn = NULL;
	RzBuffer *b = rz_buf_new_with_bytes(NULL, 0);
	if (!b) {
		return NULL;
	}
	char *res = NULL;
	size_t olen = socket_slurp(s, b);
	char *buf = malloc(olen + 1);
	if (!buf) {
		goto exit;
	}
	rz_buf_read_at(b, 0, (ut8 *)buf, olen);
	buf[olen] = 0;
	char *dnn = (char *)rz_str_casestr(buf, "\n\n");
	char *drn = (char *)rz_str_casestr(buf, "\r\n\r\n");
	if (dnn) {
		if (drn && (drn < dnn)) {
			dn = drn;
			delta = 4;
		} else {
			dn = dnn;
			delta = 2;
		}
	} else {
		dn = drn;
		delta = 4;
	}
	if (!dn) {
		goto exit;
	}

	olen -= delta;
	*dn = 0; // chop headers

	/* Follow redirects */
	p = rz_str_casestr(buf, "Location:");
	if (p) {
		if (!redirections) {
			eprintf("Too many redirects\n");
			goto exit;
		}
		p += strlen("Location:");
		const char *end_url = strchr(p, '\n');
		if (end_url) {
			size_t url_len = end_url - p;
			char *url = rz_str_ndup(p, url_len);
			rz_str_trim(url);
			res = socket_http_get_recursive(url, code, rlen, --redirections);
			free(url);
			len = *rlen;
		}
		goto exit;
	}

	/* Parse Len */
	p = rz_str_casestr(buf, "Content-Length: ");
	if (p) {
		len = atoi(p + 16);
	} else {
		len = olen - (dn - buf);
	}
	if (len > 0) {
		if (len > olen) {
			res = malloc(len + 2);
			if (!res) {
				goto exit;
			}
			olen -= dn - buf;
			memcpy(res, dn + delta, olen);
			do {
				ret = rz_socket_read_block(s, (ut8 *)res + olen, len - olen);
				if (ret < 1) {
					break;
				}
				olen += ret;
			} while (olen < len);
			res[len] = 0;
		} else {
			res = malloc(len + 1);
			if (res) {
				memcpy(res, dn + delta, len);
				res[len] = 0;
			}
		}
	} else {
		res = rz_str_dup("");
	}
exit:
	free(buf);
	rz_buf_free(b);
	rz_socket_close(s);
	if (rlen) {
		*rlen = len;
	}
	return res;
}

#if __WINDOWS__
static char *http_get_w32(const char *url, int *code, int *rlen) {
	HINTERNET hInternet = InternetOpenA("rizin " RZ_VERSION, INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
	if (!hInternet) {
		rz_sys_perror("InternetOpenA");
		return NULL;
	}
	HINTERNET hOpenUrl = InternetOpenUrlA(hInternet, url, NULL, 0, 0, 0);
	if (!hOpenUrl) {
		rz_sys_perror("InternetOpenUrlA");
		InternetCloseHandle(hInternet);
		return NULL;
	}

	char *ret = NULL;
	size_t read_sz = 0x100000;
	DWORD r = 0, w = 0;
	bool res = true;
	do {
		w += r;
		if (!res && GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
			read_sz *= 2;
		}
		char *tmp = realloc(ret, read_sz + w);
		if (!tmp) {
			RZ_FREE(ret);
			goto exit;
		}
		ret = tmp;
	} while (!(res = InternetReadFile(hOpenUrl, ret + w, read_sz, &r)) || r);

	if (res) {
		char *tmp = realloc(ret, (size_t)w + 1);
		if (tmp) {
			ret = tmp;
			ret[w] = 0;
		} else {
			RZ_FREE(ret);
		}
	} else {
		RZ_FREE(ret);
	}

exit:
	if (rlen) {
		*rlen = w;
	}
	if (code && w) {
		*code = 200;
	}
	InternetCloseHandle(hInternet);
	InternetCloseHandle(hOpenUrl);
	return ret;
}
#endif

static char *socket_http_get_recursive(const char *url, int *code, int *rlen, ut32 redirections) {
	if (code) {
		*code = 0;
	}
	if (rlen) {
		*rlen = 0;
	}
	char *curl_env = rz_sys_getenv("RZ_CURL");
	if (!RZ_STR_ISEMPTY(curl_env) && atoi(curl_env)) {
		int len;
		char *escaped_url = rz_str_escape_sh(url);
		char *command = rz_str_newf("curl -sSfL -o - \"%s\"", escaped_url);
		char *res = NULL;
		char *err = NULL;
		int ret = rz_sys_cmd_str_full(command, NULL, &res, &len, &err);
		free(escaped_url);
		free(command);
		free(curl_env);
		if (err) {
			eprintf("%s", err);
			free(err);
		}
		if (!ret || !res) {
			return NULL;
		}
		if (res) {
			if (code) {
				*code = 200;
			}
			if (rlen) {
				*rlen = len;
			}
		}
		return res;
	}
	free(curl_env);
#if __WINDOWS__
	return http_get_w32(url, code, rlen);
#else
	RzSocket *s;
	bool ssl = rz_str_startswith(url, "https://");
#if !HAVE_LIB_SSL
	if (ssl) {
		eprintf("Tried to get '%s', but SSL support is disabled, set RZ_CURL=1 to use curl\n", url);
		return NULL;
	}
#endif
	char *response, *host, *path, *port = "80";
	char *uri = rz_str_dup(url);
	if (!uri) {
		return NULL;
	}
	host = strstr(uri, "://");
	if (!host) {
		free(uri);
		eprintf("rz_socket_http_get: Invalid URI");
		return NULL;
	}
	host += 3;
	port = strchr(host, ':');
	if (!port) {
#if HAVE_LIB_SSL
		port = ssl ? "443" : "80";
#else
		port = "80";
#endif
		path = host;
	} else {
		*port++ = 0;
		path = port;
	}
	path = strchr(path, '/');
	if (!path) {
		path = "";
	} else {
		*path++ = 0;
	}
	s = rz_socket_new(ssl);
	if (!s) {
		eprintf("rz_socket_http_get: Cannot create socket\n");
		free(uri);
		return NULL;
	}
	if (rz_socket_connect_tcp(s, host, port, 0)) {
		rz_socket_printf(s,
			"GET /%s HTTP/1.1\r\n"
			"User-Agent: rizin " RZ_VERSION "\r\n"
			"Accept: */*\r\n"
			"Host: %s:%s\r\n"
			"\r\n",
			path, host, port);
		response = socket_http_answer(s, code, rlen, redirections);
	} else {
		eprintf("Cannot connect to %s:%s\n", host, port);
		response = NULL;
	}
	free(uri);
	rz_socket_free(s);
	return response;
#endif
}

RZ_API char *rz_socket_http_get(const char *url, int *code, int *rlen) {
	return socket_http_get_recursive(url, code, rlen, SOCKET_HTTP_MAX_REDIRECTS);
}

RZ_API char *rz_socket_http_post(const char *url, const char *data, int *code, int *rlen) {
	RzSocket *s;
	bool ssl = rz_str_startswith(url, "https://");
	char *uri = rz_str_dup(url);
	if (!uri) {
		return NULL;
	}

	char *host = strstr(uri, "://");
	if (!host) {
		free(uri);
		printf("Invalid URI");
		return NULL;
	}
	host += 3;
	char *port = strchr(host, ':');
	if (!port) {
		port = (ssl) ? "443" : "80";
	} else {
		*port++ = 0;
	}
	char *path = strchr(host, '/');
	if (!path) {
		path = "";
	} else {
		*path++ = 0;
	}
	s = rz_socket_new(ssl);
	if (!s) {
		printf("Cannot create socket\n");
		free(uri);
		return NULL;
	}
	if (!rz_socket_connect_tcp(s, host, port, 0)) {
		eprintf("Cannot connect to %s:%s\n", host, port);
		free(uri);
		return NULL;
	}
	/* Send */
	rz_socket_printf(s,
		"POST /%s HTTP/1.0\r\n"
		"User-Agent: rizin " RZ_VERSION "\r\n"
		"Accept: */*\r\n"
		"Host: %s\r\n"
		"Content-Length: %i\r\n"
		"Content-Type: application/x-www-form-urlencoded\r\n"
		"\r\n",
		path, host, (int)strlen(data));
	free(uri);
	rz_socket_write(s, (void *)data, strlen(data));
	return socket_http_answer(s, code, rlen, 0);
}
