/* radare - LGPL - Copyright 2012-2025 - pancake */

#include <r_socket.h>
#include <r_util.h>

#define SOCKET_HTTP_MAX_HEADERS 128
#define SOCKET_HTTP_BODY_CHUNK 0x10000
#define SOCKET_HTTP_BODY_MAX (128 * 1024 * 1024)

static R_TH_LOCAL bool *breaked = NULL;

R_API void r_socket_http_server_set_breaked(bool *b) {
	breaked = b;
}

static void http_read_body(RSocketHTTPRequest *hr, size_t content_length) {
	ut8 first;
	// one missing byte: either the leftover newline or the first body byte
	if (r_socket_read_block (hr->s, &first, 1) != 1) {
		return;
	}
	// grow as data arrives instead of trusting the declared length
	size_t cap = R_MIN (content_length, SOCKET_HTTP_BODY_CHUNK);
	ut8 *data = malloc (cap + 1);
	if (!data) {
		return;
	}
	size_t have = 0;
	if (first != '\r' && first != '\n') {
		data[have++] = first;
	}
	while (have < content_length) {
		if (have == cap) {
			cap = (cap > content_length / 2)? content_length: cap * 2;
			ut8 *bigger = realloc (data, cap + 1);
			if (!bigger) {
				break;
			}
			data = bigger;
		}
		int r = r_socket_read_block (hr->s, data + have, cap - have);
		if (r < 1) {
			break;
		}
		have += r;
	}
	data[have] = 0;
	hr->data = data;
	hr->data_length = (int)have;
}

R_API RSocketHTTPRequest *r_socket_http_accept(RSocket *s, RSocketHTTPOptions *so) {
	ut64 content_length = 0;
	int xx, yy;
	int pxx = 1, first = 0;
	char buf[1500], *p, *q;
	RSocketHTTPRequest *hr = R_NEW0 (RSocketHTTPRequest);
	if (so->accept_timeout) {
		hr->s = r_socket_accept_timeout (s, 1);
	} else {
		hr->s = r_socket_accept (s);
	}
	if (!hr->s) {
		free (hr);
		return NULL;
	}
	if (so->timeout > 0) {
		r_socket_block_time (hr->s, true, so->timeout, 0);
	}
	hr->auth = !so->httpauth;
	hr->headers = r_list_newf (free);
	for (;;) {
#if R2__WINDOWS__
		if (breaked && *breaked) {
			r_socket_http_close (hr);
			r_socket_free (hr->s);
			return NULL;
		}
#endif
		memset (buf, 0, sizeof (buf));
		xx = r_socket_gets (hr->s, buf, sizeof (buf));
		if (xx < 0) {
			// peer closed or receive timeout before the end of the headers
			r_socket_http_close (hr);
			r_socket_http_free (hr);
			return NULL;
		}
		yy = r_socket_ready (hr->s, 0, 20 * 1000); // this function uses usecs as argument
		if (!yy || (!xx && !pxx)) {
			break;
		}
		pxx = xx;

		if (first == 0) {
			first = 1;
			if (strlen (buf) < 3) {
				r_socket_http_close (hr);
				r_socket_http_free (hr);
				return NULL;
			}
			p = strchr (buf, ' ');
			if (p) {
				*p = 0;
			}
			hr->method = strdup (buf);
			if (p) {
				q = strstr (p + 1, " HTTP");
				if (q) {
					*q = 0;
				}
				hr->path = r_str_trim_dup (p + 1);
			}
		} else {
			if (r_list_length (hr->headers) >= SOCKET_HTTP_MAX_HEADERS) {
				R_LOG_WARN ("Too many HTTP headers");
				r_socket_http_close (hr);
				r_socket_http_free (hr);
				return NULL;
			}
			if (buf[0] && buf[0] != '\r' && buf[0] != '\n') {
				char *line = strdup (buf);
				r_str_trim (line);
				if (R_STR_ISNOTEMPTY (line)) {
					r_list_append (hr->headers, line);
				} else {
					free (line);
				}
			}
			if (!hr->referer && r_str_startswith (buf, "Referer: ")) {
				hr->referer = strdup (buf + 9);
			} else if (!hr->agent && r_str_startswith (buf, "User-Agent: ")) {
				hr->agent = strdup (buf + 12);
			} else if (!hr->host && r_str_startswith (buf, "Host: ")) {
				hr->host = strdup (buf + 6);
			} else if (r_str_ncasecmp (buf, "Content-Length: ", 16) == 0) {
				content_length = strtoull (buf + 16, NULL, 10);
			} else if (so->httpauth && r_str_startswith (buf, "Authorization: Basic ")) {
				int declen;
				char *dec = (char *)r_base64_decode_dyn (buf + 21, -1, &declen, false);
				/* only non-empty user:password credentials can match */
				if (R_STR_ISNOTEMPTY (dec) && strchr (dec, ':')) {
					RListIter *iter;
					char *tok;
					r_list_foreach (so->authtokens, iter, tok) {
						if (!strcmp (dec, tok)) {
							hr->auth = true;
							break;
						}
					}
				}
				free (dec);
				if (!hr->auth) {
					R_LOG_ERROR ("Failed login attempt from '%s'", hr->host);
				}
			}
		}
	}
	if (content_length > 0 && hr->auth) {
		if (content_length > SOCKET_HTTP_BODY_MAX) {
			R_LOG_WARN ("HTTP request body too large (%" PFMT64u " bytes)", content_length);
			r_socket_http_response (hr, 413, "413 Payload Too Large\n", 0, NULL);
			r_socket_http_close (hr);
			r_socket_http_free (hr);
			return NULL;
		}
		http_read_body (hr, (size_t)content_length);
	}
	return hr;
}

