1 /***********************************************************************************************************************************
2 Test HTTP
3 ***********************************************************************************************************************************/
4 #include <unistd.h>
5
6 #include "common/io/fdRead.h"
7 #include "common/io/fdWrite.h"
8 #include "common/io/tls/client.h"
9 #include "common/io/socket/client.h"
10
11 #include "common/harnessFork.h"
12 #include "common/harnessServer.h"
13
14 /***********************************************************************************************************************************
15 HTTP user agent header
16 ***********************************************************************************************************************************/
17 #define TEST_USER_AGENT \
18 HTTP_HEADER_USER_AGENT ":" PROJECT_NAME "/" PROJECT_VERSION "\r\n"
19
20 /***********************************************************************************************************************************
21 Test Run
22 ***********************************************************************************************************************************/
23 void
testRun(void)24 testRun(void)
25 {
26 FUNCTION_HARNESS_VOID();
27
28 // *****************************************************************************************************************************
29 if (testBegin("httpUriEncode() and httpUriDecode()"))
30 {
31 TEST_RESULT_STR(httpUriEncode(NULL, false), NULL, "null encodes to null");
32 TEST_RESULT_STR_Z(httpUriEncode(STRDEF("0-9_~/A Z.az"), false), "0-9_~%2FA%20Z.az", "non-path encoding");
33 TEST_RESULT_STR_Z(httpUriEncode(STRDEF("0-9_~/A Z.az"), true), "0-9_~/A%20Z.az", "path encoding");
34
35 // -------------------------------------------------------------------------------------------------------------------------
36 TEST_TITLE("decode");
37
38 TEST_RESULT_STR(httpUriDecode(NULL), NULL, "null decodes to null");
39 TEST_RESULT_STR_Z(httpUriDecode(STRDEF("0-9_~%2FA%20Z.az")), "0-9_~/A Z.az", "valid decode");
40 TEST_ERROR(httpUriDecode(STRDEF("%A")), FormatError, "invalid escape sequence length in '%A'");
41 TEST_ERROR(httpUriDecode(STRDEF("%XX")), FormatError, "unable to convert base 16 string 'XX' to unsigned int");
42 }
43
44 // *****************************************************************************************************************************
45 if (testBegin("httpDateToTime() and httpDateFromTime()"))
46 {
47 TEST_ERROR(httpDateToTime(STRDEF("Wed, 21 Bog 2015 07:28:00 GMT")), FormatError, "invalid month 'Bog'");
48 TEST_ERROR(
49 httpDateToTime(STRDEF("Wed, 1 Oct 2015 07:28:00 GMT")), FormatError, "unable to convert base 10 string ' 1' to int");
50 TEST_RESULT_INT(httpDateToTime(STRDEF("Wed, 21 Oct 2015 07:28:00 GMT")), 1445412480, "convert HTTP date to time_t");
51
52 TEST_RESULT_STR_Z(httpDateFromTime(1592743579), "Sun, 21 Jun 2020 12:46:19 GMT", "convert time_t to HTTP date");
53 }
54
55 // *****************************************************************************************************************************
56 if (testBegin("HttpHeader"))
57 {
58 HttpHeader *header = NULL;
59
60 MEM_CONTEXT_TEMP_BEGIN()
61 {
62 TEST_ASSIGN(header, httpHeaderNew(NULL), "new header");
63
64 TEST_RESULT_PTR(httpHeaderMove(header, memContextPrior()), header, "move to new context");
65 TEST_RESULT_PTR(httpHeaderMove(NULL, memContextPrior()), NULL, "move null to new context");
66 }
67 MEM_CONTEXT_TEMP_END();
68
69 TEST_RESULT_PTR(httpHeaderAdd(header, STRDEF("key2"), STRDEF("value2")), header, "add header");
70 TEST_RESULT_PTR(httpHeaderPut(header, STRDEF("key2"), STRDEF("value2a")), header, "put header");
71 TEST_RESULT_PTR(httpHeaderAdd(header, STRDEF("key2"), STRDEF("value2b")), header, "add header");
72
73 TEST_RESULT_PTR(httpHeaderAdd(header, STRDEF("key1"), STRDEF("value1")), header, "add header");
74 TEST_RESULT_STRLST_Z(httpHeaderList(header), "key1\nkey2\n", "header list");
75
76 TEST_RESULT_STR_Z(httpHeaderGet(header, STRDEF("key1")), "value1", "get value");
77 TEST_RESULT_STR_Z(httpHeaderGet(header, STRDEF("key2")), "value2a, value2b", "get value");
78 TEST_RESULT_STR(httpHeaderGet(header, STRDEF(BOGUS_STR)), NULL, "get missing value");
79
80 TEST_RESULT_STR_Z(httpHeaderToLog(header), "{key1: 'value1', key2: 'value2a, value2b'}", "log output");
81
82 TEST_RESULT_VOID(httpHeaderFree(header), "free header");
83
84 // Redacted headers
85 // -------------------------------------------------------------------------------------------------------------------------
86 StringList *redact = strLstNew();
87 strLstAddZ(redact, "secret");
88
89 TEST_ASSIGN(header, httpHeaderNew(redact), "new header with redaction");
90 httpHeaderAdd(header, STRDEF("secret"), STRDEF("secret-value"));
91 httpHeaderAdd(header, STRDEF("public"), STRDEF("public-value"));
92
93 TEST_RESULT_STR_Z(httpHeaderToLog(header), "{public: 'public-value', secret: <redacted>}", "log output");
94
95 // Duplicate
96 // -------------------------------------------------------------------------------------------------------------------------
97 redact = strLstNew();
98 strLstAddZ(redact, "public");
99
100 TEST_RESULT_STR_Z(
101 httpHeaderToLog(httpHeaderDup(header, NULL)), "{public: 'public-value', secret: <redacted>}",
102 "dup and keep redactions");
103 TEST_RESULT_STR_Z(
104 httpHeaderToLog(httpHeaderDup(header, redact)), "{public: <redacted>, secret: 'secret-value'}",
105 "dup and change redactions");
106 TEST_RESULT_PTR(httpHeaderDup(NULL, NULL), NULL, "dup null header");
107 }
108
109 // *****************************************************************************************************************************
110 if (testBegin("HttpQuery"))
111 {
112 HttpQuery *query = NULL;
113
114 MEM_CONTEXT_TEMP_BEGIN()
115 {
116 StringList *redactList = strLstNew();
117 strLstAdd(redactList, STRDEF("key2"));
118
119 TEST_ASSIGN(query, httpQueryNewP(.redactList = redactList), "new query");
120
121 TEST_RESULT_PTR(httpQueryMove(query, memContextPrior()), query, "move to new context");
122 TEST_RESULT_PTR(httpQueryMove(NULL, memContextPrior()), NULL, "move null to new context");
123 }
124 MEM_CONTEXT_TEMP_END();
125
126 TEST_RESULT_STR(httpQueryRenderP(NULL), NULL, "null query renders null");
127 TEST_RESULT_STR(httpQueryRenderP(query), NULL, "empty query renders null");
128
129 TEST_RESULT_PTR(httpQueryAdd(query, STRDEF("key2"), STRDEF("value2")), query, "add query");
130 TEST_ERROR(httpQueryAdd(query, STRDEF("key2"), STRDEF("value2")), AssertError, "key 'key2' already exists");
131 TEST_RESULT_PTR(httpQueryPut(query, STRDEF("key2"), STRDEF("value2a")), query, "put query");
132 TEST_RESULT_STR_Z(httpQueryRenderP(query), "key2=value2a", "render one query item");
133
134 TEST_RESULT_PTR(httpQueryAdd(query, STRDEF("key1"), STRDEF("value 1?")), query, "add query");
135 TEST_RESULT_STRLST_Z(httpQueryList(query), "key1\nkey2\n", "query list");
136 TEST_RESULT_STR_Z(httpQueryRenderP(query), "key1=value%201%3F&key2=value2a", "render two query items");
137 TEST_RESULT_STR_Z(
138 httpQueryRenderP(query, .redact = true), "key1=value%201%3F&key2=<redacted>", "render two query items with redaction");
139
140 TEST_RESULT_STR_Z(httpQueryGet(query, STRDEF("key1")), "value 1?", "get value");
141 TEST_RESULT_STR_Z(httpQueryGet(query, STRDEF("key2")), "value2a", "get value");
142 TEST_RESULT_STR(httpQueryGet(query, STRDEF(BOGUS_STR)), NULL, "get missing value");
143
144 TEST_RESULT_STR_Z(httpQueryToLog(query), "{key1: 'value 1?', key2: <redacted>}", "log output");
145
146 // -------------------------------------------------------------------------------------------------------------------------
147 TEST_TITLE("dup query with redaction");
148
149 StringList *redactList = strLstNew();
150 strLstAdd(redactList, STRDEF("key1"));
151
152 TEST_ASSIGN(query, httpQueryDupP(query, .redactList = redactList), "dup query");
153 TEST_RESULT_STR_Z(httpQueryToLog(query), "{key1: <redacted>, key2: 'value2a'}", "log output");
154
155 // -------------------------------------------------------------------------------------------------------------------------
156 TEST_TITLE("new query from string");
157
158 TEST_ERROR(httpQueryNewStr(STRDEF("a=b&c")), FormatError, "invalid key/value 'c' in query 'a=b&c'");
159
160 HttpQuery *query2 = NULL;
161 TEST_ASSIGN(query2, httpQueryNewStr(STRDEF("?a=%2Bb&c=d%3D")), "query from string");
162 TEST_RESULT_STR_Z(httpQueryRenderP(query2), "a=%2Bb&c=d%3D", "render query");
163
164 // -------------------------------------------------------------------------------------------------------------------------
165 TEST_TITLE("merge queries");
166
167 TEST_RESULT_STR_Z(
168 httpQueryRenderP(httpQueryMerge(query, query2)), "a=%2Bb&c=d%3D&key1=value%201%3F&key2=value2a", "render merge");
169
170 // -------------------------------------------------------------------------------------------------------------------------
171 TEST_TITLE("free query");
172
173 TEST_RESULT_VOID(httpQueryFree(query), "free");
174 }
175
176 // *****************************************************************************************************************************
177 if (testBegin("HttpUrl"))
178 {
179 HttpUrl *url = NULL;
180
181 // -------------------------------------------------------------------------------------------------------------------------
182 TEST_TITLE("invalid url");
183
184 TEST_ERROR(httpUrlNewParseP(STRDEF("ftp://" BOGUS_STR)), FormatError, "invalid URL 'ftp://BOGUS'");
185
186 // -------------------------------------------------------------------------------------------------------------------------
187 TEST_TITLE("HttpProtocolTypeStr");
188
189 TEST_RESULT_STR_Z(httpProtocolTypeStr(httpProtocolTypeHttp), "http", "check http");
190 TEST_RESULT_STR_Z(httpProtocolTypeStr(httpProtocolTypeAny), NULL, "check any");
191
192 // -------------------------------------------------------------------------------------------------------------------------
193 TEST_TITLE("simple http");
194
195 TEST_ASSIGN(url, httpUrlNewParseP(STRDEF("http://test"), .type = httpProtocolTypeHttp), "new");
196 TEST_RESULT_STR_Z(httpUrl(url), "http://test", "check url");
197 TEST_RESULT_STR_Z(httpUrlHost(url), "test", "check host");
198 TEST_RESULT_STR_Z(httpUrlPath(url), "/", "check path");
199 TEST_RESULT_UINT(httpUrlPort(url), 80, "check port");
200 TEST_RESULT_UINT(httpUrlProtocolType(url), httpProtocolTypeHttp, "check protocol");
201 TEST_RESULT_STR_Z(httpUrlToLog(url), "{http://test:80/}", "check log");
202
203 // -------------------------------------------------------------------------------------------------------------------------
204 TEST_TITLE("host and port");
205
206 TEST_ASSIGN(url, httpUrlNewParseP(STRDEF("gcs:4443"), .type = httpProtocolTypeHttps), "new");
207 TEST_RESULT_STR_Z(httpUrl(url), "gcs:4443", "check url");
208 TEST_RESULT_STR_Z(httpUrlHost(url), "gcs", "check host");
209 TEST_RESULT_STR_Z(httpUrlPath(url), "/", "check path");
210 TEST_RESULT_UINT(httpUrlPort(url), 4443, "check port");
211 TEST_RESULT_UINT(httpUrlProtocolType(url), httpProtocolTypeHttps, "check protocol");
212 TEST_RESULT_STR_Z(httpUrlToLog(url), "{https://gcs:4443/}", "check log");
213
214 // -------------------------------------------------------------------------------------------------------------------------
215 TEST_TITLE("http but expected https");
216
217 TEST_ERROR(
218 httpUrlNewParseP(STRDEF("http://test"), .type = httpProtocolTypeHttps), FormatError,
219 "expected protocol 'https' in URL 'http://test'");
220
221 // -------------------------------------------------------------------------------------------------------------------------
222 TEST_TITLE("https with port and path");
223
224 TEST_ASSIGN(url, httpUrlNewParseP(STRDEF("https://test.com:445/path")), "new");
225 TEST_RESULT_STR_Z(httpUrl(url), "https://test.com:445/path", "check url");
226 TEST_RESULT_STR_Z(httpUrlHost(url), "test.com", "check host");
227 TEST_RESULT_STR_Z(httpUrlPath(url), "/path", "check path");
228 TEST_RESULT_UINT(httpUrlPort(url), 445, "check port");
229 TEST_RESULT_UINT(httpUrlProtocolType(url), httpProtocolTypeHttps, "check protocol");
230 TEST_RESULT_STR_Z(httpUrlToLog(url), "{https://test.com:445/path}", "check log");
231
232 // -------------------------------------------------------------------------------------------------------------------------
233 TEST_TITLE("host only");
234
235 TEST_ASSIGN(url, httpUrlNewParseP(STRDEF("test.com"), .type = httpProtocolTypeHttps), "new");
236 TEST_RESULT_STR_Z(httpUrl(url), "test.com", "check url");
237 TEST_RESULT_STR_Z(httpUrlHost(url), "test.com", "check host");
238 TEST_RESULT_STR_Z(httpUrlPath(url), "/", "check path");
239 TEST_RESULT_UINT(httpUrlPort(url), 443, "check port");
240 TEST_RESULT_UINT(httpUrlProtocolType(url), httpProtocolTypeHttps, "check protocol");
241 TEST_RESULT_STR_Z(httpUrlToLog(url), "{https://test.com:443/}", "check log");
242
243 // -------------------------------------------------------------------------------------------------------------------------
244 TEST_TITLE("IPv6");
245
246 TEST_ASSIGN(url, httpUrlNewParseP(STRDEF("http://[2001:db8::ff00:42:8329]:81"), .type = httpProtocolTypeHttp), "new");
247 TEST_RESULT_STR_Z(httpUrl(url), "http://[2001:db8::ff00:42:8329]:81", "check url");
248 TEST_RESULT_STR_Z(httpUrlHost(url), "2001:db8::ff00:42:8329", "check host");
249 TEST_RESULT_STR_Z(httpUrlPath(url), "/", "check path");
250 TEST_RESULT_UINT(httpUrlPort(url), 81, "check port");
251 TEST_RESULT_UINT(httpUrlProtocolType(url), httpProtocolTypeHttp, "check protocol");
252 TEST_RESULT_STR_Z(httpUrlToLog(url), "{http://[2001:db8::ff00:42:8329]:81/}", "check log");
253
254 // -------------------------------------------------------------------------------------------------------------------------
255 TEST_TITLE("IPv6 no port");
256
257 TEST_ASSIGN(url, httpUrlNewParseP(STRDEF("http://[2001:db8::ff00:42:8329]/url"), .type = httpProtocolTypeHttp), "new");
258 TEST_RESULT_STR_Z(httpUrl(url), "http://[2001:db8::ff00:42:8329]/url", "check url");
259 TEST_RESULT_STR_Z(httpUrlHost(url), "2001:db8::ff00:42:8329", "check host");
260 TEST_RESULT_STR_Z(httpUrlPath(url), "/url", "check path");
261 TEST_RESULT_UINT(httpUrlPort(url), 80, "check port");
262 TEST_RESULT_UINT(httpUrlProtocolType(url), httpProtocolTypeHttp, "check protocol");
263 TEST_RESULT_STR_Z(httpUrlToLog(url), "{http://[2001:db8::ff00:42:8329]:80/url}", "check log");
264
265 // -------------------------------------------------------------------------------------------------------------------------
266 TEST_TITLE("free");
267
268 TEST_RESULT_VOID(httpUrlFree(url), "free");
269 }
270
271 // *****************************************************************************************************************************
272 if (testBegin("HttpClient"))
273 {
274 HttpClient *client = NULL;
275
276 TEST_ASSIGN(client, httpClientNew(sckClientNew(STRDEF("localhost"), hrnServerPort(0), 500), 500), "new client");
277
278 TEST_ERROR_FMT(
279 httpRequestResponse(httpRequestNewP(client, STRDEF("GET"), STRDEF("/")), false), HostConnectError,
280 "unable to connect to 'localhost:%u': [111] Connection refused", hrnServerPort(0));
281
282 HRN_FORK_BEGIN()
283 {
284 HRN_FORK_CHILD_BEGIN(.prefix = "test server", .timeout = 5000)
285 {
286 // Start HTTP test server
287 TEST_RESULT_VOID(hrnServerRunP(HRN_FORK_CHILD_READ(), hrnServerProtocolSocket), "http server run");
288 }
289 HRN_FORK_CHILD_END();
290
291 HRN_FORK_PARENT_BEGIN()
292 {
293 IoWrite *http = hrnServerScriptBegin(HRN_FORK_PARENT_WRITE(0));
294
295 // -----------------------------------------------------------------------------------------------------------------
296 TEST_TITLE("create client");
297
298 ioBufferSizeSet(35);
299
300 TEST_ASSIGN(client, httpClientNew(sckClientNew(hrnServerHost(), hrnServerPort(0), 5000), 5000), "new client");
301
302 // -----------------------------------------------------------------------------------------------------------------
303 TEST_TITLE("no output from server");
304
305 client->pub.timeout = 0;
306
307 hrnServerScriptAccept(http);
308
309 hrnServerScriptExpectZ(http, "GET / HTTP/1.1\r\n" TEST_USER_AGENT "\r\n");
310 hrnServerScriptSleep(http, 600);
311
312 hrnServerScriptClose(http);
313
314 TEST_ERROR(
315 httpRequestResponse(httpRequestNewP(client, STRDEF("GET"), STRDEF("/")), false), FileReadError,
316 "unexpected eof while reading line");
317
318 // -----------------------------------------------------------------------------------------------------------------
319 TEST_TITLE("no CR at end of status");
320
321 hrnServerScriptAccept(http);
322
323 hrnServerScriptExpectZ(http, "GET / HTTP/1.1\r\n" TEST_USER_AGENT "\r\n");
324 hrnServerScriptReplyZ(http, "HTTP/1.0 200 OK\n");
325
326 hrnServerScriptClose(http);
327
328 TEST_ERROR(
329 httpRequestResponse(httpRequestNewP(client, STRDEF("GET"), STRDEF("/")), false), FormatError,
330 "HTTP response status 'HTTP/1.0 200 OK' should be CR-terminated");
331
332 // -----------------------------------------------------------------------------------------------------------------
333 TEST_TITLE("status too short");
334
335 hrnServerScriptAccept(http);
336
337 hrnServerScriptExpectZ(http, "GET / HTTP/1.1\r\n" TEST_USER_AGENT "\r\n");
338 hrnServerScriptReplyZ(http, "HTTP/1.0 200\r\n");
339
340 hrnServerScriptClose(http);
341
342 TEST_ERROR(
343 httpRequestResponse(httpRequestNewP(client, STRDEF("GET"), STRDEF("/")), false), FormatError,
344 "HTTP response 'HTTP/1.0 200' has invalid length");
345
346 // -----------------------------------------------------------------------------------------------------------------
347 TEST_TITLE("invalid HTTP version");
348
349 hrnServerScriptAccept(http);
350
351 hrnServerScriptExpectZ(http, "GET / HTTP/1.1\r\n" TEST_USER_AGENT "\r\n");
352 hrnServerScriptReplyZ(http, "HTTP/1 200 OK\r\n");
353
354 hrnServerScriptClose(http);
355
356 TEST_ERROR(
357 httpRequestResponse(httpRequestNewP(client, STRDEF("GET"), STRDEF("/")), false), FormatError,
358 "HTTP version of response 'HTTP/1 200 OK' must be HTTP/1.1 or HTTP/1.0");
359
360 // -----------------------------------------------------------------------------------------------------------------
361 TEST_TITLE("no space in status");
362
363 hrnServerScriptAccept(http);
364
365 hrnServerScriptExpectZ(http, "GET / HTTP/1.1\r\n" TEST_USER_AGENT "\r\n");
366 hrnServerScriptReplyZ(http, "HTTP/1.1 200OK\r\n");
367
368 hrnServerScriptClose(http);
369
370 TEST_ERROR(
371 httpRequestResponse(httpRequestNewP(client, STRDEF("GET"), STRDEF("/")), false), FormatError,
372 "response status '200OK' must have a space after the status code");
373
374 // -----------------------------------------------------------------------------------------------------------------
375 TEST_TITLE("unexpected end of headers");
376
377 hrnServerScriptAccept(http);
378
379 hrnServerScriptExpectZ(http, "GET / HTTP/1.1\r\n" TEST_USER_AGENT "\r\n");
380 hrnServerScriptReplyZ(http, "HTTP/1.1 200 OK\r\n");
381
382 hrnServerScriptClose(http);
383
384 TEST_ERROR(
385 httpRequestResponse(httpRequestNewP(client, STRDEF("GET"), STRDEF("/")), false), FileReadError,
386 "unexpected eof while reading line");
387
388 // -----------------------------------------------------------------------------------------------------------------
389 TEST_TITLE("missing colon in header");
390
391 hrnServerScriptAccept(http);
392
393 hrnServerScriptExpectZ(http, "GET / HTTP/1.1\r\n" TEST_USER_AGENT "\r\n");
394 hrnServerScriptReplyZ(http, "HTTP/1.1 200 OK\r\nheader-value\r\n");
395
396 hrnServerScriptClose(http);
397
398 TEST_ERROR(
399 httpRequestResponse(httpRequestNewP(client, STRDEF("GET"), STRDEF("/")), false), FormatError,
400 "header 'header-value' missing colon");
401
402 // -----------------------------------------------------------------------------------------------------------------
403 TEST_TITLE("invalid transfer encoding");
404
405 hrnServerScriptAccept(http);
406
407 hrnServerScriptExpectZ(http, "GET / HTTP/1.1\r\n" TEST_USER_AGENT "\r\n");
408 hrnServerScriptReplyZ(http, "HTTP/1.1 200 OK\r\ntransfer-encoding:bogus\r\n");
409
410 hrnServerScriptClose(http);
411
412 TEST_ERROR(
413 httpRequestResponse(httpRequestNewP(client, STRDEF("GET"), STRDEF("/")), false), FormatError,
414 "only 'chunked' is supported for 'transfer-encoding' header");
415
416 // -----------------------------------------------------------------------------------------------------------------
417 TEST_TITLE("content length and transfer encoding both set");
418
419 hrnServerScriptAccept(http);
420
421 hrnServerScriptExpectZ(http, "GET / HTTP/1.1\r\n" TEST_USER_AGENT "\r\n");
422 hrnServerScriptReplyZ(http, "HTTP/1.1 200 OK\r\ntransfer-encoding:chunked\r\ncontent-length:777\r\n\r\n");
423
424 hrnServerScriptClose(http);
425
426 TEST_ERROR(
427 httpRequestResponse(httpRequestNewP(client, STRDEF("GET"), STRDEF("/")), false), FormatError,
428 "'transfer-encoding' and 'content-length' headers are both set");
429
430 // -----------------------------------------------------------------------------------------------------------------
431 TEST_TITLE("5xx error with no retry");
432
433 hrnServerScriptAccept(http);
434
435 hrnServerScriptExpectZ(http, "GET / HTTP/1.1\r\n" TEST_USER_AGENT "\r\n");
436 hrnServerScriptReplyZ(http, "HTTP/1.1 503 Slow Down\r\n\r\n");
437
438 hrnServerScriptClose(http);
439
440 TEST_ERROR(
441 httpRequestResponse(httpRequestNewP(client, STRDEF("GET"), STRDEF("/")), false), ServiceError,
442 "[503] Slow Down");
443
444 // -----------------------------------------------------------------------------------------------------------------
445 TEST_TITLE("request with no content (with an internal error)");
446
447 hrnServerScriptAccept(http);
448
449 hrnServerScriptExpectZ(http,
450 "GET /?name=%2Fpath%2FA%20Z.txt&type=test HTTP/1.1\r\n" TEST_USER_AGENT "host:myhost.com\r\n\r\n");
451 hrnServerScriptReplyZ(http, "HTTP/1.1 500 Internal Error\r\nConnection:close\r\n\r\n");
452
453 hrnServerScriptClose(http);
454 hrnServerScriptAccept(http);
455
456 hrnServerScriptExpectZ(
457 http, "GET /?name=%2Fpath%2FA%20Z.txt&type=test HTTP/1.1\r\n" TEST_USER_AGENT "host:myhost.com\r\n\r\n");
458 hrnServerScriptReplyZ(
459 http, "HTTP/1.1 200 OK\r\nkey1:0\r\n key2 : value2\r\nConnection:ack\r\ncontent-length:0\r\n\r\n");
460
461 HttpHeader *headerRequest = httpHeaderNew(NULL);
462 httpHeaderAdd(headerRequest, STRDEF("host"), STRDEF("myhost.com"));
463
464 HttpQuery *query = httpQueryNewP();
465 httpQueryAdd(query, STRDEF("name"), STRDEF("/path/A Z.txt"));
466 httpQueryAdd(query, STRDEF("type"), STRDEF("test"));
467
468 client->pub.timeout = 5000;
469
470 HttpRequest *request = NULL;
471 HttpResponse *response = NULL;
472
473 MEM_CONTEXT_TEMP_BEGIN()
474 {
475 TEST_ASSIGN(
476 request, httpRequestNewP(client, STRDEF("GET"), STRDEF("/"), .query = query, .header = headerRequest),
477 "request");
478 TEST_ASSIGN(response, httpRequestResponse(request, false), "request");
479
480 TEST_RESULT_VOID(httpRequestMove(request, memContextPrior()), "move request");
481 TEST_RESULT_VOID(httpResponseMove(response, memContextPrior()), "move response");
482 }
483 MEM_CONTEXT_TEMP_END();
484
485 TEST_RESULT_STR_Z(httpRequestVerb(request), "GET", "check request verb");
486 TEST_RESULT_STR_Z(httpRequestPath(request), "/", "check request path");
487 TEST_RESULT_STR_Z(
488 httpQueryRenderP(httpRequestQuery(request)), "name=%2Fpath%2FA%20Z.txt&type=test", "check request query");
489 TEST_RESULT_PTR_NE(httpRequestHeader(request), NULL, "check request headers");
490
491 TEST_RESULT_UINT(httpResponseCode(response), 200, "check response code");
492 TEST_RESULT_BOOL(httpResponseCodeOk(response), true, "check response code ok");
493 TEST_RESULT_STR_Z(httpResponseReason(response), "OK", "check response message");
494 TEST_RESULT_UINT(httpResponseEof(response), true, "io is eof");
495 TEST_RESULT_STR_Z(
496 httpHeaderToLog(httpResponseHeader(response)),
497 "{connection: 'ack', content-length: '0', key1: '0', key2: 'value2'}", "check response headers");
498 TEST_RESULT_UINT(bufSize(httpResponseContent(response)), 0, "content is empty");
499
500 TEST_RESULT_VOID(httpResponseFree(response), "free response");
501 TEST_RESULT_VOID(httpRequestFree(request), "free request");
502
503 // -----------------------------------------------------------------------------------------------------------------
504 TEST_TITLE("head request with content-length but no content");
505
506 hrnServerScriptExpectZ(http, "HEAD / HTTP/1.1\r\n" TEST_USER_AGENT "\r\n");
507 hrnServerScriptReplyZ(http, "HTTP/1.0 200 OK\r\ncontent-length:380\r\n\r\n");
508
509 hrnServerScriptClose(http);
510
511 TEST_ASSIGN(response, httpRequestResponse(httpRequestNewP(client, STRDEF("HEAD"), STRDEF("/")), true), "request");
512 TEST_RESULT_UINT(httpResponseCode(response), 200, "check response code");
513 TEST_RESULT_STR_Z(httpResponseReason(response), "OK", "check response message");
514 TEST_RESULT_BOOL(httpResponseEof(response), true, "io is eof");
515 TEST_RESULT_PTR(response->session, NULL, "session is not busy");
516 TEST_RESULT_STR_Z(
517 httpHeaderToLog(httpResponseHeader(response)), "{content-length: '380'}", "check response headers");
518
519 // -----------------------------------------------------------------------------------------------------------------
520 TEST_TITLE("head request with transfer encoding but no content");
521
522 hrnServerScriptAccept(http);
523
524 hrnServerScriptExpectZ(http, "HEAD / HTTP/1.1\r\n" TEST_USER_AGENT "\r\n");
525 hrnServerScriptReplyZ(http, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");
526
527 TEST_ASSIGN(response, httpRequestResponse(httpRequestNewP(client, STRDEF("HEAD"), STRDEF("/")), true), "request");
528 TEST_RESULT_UINT(httpResponseCode(response), 200, "check response code");
529 TEST_RESULT_STR_Z(httpResponseReason(response), "OK", "check response message");
530 TEST_RESULT_BOOL(httpResponseEof(response), true, "io is eof");
531 TEST_RESULT_PTR(response->session, NULL, "session is not busy");
532 TEST_RESULT_STR_Z(
533 httpHeaderToLog(httpResponseHeader(response)), "{transfer-encoding: 'chunked'}", "check response headers");
534
535 // -----------------------------------------------------------------------------------------------------------------
536 TEST_TITLE("head request with connection close but no content");
537
538 hrnServerScriptExpectZ(http, "HEAD / HTTP/1.1\r\n" TEST_USER_AGENT "\r\n");
539 hrnServerScriptReplyZ(http, "HTTP/1.1 200 OK\r\nConnection:close\r\n\r\n");
540
541 hrnServerScriptClose(http);
542
543 TEST_ASSIGN(response, httpRequestResponse(httpRequestNewP(client, STRDEF("HEAD"), STRDEF("/")), true), "request");
544 TEST_RESULT_UINT(httpResponseCode(response), 200, "check response code");
545 TEST_RESULT_STR_Z(httpResponseReason(response), "OK", "check response message");
546 TEST_RESULT_BOOL(httpResponseEof(response), true, "io is eof");
547 TEST_RESULT_PTR(response->session, NULL, "session is not busy");
548 TEST_RESULT_STR_Z(
549 httpHeaderToLog(httpResponseHeader(response)), "{connection: 'close'}", "check response headers");
550
551 // -----------------------------------------------------------------------------------------------------------------
552 TEST_TITLE("error with content (with a few slow down errors)");
553
554 hrnServerScriptAccept(http);
555
556 hrnServerScriptExpectZ(http, "GET / HTTP/1.1\r\n" TEST_USER_AGENT "\r\n");
557 hrnServerScriptReplyZ(http, "HTTP/1.1 503 Slow Down\r\ncontent-length:3\r\nConnection:close\r\n\r\n123");
558
559 hrnServerScriptClose(http);
560 hrnServerScriptAccept(http);
561
562 hrnServerScriptExpectZ(http, "GET / HTTP/1.1\r\n" TEST_USER_AGENT "\r\n");
563 hrnServerScriptReplyZ(
564 http, "HTTP/1.1 503 Slow Down\r\nTransfer-Encoding:chunked\r\nConnection:close\r\n\r\n0\r\n\r\n");
565
566 hrnServerScriptClose(http);
567 hrnServerScriptAccept(http);
568
569 hrnServerScriptExpectZ(http, "GET / HTTP/1.1\r\n" TEST_USER_AGENT "\r\n");
570 hrnServerScriptReplyZ(http, "HTTP/1.1 404 Not Found\r\n\r\n");
571
572 TEST_ASSIGN(request, httpRequestNewP(client, STRDEF("GET"), STRDEF("/")), "request");
573 TEST_ASSIGN(response, httpRequestResponse(request, false), "response");
574 TEST_RESULT_UINT(httpResponseCode(response), 404, "check response code");
575 TEST_RESULT_BOOL(httpResponseCodeOk(response), false, "check response code error");
576 TEST_RESULT_STR_Z(httpResponseReason(response), "Not Found", "check response message");
577 TEST_RESULT_STR_Z(
578 httpHeaderToLog(httpResponseHeader(response)), "{}", "check response headers");
579
580 TEST_ERROR(
581 httpRequestError(request, response), ProtocolError,
582 "HTTP request failed with 404 (Not Found):\n"
583 "*** Path/Query ***:\n"
584 "/");
585
586 // -----------------------------------------------------------------------------------------------------------------
587 TEST_TITLE("error with content");
588
589 hrnServerScriptExpectZ(http, "GET /?a=b HTTP/1.1\r\n" TEST_USER_AGENT "hdr1:1\r\nhdr2:2\r\n\r\n");
590 hrnServerScriptReplyZ(http, "HTTP/1.1 403 \r\ncontent-length:7\r\n\r\nCONTENT");
591
592 StringList *headerRedact = strLstNew();
593 strLstAdd(headerRedact, STRDEF("hdr2"));
594 headerRequest = httpHeaderNew(headerRedact);
595 httpHeaderAdd(headerRequest, STRDEF("hdr1"), STRDEF("1"));
596 httpHeaderAdd(headerRequest, STRDEF("hdr2"), STRDEF("2"));
597
598 TEST_ASSIGN(
599 request,
600 httpRequestNewP(
601 client, STRDEF("GET"), STRDEF("/"), .query = httpQueryAdd(httpQueryNewP(), STRDEF("a"), STRDEF("b")),
602 .header = headerRequest),
603 "request");
604 TEST_ASSIGN(response, httpRequestResponse(request, false), "response");
605 TEST_RESULT_UINT(httpResponseCode(response), 403, "check response code");
606 TEST_RESULT_STR_Z(httpResponseReason(response), "", "check empty response message");
607 TEST_RESULT_STR_Z(
608 httpHeaderToLog(httpResponseHeader(response)), "{content-length: '7'}", "check response headers");
609 TEST_RESULT_STR_Z(strNewBuf(httpResponseContent(response)), "CONTENT", "check response content");
610
611 TEST_ERROR(
612 httpRequestError(request, response), ProtocolError,
613 "HTTP request failed with 403:\n"
614 "*** Path/Query ***:\n"
615 "/?a=b\n"
616 "*** Request Headers ***:\n"
617 "hdr1: 1\n"
618 "hdr2: <redacted>\n"
619 "*** Response Headers ***:\n"
620 "content-length: 7\n"
621 "*** Response Content ***:\n"
622 "CONTENT");
623
624 // -----------------------------------------------------------------------------------------------------------------
625 TEST_TITLE("request with content using content-length");
626
627 hrnServerScriptExpectZ(
628 http, "GET /path/file%201.txt HTTP/1.1\r\n" TEST_USER_AGENT "content-length:30\r\n\r\n"
629 "012345678901234567890123456789");
630 hrnServerScriptReplyZ(http, "HTTP/1.1 200 OK\r\nConnection:close\r\n\r\n01234567890123456789012345678901");
631
632 hrnServerScriptClose(http);
633
634 ioBufferSizeSet(30);
635
636 TEST_ASSIGN(
637 response,
638 httpRequestResponse(
639 httpRequestNewP(
640 client, STRDEF("GET"), httpUriEncode(STRDEF("/path/file 1.txt"), true),
641 .header = httpHeaderAdd(httpHeaderNew(NULL), STRDEF("content-length"), STRDEF("30")),
642 .content = BUFSTRDEF("012345678901234567890123456789")), true),
643 "request");
644 TEST_RESULT_STR_Z(
645 httpHeaderToLog(httpResponseHeader(response)), "{connection: 'close'}", "check response headers");
646 TEST_RESULT_STR_Z(strNewBuf(httpResponseContent(response)), "01234567890123456789012345678901", "check response");
647 TEST_RESULT_UINT(httpResponseRead(response, bufNew(1), true), 0, "call internal read to check eof");
648
649 // -----------------------------------------------------------------------------------------------------------------
650 TEST_TITLE("request with eof before content complete with retry");
651
652 hrnServerScriptAccept(http);
653
654 hrnServerScriptExpectZ(http, "GET /path/file%201.txt HTTP/1.1\r\n" TEST_USER_AGENT "\r\n");
655 hrnServerScriptReplyZ(http, "HTTP/1.1 200 OK\r\ncontent-length:32\r\n\r\n0123456789012345678901234567890");
656
657 hrnServerScriptClose(http);
658 hrnServerScriptAccept(http);
659
660 hrnServerScriptExpectZ(http, "GET /path/file%201.txt HTTP/1.1\r\n" TEST_USER_AGENT "\r\n");
661 hrnServerScriptReplyZ(http, "HTTP/1.1 200 OK\r\ncontent-length:32\r\n\r\n01234567890123456789012345678901");
662
663 TEST_ASSIGN(
664 response,
665 httpRequestResponse(
666 httpRequestNewP(client, STRDEF("GET"), httpUriEncode(STRDEF("/path/file 1.txt"), true)), true),
667 "request");
668 TEST_RESULT_STR_Z(strNewBuf(httpResponseContent(response)), "01234567890123456789012345678901", "check response");
669 TEST_RESULT_UINT(httpResponseRead(response, bufNew(1), true), 0, "call internal read to check eof");
670
671 // -----------------------------------------------------------------------------------------------------------------
672 TEST_TITLE("request with eof before content complete");
673
674 hrnServerScriptExpectZ(http, "GET /path/file%201.txt HTTP/1.1\r\n" TEST_USER_AGENT "\r\n");
675 hrnServerScriptReplyZ(http, "HTTP/1.1 200 OK\r\ncontent-length:32\r\n\r\n0123456789012345678901234567890");
676
677 hrnServerScriptClose(http);
678
679 TEST_ASSIGN(
680 response,
681 httpRequestResponse(
682 httpRequestNewP(client, STRDEF("GET"), httpUriEncode(STRDEF("/path/file 1.txt"), true)), false),
683 "request");
684 TEST_RESULT_PTR_NE(response->session, NULL, "session is busy");
685 TEST_ERROR(ioRead(httpResponseIoRead(response), bufNew(32)), FileReadError, "unexpected EOF reading HTTP content");
686 TEST_RESULT_PTR_NE(response->session, NULL, "session is still busy");
687
688 // -----------------------------------------------------------------------------------------------------------------
689 TEST_TITLE("request with chunked content");
690
691 hrnServerScriptAccept(http);
692
693 hrnServerScriptExpectZ(http, "GET / HTTP/1.1\r\n" TEST_USER_AGENT "\r\n");
694 hrnServerScriptReplyZ(
695 http,
696 "HTTP/1.1 200 OK\r\nTransfer-Encoding:chunked\r\n\r\n"
697 "20\r\n01234567890123456789012345678901\r\n"
698 "10\r\n0123456789012345\r\n"
699 "0\r\n\r\n");
700
701 TEST_ASSIGN(response, httpRequestResponse(httpRequestNewP(client, STRDEF("GET"), STRDEF("/")), false), "request");
702 TEST_RESULT_STR_Z(
703 httpHeaderToLog(httpResponseHeader(response)), "{transfer-encoding: 'chunked'}", "check response headers");
704
705 Buffer *buffer = bufNew(35);
706
707 TEST_RESULT_VOID(ioRead(httpResponseIoRead(response), buffer), "read response");
708 TEST_RESULT_STR_Z(strNewBuf(buffer), "01234567890123456789012345678901012", "check response");
709
710 // -----------------------------------------------------------------------------------------------------------------
711 TEST_TITLE("close connection and end server process");
712
713 hrnServerScriptClose(http);
714 hrnServerScriptEnd(http);
715 }
716 HRN_FORK_PARENT_END();
717 }
718 HRN_FORK_END();
719
720 // -------------------------------------------------------------------------------------------------------------------------
721 TEST_TITLE("statistics exist");
722
723 TEST_RESULT_BOOL(varLstEmpty(kvKeyList(statToKv())), false, "check");
724 }
725
726 FUNCTION_HARNESS_RETURN_VOID();
727 }
728