~sircmpwn/hare

hare/net/uri/query.ha -rw-r--r-- 2.3 KiB
b801af0cBor Grošelj Simić regex: simplify charclass name -> func mapping a day ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
use io;
use strings;
use strio;

export type query_decoder = struct {
	tokenizer: strings::tokenizer,
	bufs: (strio::stream, strio::stream),
};

// Initializes a decoder for a query string. Use [[query_next]] to walk it. The
// caller must call [[query_finish]] once they're done using it.
export fn decodequery(q: const str) query_decoder = query_decoder {
	tokenizer = strings::tokenize(q, "&"),
	bufs = (strio::dynamic(), strio::dynamic()),
};

// Frees resources associated with the [[query_decoder]].
export fn query_finish(dec: *query_decoder) void = {
	io::close(&dec.bufs.0)!;
	io::close(&dec.bufs.1)!;
};

// Retrieves the next (key, value) pair from the query. The return value is
// borrowed from the decoder and will be replaced on the next call, use
// [[strings::dup]] to extend its lifetime.
export fn query_next(dec: *query_decoder) ((str, str) | invalid | void) = {
	const tok = match (strings::next_token(&dec.tokenizer)) {
	case let s: str =>
		yield s;
	case => return;
	};

	const raw = strings::cut(tok, "=");
	strio::reset(&dec.bufs.0);
	percent_decode_static(&dec.bufs.0, raw.0)?;
	strio::reset(&dec.bufs.1);
	percent_decode_static(&dec.bufs.1, raw.1)?;
	return (
		strio::string(&dec.bufs.0),
		strio::string(&dec.bufs.1),
	);
};

// Encodes (key, value) pairs into a URI query string. The result must be
// freed by the caller.
export fn encodequery(pairs: [](str, str)) str = {
	const buf = strio::dynamic();
	for (let i = 0z; i < len(pairs); i += 1) {
		const pair = pairs[i];
		if (i > 0) strio::appendrune(&buf, '&')!;

		assert(len(pair.0) > 0);
		percent_encode(&buf, pair.0)!;
		if (len(pair.1) > 0) {
			strio::appendrune(&buf, '=')!;
			percent_encode(&buf, pair.1)!;
		};
	};

	return strio::string(&buf);
};

@test fn decodequery() void = {
	const u = parse("https://sr.ht/projects?search=%23risc-v&sort=longest-active")!;
	defer finish(&u);

	const query = decodequery(u.query);
	defer query_finish(&query);
	const pair = query_next(&query)! as (str, str);
	assert(pair.0 == "search");
	assert(pair.1 == "#risc-v");

	const pair = query_next(&query)! as (str, str);
	assert(pair.0 == "sort");
	assert(pair.1 == "longest-active");
};

@test fn encodequery() void = {
	const pairs = [
		("search", "#risc-v"),
		("sort", "longest-active"),
	];
	const encoded = encodequery(pairs);
	defer free(encoded);

	assert(encoded == "search=%23risc-v&sort=longest-active");
};