static const char *httpcodestr(int code) {
	switch (code) {
	case 200: return "OK";
	case 202: return "Accepted";
	case 203: return "Non-Authoritative Information";
	case 204: return "No Content";
	case 206: return "Partial Content";
	case 301: return "Moved Permanently";
	case 302: return "Found";
	case 303: return "See Other";
	case 304: return "Not Modified";
	case 305: return "Use Proxy";
	case 307: return "Temporary Redirect";
	case 308: return "Permanent Redirect";
	case 400: return "Bad Request";
	case 401: return "Unauthorized";
	case 403: return "Forbidden";
	case 404: return "Not Found";
	case 405: return "Method Not Allowed";
	case 406: return "Not Acceptable";
	case 407: return "Proxy Authentication Required";
	case 408: return "Request Timeout";
	case 410: return "Gone";
	case 411: return "Length Required";
	case 413: return "Payload Too Large";
	case 414: return "URI Too Long";
	case 415: return "Unsupported Media Type";
	case 416: return "Range Not Satisfiable";
	case 417: return "Expectation Failed";
	case 418: return "I'm a teapot";
	case 421: return "Misdirected Request";
	case 422: return "Unprocessable Entity";
	case 423: return "Locked";
	case 424: return "Failed Dependency";
	case 425: return "Too Early";
	case 426: return "Upgrade Required";
	case 428: return "Precondition Required";
	case 429: return "Too Many Requests";
	case 431: return "Request Header Fields Too Large";
	case 451: return "Unavailable For Legal Reasons";
	case 500: return "Internal Server Error";
	case 501: return "Not Implemented";
	case 502: return "Bad Gateway";
	case 503: return "Service Unavailable";
	case 504: return "Gateway Timeout";
	case 505: return "HTTP Version Not Supported";
	case 506: return "Variant Also Negotiates";
	case 507: return "Insufficient Storage";
	}
	return "Unknown";
}

R_API void r_socket_http_response(RSocketHTTPRequest *rs, int code, const char *out, int len, const char *headers) {
	R_RETURN_IF_FAIL (rs);
	const char *strcode = httpcodestr (code);
	if (len < 1) {
		len = out? strlen (out): 0;
	}
	if (!headers) {
		headers = code == 401? "WWW-Authenticate: Basic realm=\"R2 Web UI Access\"\n": "";
	}
	r_socket_printf (rs->s, "HTTP/1.0 %d %s\r\n%s"
			"Connection: close\r\nContent-Length: %d\r\n\r\n",
		code, strcode, headers, len);
	if (out && len > 0) {
		r_socket_write (rs->s, (void *)out, len);
	}
}

