|
1 /* |
|
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. |
|
3 * |
|
4 * Copyright 2023 Olaf Wintermann. All rights reserved. |
|
5 * |
|
6 * Redistribution and use in source and binary forms, with or without |
|
7 * modification, are permitted provided that the following conditions are met: |
|
8 * |
|
9 * 1. Redistributions of source code must retain the above copyright |
|
10 * notice, this list of conditions and the following disclaimer. |
|
11 * |
|
12 * 2. Redistributions in binary form must reproduce the above copyright |
|
13 * notice, this list of conditions and the following disclaimer in the |
|
14 * documentation and/or other materials provided with the distribution. |
|
15 * |
|
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
|
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
|
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
|
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
|
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
|
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
|
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
|
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|
26 * POSSIBILITY OF SUCH DAMAGE. |
|
27 */ |
|
28 |
|
29 #include "io.h" |
|
30 |
|
31 #include "testutils.h" |
|
32 |
|
33 |
|
34 UCX_TEST(test_io_http_stream_parse_chunk_header_hdronly_first) { |
|
35 char *str = strdup("100\r\n"); |
|
36 size_t len = strlen(str); |
|
37 char *str2 = strdup("12345\r\n"); |
|
38 size_t len2 = strlen(str2); |
|
39 char *str3 = strdup("FF\r\n"); |
|
40 size_t len3 = strlen(str3); |
|
41 |
|
42 UCX_TEST_BEGIN; |
|
43 |
|
44 int64_t chunklen; |
|
45 int ret = http_stream_parse_chunk_header(str, len, TRUE, &chunklen); |
|
46 UCX_TEST_ASSERT(ret == 5, "ret != 5"); |
|
47 UCX_TEST_ASSERT(chunklen == 0x100, "wrong chunk length"); |
|
48 |
|
49 // test 2 |
|
50 ret = http_stream_parse_chunk_header(str2, len2, TRUE, &chunklen); |
|
51 UCX_TEST_ASSERT(ret == 7, "ret != 7 (test 2)"); |
|
52 UCX_TEST_ASSERT(chunklen == 0x12345, "wrong chunk length (test 2)"); |
|
53 |
|
54 // test 3: hex test |
|
55 ret = http_stream_parse_chunk_header(str3, len3, TRUE, &chunklen); |
|
56 UCX_TEST_ASSERT(ret == 4, "ret != 7 (test 3)"); |
|
57 UCX_TEST_ASSERT(chunklen == 0xFF, "wrong chunk length (test 3)"); |
|
58 |
|
59 UCX_TEST_END; |
|
60 free(str); |
|
61 free(str2); |
|
62 } |
|
63 |
|
64 UCX_TEST(test_io_http_stream_parse_chunk_header_hdronly) { |
|
65 char *str = strdup("\r\n100\r\n"); |
|
66 size_t len = strlen(str); |
|
67 char *str2 = strdup("\nab\n"); |
|
68 size_t len2 = strlen(str2); |
|
69 |
|
70 UCX_TEST_BEGIN; |
|
71 |
|
72 int64_t chunklen; |
|
73 int ret = http_stream_parse_chunk_header(str, len, FALSE, &chunklen); |
|
74 UCX_TEST_ASSERT(ret == 7, "ret != 7"); |
|
75 UCX_TEST_ASSERT(chunklen == 0x100, "wrong chunk length"); |
|
76 |
|
77 // test 2 with just \n as line break |
|
78 ret = http_stream_parse_chunk_header(str2, len2, FALSE, &chunklen); |
|
79 UCX_TEST_ASSERT(ret == 4, "ret != 5 (test 2)"); |
|
80 UCX_TEST_ASSERT(chunklen == 0xab, "wrong chunk length (test 2)"); |
|
81 |
|
82 UCX_TEST_END; |
|
83 free(str); |
|
84 free(str2); |
|
85 } |
|
86 |
|
87 UCX_TEST(test_io_http_stream_parse_chunk_header_hdronly_seq_fail) { |
|
88 // test: after the first chunk header, \r\n is required before any new header |
|
89 char *str = strdup("ff\r\n"); |
|
90 size_t len = strlen(str); |
|
91 |
|
92 UCX_TEST_BEGIN; |
|
93 |
|
94 int64_t chunklen; |
|
95 int ret = http_stream_parse_chunk_header(str, len, FALSE, &chunklen); |
|
96 UCX_TEST_ASSERT(ret == -1, "ret != -1"); |
|
97 |
|
98 UCX_TEST_END; |
|
99 free(str); |
|
100 } |
|
101 |
|
102 UCX_TEST(test_io_http_stream_parse_chunk_header_hdr_data) { |
|
103 char *str = strdup("\r\nb\r\nhello world\r\n"); |
|
104 size_t len = strlen(str); |
|
105 |
|
106 UCX_TEST_BEGIN; |
|
107 |
|
108 int64_t chunklen; |
|
109 int ret = http_stream_parse_chunk_header(str, len, FALSE, &chunklen); |
|
110 UCX_TEST_ASSERT(ret == 5, "ret != 5"); |
|
111 |
|
112 UCX_TEST_END; |
|
113 free(str); |
|
114 } |
|
115 |
|
116 UCX_TEST(test_io_http_stream_parse_chunk_header_empty) { |
|
117 char *str = ""; |
|
118 size_t len = strlen(str); |
|
119 |
|
120 UCX_TEST_BEGIN; |
|
121 |
|
122 int64_t chunklen; |
|
123 int ret = http_stream_parse_chunk_header(str, len, FALSE, &chunklen); |
|
124 UCX_TEST_ASSERT(ret == 0, "ret != 0"); |
|
125 |
|
126 ret = http_stream_parse_chunk_header(str, len, TRUE, &chunklen); |
|
127 UCX_TEST_ASSERT(ret == 0, "ret != 0 (test 2)"); |
|
128 |
|
129 UCX_TEST_END; |
|
130 } |
|
131 |
|
132 UCX_TEST(test_io_http_stream_parse_chunk_header_partial_first) { |
|
133 char *str = strdup("123"); |
|
134 size_t len = strlen(str); |
|
135 |
|
136 UCX_TEST_BEGIN; |
|
137 |
|
138 int64_t chunklen; |
|
139 int ret = http_stream_parse_chunk_header(str, len, TRUE, &chunklen); |
|
140 UCX_TEST_ASSERT(ret == 0, "ret != 0"); |
|
141 |
|
142 UCX_TEST_END; |
|
143 free(str); |
|
144 } |
|
145 |
|
146 UCX_TEST(test_io_http_stream_parse_chunk_header_partial) { |
|
147 char *str = strdup("123"); |
|
148 size_t len = strlen(str); |
|
149 char *str2 = strdup("\r\n"); |
|
150 size_t len2 = strlen(str2); |
|
151 char *str3 = strdup("\r"); |
|
152 size_t len3 = strlen(str3); |
|
153 char *str4 = strdup("\r\n123"); |
|
154 size_t len4 = strlen(str4); |
|
155 |
|
156 UCX_TEST_BEGIN; |
|
157 |
|
158 int64_t chunklen; |
|
159 int ret = http_stream_parse_chunk_header(str, len, TRUE, &chunklen); |
|
160 UCX_TEST_ASSERT(ret == 0, "ret != 0"); |
|
161 ret = http_stream_parse_chunk_header(str2, len2, FALSE, &chunklen); |
|
162 UCX_TEST_ASSERT(ret == 0, "ret != 0"); |
|
163 ret = http_stream_parse_chunk_header(str3, len3, FALSE, &chunklen); |
|
164 UCX_TEST_ASSERT(ret == 0, "ret != 0"); |
|
165 ret = http_stream_parse_chunk_header(str4, len4, FALSE, &chunklen); |
|
166 UCX_TEST_ASSERT(ret == 0, "ret != 0"); |
|
167 |
|
168 UCX_TEST_END; |
|
169 free(str); |
|
170 free(str2); |
|
171 free(str3); |
|
172 free(str4); |
|
173 } |
|
174 |
|
175 UCX_TEST(test_io_http_stream_parse_chunk_header_invalid) { |
|
176 char *str = strdup("hello\r\n"); |
|
177 size_t len = strlen(str); |
|
178 char *str2 = strdup("x4\r\n\r\n123\r\n"); |
|
179 size_t len2 = strlen(str2); |
|
180 char *str3 = strdup("\r\n\r\n123\r\n"); |
|
181 size_t len3 = strlen(str3); |
|
182 |
|
183 UCX_TEST_BEGIN; |
|
184 |
|
185 int64_t chunklen; |
|
186 int ret; |
|
187 |
|
188 ret = http_stream_parse_chunk_header(str, len, TRUE, &chunklen); |
|
189 UCX_TEST_ASSERT(ret == -1, "ret != -1 (test 1a)"); |
|
190 ret = http_stream_parse_chunk_header(str, len, FALSE, &chunklen); |
|
191 UCX_TEST_ASSERT(ret == -1, "ret != -1 (test 1b)"); |
|
192 |
|
193 ret = http_stream_parse_chunk_header(str2, len2, TRUE, &chunklen); |
|
194 UCX_TEST_ASSERT(ret == -1, "ret != -1 (test 1a)"); |
|
195 ret = http_stream_parse_chunk_header(str2, len2, FALSE, &chunklen); |
|
196 UCX_TEST_ASSERT(ret == -1, "ret != -1 (test 1b)"); |
|
197 |
|
198 ret = http_stream_parse_chunk_header(str3, len3, TRUE, &chunklen); |
|
199 UCX_TEST_ASSERT(ret == -1, "ret != -1 (test 1a)"); |
|
200 ret = http_stream_parse_chunk_header(str3, len3, FALSE, &chunklen); |
|
201 UCX_TEST_ASSERT(ret == -1, "ret != -1 (test 1b)"); |
|
202 |
|
203 UCX_TEST_END; |
|
204 free(str); |
|
205 free(str2); |
|
206 free(str3); |
|
207 } |
|
208 |
|
209 UCX_TEST(test_io_http_stream_parse_chunk_header_zero) { |
|
210 char *str = strdup("\r\n0\r\n\r\n"); |
|
211 size_t len = strlen(str); |
|
212 char *str2 = strdup("0\r\n\r\n"); |
|
213 size_t len2 = strlen(str2); |
|
214 |
|
215 // incomplete |
|
216 char *str3 = strdup("\r\n0\r\n"); |
|
217 size_t len3 = strlen(str3); |
|
218 char *str4 = strdup("\r\n0"); |
|
219 size_t len4 = strlen(str4); |
|
220 |
|
221 |
|
222 UCX_TEST_BEGIN; |
|
223 |
|
224 int64_t chunklen = -1; |
|
225 int ret = http_stream_parse_chunk_header(str, len, FALSE, &chunklen); |
|
226 UCX_TEST_ASSERT(ret == 7, "ret != 7"); |
|
227 UCX_TEST_ASSERT(chunklen == 0, "chunklen != 0"); |
|
228 |
|
229 chunklen = -1; |
|
230 ret = http_stream_parse_chunk_header(str2, len2, TRUE, &chunklen); |
|
231 UCX_TEST_ASSERT(ret == 5, "ret != 5 (test 2)"); |
|
232 UCX_TEST_ASSERT(chunklen == 0, "chunklen != 0 (test 2)"); |
|
233 |
|
234 // expect 0 (incomplete) |
|
235 ret = http_stream_parse_chunk_header(str3, len3, FALSE, &chunklen); |
|
236 UCX_TEST_ASSERT(ret == 0, "ret != 3 (test 3)"); |
|
237 |
|
238 ret = http_stream_parse_chunk_header(str4, len4, FALSE, &chunklen); |
|
239 UCX_TEST_ASSERT(ret == 0, "ret != 3 (test 4)"); |
|
240 |
|
241 UCX_TEST_END; |
|
242 free(str); |
|
243 free(str2); |
|
244 free(str3); |
|
245 free(str4); |
|
246 } |
|
247 |
|
248 |
|
249 UCX_TEST(test_io_httpstream_write) { |
|
250 Session *sn = testutil_session(); |
|
251 |
|
252 TestIOStream *st = testutil_iostream(2048, TRUE); |
|
253 IOStream *http = httpstream_new(sn->pool, (IOStream*)st); |
|
254 |
|
255 UCX_TEST_BEGIN; |
|
256 |
|
257 char *msg = "hello world!"; |
|
258 size_t msglen = strlen(msg); |
|
259 |
|
260 ssize_t w = net_write(http, msg, msglen); |
|
261 |
|
262 UCX_TEST_ASSERT(w == msglen, "wrong size returned by net_write"); |
|
263 UCX_TEST_ASSERT(st->buf->size == msglen, "wrong buffer size"); |
|
264 UCX_TEST_ASSERT(!memcmp(st->buf->space, msg, msglen), "wrong buffer content"); |
|
265 |
|
266 // test again, make sure the second message is written directly after the wirst one |
|
267 char *msg2 = "test"; |
|
268 size_t msglen2 = strlen(msg2); |
|
269 |
|
270 w = net_write(http, msg2, msglen2); |
|
271 |
|
272 UCX_TEST_ASSERT(w == msglen2, "wrong size returned by net_write (2)"); |
|
273 UCX_TEST_ASSERT(st->buf->size == msglen+msglen2, "wrong buffer size (2)"); |
|
274 UCX_TEST_ASSERT(!memcmp(st->buf->space + msglen, msg2, msglen2), "wrong buffer content (2)"); |
|
275 |
|
276 UCX_TEST_END; |
|
277 |
|
278 testutil_destroy_session(sn); |
|
279 } |
|
280 |
|
281 UCX_TEST(test_io_httpstream_chunked_write) { |
|
282 Session *sn = testutil_session(); |
|
283 |
|
284 TestIOStream *st = testutil_iostream(2048, TRUE); |
|
285 IOStream *http = httpstream_new(sn->pool, (IOStream*)st); |
|
286 httpstream_enable_chunked_write(http); |
|
287 |
|
288 UCX_TEST_BEGIN; |
|
289 |
|
290 char *msg = "hello world!"; |
|
291 size_t msglen = strlen(msg); |
|
292 |
|
293 char *bufmsg = "c\r\nhello world!\r\n"; |
|
294 size_t bufmsglen = strlen(bufmsg); |
|
295 |
|
296 ssize_t w = net_write(http, msg, msglen); |
|
297 |
|
298 cxstring s1 = cx_strn(st->buf->space, st->buf->size); |
|
299 cxstring s2 = cx_strn(bufmsg, bufmsglen); |
|
300 |
|
301 UCX_TEST_ASSERT(w == msglen, "wrong size returned by net_write"); |
|
302 UCX_TEST_ASSERT(st->buf->size == bufmsglen, "wrong buffer size"); |
|
303 UCX_TEST_ASSERT(!cx_strcasecmp(s1, s2), "wrong buffer content"); |
|
304 |
|
305 // write again |
|
306 w = net_write(http, msg, msglen); |
|
307 UCX_TEST_ASSERT(w == msglen, "write 2: wrong return value"); |
|
308 UCX_TEST_ASSERT(st->buf->size == 2*bufmsglen, "write 2: wrong buf size"); |
|
309 |
|
310 cxstring s3 = cx_strn(st->buf->space+bufmsglen, bufmsglen); |
|
311 UCX_TEST_ASSERT(!cx_strcasecmp(s2, s3), "write 2: wrong buf content"); |
|
312 |
|
313 |
|
314 UCX_TEST_END; |
|
315 } |
|
316 |
|
317 UCX_TEST(test_io_httpstream_chunked_write_end) { |
|
318 Session *sn = testutil_session(); |
|
319 |
|
320 TestIOStream *st = testutil_iostream(2048, TRUE); |
|
321 IOStream *http = httpstream_new(sn->pool, (IOStream*)st); |
|
322 httpstream_enable_chunked_write(http); |
|
323 |
|
324 UCX_TEST_BEGIN; |
|
325 |
|
326 char *msg = "hello world!"; |
|
327 size_t msglen = strlen(msg); |
|
328 |
|
329 char *bufmsg = "c\r\nhello world!\r\n0\r\n\r\n"; |
|
330 size_t bufmsglen = strlen(bufmsg); |
|
331 |
|
332 ssize_t w = net_write(http, msg, msglen); |
|
333 net_finish(http); |
|
334 |
|
335 cxstring s1 = cx_strn(st->buf->space, st->buf->size); |
|
336 cxstring s2 = cx_strn(bufmsg, bufmsglen); |
|
337 |
|
338 UCX_TEST_ASSERT(w == msglen, "wrong size returned by net_write"); |
|
339 UCX_TEST_ASSERT(st->buf->size == bufmsglen, "wrong buffer size"); |
|
340 UCX_TEST_ASSERT(!cx_strcasecmp(s1, s2), "wrong buffer content"); |
|
341 |
|
342 |
|
343 UCX_TEST_END; |
|
344 } |
|
345 |
|
346 UCX_TEST(test_io_httpstream_chunked_write_xx) { |
|
347 // This test creates a giant buffer and writes it with |
|
348 // chunked transfer encoding to the http stream with varying chunk length |
|
349 |
|
350 Session *sn = testutil_session(); |
|
351 |
|
352 TestIOStream *st = testutil_iostream(2048, TRUE); |
|
353 IOStream *http = httpstream_new(sn->pool, (IOStream*)st); |
|
354 httpstream_enable_chunked_write(http); |
|
355 |
|
356 UCX_TEST_BEGIN; |
|
357 |
|
358 // create test data |
|
359 CxBuffer *testdata = cxBufferCreate(NULL, 1024*1024*4, cxDefaultAllocator, 0); |
|
360 for(size_t i=0;i<testdata->capacity;i++) { |
|
361 cxBufferPut(testdata, 35+(i%91)); |
|
362 } |
|
363 |
|
364 // write chunks, start with single diget chunk length and increase |
|
365 // chunk size with each step |
|
366 size_t pos = 0; |
|
367 int j = 0; |
|
368 ssize_t i=15; |
|
369 while(pos<testdata->size) { |
|
370 char *buf = testdata->space + pos; |
|
371 size_t remaining = testdata->size - pos; |
|
372 ssize_t len = pos + i < remaining ? i : remaining; |
|
373 pos += len; |
|
374 |
|
375 ssize_t w = net_write(http, buf, len); |
|
376 |
|
377 UCX_TEST_ASSERT(w == len, "wrong size returned by net_write"); |
|
378 i+=100; // increase chunk size |
|
379 j++; // debug |
|
380 } |
|
381 |
|
382 // terminate chunk |
|
383 net_finish(http); |
|
384 |
|
385 // code below also used in test_io_httpstream_chunked_write_xx_limit |
|
386 |
|
387 // make sure the output is correctly encoded |
|
388 // extract chunks from st->buf by using http_stream_parse_chunk_header |
|
389 // (which should be well-tested) |
|
390 WSBool first_chunk = TRUE; |
|
391 int64_t chunklen = 0; |
|
392 |
|
393 char *buf = st->buf->space; |
|
394 size_t bufsize = st->buf->size; |
|
395 |
|
396 pos = 0; // st->buf position |
|
397 size_t srcpos = 0; // testdata position |
|
398 int debug_counter = 0; |
|
399 while(pos < bufsize) { |
|
400 ssize_t remaining = bufsize - pos; |
|
401 ssize_t src_remaining = testdata->size - srcpos; |
|
402 |
|
403 int ret = http_stream_parse_chunk_header(buf+pos, remaining, first_chunk, &chunklen); |
|
404 first_chunk = FALSE; |
|
405 |
|
406 // ret must always be > 0 (0: incomplete chunk header, -1: invalid syntax) |
|
407 UCX_TEST_ASSERT(ret > 0, "http_stream_parse_chunk_header ret <= 0"); |
|
408 if(chunklen == 0) { |
|
409 UCX_TEST_ASSERT(src_remaining == 0, "stream end reached but src_remaining > 0"); |
|
410 break; |
|
411 } |
|
412 |
|
413 UCX_TEST_ASSERT(chunklen <= src_remaining, "chunklen > src_remaining"); |
|
414 |
|
415 char *src_chunk = testdata->space+srcpos; |
|
416 char *buf_chunk = buf+pos+ret; |
|
417 |
|
418 UCX_TEST_ASSERT(!memcmp(buf_chunk, src_chunk, chunklen), "memcmp failed"); |
|
419 |
|
420 pos += ret + chunklen; |
|
421 srcpos += chunklen; |
|
422 |
|
423 debug_counter++; |
|
424 } |
|
425 |
|
426 cxBufferFree(testdata); |
|
427 testutil_destroy_session(sn); |
|
428 testutil_iostream_destroy(st); |
|
429 |
|
430 UCX_TEST_END; |
|
431 } |
|
432 |
|
433 UCX_TEST(test_io_httpstream_chunked_write_partial_header) { |
|
434 Session *sn = testutil_session(); |
|
435 |
|
436 TestIOStream *st = testutil_iostream(2048, TRUE); |
|
437 IOStream *http = httpstream_new(sn->pool, (IOStream*)st); |
|
438 httpstream_enable_chunked_write(http); |
|
439 |
|
440 UCX_TEST_BEGIN; |
|
441 |
|
442 memset(st->buf->space, 0, st->buf->capacity); |
|
443 |
|
444 char *msg = "hello world!"; |
|
445 size_t msglen = strlen(msg); |
|
446 |
|
447 st->max_write = 1; // limit the test stream max write size |
|
448 |
|
449 // only 1 byte of the header is written, 0 bytes of msg |
|
450 ssize_t w = net_write(http, msg, msglen); |
|
451 UCX_TEST_ASSERT(w == 0, "write 1: wrong return value"); |
|
452 UCX_TEST_ASSERT(st->buf->size == 1, "write 1: wrong buf size"); |
|
453 UCX_TEST_ASSERT(tolower(st->buf->space[0]) == 'c', "write 1: wrong buf content"); |
|
454 |
|
455 // next header byte: '\r' |
|
456 w = net_write(http, msg, msglen); |
|
457 UCX_TEST_ASSERT(w == 0, "write 2: wrong return value"); |
|
458 UCX_TEST_ASSERT(st->buf->size == 2, "write 2: wrong buf size"); |
|
459 UCX_TEST_ASSERT(st->buf->space[1] == '\r', "write 2: wrong content"); |
|
460 |
|
461 // next header byte: '\n' |
|
462 w = net_write(http, msg, msglen); |
|
463 UCX_TEST_ASSERT(w == 0, "write 3: wrong return value"); |
|
464 UCX_TEST_ASSERT(st->buf->size == 3, "write 3: wrong buf size"); |
|
465 UCX_TEST_ASSERT(st->buf->space[2] == '\n', "write 3: wrong content"); |
|
466 |
|
467 // next: content |
|
468 w = net_write(http, msg, msglen); |
|
469 UCX_TEST_ASSERT(w == 1, "write 4: wrong return value"); |
|
470 UCX_TEST_ASSERT(st->buf->size == 4, "write 3: wrong buf size"); |
|
471 UCX_TEST_ASSERT(st->buf->space[3] == msg[0], "write 3: wrong content"); |
|
472 |
|
473 testutil_destroy_session(sn); |
|
474 testutil_iostream_destroy(st); |
|
475 |
|
476 UCX_TEST_END; |
|
477 } |
|
478 |
|
479 UCX_TEST(test_io_httpstream_chunked_write_partial_data) { |
|
480 Session *sn = testutil_session(); |
|
481 |
|
482 TestIOStream *st = testutil_iostream(2048, TRUE); |
|
483 IOStream *http = httpstream_new(sn->pool, (IOStream*)st); |
|
484 httpstream_enable_chunked_write(http); |
|
485 |
|
486 UCX_TEST_BEGIN; |
|
487 |
|
488 memset(st->buf->space, 0, st->buf->capacity); |
|
489 |
|
490 char *msg = "hello world!"; |
|
491 size_t msglen = strlen(msg); |
|
492 size_t msglen_orig = msglen; |
|
493 |
|
494 // limit first write to 3 to only write the header |
|
495 st->max_write = 3; |
|
496 |
|
497 ssize_t w = net_write(http, msg, msglen); |
|
498 |
|
499 UCX_TEST_ASSERT(w == 0, "write 1: wrong return value"); |
|
500 UCX_TEST_ASSERT(st->buf->size == 3, "write 1: wrong buf size"); |
|
501 UCX_TEST_ASSERT(st->buf->space[0] == 'c', "write 1: wrong buf content"); |
|
502 UCX_TEST_ASSERT(st->buf->space[2] == '\n', "write 1: wrong buf content"); |
|
503 |
|
504 w = net_write(http, msg, msglen); |
|
505 UCX_TEST_ASSERT(w == 3, "write 2: wrong return value"); |
|
506 UCX_TEST_ASSERT(st->buf->size == 6, "write 2: wrong buf size"); |
|
507 UCX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhel\0", 7), "write 2: wrong buf content"); |
|
508 |
|
509 msg += w; |
|
510 msglen -= w; |
|
511 |
|
512 w = net_write(http, msg, msglen); |
|
513 UCX_TEST_ASSERT(w == 3, "write 3: wrong return value"); |
|
514 UCX_TEST_ASSERT(st->buf->size == 9, "write 3: wrong buf size"); |
|
515 UCX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello \0", 10), "write 3: wrong buf content"); |
|
516 |
|
517 st->max_write = 1024; |
|
518 msg += w; |
|
519 msglen -= w; |
|
520 |
|
521 w = net_write(http, msg, msglen); |
|
522 UCX_TEST_ASSERT(w == msglen, "write 4: wrong return value"); |
|
523 UCX_TEST_ASSERT(st->buf->size == 3 + msglen_orig + 2, "write 4: wrong buf size"); |
|
524 UCX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello world!\r\n", st->buf->size), "write 4: wrong buf content"); |
|
525 |
|
526 testutil_destroy_session(sn); |
|
527 testutil_iostream_destroy(st); |
|
528 |
|
529 UCX_TEST_END; |
|
530 } |
|
531 |
|
532 UCX_TEST(test_io_httpstream_chunked_write_partial_trailer) { |
|
533 Session *sn = testutil_session(); |
|
534 |
|
535 TestIOStream *st = testutil_iostream(2048, TRUE); |
|
536 IOStream *http = httpstream_new(sn->pool, (IOStream*)st); |
|
537 httpstream_enable_chunked_write(http); |
|
538 |
|
539 UCX_TEST_BEGIN; |
|
540 |
|
541 memset(st->buf->space, 0, st->buf->capacity); |
|
542 |
|
543 char *msg = "hello world!"; |
|
544 size_t msglen = strlen(msg); |
|
545 |
|
546 char *msg2 = "newmsg"; |
|
547 size_t msglen2 = strlen(msg2); |
|
548 |
|
549 char *msg3 = "msg3"; |
|
550 size_t msglen3 = strlen(msg3); |
|
551 |
|
552 st->max_write = 3 + msglen; // header + msg, but without trailer |
|
553 |
|
554 ssize_t w = net_write(http, msg, msglen); |
|
555 |
|
556 UCX_TEST_ASSERT(w == msglen, "write 1: wrong return value"); |
|
557 UCX_TEST_ASSERT(st->buf->size == 3 + msglen, "write 1: wrong buf size"); |
|
558 UCX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello world!\0", st->buf->size + 1), "write 1: wrong buf content"); |
|
559 |
|
560 st->max_write = 2 + 3 + msglen2; // trailer + new header + new msg, without new trailer |
|
561 |
|
562 w = net_write(http, msg2, msglen2); |
|
563 UCX_TEST_ASSERT(w == msglen2, "write 2: wrong return value"); |
|
564 UCX_TEST_ASSERT(st->buf->size == 3 + msglen + 2 + 3 + msglen2, "write 2: wrong buf size"); |
|
565 UCX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello world!\r\n6\r\nnewmsg\0", st->buf->size + 1), "write 2: wrong buf content"); |
|
566 |
|
567 // limit write to 1 byte: two writes required for trailer, net_write should return 0 |
|
568 st->max_write = 1; |
|
569 |
|
570 w = net_write(http, "dummymsg", 8); |
|
571 UCX_TEST_ASSERT(w == 0, "write 3: wrong return value"); |
|
572 |
|
573 w = net_write(http, "dummymsg", 8); |
|
574 UCX_TEST_ASSERT(w == 0, "write 4: wrong return value"); |
|
575 UCX_TEST_ASSERT(st->buf->size == 3 + msglen + 2 + 3 + msglen2 + 2, "write 4: wrong buf size"); |
|
576 UCX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello world!\r\n6\r\nnewmsg\r\n\0", st->buf->size + 1), "write 4: wrong buf content"); |
|
577 |
|
578 st->max_write = 1024; |
|
579 w = net_write(http, msg3, msglen3); |
|
580 |
|
581 UCX_TEST_ASSERT(w == msglen3, "write 5: wrong return value"); |
|
582 UCX_TEST_ASSERT(st->buf->size == 3 + msglen + 2 + 3 + msglen2 + 2 + 3 + msglen3 + 2, "write 5: wrong buf size"); |
|
583 UCX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello world!\r\n6\r\nnewmsg\r\n4\r\nmsg3\r\n", st->buf->size + 1), "write 5: wrong buf content"); |
|
584 |
|
585 |
|
586 testutil_destroy_session(sn); |
|
587 testutil_iostream_destroy(st); |
|
588 |
|
589 UCX_TEST_END; |
|
590 } |
|
591 |
|
592 UCX_TEST(test_io_httpstream_chunked_write_partial_trailer_partial_header) { |
|
593 Session *sn = testutil_session(); |
|
594 |
|
595 TestIOStream *st = testutil_iostream(2048, TRUE); |
|
596 IOStream *http = httpstream_new(sn->pool, (IOStream*)st); |
|
597 httpstream_enable_chunked_write(http); |
|
598 |
|
599 UCX_TEST_BEGIN; |
|
600 |
|
601 memset(st->buf->space, 0, st->buf->capacity); |
|
602 |
|
603 char *msg = "hello world!"; |
|
604 size_t msglen = strlen(msg); |
|
605 |
|
606 char *msg2 = "newmsg"; |
|
607 size_t msglen2 = strlen(msg2); |
|
608 |
|
609 // Test: write partial trailer followed by partial header write |
|
610 |
|
611 st->max_write = 3 + msglen + 1; |
|
612 |
|
613 ssize_t w = net_write(http, msg, msglen); |
|
614 |
|
615 UCX_TEST_ASSERT(w == msglen, "write 1: wrong return value"); |
|
616 UCX_TEST_ASSERT(st->buf->size == 3 + msglen + 1, "write 1: wrong buf size"); |
|
617 UCX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello world!\r\0", st->buf->size + 1), "write 1: wrong buf content"); |
|
618 |
|
619 st->max_write = 2; // write 1 trailer byte and 1 header byte |
|
620 |
|
621 w = net_write(http, msg2, msglen2); |
|
622 |
|
623 UCX_TEST_ASSERT(w == 0, "write 2: wrong return value"); |
|
624 UCX_TEST_ASSERT(st->buf->size == 3 + msglen + 2 + 1, "write 2: wrong buf size"); |
|
625 UCX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello world!\r\n6\0", st->buf->size + 1), "write 2: wrong buf content"); |
|
626 |
|
627 // force partial header write again |
|
628 st->max_write = 1; |
|
629 |
|
630 w = net_write(http, msg2, msglen2); |
|
631 |
|
632 UCX_TEST_ASSERT(w == 0, "write 3: wrong return value"); |
|
633 UCX_TEST_ASSERT(st->buf->size == 3 + msglen + 2 + 2, "write 3: wrong buf size"); |
|
634 UCX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello world!\r\n6\r\0", st->buf->size + 1), "write 3: wrong buf content"); |
|
635 |
|
636 st->max_write = 1024; |
|
637 |
|
638 w = net_write(http, msg2, msglen2); |
|
639 |
|
640 UCX_TEST_ASSERT(w ==msglen2, "write 4: wrong return value"); |
|
641 UCX_TEST_ASSERT(st->buf->size == 3 + msglen + 2 + 3 + msglen2 + 2, "write 4: wrong buf size"); |
|
642 UCX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello world!\r\n6\r\nnewmsg\r\n", st->buf->size + 1), "write 4: wrong buf content"); |
|
643 |
|
644 |
|
645 testutil_destroy_session(sn); |
|
646 testutil_iostream_destroy(st); |
|
647 |
|
648 UCX_TEST_END; |
|
649 } |
|
650 |
|
651 UCX_TEST(test_io_httpstream_chunked_write_data_2x) { |
|
652 Session *sn = testutil_session(); |
|
653 |
|
654 TestIOStream *st = testutil_iostream(2048, TRUE); |
|
655 IOStream *http = httpstream_new(sn->pool, (IOStream*)st); |
|
656 httpstream_enable_chunked_write(http); |
|
657 |
|
658 UCX_TEST_BEGIN; |
|
659 |
|
660 memset(st->buf->space, 0, st->buf->capacity); |
|
661 |
|
662 // Test: First write a partial header, which forces a chunk with a specific |
|
663 // size. After that, write a message, that is bigger than the first |
|
664 // chunk, forcing a start of a second chunk, in one big writev op. |
|
665 |
|
666 char *msg = "hello world!"; |
|
667 size_t msglen = strlen(msg); |
|
668 |
|
669 char *msg2 = "newmsg"; |
|
670 size_t msglen2 = strlen(msg2); |
|
671 |
|
672 char *msg_big = "hello world!newmsg"; |
|
673 size_t msglen_big = strlen(msg_big); |
|
674 |
|
675 st->max_write = 1; |
|
676 |
|
677 ssize_t w = net_write(http, msg, msglen); // first chunk: msg |
|
678 |
|
679 UCX_TEST_ASSERT(w == 0, "write 1: wrong return value"); |
|
680 UCX_TEST_ASSERT(st->buf->size == 1, "write 1: wrong buf size"); |
|
681 |
|
682 st->max_write = 1024; |
|
683 |
|
684 w = net_write(http, msg_big, msglen_big); // first chunk + new chunk |
|
685 |
|
686 UCX_TEST_ASSERT(w == msglen_big, "write 2: wrong return value"); |
|
687 UCX_TEST_ASSERT(st->buf->size == 3 + msglen + 2 + 3 + msglen2 + 2, "write 2: wrong buf size"); |
|
688 UCX_TEST_ASSERT(!memcmp(st->buf->space, "c\r\nhello world!\r\n6\r\nnewmsg\r\n", st->buf->size + 1), "write 2: wrong buf content"); |
|
689 |
|
690 |
|
691 testutil_destroy_session(sn); |
|
692 testutil_iostream_destroy(st); |
|
693 |
|
694 UCX_TEST_END; |
|
695 } |
|
696 |
|
697 UCX_TEST(test_io_httpstream_chunked_write_xx_limit) { |
|
698 Session *sn = testutil_session(); |
|
699 |
|
700 TestIOStream *st = testutil_iostream(2048, TRUE); |
|
701 IOStream *http = httpstream_new(sn->pool, (IOStream*)st); |
|
702 httpstream_enable_chunked_write(http); |
|
703 |
|
704 UCX_TEST_BEGIN; |
|
705 |
|
706 // Test: create testdata and write it in varying chunk sizes, but |
|
707 // limit TestIOStream to 1 to 3 byte writes |
|
708 |
|
709 // create test data |
|
710 CxBuffer *testdata = cxBufferCreate(NULL, 1024*16, cxDefaultAllocator, 0); |
|
711 for(size_t i=0;i<testdata->capacity;i++) { |
|
712 cxBufferPut(testdata, 35+(i%91)); |
|
713 } |
|
714 |
|
715 st->max_write = 1; |
|
716 |
|
717 size_t pos = 0; |
|
718 int chunksize = 1; |
|
719 while(pos < testdata->size) { |
|
720 size_t available = testdata->size - pos; |
|
721 |
|
722 char *chunk = testdata->space + pos; |
|
723 size_t chunklen = chunksize > available ? available : chunksize; |
|
724 |
|
725 // write chunk |
|
726 size_t chunkpos = 0; |
|
727 int max_writes = chunklen + 24; // max number of write attempts |
|
728 int writes = 0; |
|
729 while(chunkpos < chunklen) { |
|
730 ssize_t w = net_write(http, chunk+chunkpos, chunklen-chunkpos); |
|
731 UCX_TEST_ASSERT(w >= 0, "net_write failed"); |
|
732 chunkpos += w; |
|
733 |
|
734 writes++; |
|
735 UCX_TEST_ASSERT(writes < max_writes, "max writes attempts reached"); |
|
736 } |
|
737 |
|
738 pos += chunklen; |
|
739 chunksize += 5; |
|
740 |
|
741 // increase max write size at some point |
|
742 if(pos + chunksize >= testdata->size) { |
|
743 st->max_write = INT_MAX; |
|
744 } else if(pos > 1024*2) { |
|
745 if(pos < 1024*8) { |
|
746 st->max_write = 2; |
|
747 } else { |
|
748 st->max_write = 3; |
|
749 } |
|
750 } |
|
751 } |
|
752 |
|
753 // terminate chunk |
|
754 net_finish(http); |
|
755 |
|
756 |
|
757 // same code as test_io_httpstream_chunked_write_xx |
|
758 |
|
759 // make sure the output is correctly encoded |
|
760 // extract chunks from st->buf by using http_stream_parse_chunk_header |
|
761 // (which should be well-tested) |
|
762 |
|
763 WSBool first_chunk = TRUE; |
|
764 int64_t chunklen = 0; |
|
765 |
|
766 char *buf = st->buf->space; |
|
767 size_t bufsize = st->buf->size; |
|
768 |
|
769 pos = 0; // st->buf position |
|
770 size_t srcpos = 0; // testdata position |
|
771 int debug_counter = 0; |
|
772 while(pos < bufsize) { |
|
773 ssize_t remaining = bufsize - pos; |
|
774 ssize_t src_remaining = testdata->size - srcpos; |
|
775 |
|
776 int ret = http_stream_parse_chunk_header(buf+pos, remaining, first_chunk, &chunklen); |
|
777 first_chunk = FALSE; |
|
778 |
|
779 // ret must always be > 0 (0: incomplete chunk header, -1: invalid syntax) |
|
780 UCX_TEST_ASSERT(ret > 0, "http_stream_parse_chunk_header ret <= 0"); |
|
781 if(chunklen == 0) { |
|
782 UCX_TEST_ASSERT(src_remaining == 0, "stream end reached but src_remaining > 0"); |
|
783 break; |
|
784 } |
|
785 |
|
786 UCX_TEST_ASSERT(chunklen <= src_remaining, "chunklen > src_remaining"); |
|
787 |
|
788 char *src_chunk = testdata->space+srcpos; |
|
789 char *buf_chunk = buf+pos+ret; |
|
790 |
|
791 UCX_TEST_ASSERT(!memcmp(buf_chunk, src_chunk, chunklen), "memcmp failed"); |
|
792 |
|
793 pos += ret + chunklen; |
|
794 srcpos += chunklen; |
|
795 |
|
796 debug_counter++; |
|
797 } |
|
798 |
|
799 |
|
800 testutil_destroy_session(sn); |
|
801 testutil_iostream_destroy(st); |
|
802 cxBufferFree(testdata); |
|
803 |
|
804 UCX_TEST_END; |
|
805 } |