changeset 407:2ae34a90f8d4

http: roll our own URL encode/decode functions works for basically every kind of data
author Paper <paper@tflc.us>
date Wed, 21 Jan 2026 11:27:01 -0500
parents 31ce85df55a8
children 9323153786dc
files src/core/http.cc
diffstat 1 files changed, 80 insertions(+), 28 deletions(-) [+]
line wrap: on
line diff
--- a/src/core/http.cc	Mon Jan 19 22:48:56 2026 -0500
+++ b/src/core/http.cc	Wed Jan 21 11:27:01 2026 -0500
@@ -10,44 +10,96 @@
 
 std::string UrlEncode(const std::string &data)
 {
-	/* why do I need to init curl just for this? wtf? */
-	CURL *curl = curl_easy_init();
-	if (!curl)
-		return ""; /* no way! */
+	std::string res;
+	std::size_t sz = data.size();
+	std::size_t i;
+
+	// output string will always be at least data.size()
+	// so reserve that much space beforehand
+	res.reserve(sz);
 
-	char *output = curl_easy_escape(curl, data.data(), data.size());
-	if (!output) {
-		curl_easy_cleanup(curl);
-		return "";
+	for (i = 0; i < sz; i++) {
+		// This works correctly for UTF-8 because of the way
+		// the data is laid out.
+		static const char lut[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.!~*'()";
+		unsigned char c = data[i];
+
+		if (std::memchr(lut, c, sizeof(lut) - 1)) {
+			res.push_back(c);
+		} else {
+			static const char lut[] = "0123456789ABCDEF";
+			res.push_back('%');
+			res.push_back(lut[c >> 4]);
+			res.push_back(lut[c & 15]);
+		}
 	}
 
-	std::string str(output);
-
-	curl_free(output);
-	curl_easy_cleanup(curl);
-
-	return str;
+	return res;
 }
 
+// NOTE: This function is not guaranteed to return
+// UTF-8, or even text at all.
 std::string UrlDecode(const std::string &data)
 {
-	CURL *curl = curl_easy_init();
-	if (!curl)
-		return "";
+	std::string res;
+
+	const char *ptr = data.data();
+	std::size_t len = data.size();
+
+	// reserve space beforehand
+	res.reserve(len);
+
+	while (len > 0) {
+		// find the next percent character
+		// there's probably a better way to do this!
+		const char *next = reinterpret_cast<const char *>(std::memchr(reinterpret_cast<const void *>(ptr), '%', len));
+
+		if (next) {
+			res.insert(res.end(), ptr, next);
+			len = (next - ptr);
+			ptr = next;
+
+			// now process the two hex chars
+			if (len >= 3) {
+				unsigned char hi, lo;
+
+				auto hex_char_to_value = [](unsigned char x, unsigned char &v) {
+					if (x >= 'A' && x <= 'F') {
+						v = x - 'A' + 0xA;
+						return true;
+					}
 
-	int outlength;
-	char *output = curl_easy_unescape(curl, data.data(), data.size(), &outlength);
-	if (!output) {
-		curl_easy_cleanup(curl);
-		return "";
+					if (x >= 'a' && x <= 'f') {
+						v = x - 'a' + 0xA;
+						return true;
+					}
+
+					if (x >= '0' && x <= '9') {
+						v = x - '0';
+						return true;
+					}
+
+					return false;
+				};
+
+				if (hex_char_to_value(ptr[1], hi) && hex_char_to_value(ptr[2], lo)) {
+					ptr += 3;
+					len -= 3;
+				} else {
+					len = 0;
+				}
+			} else {
+				// uh oh
+				len = 0;
+			}
+		} else {
+			res.insert(res.end(), ptr, ptr + len);
+			//not needed: ptr += len;
+			len = 0;
+		}
 	}
 
-	std::string str(output, outlength);
-
-	curl_free(output);
-	curl_easy_cleanup(curl);
-
-	return str;
+	return res;
 }
 
 std::string EncodeParamsList(std::string base, const std::map<std::string, std::string> &params)