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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
|
/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
It is considered beerware. Prost. Skol. Cheers or whatever.
$id$ */
/* System */
#include <string.h>
/* Opentracker */
#include "scan_urlencoded_query.h"
#include "test.h"
/* The scanner returns the number of bytes written to the destination, or
-1 for a parse error and -2 for a hard terminator encountered before any
output was produced. It advances the source pointer past the separator it
stopped on, except for hard terminators and the '&' ending a parameter,
which it leaves for the next call to see. */
static void case_path(void) {
char query[] = "/announce?info_hash=A";
char dest[64];
char *p = query;
ssize_t n;
test_case("path stops at the question mark");
n = scan_urlencoded_query(&p, dest, SCAN_PATH);
CHECK_INT(n, 9);
dest[n] = 0;
CHECK_STR(dest, "/announce");
CHECK_INT(*p, 'i');
}
static void case_param_and_value(void) {
char query[] = "info_hash=abc&port=80";
char dest[64];
char *p = query;
ssize_t n;
test_case("parameter stops at '=', value stops at '&'");
n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_PARAM);
CHECK_INT(n, 9);
dest[n] = 0;
CHECK_STR(dest, "info_hash");
n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE);
CHECK_INT(n, 3);
dest[n] = 0;
CHECK_STR(dest, "abc");
n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_PARAM);
CHECK_INT(n, 4);
dest[n] = 0;
CHECK_STR(dest, "port");
n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE);
CHECK_INT(n, 2);
dest[n] = 0;
CHECK_STR(dest, "80");
}
static void case_percent_decoding(void) {
char query[] = "%41%42%43&x=1";
char dest[64];
char *p = query;
ssize_t n;
test_case("percent escapes decode");
n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE);
CHECK_INT(n, 3);
CHECK_MEM(dest, "ABC", 3);
}
static void case_binary_info_hash(void) {
/* The twenty byte info_hash is what the announce path really decodes */
char query[] = "%00%01%02%ff%fe%7f%80ABCDEFGHIJKLM&x=1";
unsigned char want[] = {0x00, 0x01, 0x02, 0xff, 0xfe, 0x7f, 0x80, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M'};
char dest[64];
char *p = query;
ssize_t n;
test_case("twenty byte binary value survives decoding");
n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE);
CHECK_INT(n, 20);
CHECK_MEM(dest, want, 20);
}
static void case_in_place(void) {
char query[] = "%41%42&x=1";
char *p = query;
ssize_t n;
test_case("decoding in place over the source is safe");
n = scan_urlencoded_query(&p, query, SCAN_SEARCHPATH_VALUE);
CHECK_INT(n, 2);
CHECK_MEM(query, "AB", 2);
}
static void case_bad_escape(void) {
char query[] = "%zz";
char dest[64];
char *p = query;
ssize_t n;
test_case("a non hexadecimal escape is a parse error");
n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE);
CHECK_INT(n, -1);
}
static void case_truncated_escape(void) {
char query[] = "%4";
char dest[64];
char *p = query;
ssize_t n;
test_case("an escape cut short by the terminator is a parse error");
n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE);
CHECK_INT(n, -1);
}
static void case_terminator(void) {
char empty[] = "";
char space[] = " rest";
char dest[64];
char *p = empty;
ssize_t n;
test_case("a hard terminator before any output reports end of input");
n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_PARAM);
CHECK_INT(n, -2);
p = space;
n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_PARAM);
CHECK_INT(n, -2);
}
static void case_terminator_after_output(void) {
char query[] = "abc";
char dest[64];
char *p = query;
ssize_t n;
test_case("a hard terminator after output is left for the next call");
n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE);
CHECK_INT(n, 3);
CHECK_INT(*p, 0);
n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE);
CHECK_INT(n, -2);
}
/* The character matrix makes '?' terminate a path only, and '=' terminate a
path or a parameter only. Both are ordinary characters inside a value, so a
value is free to contain a nested query string. */
static void case_separators_are_contextual(void) {
char question[] = "a?b&x=1";
char equals[] = "a=b&x=1";
char dest[64];
char *p = question;
ssize_t n;
test_case("a question mark is literal inside a value");
n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE);
CHECK_INT(n, 3);
dest[n] = 0;
CHECK_STR(dest, "a?b");
test_case("an equals sign is literal inside a value");
p = equals;
n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE);
CHECK_INT(n, 3);
dest[n] = 0;
CHECK_STR(dest, "a=b");
}
static void case_ampersand_in_path(void) {
char query[] = "/path&x";
char dest[64];
char *p = query;
ssize_t n;
test_case("an ampersand in a path is a parse error");
n = scan_urlencoded_query(&p, dest, SCAN_PATH);
CHECK_INT(n, -1);
}
static void case_raw_high_byte(void) {
char query[] = {'a', 'b', (char)0x80, 0};
char dest[64];
char *p = query;
ssize_t n;
test_case("an undecoded byte above ASCII is a parse error");
n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE);
CHECK_INT(n, -1);
}
static void case_skipvalue(void) {
char query[] = "abc&next=1";
char bare[] = "abc";
char *p = query;
test_case("skipvalue steps over the value and its separator");
scan_urlencoded_skipvalue(&p);
CHECK_STR(p, "next=1");
test_case("skipvalue leaves a hard terminator in place");
p = bare;
scan_urlencoded_skipvalue(&p);
CHECK_INT(*p, 0);
}
static void case_find_keywords(void) {
static const ot_keywords keywords[] = {{"port", 1}, {"left", 2}, {"compact", 3}, {NULL, -3}};
char match[] = "left=0";
char nomatch[] = "bogus=1";
char prefix[] = "por=1";
char *p = match;
test_case("a known keyword returns its value");
CHECK_INT(scan_find_keywords(keywords, &p, SCAN_SEARCHPATH_PARAM), 2);
test_case("an unknown keyword returns -3");
p = nomatch;
CHECK_INT(scan_find_keywords(keywords, &p, SCAN_SEARCHPATH_PARAM), -3);
test_case("a prefix of a keyword does not match it");
p = prefix;
CHECK_INT(scan_find_keywords(keywords, &p, SCAN_SEARCHPATH_PARAM), -3);
}
static void case_fixed_int(void) {
char positive[] = "123";
char negative[] = "-45";
char trailing[] = "12a";
int value;
test_case("scan_fixed_int returns the count of unparsed bytes");
CHECK_INT(scan_fixed_int(positive, 3, &value), 0);
CHECK_INT(value, 123);
CHECK_INT(scan_fixed_int(negative, 3, &value), 0);
CHECK_INT(value, -45);
CHECK_INT(scan_fixed_int(trailing, 3, &value), 1);
CHECK_INT(value, 12);
}
static void case_fixed_int_bounded(void) {
char digits[] = "12345";
int value;
test_case("scan_fixed_int honours the length it is given");
CHECK_INT(scan_fixed_int(digits, 3, &value), 0);
CHECK_INT(value, 123);
}
void test_scan_urlencoded_query(void) {
case_path();
case_param_and_value();
case_percent_decoding();
case_binary_info_hash();
case_in_place();
case_bad_escape();
case_truncated_escape();
case_terminator();
case_terminator_after_output();
case_separators_are_contextual();
case_ampersand_in_path();
case_raw_high_byte();
case_skipvalue();
case_find_keywords();
case_fixed_int();
case_fixed_int_bounded();
}
|