R_API ut8 *r_socket_http_handle_upload(const ut8 *str, int len, int *retlen) {
	if (retlen) {
		*retlen = 0;
	}
	if (len >= 40 && r_str_startswith ((const char *)str, "----------")) {
		int datalen;
		char *ret;
		const char *data, *token = (const char *)str + 10;
		const char *end = strchr (token, '\n');
		if (!end) {
			return NULL;
		}
		data = strstr (end, "Content-Disposition: form-data; ");
		if (data) {
			data = strchr (data, '\n');
			if (data) {
				data = strchr (data + 1, '\n');
			}
		}
		if (data) {
			while (*data == 10 || *data == 13) {
				data++;
			}
			end = (const char *)str + len - 40;
			while (end > data && *end == '-') {
				end--;
			}
			if (end > data && (*end == 10 || *end == 13)) {
				end--;
			}
			if (end < data) {
				return NULL;
			}
			datalen = (size_t) (end - data);
			ret = malloc (datalen + 1);
			if (!ret) {
				return NULL;
			}
			memcpy (ret, data, datalen);
			ret[datalen] = 0;
			if (retlen) {
				*retlen = datalen;
			}
			return (ut8 *)ret;
		}
	}
	return NULL;
}

R_API void r_socket_http_close(RSocketHTTPRequest *rs) {
	if (rs) {
		r_socket_close (rs->s);
	}
}

/* close client socket and free struct */
R_API void r_socket_http_free(RSocketHTTPRequest *rs) {
	if (rs) {
		r_socket_free (rs->s);
		free (rs->path);
		free (rs->host);
		free (rs->agent);
		free (rs->method);
		free (rs->referer);
		free (rs->data);
		r_list_free (rs->headers);
		free (rs);
	}
}

R_API const char *r_socket_http_header(RSocketHTTPRequest *rs, const char *name) {
	R_RETURN_VAL_IF_FAIL (rs && name, NULL);
	if (!rs->headers) {
		return NULL;
	}
	const size_t nlen = strlen (name);
	RListIter *it;
	const char *line;
	r_list_foreach (rs->headers, it, line) {
		if (!r_str_ncasecmp (line, name, nlen) && line[nlen] == ':') {
			const char *v = line + nlen + 1;
			while (*v == ' ' || *v == '\t') {
				v++;
			}
			return v;
		}
	}
	return NULL;
}

#if MAIN
int main() {
	RSocket *s = r_socket_new (false);
	if (!r_socket_listen (s, "8080", NULL)) {
		R_LOG_ERROR ("Cannot listen here");
		return 1;
	}
	for (;;) {
		RSocketHTTPRequest *rs = r_socket_http_accept (s, 0);
		if (!rs) {
			continue;
		}
		if (!strcmp (rs->method, "GET")) {
			r_socket_http_response (rs, 200,
				"<html><body><form method=post action=/>"
				"<input name=a /><input type=button></form></body>");
		} else if (!strcmp (rs->method, "POST")) {
			char *buf = malloc (rs->data_length + 50);
			if (buf) {
				strcpy (buf, "<html><body><h2>XSS test</h2>\n");
				r_str_unescape (rs->data);
				strcat (buf, rs->data);
				r_socket_http_response (rs, 200, buf);
				free (buf);
			} else {
				R_LOG_ERROR ("Cannot allocate %d bytes", rs->data_length + 50);
			}
		} else if (!strcmp (rs->method, "OPTIONS")) {
			r_socket_http_response (rs, 200, "");
		} else {
			r_socket_http_response (rs, 404, "Invalid protocol");
		}
		r_socket_http_close (rs);
		r_socket_http_free (rs);
	}
}
#endif
