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