File: | d/tclhttp.c |
Warning: | line 2922, column 13 Duplicate code detected |
Note: | line 4571, column 9 Similar code here |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | /* |
2 | * The contents of this file are subject to the AOLserver Public License |
3 | * Version 1.1 (the "License"); you may not use this file except in |
4 | * compliance with the License. You may obtain a copy of the License at |
5 | * http://aolserver.com/. |
6 | * |
7 | * Software distributed under the License is distributed on an "AS IS" |
8 | * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See |
9 | * the License for the specific language governing rights and limitations |
10 | * under the License. |
11 | * |
12 | * The Original Code is AOLserver Code and related documentation |
13 | * distributed by AOL. |
14 | * |
15 | * The Initial Developer of the Original Code is America Online, |
16 | * Inc. Portions created by AOL are Copyright (C) 1999 America Online, |
17 | * Inc. All Rights Reserved. |
18 | * |
19 | * Alternatively, the contents of this file may be used under the terms |
20 | * of the GNU General Public License (the "GPL"), in which case the |
21 | * provisions of GPL are applicable instead of those above. If you wish |
22 | * to allow use of your version of this file only under the terms of the |
23 | * GPL and not to allow others to use your version of this file under the |
24 | * License, indicate your decision by deleting the provisions above and |
25 | * replace them with the notice and other provisions required by the GPL. |
26 | * If you do not delete the provisions above, a recipient may use your |
27 | * version of this file under either the License or the GPL. |
28 | */ |
29 | |
30 | /* |
31 | * tclhttp.c -- |
32 | * |
33 | * Support for the ns_http command. |
34 | * Uses the Ns_Task interface to run/queue HTTP tasks. |
35 | */ |
36 | |
37 | #include "nsd.h" |
38 | |
39 | #ifdef HAVE_OPENSSL_EVP_H1 |
40 | #include <openssl/err.h> |
41 | #endif |
42 | |
43 | #define TCLHTTP_USE_EXTERNALTOUTF1 1 |
44 | |
45 | /* |
46 | * The maximum number of bytes we can send to TLS |
47 | * in one operation is 2^14 => 16384 (see RFC 5246). |
48 | * This is used when reading data from file/channel |
49 | * and writing data to the connected socket. |
50 | * |
51 | * At some point, this should be abstracted by the |
52 | * future socket communication module. |
53 | */ |
54 | #define CHUNK_SIZE16384 16384 |
55 | |
56 | /* |
57 | * String equivalents of some methods, header keys |
58 | */ |
59 | static const char *transferEncodingHeader = "Transfer-Encoding"; |
60 | static const char *acceptEncodingHeader = "Accept-Encoding"; |
61 | static const char *contentEncodingHeader = "Content-Encoding"; |
62 | static const char *contentTypeHeader = "Content-Type"; |
63 | static const char *contentLengthHeader = "Content-Length"; |
64 | static const char *connectionHeader = "Connection"; |
65 | static const char *trailersHeader = "Trailers"; |
66 | static const char *hostHeader = "Host"; |
67 | static const char *userAgentHeader = "User-Agent"; |
68 | static const char *connectMethod = "CONNECT"; |
69 | |
70 | static const int acceptEncodingHeaderLength = 15; |
71 | |
72 | /* |
73 | * Attempt to maintain Tcl errorCode variable. |
74 | * This is still not done thoroughly through the code. |
75 | */ |
76 | static const char *errorCodeTimeoutString = "NS_TIMEOUT"; |
77 | |
78 | /* |
79 | * For http task mutex naming |
80 | */ |
81 | static uint64_t httpClientRequestCount = 0u; /* MT: static variable! */ |
82 | |
83 | /* |
84 | * Local functions defined in this file |
85 | */ |
86 | |
87 | static int HttpQueue( |
88 | NsInterp *itPtr, |
89 | int objc, |
90 | Tcl_Obj *const* |
91 | objv, |
92 | bool_Bool run |
93 | ) NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1))); |
94 | |
95 | static int HttpConnect( |
96 | NsInterp *itPtr, |
97 | const char *method, |
98 | const char *url, |
99 | Tcl_Obj *proxyObj, |
100 | Ns_Set *hdrPtr, |
101 | ssize_t bodySize, |
102 | Tcl_Obj *bodyObj, |
103 | const char *bodyFileName, |
104 | const char *cert, |
105 | const char *caFile, |
106 | const char *caPath, |
107 | const char *sniHostname, |
108 | bool_Bool verifyCert, |
109 | bool_Bool keepHostHdr, |
110 | Ns_Time *timeoutPtr, |
111 | Ns_Time *expirePtr, |
112 | NsHttpTask **httpPtrPtr |
113 | ) NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1))) NS_GNUC_NONNULL(2)__attribute__((__nonnull__(2))) NS_GNUC_NONNULL(3)__attribute__((__nonnull__(3))) NS_GNUC_NONNULL(17)__attribute__((__nonnull__(17))); |
114 | |
115 | static bool_Bool HttpGet( |
116 | NsInterp *itPtr, |
117 | const char *taskID, |
118 | NsHttpTask **httpPtrPtr, |
119 | bool_Bool remove |
120 | ) NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1))) NS_GNUC_NONNULL(2)__attribute__((__nonnull__(2))) NS_GNUC_NONNULL(3)__attribute__((__nonnull__(3))); |
121 | |
122 | static void HttpClose( |
123 | NsHttpTask *httpPtr |
124 | ) NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1))); |
125 | |
126 | static void HttpCancel( |
127 | NsHttpTask *httpPtr |
128 | ) NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1))); |
129 | |
130 | static int HttpAppendContent( |
131 | NsHttpTask *httpPtr, |
132 | const char *buffer, |
133 | size_t size |
134 | ) NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1))) NS_GNUC_NONNULL(2)__attribute__((__nonnull__(2))); |
135 | |
136 | static int HttpAppendChunked( |
137 | NsHttpTask *httpPtr, |
138 | const char *buffer, |
139 | size_t size |
140 | ) NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1))) NS_GNUC_NONNULL(2)__attribute__((__nonnull__(2))); |
141 | |
142 | static int HttpAppendBuffer( |
143 | NsHttpTask *httpPtr, |
144 | const char *buffer, |
145 | size_t size |
146 | ) NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1))) NS_GNUC_NONNULL(2)__attribute__((__nonnull__(2))); |
147 | |
148 | static int HttpAppendRawBuffer( |
149 | NsHttpTask *httpPtr, |
150 | const char *buffer, |
151 | size_t size |
152 | ) NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1))) NS_GNUC_NONNULL(2)__attribute__((__nonnull__(2))); |
153 | |
154 | static Ns_ReturnCode HttpWaitForSocketEvent( |
155 | NS_SOCKETint sock, |
156 | short events, |
157 | Ns_Time *timeoutPtr |
158 | ); |
159 | |
160 | static void HttpAddInfo( |
161 | NsHttpTask *httpPtr, |
162 | const char *key, |
163 | const char *value |
164 | ) NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1))) NS_GNUC_NONNULL(2)__attribute__((__nonnull__(2))) NS_GNUC_NONNULL(2)__attribute__((__nonnull__(2))); |
165 | |
166 | static void HttpCheckHeader( |
167 | NsHttpTask *httpPtr |
168 | ) NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1))); |
169 | |
170 | static Ns_ReturnCode HttpCheckSpool( |
171 | NsHttpTask *httpPtr |
172 | ) NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1))); |
173 | |
174 | static ssize_t HttpTaskSend( |
175 | const NsHttpTask *httpPtr, |
176 | const void *buffer, |
177 | size_t length |
178 | ) NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1))) NS_GNUC_NONNULL(2)__attribute__((__nonnull__(2))); |
179 | |
180 | static ssize_t HttpTaskRecv( |
181 | const NsHttpTask *httpPtr, |
182 | char *buffer, |
183 | size_t length, |
184 | Ns_SockState *statePtr |
185 | ) NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1))) NS_GNUC_NONNULL(2)__attribute__((__nonnull__(2))); |
186 | |
187 | static int HttpCutChannel( |
188 | Tcl_Interp *interp, |
189 | Tcl_Channel chan |
190 | ) NS_GNUC_NONNULL(2)__attribute__((__nonnull__(2))); |
191 | |
192 | static void HttpSpliceChannel( |
193 | Tcl_Interp *interp, |
194 | Tcl_Channel chan |
195 | ) NS_GNUC_NONNULL(2)__attribute__((__nonnull__(2))); |
196 | |
197 | static void HttpSpliceChannels( |
198 | Tcl_Interp *interp, |
199 | NsHttpTask *httpPtr |
200 | ) NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1))) NS_GNUC_NONNULL(2)__attribute__((__nonnull__(2))); |
201 | |
202 | static int HttpGetResult( |
203 | Tcl_Interp *interp, |
204 | NsHttpTask *httpPtr |
205 | ) NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1))) NS_GNUC_NONNULL(2)__attribute__((__nonnull__(2))); |
206 | |
207 | static void HttpDoneCallback( |
208 | NsHttpTask *httpPtr |
209 | ) NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1))); |
210 | |
211 | static void HttpClientLogWrite( |
212 | const NsHttpTask *httpPtr, |
213 | const char *causeString |
214 | ) NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1))) NS_GNUC_NONNULL(2)__attribute__((__nonnull__(2))); |
215 | |
216 | static NS_SOCKETint HttpTunnel( |
217 | NsInterp *itPtr, |
218 | const char *proxyhost, |
219 | unsigned short proxyport, |
220 | const char *host, |
221 | unsigned short port, |
222 | const Ns_Time *timeout |
223 | ) NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1))) NS_GNUC_NONNULL(2)__attribute__((__nonnull__(2))) NS_GNUC_NONNULL(4)__attribute__((__nonnull__(4))); |
224 | |
225 | static Ns_LogCallbackProc HttpClientLogOpen; |
226 | static Ns_LogCallbackProc HttpClientLogClose; |
227 | static Ns_LogCallbackProc HttpClientLogRoll; |
228 | static Ns_SchedProc SchedLogRollCallback; |
229 | static Ns_ArgProc SchedLogArg; |
230 | |
231 | static Ns_TaskProc HttpProc; |
232 | |
233 | /* |
234 | * Function implementing the Tcl interface. |
235 | */ |
236 | static Tcl_ObjCmdProc HttpCancelObjCmd; |
237 | static Tcl_ObjCmdProc HttpCleanupObjCmd; |
238 | static Tcl_ObjCmdProc HttpListObjCmd; |
239 | static Tcl_ObjCmdProc HttpStatsObjCmd; |
240 | static Tcl_ObjCmdProc HttpQueueObjCmd; |
241 | static Tcl_ObjCmdProc HttpRunObjCmd; |
242 | static Tcl_ObjCmdProc HttpWaitObjCmd; |
243 | |
244 | static NsHttpParseProc ParseCRProc; |
245 | static NsHttpParseProc ParseLFProc; |
246 | static NsHttpParseProc ParseLengthProc; |
247 | static NsHttpParseProc ChunkInitProc; |
248 | static NsHttpParseProc ParseBodyProc; |
249 | static NsHttpParseProc TrailerInitProc; |
250 | static NsHttpParseProc ParseTrailerProc; |
251 | static NsHttpParseProc ParseEndProc; |
252 | |
253 | static char* SkipDigits(char *chars) NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1))); |
254 | |
255 | /* |
256 | * Callbacks for the chunked-encoding state machine |
257 | * to parse variable number of chunks. |
258 | */ |
259 | static NsHttpParseProc* ChunkParsers[] = { |
260 | &ChunkInitProc, |
261 | &ParseLengthProc, |
262 | &ParseCRProc, |
263 | &ParseLFProc, |
264 | &ParseBodyProc, |
265 | &ParseCRProc, |
266 | &ParseLFProc, |
267 | NULL((void*)0) |
268 | }; |
269 | |
270 | /* |
271 | * Callbacks for the chunked-encoding parse machine |
272 | * to parse variable number of optional trailers. |
273 | */ |
274 | static NsHttpParseProc* TrailerParsers[] = { |
275 | &TrailerInitProc, |
276 | &ParseTrailerProc, |
277 | &ParseCRProc, |
278 | &ParseLFProc, |
279 | NULL((void*)0) |
280 | }; |
281 | |
282 | /* |
283 | * Callbacks for the chunked-encoding parse machine |
284 | * to parse terminating frame (CRLF sequence). |
285 | */ |
286 | static NsHttpParseProc* EndParsers[] = { |
287 | &ParseCRProc, |
288 | &ParseLFProc, |
289 | &ParseEndProc, |
290 | NULL((void*)0) |
291 | }; |
292 | |
293 | |
294 | /* |
295 | *---------------------------------------------------------------------- |
296 | * |
297 | * NsInitLog -- |
298 | * |
299 | * Initialize the log API and TLS slot. |
300 | * |
301 | * Results: |
302 | * None. |
303 | * |
304 | * Side effects: |
305 | * None. |
306 | * |
307 | *---------------------------------------------------------------------- |
308 | */ |
309 | |
310 | void |
311 | NsInitHttp(NsServer *servPtr) |
312 | { |
313 | const char *path; |
314 | |
315 | NS_NONNULL_ASSERT(servPtr != NULL)((void) (0)); |
316 | |
317 | Ns_MutexInit(&servPtr->httpclient.lock); |
318 | Ns_MutexSetName2(&servPtr->httpclient.lock, "httpclientlog", servPtr->server); |
319 | |
320 | path = Ns_ConfigSectionPath(NULL((void*)0), servPtr->server, NULL((void*)0), "httpclient", (char *)0L); |
321 | servPtr->httpclient.logging = Ns_ConfigBool(path, "logging", NS_FALSE0); |
322 | |
323 | if (servPtr->httpclient.logging) { |
324 | const char *filename; |
325 | Tcl_DString defaultLogFileName; |
326 | |
327 | Tcl_DStringInit(&defaultLogFileName); |
328 | filename = Ns_ConfigString(path, "logfile", NULL((void*)0)); |
329 | if (filename == NULL((void*)0)) { |
330 | Tcl_DStringAppend(&defaultLogFileName, "httpclient-", 11); |
331 | Tcl_DStringAppend(&defaultLogFileName, servPtr->server, -1); |
332 | Tcl_DStringAppend(&defaultLogFileName, ".log", 10); |
333 | filename = defaultLogFileName.string; |
334 | } |
335 | |
336 | if (Ns_PathIsAbsolute(filename) == NS_TRUE1) { |
337 | servPtr->httpclient.logFileName = ns_strdup(filename); |
338 | } else { |
339 | Tcl_DString ds; |
340 | |
341 | Tcl_DStringInit(&ds); |
342 | (void) Ns_HomePath(&ds, "logs", "/", filename, (char *)0L); |
343 | servPtr->httpclient.logFileName = Ns_DStringExport(&ds); |
344 | } |
345 | Tcl_DStringFree(&defaultLogFileName); |
346 | servPtr->httpclient.logRollfmt = ns_strcopy(Ns_ConfigGetValue(path, "logrollfmt")); |
347 | servPtr->httpclient.logMaxbackup = Ns_ConfigIntRange(path, "logmaxbackup", |
348 | 100, 1, INT_MAX2147483647); |
349 | |
350 | HttpClientLogOpen(servPtr); |
351 | |
352 | /* |
353 | * Schedule various log roll and shutdown options. |
354 | */ |
355 | |
356 | if (Ns_ConfigBool(path, "logroll", NS_TRUE1)) { |
357 | int hour = Ns_ConfigIntRange(path, "logrollhour", 0, 0, 23); |
358 | |
359 | Ns_ScheduleDaily(SchedLogRollCallback, servPtr, 0u, |
360 | hour, 0, NULL((void*)0)); |
361 | } |
362 | if (Ns_ConfigBool(path, "logrollonsignal", NS_FALSE0)) { |
363 | Ns_RegisterAtSignal((Ns_Callback *)(ns_funcptr_t)SchedLogRollCallback, servPtr); |
364 | } |
365 | |
366 | Ns_RegisterProcInfo((ns_funcptr_t)SchedLogRollCallback, "httpclientlog:roll", SchedLogArg); |
367 | |
368 | } else { |
369 | servPtr->httpclient.fd = NS_INVALID_FD(-1); |
370 | servPtr->httpclient.logFileName = NULL((void*)0); |
371 | } |
372 | } |
373 | |
374 | /* |
375 | *---------------------------------------------------------------------- |
376 | * |
377 | * SchedLogRollCallback -- |
378 | * |
379 | * Callback for scheduled procedure to roll the client logfile. |
380 | * |
381 | * Results: |
382 | * None |
383 | * |
384 | * Side effects: |
385 | * Rolling the client logfile when configured. |
386 | * |
387 | *---------------------------------------------------------------------- |
388 | */ |
389 | static void |
390 | SchedLogRollCallback(void *arg, int UNUSED(id)UNUSED_id __attribute__((__unused__))) |
391 | { |
392 | NsServer *servPtr = (NsServer *)arg; |
393 | |
394 | Ns_Log(Notice, "httpclient: scheduled callback '%s'", |
395 | servPtr->httpclient.logFileName); |
396 | |
397 | HttpClientLogRoll(servPtr); |
398 | } |
399 | |
400 | /* |
401 | *---------------------------------------------------------------------- |
402 | * |
403 | * SchedLogArg -- |
404 | * |
405 | * Copy log filename as argument for callback introspection queries. |
406 | * |
407 | * Results: |
408 | * None. |
409 | * |
410 | * Side effects: |
411 | * None. |
412 | * |
413 | *---------------------------------------------------------------------- |
414 | */ |
415 | |
416 | static void |
417 | SchedLogArg(Tcl_DString *dsPtr, const void *arg) |
418 | { |
419 | const NsServer *servPtr = (NsServer *)arg; |
420 | |
421 | Tcl_DStringAppendElement(dsPtr, servPtr->httpclient.logFileName); |
422 | } |
423 | |
424 | /* |
425 | *---------------------------------------------------------------------- |
426 | * |
427 | * HttpClientLogRoll -- |
428 | * |
429 | * Rolling function for the client logfile. |
430 | * |
431 | * Results: |
432 | * None |
433 | * |
434 | * Side effects: |
435 | * Rolling the client logfile when configured. |
436 | * |
437 | *---------------------------------------------------------------------- |
438 | */ |
439 | static Ns_ReturnCode |
440 | HttpClientLogRoll(void *arg) |
441 | { |
442 | Ns_ReturnCode status = NS_OK; |
443 | NsServer *servPtr = (NsServer *)arg; |
444 | |
445 | Ns_Log(Notice, "httpclient: client roll '%s' (logging %d)", |
446 | servPtr->httpclient.logFileName, servPtr->httpclient.logging); |
447 | |
448 | if (servPtr->httpclient.logging) { |
449 | status = Ns_RollFileCondFmt(HttpClientLogOpen, HttpClientLogClose, servPtr, |
450 | servPtr->httpclient.logFileName, |
451 | servPtr->httpclient.logRollfmt, |
452 | servPtr->httpclient.logMaxbackup); |
453 | } |
454 | return status; |
455 | } |
456 | |
457 | /* |
458 | *---------------------------------------------------------------------- |
459 | * |
460 | * HttpClientLogOpen -- |
461 | * |
462 | * Function for opening the client logfile. This function is only called, |
463 | * when logging is configured. |
464 | * |
465 | * Results: |
466 | * NS_OK, NS_ERROR |
467 | * |
468 | * Side effects: |
469 | * Opening the client logfile. |
470 | * |
471 | *---------------------------------------------------------------------- |
472 | */ |
473 | static Ns_ReturnCode |
474 | HttpClientLogOpen(void *arg) |
475 | { |
476 | Ns_ReturnCode status; |
477 | NsServer *servPtr = (NsServer *)arg; |
478 | |
479 | servPtr->httpclient.fd = ns_openopen(servPtr->httpclient.logFileName, |
480 | O_APPEND02000 | O_WRONLY01 | O_CREAT0100 | O_CLOEXEC02000000, |
481 | 0644); |
482 | if (servPtr->httpclient.fd == NS_INVALID_FD(-1)) { |
483 | Ns_Log(Error, "httpclient: error '%s' opening '%s'", |
484 | strerror(errno(*__errno_location ())), servPtr->httpclient.logFileName); |
485 | status = NS_ERROR; |
486 | } else { |
487 | Ns_Log(Notice, "httpclient: logfile '%s' opened", |
488 | servPtr->httpclient.logFileName); |
489 | status = NS_OK; |
490 | } |
491 | return status; |
492 | } |
493 | |
494 | /* |
495 | *---------------------------------------------------------------------- |
496 | * |
497 | * HttpClientLogClose -- |
498 | * |
499 | * Function for closing the client logfile when configured. |
500 | * |
501 | * Results: |
502 | * NS_OK, NS_ERROR |
503 | * |
504 | * Side effects: |
505 | * Closing the client logfile when configured. |
506 | * |
507 | *---------------------------------------------------------------------- |
508 | */ |
509 | static Ns_ReturnCode |
510 | HttpClientLogClose(void *arg) |
511 | { |
512 | Ns_ReturnCode status = NS_OK; |
513 | NsServer *servPtr = (NsServer *)arg; |
514 | |
515 | if (servPtr->httpclient.fd != NS_INVALID_FD(-1)) { |
516 | Ns_Log(Notice, "httpclient: logfile '%s' try to close (fd %d)", |
517 | servPtr->httpclient.logFileName, servPtr->httpclient.fd); |
518 | |
519 | ns_closeclose(servPtr->httpclient.fd); |
520 | servPtr->httpclient.fd = NS_INVALID_FD(-1); |
521 | Ns_Log(Notice, "httpclient: logfile '%s' closed", |
522 | servPtr->httpclient.logFileName); |
523 | } |
524 | |
525 | return status; |
526 | } |
527 | |
528 | /* |
529 | *---------------------------------------------------------------------- |
530 | * |
531 | * NsStopHttp -- |
532 | * |
533 | * Function to be called, when the server shuts down. |
534 | * |
535 | * Results: |
536 | * None |
537 | * |
538 | * Side effects: |
539 | * Closing the client logfile when configured. |
540 | * |
541 | *---------------------------------------------------------------------- |
542 | */ |
543 | void |
544 | NsStopHttp(NsServer *servPtr) |
545 | { |
546 | NS_NONNULL_ASSERT(servPtr != NULL)((void) (0)); |
547 | |
548 | (void)HttpClientLogClose(servPtr); |
549 | } |
550 | |
551 | |
552 | /* |
553 | *---------------------------------------------------------------------- |
554 | * |
555 | * SkipDigits -- |
556 | * |
557 | * Helper function of Ns_HttpParseHost() to skip digits in a string. |
558 | * |
559 | * Results: |
560 | * First non-digit character. |
561 | * |
562 | * Side effects: |
563 | * none |
564 | * |
565 | *---------------------------------------------------------------------- |
566 | */ |
567 | static char* |
568 | SkipDigits(char *chars) |
569 | { |
570 | for (; *chars >= '0' && *chars <= '9'; chars++) { |
571 | ; |
572 | } |
573 | return chars; |
574 | } |
575 | |
576 | |
577 | |
578 | /* |
579 | *---------------------------------------------------------------------- |
580 | * |
581 | * Ns_HttpParseHost2, Ns_HttpParseHost -- |
582 | * |
583 | * Obtain the hostname from a writable string |
584 | * using syntax as specified in RFC 3986 section 3.2.2. |
585 | * |
586 | * Examples: |
587 | * |
588 | * [2001:db8:1f70::999:de8:7648:6e8]:8000 (IP-literal notation) |
589 | * openacs.org:80 (reg-name notation) |
590 | * |
591 | * Ns_HttpParseHost() is the legacy version of Ns_HttpParseHost2(). |
592 | * |
593 | * Results: |
594 | * Boolean value indicating success. |
595 | * |
596 | * In addition, parts of the parsed content is returned via the |
597 | * provided pointers: |
598 | * |
599 | * - If a port is indicated after the hostname, the "portStart" |
600 | * will contain a string starting with ":", otherwise NULL. |
601 | * |
602 | * - If "hostStart" is non-null, a pointer will point to the |
603 | * hostname, which will be terminated by '\0' in case of an IPv6 |
604 | * address in IP-literal notation. |
605 | * |
606 | * Note: Ns_HttpParseHost2 can be used to parse empty host/port |
607 | * values. To detect these cases, use a test like |
608 | * |
609 | * if (hostParsedOk && hostString != end && hostStart != portStart) ... |
610 | * |
611 | * Side effects: |
612 | * May write NUL character '\0' into the passed hostString. |
613 | * |
614 | *---------------------------------------------------------------------- |
615 | */ |
616 | void |
617 | Ns_HttpParseHost( |
618 | char *hostString, |
619 | char **hostStart, |
620 | char **portStart |
621 | ) { |
622 | char *end; |
623 | |
624 | NS_NONNULL_ASSERT(hostString != NULL)((void) (0)); |
625 | NS_NONNULL_ASSERT(portStart != NULL)((void) (0)); |
626 | |
627 | (void) Ns_HttpParseHost2(hostString, NS_FALSE0, hostStart, portStart, &end); |
628 | if (*portStart != NULL((void*)0)) { |
629 | /* |
630 | * The old version was returning in portStart the position of the |
631 | * character BEFORE the port (usually ':'). So, keep compatibility. |
632 | */ |
633 | *portStart = *portStart-1; |
634 | } |
635 | } |
636 | |
637 | bool_Bool |
638 | Ns_HttpParseHost2( |
639 | char *hostString, |
640 | bool_Bool strict, |
641 | char **hostStart, |
642 | char **portStart, |
643 | char **end |
644 | ) { |
645 | bool_Bool ipLiteral = NS_FALSE0, success = NS_TRUE1; |
646 | |
647 | /* |
648 | * RFC 3986 defines |
649 | * |
650 | * reg-name = *( unreserved / pct-encoded / sub-delims ) |
651 | * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" |
652 | * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" |
653 | * / "*" / "+" / "," / ";" / "=" |
654 | * |
655 | * ALPHA = (%41-%5A and %61-%7A) |
656 | * DIGIT = (%30-%39), |
657 | * hyphen (%2D), period (%2E), underscore (%5F), tilde (%7E) |
658 | * exclam (%21) dollar (%24) amp (%26) singlequote (%27) |
659 | * lparen (%28) lparen (%29) asterisk (%2A) plus (%2B) |
660 | * comma (%2C) semicolon (%3B) equals (%3D) |
661 | * |
662 | * However, errata #4942 of RFC 3986 says: |
663 | * |
664 | * reg-name = *( unreserved / pct-encoded / "-" / ".") |
665 | * |
666 | * A reg-name consists of a sequence of domain labels separated by ".", |
667 | * each domain label starting and ending with an alphanumeric character |
668 | * and possibly also containing "-" characters. The rightmost domain |
669 | * label of a fully qualified domain name in DNS may be followed by a |
670 | * single "." and should be if it is necessary to distinguish between the |
671 | * complete domain name and some local domain. |
672 | * |
673 | * Percent-encoded is just checked by the character range, but does not |
674 | * check the two following (number) chars. |
675 | * |
676 | * percent (%25) ... for percent-encoded |
677 | */ |
678 | static const bool_Bool regname_table[256] = { |
679 | /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ |
680 | /* 0x00 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
681 | /* 0x10 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
682 | /* 0x20 */ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, |
683 | /* 0x30 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, |
684 | /* 0x40 */ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
685 | /* 0x50 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, |
686 | /* 0x60 */ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
687 | /* 0x70 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, |
688 | /* 0x80 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
689 | /* 0x90 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
690 | /* 0xa0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
691 | /* 0xb0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
692 | /* 0xc0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
693 | /* 0xd0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
694 | /* 0xe0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
695 | /* 0xf0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 |
696 | }; |
697 | |
698 | /* |
699 | * Host name delimiters ":/?#" and NUL |
700 | */ |
701 | static const bool_Bool delimiter_table[256] = { |
702 | /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ |
703 | /* 0x00 */ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
704 | /* 0x10 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
705 | /* 0x20 */ 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, |
706 | /* 0x30 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, |
707 | /* 0x40 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
708 | /* 0x50 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
709 | /* 0x60 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
710 | /* 0x70 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
711 | /* 0x80 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
712 | /* 0x90 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
713 | /* 0xa0 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
714 | /* 0xb0 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
715 | /* 0xc0 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
716 | /* 0xd0 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
717 | /* 0xe0 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
718 | /* 0xf0 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 |
719 | }; |
720 | |
721 | NS_NONNULL_ASSERT(hostString != NULL)((void) (0)); |
722 | NS_NONNULL_ASSERT(portStart != NULL)((void) (0)); |
723 | NS_NONNULL_ASSERT(end != NULL)((void) (0)); |
724 | |
725 | /* |
726 | * RFC 3986 defines |
727 | * |
728 | * host = IP-literal / IPv4address / reg-name |
729 | * IP-literal = "[" ( IPv6address / IPvFuture ) "]" |
730 | */ |
731 | if (*hostString == '[') { |
732 | char *p; |
733 | |
734 | /* |
735 | * This looks like an address in IP-literal notation in square brackets. |
736 | */ |
737 | p = strchr(hostString + 1, INTCHAR(']')((int)((unsigned char)((']'))))); |
738 | if (p != NULL((void*)0)) { |
739 | ipLiteral = NS_TRUE1; |
740 | |
741 | /* |
742 | * Zero-byte terminate the IP-literal if hostStart is given. |
743 | */ |
744 | if (hostStart != NULL((void*)0)) { |
745 | *p = '\0'; |
746 | *hostStart = hostString + 1; |
747 | } |
748 | p++; |
749 | if (*p == ':') { |
750 | *(p++) = '\0'; |
751 | *portStart = p; |
752 | *end = SkipDigits(p); |
753 | } else { |
754 | *portStart = NULL((void*)0); |
755 | *end = p; |
756 | } |
757 | /*fprintf(stderr, "==== IP literal portStart '%s' end '%s'\n", *portStart, *end);*/ |
758 | } else { |
759 | /* |
760 | * There is no closing square bracket |
761 | */ |
762 | success = NS_FALSE0; |
763 | *portStart = NULL((void*)0); |
764 | if (hostStart != NULL((void*)0)) { |
765 | *hostStart = NULL((void*)0); |
766 | } |
767 | *end = p; |
768 | } |
769 | } |
770 | if (success && !ipLiteral) { |
771 | char *p; |
772 | |
773 | /* |
774 | * Still to handle from the RFC 3986 "host" rule: |
775 | * |
776 | * host = .... / IPv4address / reg-name |
777 | * |
778 | * Character-wise, IPv4address is a special case of reg-name. |
779 | * |
780 | * reg-name = *( unreserved / pct-encoded / sub-delims ) |
781 | * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" |
782 | * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" |
783 | * / "*" / "+" / "," / ";" / "=" |
784 | * |
785 | * However: errata #4942 of RFC 3986 says: |
786 | * |
787 | * reg-name = *( unreserved / pct-encoded / "-" / ".") |
788 | * |
789 | * which is more in sync with reality. In the errata, the two |
790 | * explicitly mentioned characters are not needed, since these are |
791 | * already part of "unreserved". Probably, there are characters in |
792 | * "unreserved", which are not desired either. |
793 | * |
794 | * RFC 3986 sec 3.2: The authority component is preceded by a double |
795 | * slash ("//") and is terminated by the next slash ("/"), question |
796 | * mark ("?"), or number sign ("#") character, or by the end of the |
797 | * URI. |
798 | * |
799 | */ |
800 | |
801 | if (strict) { |
802 | /* |
803 | * Use the table based on regname + errata in RFC 3986. |
804 | */ |
805 | for (p = hostString; regname_table[UCHAR(*p)((unsigned char)(*p))]; p++) { |
806 | ; |
807 | } |
808 | } else { |
809 | /* |
810 | * Just scan for the bare necessity based on delimiters. |
811 | */ |
812 | for (p = hostString; delimiter_table[UCHAR(*p)((unsigned char)(*p))]; p++) { |
813 | ; |
814 | } |
815 | |
816 | } |
817 | /* |
818 | * The host is not allowed to start with a dot ("dots are separators |
819 | * for labels"), and it has to be at least one character long. |
820 | * |
821 | * Colon is not part of the allowed characters in reg-name, so we can |
822 | * use it to determine the (optional) port. |
823 | * |
824 | */ |
825 | success = (*hostString != '.' |
826 | && (*p == '\0' || *p == ':' || *p == '/' || *p == '?' || *p == '#')); |
827 | if (*p == ':') { |
828 | *(p++) = '\0'; |
829 | *portStart = p; |
830 | *end = SkipDigits(p); |
831 | } else { |
832 | *portStart = NULL((void*)0); |
833 | *end = p; |
834 | } |
835 | |
836 | /* fprintf(stderr, "==== p %.2x, success %d '%s'\n", *p, success, hostString); */ |
837 | |
838 | if (hostStart != NULL((void*)0)) { |
839 | *hostStart = hostString; |
840 | } |
841 | } |
842 | |
843 | /* |
844 | * When a port is found, make sure, the port is at least one digit. |
845 | * We could consider making the test only in the non-strict case, |
846 | * but it is hard to believe that zero-byte ports make sense in any |
847 | * scenario. |
848 | */ |
849 | if (success && *portStart != NULL((void*)0)) { |
850 | success = (*portStart != *end); |
851 | } |
852 | |
853 | return success; |
854 | } |
855 | |
856 | |
857 | /* |
858 | *---------------------------------------------------------------------- |
859 | * |
860 | * Ns_HttpLocationString -- |
861 | * |
862 | * Build an HTTP location string following the IP literation |
863 | * notation as in RFC 3986 section 3.2.2 in the provided |
864 | * Tcl_DString. In case protoString is non-null, prepend |
865 | * the protocol. In case port != defPort, append the port. |
866 | * |
867 | * Results: |
868 | * Location strings such as e.g. |
869 | * [2001:db8:1f70::999:de8:7648:6e8]:8000 (IP-literal notation) |
870 | * https://openacs.org (reg-name notation) |
871 | * |
872 | * Side effects: |
873 | * Modifies passed Tcl_DString |
874 | * |
875 | *---------------------------------------------------------------------- |
876 | */ |
877 | |
878 | char * |
879 | Ns_HttpLocationString( |
880 | Tcl_DString *dsPtr, |
881 | const char *protoString, |
882 | const char *hostString, |
883 | unsigned short port, |
884 | unsigned short defPort |
885 | ) { |
886 | NS_NONNULL_ASSERT(dsPtr != NULL)((void) (0)); |
887 | NS_NONNULL_ASSERT(hostString != NULL)((void) (0)); |
888 | |
889 | if (protoString != NULL((void*)0)) { |
890 | Ns_DStringVarAppend(dsPtr, protoString, "://", (char *)0L); |
891 | } |
892 | if (port == 0 && defPort == 0) { |
893 | /* |
894 | * We assume, that the host contains already a port (as provided from |
895 | * the host header field), and all we have to do is to prepend the |
896 | * protocol prefix. |
897 | */ |
898 | Ns_DStringVarAppend(dsPtr, hostString, (char *)0L); |
899 | } else { |
900 | if (strchr(hostString, INTCHAR(':')((int)((unsigned char)((':'))))) != NULL((void*)0)) { |
901 | Ns_DStringVarAppend(dsPtr, "[", hostString, "]", (char *)0L); |
902 | } else { |
903 | Ns_DStringVarAppend(dsPtr, hostString, (char *)0L); |
904 | } |
905 | if (port != defPort) { |
906 | (void) Ns_DStringPrintf(dsPtr, ":%d", port); |
907 | } |
908 | } |
909 | |
910 | return dsPtr->string; |
911 | } |
912 | |
913 | |
914 | /* |
915 | *---------------------------------------------------------------------- |
916 | * |
917 | * Ns_HttpMessageParse -- |
918 | * |
919 | * Parse an HTTP message (response line and headers). |
920 | * The headers are returned into the provided Ns_Set, |
921 | * while the rest is returned via output args. |
922 | * |
923 | * Results: |
924 | * Ns_ReturnCode |
925 | * |
926 | * Side effects: |
927 | * None. |
928 | * |
929 | *---------------------------------------------------------------------- |
930 | */ |
931 | |
932 | Ns_ReturnCode |
933 | Ns_HttpMessageParse( |
934 | char *message, |
935 | size_t size, |
936 | Ns_Set *hdrPtr, |
937 | int *majorPtr, |
938 | int *minorPtr, |
939 | int *statusPtr, |
940 | char **payloadPtr |
941 | ) { |
942 | Ns_ReturnCode status = NS_OK; |
943 | int items, major, minor; |
944 | |
945 | NS_NONNULL_ASSERT(hdrPtr != NULL)((void) (0)); |
946 | NS_NONNULL_ASSERT(message != NULL)((void) (0)); |
947 | NS_NONNULL_ASSERT(statusPtr != NULL)((void) (0)); |
948 | |
949 | if (majorPtr == NULL((void*)0)) { |
950 | majorPtr = &major; |
951 | } |
952 | if (minorPtr == NULL((void*)0)) { |
953 | minorPtr = &minor; |
954 | } |
955 | /* |
956 | * If provided, set *payloadPtr always to a sensible value. |
957 | * And... risk the memory leak! |
958 | */ |
959 | if (payloadPtr != NULL((void*)0)) { |
960 | *payloadPtr = NULL((void*)0); |
961 | } |
962 | |
963 | items = sscanf(message, "HTTP/%2d.%2d %3d", majorPtr, minorPtr, statusPtr); |
964 | if (items != 3) { |
965 | status = NS_ERROR; |
966 | } else { |
967 | char *p, *eol; |
968 | int firsthdr = 1; |
969 | size_t parsed; |
970 | |
971 | p = message; |
972 | while ((eol = strchr(p, INTCHAR('\n')((int)((unsigned char)(('\n')))))) != NULL((void*)0)) { |
973 | size_t len; |
974 | |
975 | *eol++ = '\0'; |
976 | len = (size_t)((eol-p)-1); |
977 | |
978 | if (len > 0u && p[len - 1u] == '\r') { |
979 | p[len - 1u] = '\0'; |
980 | } |
981 | if (firsthdr != 0) { |
982 | if (hdrPtr->name != NULL((void*)0)) { |
983 | ns_free((void *)hdrPtr->name); |
984 | } |
985 | hdrPtr->name = ns_strdup(p); |
986 | firsthdr = 0; |
987 | } else if (len < 2 || Ns_ParseHeader(hdrPtr, p, NULL((void*)0), ToLower, NULL((void*)0)) != NS_OK) { |
988 | break; |
989 | } |
990 | p = eol; |
991 | } |
992 | parsed = (size_t)(p - message); |
993 | |
994 | Ns_Log(Ns_LogRequestDebug, "Ns_ParseHeader <%s> len %" PRIuz"zu" " parsed %" |
995 | PRIuz"zu", message, size, parsed); |
996 | |
997 | if (payloadPtr != NULL((void*)0) && (size - parsed) >= 2u) { |
998 | p += 2; |
999 | *payloadPtr = p; |
1000 | } |
1001 | } |
1002 | |
1003 | return status; |
1004 | } |
1005 | |
1006 | |
1007 | /* |
1008 | *---------------------------------------------------------------------- |
1009 | * |
1010 | * NsTclHttpObjCmd -- |
1011 | * |
1012 | * Implements "ns_http". This caommand is the general interface for |
1013 | * handling HTTP client requests. |
1014 | * |
1015 | * Results: |
1016 | * Standard Tcl result. |
1017 | * |
1018 | * Side effects: |
1019 | * Depens on the subcommand. |
1020 | * |
1021 | *---------------------------------------------------------------------- |
1022 | */ |
1023 | |
1024 | int |
1025 | NsTclHttpObjCmd( |
1026 | ClientData clientData, |
1027 | Tcl_Interp *interp, |
1028 | int objc, |
1029 | Tcl_Obj *const* objv |
1030 | ) { |
1031 | const Ns_SubCmdSpec subcmds[] = { |
1032 | {"cancel", HttpCancelObjCmd}, |
1033 | {"cleanup", HttpCleanupObjCmd}, |
1034 | {"list", HttpListObjCmd}, |
1035 | {"queue", HttpQueueObjCmd}, |
1036 | {"run", HttpRunObjCmd}, |
1037 | {"stats", HttpStatsObjCmd}, |
1038 | {"wait", HttpWaitObjCmd}, |
1039 | {NULL((void*)0), NULL((void*)0)} |
1040 | }; |
1041 | |
1042 | return Ns_SubcmdObjv(subcmds, clientData, interp, objc, objv); |
1043 | } |
1044 | |
1045 | |
1046 | /* |
1047 | *---------------------------------------------------------------------- |
1048 | * |
1049 | * HttpRunObjCmd |
1050 | * |
1051 | * Implements "ns_http run". |
1052 | * |
1053 | * Results: |
1054 | * Standard Tcl result. |
1055 | * |
1056 | * Side effects: |
1057 | * None. |
1058 | * |
1059 | *---------------------------------------------------------------------- |
1060 | */ |
1061 | |
1062 | static int |
1063 | HttpRunObjCmd( |
1064 | ClientData clientData, |
1065 | Tcl_Interp *UNUSED(interp)UNUSED_interp __attribute__((__unused__)), |
1066 | int objc, |
1067 | Tcl_Obj *const* objv |
1068 | ) { |
1069 | return HttpQueue(clientData, objc, objv, NS_TRUE1); |
1070 | } |
1071 | |
1072 | |
1073 | /* |
1074 | *---------------------------------------------------------------------- |
1075 | * |
1076 | * HttpQueueObjCmd |
1077 | * |
1078 | * Implements "ns_http queue". |
1079 | * |
1080 | * Results: |
1081 | * Standard Tcl result. |
1082 | * |
1083 | * Side effects: |
1084 | * None. |
1085 | * |
1086 | *---------------------------------------------------------------------- |
1087 | */ |
1088 | |
1089 | static int |
1090 | HttpQueueObjCmd( |
1091 | ClientData clientData, |
1092 | Tcl_Interp *UNUSED(interp)UNUSED_interp __attribute__((__unused__)), |
1093 | int objc, |
1094 | Tcl_Obj *const* objv |
1095 | ) { |
1096 | return HttpQueue(clientData, objc, objv, NS_FALSE0); |
1097 | } |
1098 | |
1099 | |
1100 | /* |
1101 | *---------------------------------------------------------------------- |
1102 | * |
1103 | * HttpWaitObjCmd -- |
1104 | * |
1105 | * Implements "ns_http wait". |
1106 | * |
1107 | * Results: |
1108 | * Standard Tcl result. |
1109 | * |
1110 | * Side effects: |
1111 | * Typically closing request. |
1112 | * |
1113 | * The current [ns_http wait] API is broken w.r.t options |
1114 | * being accepted on the command line, as some of the |
1115 | * options may influence the request processing in the |
1116 | * detached task which is running asynchronously in the |
1117 | * task thread. |
1118 | * |
1119 | * At the time of [ns_http wait] the task may have been |
1120 | * completed already, so manipulating task options at |
1121 | * this point is meaningless and error-prone. |
1122 | * |
1123 | * The "problematic" options include: |
1124 | * |
1125 | * -headers |
1126 | * Every dispatched task stores reply headers in the |
1127 | * private ns_set and this set is provided as a part |
1128 | * of the command result. Putting extra headers will |
1129 | * only copy the internal set over, thus adding nothing |
1130 | * more of a value than a waste of time. |
1131 | * |
1132 | * -spoolsize |
1133 | * This limits the size of the reply content that is |
1134 | * being stored in memory during the task processing. |
1135 | * However, the task may already handle the body at |
1136 | * the time somebody calls [ns_http wait] so changing |
1137 | * this value may have no real effect (any more). |
1138 | * |
1139 | * -outputfile |
1140 | * This, in conjunction with -spoolsize instructs the task |
1141 | * to store response content in a given file. But again, at |
1142 | * the time this command is called, the task may have been |
1143 | * completely done and the content may already sit in a |
1144 | * temporary file (name of which can be obtained by -file). |
1145 | * |
1146 | * -decompress |
1147 | * This flag tells the task to automatically decompress |
1148 | * gzip'ed content. At the time of [ns_http wait] the |
1149 | * content may have been received and left compressed |
1150 | * already, so setting this flag may have no effect. |
1151 | * |
1152 | * We should eliminate above options from the API at some time. |
1153 | * At the moment they are declared deprecated but the old |
1154 | * implementation is still there. However, be aware that it may |
1155 | * not work as you expect. |
1156 | * |
1157 | * At the same time, all of the optional variables that |
1158 | * might receive information about the wait'ed task are |
1159 | * deprecated. The command result returns a Tcl dict with |
1160 | * all of those already calculated, so there is no need |
1161 | * for extra command options any more. |
1162 | * |
1163 | *---------------------------------------------------------------------- |
1164 | */ |
1165 | |
1166 | static int |
1167 | HttpWaitObjCmd( |
1168 | ClientData clientData, |
1169 | Tcl_Interp *interp, |
1170 | int objc, |
1171 | Tcl_Obj *const* objv |
1172 | ) { |
1173 | NsInterp *itPtr = clientData; |
1174 | NsHttpTask *httpPtr = NULL((void*)0); |
1175 | |
1176 | char *id = NULL((void*)0), *outputFileName = NULL((void*)0); |
1177 | int result = TCL_OK0, decompress = 0, binary = 0; |
1178 | Tcl_WideInt spoolLimit = -1; |
1179 | Tcl_Obj *elapsedVarObj = NULL((void*)0), |
1180 | *resultVarObj = NULL((void*)0), |
1181 | *statusVarObj = NULL((void*)0), |
1182 | *fileVarObj = NULL((void*)0); |
1183 | Ns_Set *replyHeaders = NULL((void*)0); |
1184 | Ns_Time *timeoutPtr = NULL((void*)0); |
1185 | |
1186 | Ns_ObjvSpec opts[] = { |
1187 | {"-binary", Ns_ObjvBool, &binary, INT2PTR(NS_TRUE)((void *)(intptr_t)(1))}, |
1188 | {"-decompress", Ns_ObjvBool, &decompress, INT2PTR(NS_TRUE)((void *)(intptr_t)(1))}, |
1189 | {"-elapsed", Ns_ObjvObj, &elapsedVarObj, NULL((void*)0)}, |
1190 | {"-file", Ns_ObjvObj, &fileVarObj, NULL((void*)0)}, |
1191 | {"-headers", Ns_ObjvSet, &replyHeaders, NULL((void*)0)}, |
1192 | {"-outputfile", Ns_ObjvString, &outputFileName, NULL((void*)0)}, |
1193 | {"-result", Ns_ObjvObj, &resultVarObj, NULL((void*)0)}, |
1194 | {"-spoolsize", Ns_ObjvMemUnit, &spoolLimit, NULL((void*)0)}, |
1195 | {"-status", Ns_ObjvObj, &statusVarObj, NULL((void*)0)}, |
1196 | {"-timeout", Ns_ObjvTime, &timeoutPtr, NULL((void*)0)}, |
1197 | {NULL((void*)0), NULL((void*)0), NULL((void*)0), NULL((void*)0)} |
1198 | }; |
1199 | Ns_ObjvSpec args[] = { |
1200 | {"id", Ns_ObjvString, &id, NULL((void*)0)}, |
1201 | {NULL((void*)0), NULL((void*)0), NULL((void*)0), NULL((void*)0)} |
1202 | }; |
1203 | |
1204 | NS_NONNULL_ASSERT(itPtr != NULL)((void) (0)); |
1205 | |
1206 | if (Ns_ParseObjv(opts, args, interp, 2, objc, objv) != NS_OK) { |
1207 | result = TCL_ERROR1; |
1208 | |
1209 | } else if (HttpGet(itPtr, id, &httpPtr, NS_TRUE1) == NS_FALSE0) { |
1210 | result = TCL_ERROR1; |
1211 | |
1212 | } else { |
1213 | Ns_ReturnCode rc; |
1214 | |
1215 | /* |
1216 | * All following options are not supposed to be present here. |
1217 | * The command API should be cleansed, but for now, lets play |
1218 | * backward compatibility... |
1219 | */ |
1220 | if (replyHeaders != NULL((void*)0)) { |
1221 | Ns_Log(Warning, "ns_http_wait: -headers option is deprecated"); |
1222 | } |
1223 | if (decompress != 0) { |
1224 | Ns_Log(Warning, "ns_http_wait: ignore obsolete flag -decompress"); |
1225 | } |
1226 | |
1227 | if (binary != 0) { |
1228 | Ns_Log(Warning, "ns_http_wait: -binary option is deprecated"); |
1229 | httpPtr->flags |= NS_HTTP_FLAG_BINARY(1u<<4); |
1230 | } |
1231 | if (spoolLimit > -1) { |
1232 | Ns_Log(Warning, "ns_http_wait: -spoolsize option is deprecated"); |
1233 | httpPtr->spoolLimit = spoolLimit; |
1234 | } |
1235 | if (outputFileName != NULL((void*)0)) { |
1236 | Ns_Log(Warning, "ns_http_wait: -outputfile option is deprecated"); |
1237 | Ns_MutexLock(&httpPtr->lock); |
1238 | if (httpPtr->spoolFileName != NULL((void*)0)) { |
1239 | Ns_Log(Warning, "ns_http_wait: the -outputfile was already" |
1240 | " set in the ns_http_queue; ignored!"); |
1241 | } else { |
1242 | httpPtr->spoolFileName = ns_strdup(outputFileName); |
1243 | } |
1244 | Ns_MutexUnlock(&httpPtr->lock); |
1245 | } |
1246 | /* |
1247 | * Always decompress unless told differently. Here we do not have the |
1248 | * "-raw" option, since we do not need backward compatibility. |
1249 | */ |
1250 | httpPtr->flags |= NS_HTTP_FLAG_DECOMPRESS(1u<<0); |
1251 | |
1252 | if (elapsedVarObj != NULL((void*)0)) { |
1253 | Ns_Log(Warning, "ns_http_wait: -elapsed option is deprecated"); |
1254 | } |
1255 | if (resultVarObj != NULL((void*)0)) { |
1256 | Ns_Log(Warning, "ns_http_wait: -result option is deprecated"); |
1257 | } |
1258 | if (statusVarObj != NULL((void*)0)) { |
1259 | Ns_Log(Warning, "ns_http_wait: -status option is deprecated"); |
1260 | } |
1261 | if (fileVarObj != NULL((void*)0)) { |
1262 | Ns_Log(Warning, "ns_http_wait: -file option is deprecated"); |
1263 | } |
1264 | if (timeoutPtr == NULL((void*)0)) { |
1265 | timeoutPtr = httpPtr->timeout; |
1266 | } |
1267 | |
1268 | rc = Ns_TaskWait(httpPtr->task, timeoutPtr); |
1269 | |
1270 | if (likely(rc == NS_OK)(__builtin_expect((rc == NS_OK), 1))) { |
1271 | result = HttpGetResult(interp, httpPtr); |
1272 | } else { |
1273 | HttpCancel(httpPtr); |
1274 | Tcl_SetObjResult(interp, Tcl_NewStringObj(httpPtr->error, -1)); |
1275 | if (rc == NS_TIMEOUT) { |
1276 | Tcl_SetErrorCode(interp, errorCodeTimeoutString, (char *)0L); |
1277 | Ns_Log(Ns_LogTimeoutDebug, "ns_http request '%s' runs into timeout", |
1278 | httpPtr->url); |
1279 | HttpClientLogWrite(httpPtr, "tasktimeout"); |
1280 | } |
1281 | result = TCL_ERROR1; |
1282 | } |
1283 | |
1284 | /* |
1285 | * This part is deprecated and can be removed |
1286 | * once we go up to a next major version |
1287 | * where [ns_http wait] will accept no options. |
1288 | */ |
1289 | if (result == TCL_OK0) { |
1290 | int ii; |
1291 | Tcl_Obj *rObj, *vObj, *oObj[8]; |
1292 | |
1293 | /* |
1294 | * Pick up corresponding dictionary elements |
1295 | * and fill-in passed variables. |
1296 | */ |
1297 | oObj[0] = Tcl_NewStringObj("time", 4); |
1298 | oObj[1] = elapsedVarObj; |
1299 | |
1300 | oObj[2] = Tcl_NewStringObj("body", 4); |
1301 | oObj[3] = resultVarObj; |
1302 | |
1303 | oObj[4] = Tcl_NewStringObj("status", 6); |
1304 | oObj[5] = statusVarObj; |
1305 | |
1306 | oObj[6] = Tcl_NewStringObj("file", 4); |
1307 | oObj[7] = fileVarObj; |
1308 | |
1309 | rObj = Tcl_GetObjResult(interp); |
1310 | |
1311 | for (ii = 0; ii < 8; ii += 2) { |
1312 | Tcl_DictObjGet(interp, rObj, oObj[ii], &vObj); |
1313 | if (oObj[ii+1] != NULL((void*)0) && vObj != NULL((void*)0)) { |
1314 | if (Ns_SetNamedVar(interp, oObj[ii+1], vObj) == NS_FALSE0) { |
1315 | result = TCL_ERROR1; |
1316 | } |
1317 | } |
1318 | Tcl_DecrRefCount(oObj[ii])do { Tcl_Obj *_objPtr = (oObj[ii]); if (_objPtr->refCount-- <= 1) { TclFreeObj(_objPtr); } } while(0); |
1319 | } |
1320 | |
1321 | if (replyHeaders != NULL((void*)0)) { |
1322 | Ns_Set *headers; |
1323 | Tcl_Obj *kObj; |
1324 | |
1325 | /* |
1326 | * Merge reply headers into the user-passed set. |
1327 | */ |
1328 | kObj = Tcl_NewStringObj("headers", 7); |
1329 | Tcl_DictObjGet(interp, rObj, kObj, &vObj); |
1330 | Tcl_DecrRefCount(kObj)do { Tcl_Obj *_objPtr = (kObj); if (_objPtr->refCount-- <= 1) { TclFreeObj(_objPtr); } } while(0); |
1331 | NS_NONNULL_ASSERT(vObj != NULL)((void) (0)); |
1332 | headers = Ns_TclGetSet(interp, Tcl_GetString(vObj)); |
1333 | NS_NONNULL_ASSERT(headers != NULL)((void) (0)); |
1334 | Ns_SetMerge(replyHeaders, headers); |
1335 | } |
1336 | } |
1337 | } |
1338 | |
1339 | if (httpPtr != NULL((void*)0)) { |
1340 | HttpSpliceChannels(interp, httpPtr); |
1341 | HttpClose(httpPtr); |
1342 | } |
1343 | |
1344 | return result; |
1345 | } |
1346 | |
1347 | |
1348 | /* |
1349 | *---------------------------------------------------------------------- |
1350 | * |
1351 | * HttpCancelObjCmd -- |
1352 | * |
1353 | * Implements "ns_http cancel". |
1354 | * |
1355 | * Results: |
1356 | * Standard Tcl result. |
1357 | * |
1358 | * Side effects: |
1359 | * Typically aborting and closing request. |
1360 | * |
1361 | *---------------------------------------------------------------------- |
1362 | */ |
1363 | |
1364 | static int |
1365 | HttpCancelObjCmd( |
1366 | ClientData clientData, |
1367 | Tcl_Interp *interp, |
1368 | int objc, |
1369 | Tcl_Obj *const* objv |
1370 | ) { |
1371 | NsInterp *itPtr = clientData; |
1372 | char *idString; |
1373 | int result = TCL_OK0; |
1374 | |
1375 | Ns_ObjvSpec args[] = { |
1376 | {"id", Ns_ObjvString, &idString, NULL((void*)0)}, |
1377 | {NULL((void*)0), NULL((void*)0), NULL((void*)0), NULL((void*)0)} |
1378 | }; |
1379 | |
1380 | if (Ns_ParseObjv(NULL((void*)0), args, interp, 2, objc, objv) != NS_OK) { |
1381 | result = TCL_ERROR1; |
1382 | |
1383 | } else { |
1384 | NsHttpTask *httpPtr = NULL((void*)0); |
1385 | |
1386 | if (HttpGet(itPtr, idString, &httpPtr, NS_TRUE1) == NS_FALSE0) { |
1387 | result = TCL_ERROR1; |
1388 | } else { |
1389 | HttpCancel(httpPtr); |
1390 | HttpSpliceChannels(interp, httpPtr); |
1391 | HttpClose(httpPtr); |
1392 | } |
1393 | } |
1394 | |
1395 | return result; |
1396 | } |
1397 | |
1398 | |
1399 | /* |
1400 | *---------------------------------------------------------------------- |
1401 | * |
1402 | * HttpCleanupObjCmd |
1403 | * |
1404 | * Implements "ns_http cleanup". |
1405 | * |
1406 | * Results: |
1407 | * Standard Tcl result. |
1408 | * |
1409 | * Side effects: |
1410 | * Cancel all pending requests. |
1411 | * Dirty-close of any task-associated body/output channels. |
1412 | * |
1413 | *---------------------------------------------------------------------- |
1414 | */ |
1415 | |
1416 | static int |
1417 | HttpCleanupObjCmd( |
1418 | ClientData clientData, |
1419 | Tcl_Interp *interp, |
1420 | int objc, |
1421 | Tcl_Obj *const* objv |
1422 | ) { |
1423 | NsInterp *itPtr = clientData; |
1424 | int result = TCL_OK0; |
1425 | |
1426 | if (Ns_ParseObjv(NULL((void*)0), NULL((void*)0), interp, 2, objc, objv) != NS_OK) { |
1427 | result = TCL_ERROR1; |
1428 | |
1429 | } else { |
1430 | Tcl_HashSearch search; |
1431 | Tcl_HashEntry *hPtr; |
1432 | |
1433 | for (hPtr = Tcl_FirstHashEntry(&itPtr->httpRequests, &search); |
1434 | hPtr != NULL((void*)0); |
1435 | hPtr = Tcl_NextHashEntry(&search)) { |
1436 | NsHttpTask *httpPtr; |
1437 | char *taskName; |
1438 | |
1439 | httpPtr = (NsHttpTask *)Tcl_GetHashValue(hPtr)((hPtr)->clientData); |
1440 | assert(httpPtr != NULL)((void) (0)); |
1441 | |
1442 | taskName = Tcl_GetHashKey(&itPtr->httpRequests, hPtr)((void *) (((&itPtr->httpRequests)->keyType == (1) || (&itPtr->httpRequests)->keyType == (-1)) ? (hPtr)-> key.oneWordValue : (hPtr)->key.string)); |
1443 | |
1444 | Ns_Log(Warning, "HttpCleanup: cancel task:%s", taskName); |
1445 | |
1446 | HttpCancel(httpPtr); |
1447 | |
1448 | /* |
1449 | * Normally, channels should be re-integrated |
1450 | * into the running interp and [close]'d from |
1451 | * there. But our current cleanup semantics |
1452 | * does not allow that, so we simply and dirty |
1453 | * close the channels here. At this point they |
1454 | * should be not part of any thread (must have |
1455 | * been Tcl_Cut'ed) nor interp (must have been |
1456 | * Tcl_Unregister'ed). Failure to do so may |
1457 | * wreak havoc with our memory. |
1458 | * As with the current design, the channel must |
1459 | * have a refcount of 1 at this place, since we |
1460 | * reserved it in the HttpCutChannel() call. |
1461 | * Now we must do the reverse here, but do the |
1462 | * unregister with NULL interp just to reduce |
1463 | * the refcount. This should also implicitly |
1464 | * close the channel. If not, there is a leak. |
1465 | */ |
1466 | if (httpPtr->bodyChan != NULL((void*)0)) { |
1467 | Tcl_SpliceChannel(httpPtr->bodyChan); |
1468 | Tcl_UnregisterChannel((Tcl_Interp *)NULL((void*)0), httpPtr->bodyChan); |
1469 | httpPtr->bodyChan = NULL((void*)0); |
1470 | } |
1471 | if (httpPtr->spoolChan != NULL((void*)0)) { |
1472 | Tcl_SpliceChannel(httpPtr->spoolChan); |
1473 | Tcl_UnregisterChannel((Tcl_Interp *)NULL((void*)0), httpPtr->spoolChan); |
1474 | httpPtr->spoolChan = NULL((void*)0); |
1475 | } |
1476 | |
1477 | HttpClose(httpPtr); |
1478 | Tcl_DeleteHashEntry(hPtr); |
1479 | } |
1480 | } |
1481 | |
1482 | return result; |
1483 | } |
1484 | |
1485 | |
1486 | /* |
1487 | *---------------------------------------------------------------------- |
1488 | * |
1489 | * HttpListObjCmd |
1490 | * |
1491 | * Implements "ns_http list". |
1492 | * |
1493 | * Results: |
1494 | * Standard Tcl result. |
1495 | * |
1496 | * Side effects: |
1497 | * None. |
1498 | * |
1499 | *---------------------------------------------------------------------- |
1500 | */ |
1501 | |
1502 | static int |
1503 | HttpListObjCmd( |
1504 | ClientData clientData, |
1505 | Tcl_Interp *interp, |
1506 | int objc, |
1507 | Tcl_Obj *const* objv |
1508 | ) { |
1509 | NsInterp *itPtr = clientData; |
1510 | char *idString = NULL((void*)0); |
1511 | int result = TCL_OK0; |
1512 | Tcl_Obj *resultObj; |
1513 | Tcl_HashEntry *hPtr; |
1514 | Tcl_HashSearch search; |
1515 | |
1516 | /* |
1517 | * Syntax: ns_http list ?id? |
1518 | */ |
1519 | if (objc == 3) { |
1520 | idString = Tcl_GetString(objv[2]); |
1521 | } |
1522 | |
1523 | resultObj = Tcl_NewListObj(0, NULL((void*)0)); |
1524 | |
1525 | for (hPtr = Tcl_FirstHashEntry(&itPtr->httpRequests, &search); |
1526 | hPtr != NULL((void*)0); |
1527 | hPtr = Tcl_NextHashEntry(&search) ) { |
1528 | char *taskString = Tcl_GetHashKey(&itPtr->httpRequests, hPtr)((void *) (((&itPtr->httpRequests)->keyType == (1) || (&itPtr->httpRequests)->keyType == (-1)) ? (hPtr)-> key.oneWordValue : (hPtr)->key.string)); |
1529 | |
1530 | if (idString == NULL((void*)0) || STREQ(taskString, idString)(((*(taskString)) == (*(idString))) && (strcmp((taskString ),(idString)) == 0))) { |
1531 | const char *taskState; |
1532 | NsHttpTask *httpPtr = (NsHttpTask *)Tcl_GetHashValue(hPtr)((hPtr)->clientData); |
1533 | |
1534 | assert(httpPtr != NULL)((void) (0)); |
1535 | |
1536 | if (Ns_TaskCompleted(httpPtr->task) == NS_TRUE1) { |
1537 | taskState = "done"; |
1538 | } else if (httpPtr->error != NULL((void*)0)) { |
1539 | taskState = "error"; |
1540 | } else { |
1541 | taskState = "running"; |
1542 | } |
1543 | |
1544 | Tcl_ListObjAppendElement |
1545 | (interp, resultObj, Tcl_NewStringObj(taskString, -1)); |
1546 | |
1547 | Tcl_ListObjAppendElement |
1548 | (interp, resultObj, Tcl_NewStringObj(httpPtr->url, -1)); |
1549 | |
1550 | Tcl_ListObjAppendElement |
1551 | (interp, resultObj, Tcl_NewStringObj(taskState, -1)); |
1552 | } |
1553 | } |
1554 | |
1555 | Tcl_SetObjResult(interp, resultObj); |
1556 | |
1557 | return result; |
1558 | } |
1559 | |
1560 | |
1561 | /* |
1562 | *---------------------------------------------------------------------- |
1563 | * |
1564 | * HttpStatsObjCmd |
1565 | * |
1566 | * Implements "ns_http stats". |
1567 | * |
1568 | * Results: |
1569 | * Standard Tcl result. |
1570 | * |
1571 | * Side effects: |
1572 | * None. |
1573 | * |
1574 | *---------------------------------------------------------------------- |
1575 | */ |
1576 | |
1577 | static int |
1578 | HttpStatsObjCmd( |
1579 | ClientData clientData, |
1580 | Tcl_Interp *interp, |
1581 | int objc, |
1582 | Tcl_Obj *const* objv |
1583 | ) { |
1584 | NsInterp *itPtr = clientData; |
1585 | char *idString = NULL((void*)0); |
1586 | int result = TCL_OK0; |
1587 | Tcl_Obj *resultObj = NULL((void*)0); |
1588 | Tcl_HashEntry *hPtr; |
1589 | Tcl_HashSearch search; |
1590 | |
1591 | /* |
1592 | * Syntax: ns_http stats ?id? |
1593 | */ |
1594 | if (objc == 3) { |
1595 | idString = Tcl_GetString(objv[2]); |
1596 | } |
1597 | |
1598 | if (idString == NULL((void*)0)) { |
1599 | resultObj = Tcl_NewListObj(0, NULL((void*)0)); |
1600 | } |
1601 | |
1602 | for (hPtr = Tcl_FirstHashEntry(&itPtr->httpRequests, &search); |
1603 | hPtr != NULL((void*)0); |
1604 | hPtr = Tcl_NextHashEntry(&search)) { |
1605 | |
1606 | const char *taskString; |
1607 | |
1608 | taskString = Tcl_GetHashKey(&itPtr->httpRequests, hPtr)((void *) (((&itPtr->httpRequests)->keyType == (1) || (&itPtr->httpRequests)->keyType == (-1)) ? (hPtr)-> key.oneWordValue : (hPtr)->key.string)); |
1609 | |
1610 | if (idString == NULL((void*)0) || STREQ(taskString, idString)(((*(taskString)) == (*(idString))) && (strcmp((taskString ),(idString)) == 0))) { |
1611 | NsHttpTask *httpPtr; |
1612 | Tcl_Obj *entryObj; |
1613 | |
1614 | httpPtr = (NsHttpTask *)Tcl_GetHashValue(hPtr)((hPtr)->clientData); |
1615 | NS_NONNULL_ASSERT(httpPtr != NULL)((void) (0)); |
1616 | |
1617 | entryObj = Tcl_NewDictObj(); |
1618 | |
1619 | /* |
1620 | * Following are not being changed by the task thread |
1621 | * so we need no extra lock here. |
1622 | */ |
1623 | |
1624 | (void) Tcl_DictObjPut |
1625 | (interp, entryObj, Tcl_NewStringObj("task", 4), |
1626 | Tcl_NewStringObj(taskString, -1)); |
1627 | |
1628 | (void) Tcl_DictObjPut |
1629 | (interp, entryObj, Tcl_NewStringObj("url", 3), |
1630 | Tcl_NewStringObj(httpPtr->url, -1)); |
1631 | |
1632 | (void) Tcl_DictObjPut |
1633 | (interp, entryObj, Tcl_NewStringObj("requestlength", 13), |
1634 | Tcl_NewWideIntObj((Tcl_WideInt)httpPtr->requestLength)); |
1635 | |
1636 | /* |
1637 | * Following may be subject to change by the task thread |
1638 | * so we sync-up on the mutex. |
1639 | */ |
1640 | |
1641 | Ns_MutexLock(&httpPtr->lock); |
1642 | |
1643 | /* |
1644 | * This element is a misnomer, but we leave it for the |
1645 | * sake of backwards compatibility. Actually, this is |
1646 | * the value of the returned Content-Length header. |
1647 | */ |
1648 | (void) Tcl_DictObjPut |
1649 | (interp, entryObj, Tcl_NewStringObj("replylength", 11), |
1650 | Tcl_NewWideIntObj((Tcl_WideInt)httpPtr->replyLength)); |
1651 | |
1652 | /* |
1653 | * Counter of bytes of the request sent so far. |
1654 | * It includes all of the request (status line, headers, body). |
1655 | */ |
1656 | (void) Tcl_DictObjPut |
1657 | (interp, entryObj, Tcl_NewStringObj("sent", 4), |
1658 | Tcl_NewWideIntObj((Tcl_WideInt)httpPtr->sent)); |
1659 | |
1660 | /* |
1661 | * Counter of bytes of the reply received so far. |
1662 | * It includes all of the reply (status line, headers, body). |
1663 | */ |
1664 | (void) Tcl_DictObjPut |
1665 | (interp, entryObj, Tcl_NewStringObj("received", 8), |
1666 | Tcl_NewWideIntObj((Tcl_WideInt)httpPtr->received)); |
1667 | |
1668 | /* |
1669 | * Counter of the request body sent so far. |
1670 | */ |
1671 | (void) Tcl_DictObjPut |
1672 | (interp, entryObj, Tcl_NewStringObj("sendbodysize", 12), |
1673 | Tcl_NewWideIntObj((Tcl_WideInt)httpPtr->sendBodySize)); |
1674 | |
1675 | /* |
1676 | * Counter of processed (potentially deflated) |
1677 | * reply body received so far. |
1678 | */ |
1679 | (void) Tcl_DictObjPut |
1680 | (interp, entryObj, Tcl_NewStringObj("replybodysize", 13), |
1681 | Tcl_NewWideIntObj((Tcl_WideInt)httpPtr->replyBodySize)); |
1682 | |
1683 | /* |
1684 | * Counter of the non-processed (potentially compressed) |
1685 | * reply body received so far. |
1686 | * For compressed but not deflated reply content |
1687 | * the replybodysize and replysize will be equal. |
1688 | */ |
1689 | (void) Tcl_DictObjPut |
1690 | (interp, entryObj, Tcl_NewStringObj("replysize", 9), |
1691 | Tcl_NewWideIntObj((Tcl_WideInt)httpPtr->replySize)); |
1692 | |
1693 | Ns_MutexUnlock(&httpPtr->lock); |
1694 | |
1695 | if (resultObj == NULL((void*)0)) { |
1696 | Tcl_SetObjResult(interp, entryObj); |
1697 | } else { |
1698 | (void) Tcl_ListObjAppendElement(interp, resultObj, entryObj); |
1699 | } |
1700 | } |
1701 | } |
1702 | |
1703 | if (resultObj != NULL((void*)0)) { |
1704 | Tcl_SetObjResult(interp, resultObj); |
1705 | } |
1706 | |
1707 | return result; |
1708 | } |
1709 | |
1710 | |
1711 | /* |
1712 | *---------------------------------------------------------------------- |
1713 | * |
1714 | * HttpQueue -- |
1715 | * |
1716 | * Enqueues the HTTP task and optionally returns the taskID |
1717 | * in the interp result. This taskID can be used by other |
1718 | * commands to cancel or wait for the task to finish. |
1719 | * |
1720 | * The taskID is not returned if the "-doneCallback" option |
1721 | * is specified. In that case, the task is handled and |
1722 | * garbage collected by the thread executing the task. |
1723 | * |
1724 | * Results: |
1725 | * Standard Tcl result. |
1726 | * |
1727 | * Side effects: |
1728 | * May queue an HTTP request. |
1729 | * |
1730 | *---------------------------------------------------------------------- |
1731 | */ |
1732 | |
1733 | static int |
1734 | HttpQueue( |
1735 | NsInterp *itPtr, |
1736 | int objc, |
1737 | Tcl_Obj *const* objv, |
1738 | bool_Bool run |
1739 | ) { |
1740 | Tcl_Interp *interp; |
1741 | int result = TCL_OK0, decompress = 0, raw = 0, binary = 0; |
1742 | Tcl_WideInt spoolLimit = -1; |
1743 | int verifyCert = 0, keepHostHdr = 0; |
1744 | NsHttpTask *httpPtr = NULL((void*)0); |
1745 | char *cert = NULL((void*)0), |
1746 | *caFile = NULL((void*)0), |
1747 | *caPath = NULL((void*)0), |
1748 | *sniHostname = NULL((void*)0), |
1749 | *outputFileName = NULL((void*)0), |
1750 | *outputChanName = NULL((void*)0), |
1751 | *method = (char *)"GET", |
1752 | *url = NULL((void*)0), |
1753 | *doneCallback = NULL((void*)0), |
1754 | *bodyChanName = NULL((void*)0), |
1755 | *bodyFileName = NULL((void*)0); |
1756 | Ns_Set *requestHdrPtr = NULL((void*)0); |
1757 | Tcl_Obj *bodyObj = NULL((void*)0), *proxyObj = NULL((void*)0); |
1758 | Ns_Time *timeoutPtr = NULL((void*)0), *expirePtr = NULL((void*)0); |
1759 | Tcl_WideInt bodySize = 0; |
1760 | Tcl_Channel bodyChan = NULL((void*)0), spoolChan = NULL((void*)0); |
1761 | Ns_ObjvValueRange sizeRange = {0, LLONG_MAX9223372036854775807LL}; |
1762 | |
1763 | Ns_ObjvSpec opts[] = { |
1764 | {"-binary", Ns_ObjvBool, &binary, INT2PTR(NS_TRUE)((void *)(intptr_t)(1))}, |
1765 | {"-body", Ns_ObjvObj, &bodyObj, NULL((void*)0)}, |
1766 | {"-body_chan", Ns_ObjvString, &bodyChanName, NULL((void*)0)}, |
1767 | {"-body_file", Ns_ObjvString, &bodyFileName, NULL((void*)0)}, |
1768 | {"-body_size", Ns_ObjvWideInt, &bodySize, &sizeRange}, |
1769 | {"-cafile", Ns_ObjvString, &caFile, NULL((void*)0)}, |
1770 | {"-capath", Ns_ObjvString, &caPath, NULL((void*)0)}, |
1771 | {"-cert", Ns_ObjvString, &cert, NULL((void*)0)}, |
1772 | {"-raw", Ns_ObjvBool, &raw, INT2PTR(NS_TRUE)((void *)(intptr_t)(1))}, |
1773 | {"-decompress", Ns_ObjvBool, &decompress, INT2PTR(NS_TRUE)((void *)(intptr_t)(1))}, |
1774 | {"-donecallback", Ns_ObjvString, &doneCallback, NULL((void*)0)}, |
1775 | {"-headers", Ns_ObjvSet, &requestHdrPtr, NULL((void*)0)}, |
1776 | {"-hostname", Ns_ObjvString, &sniHostname, NULL((void*)0)}, |
1777 | {"-keep_host_header", Ns_ObjvBool, &keepHostHdr, INT2PTR(NS_TRUE)((void *)(intptr_t)(1))}, |
1778 | {"-method", Ns_ObjvString, &method, NULL((void*)0)}, |
1779 | {"-outputchan", Ns_ObjvString, &outputChanName, NULL((void*)0)}, |
1780 | {"-outputfile", Ns_ObjvString, &outputFileName, NULL((void*)0)}, |
1781 | {"-spoolsize", Ns_ObjvMemUnit, &spoolLimit, NULL((void*)0)}, |
1782 | {"-expire", Ns_ObjvTime, &expirePtr, NULL((void*)0)}, |
1783 | {"-timeout", Ns_ObjvTime, &timeoutPtr, NULL((void*)0)}, |
1784 | {"-verify", Ns_ObjvBool, &verifyCert, INT2PTR(NS_FALSE)((void *)(intptr_t)(0))}, |
1785 | {"-proxy", Ns_ObjvObj, &proxyObj, NULL((void*)0)}, |
1786 | {NULL((void*)0), NULL((void*)0), NULL((void*)0), NULL((void*)0)} |
1787 | }; |
1788 | Ns_ObjvSpec args[] = { |
1789 | {"url", Ns_ObjvString, &url, NULL((void*)0)}, |
1790 | {NULL((void*)0), NULL((void*)0), NULL((void*)0), NULL((void*)0)} |
1791 | }; |
1792 | |
1793 | NS_NONNULL_ASSERT(itPtr != NULL)((void) (0)); |
1794 | interp = itPtr->interp; |
1795 | |
1796 | if (Ns_ParseObjv(opts, args, interp, 2, objc, objv) != NS_OK) { |
1797 | result = TCL_ERROR1; |
1798 | } else if (run == NS_TRUE1 && doneCallback != NULL((void*)0)) { |
1799 | Ns_TclPrintfResult(interp, "option -doneCallback allowed only" |
1800 | " for [ns_http_queue]"); |
1801 | result = TCL_ERROR1; |
1802 | } else if (outputFileName != NULL((void*)0) && outputChanName != NULL((void*)0)) { |
1803 | Ns_TclPrintfResult(interp, "only one of -outputchan or -outputfile" |
1804 | " options are allowed"); |
1805 | result = TCL_ERROR1; |
1806 | } else if (((bodyFileName != NULL((void*)0)) + (bodyChanName != NULL((void*)0)) + (bodyObj != NULL((void*)0))) > 1) { |
1807 | Ns_TclPrintfResult(interp, "only one of -body, -body_chan or -body_file" |
1808 | " options are allowed"); |
1809 | result = TCL_ERROR1; |
1810 | } else if (unlikely(decompress != 0)(__builtin_expect((decompress != 0), 0))) { |
1811 | Ns_Log(Warning, "ignore obsolete flag -decompress"); |
1812 | } else if (raw != 1) { |
1813 | decompress = 1; |
1814 | } |
1815 | |
1816 | if (result == TCL_OK0 && bodyFileName != NULL((void*)0)) { |
1817 | struct stat bodyStat; |
1818 | |
1819 | if (Ns_Stat(bodyFileName, &bodyStat) == NS_TRUE1) { |
1820 | if (bodySize == 0) { |
1821 | bodySize = (Tcl_WideInt)bodyStat.st_size; |
1822 | } |
1823 | } else { |
1824 | Ns_TclPrintfResult(interp, "cannot stat: %s ", bodyFileName); |
1825 | result = TCL_ERROR1; |
1826 | } |
1827 | } |
1828 | |
1829 | if (result == TCL_OK0 && bodyChanName != NULL((void*)0)) { |
1830 | if (Ns_TclGetOpenChannel(interp, bodyChanName, /* write */ 0, |
1831 | /* check */ 1, &bodyChan) != TCL_OK0) { |
1832 | result = TCL_ERROR1; |
1833 | } else if (bodySize == 0) { |
1834 | bodySize = Tcl_Seek(bodyChan, 0, SEEK_END2); |
1835 | if (bodySize == -1) { |
1836 | Ns_TclPrintfResult(interp, "can't seek channel: %s", |
1837 | Tcl_ErrnoMsg(Tcl_GetErrno())); |
1838 | result = TCL_ERROR1; |
1839 | } |
1840 | } |
1841 | } |
1842 | |
1843 | if (result == TCL_OK0 && outputChanName != NULL((void*)0)) { |
1844 | if (Ns_TclGetOpenChannel(interp, outputChanName, /* write */ 1, |
1845 | /* check */ 1, &spoolChan) != TCL_OK0) { |
1846 | result = TCL_ERROR1; |
1847 | } |
1848 | } |
1849 | |
1850 | if (result == TCL_OK0) { |
1851 | result = HttpConnect(itPtr, |
1852 | method, |
1853 | url, |
1854 | proxyObj, |
1855 | requestHdrPtr, |
1856 | bodySize, |
1857 | bodyObj, |
1858 | bodyFileName, |
1859 | cert, |
1860 | caFile, |
1861 | caPath, |
1862 | sniHostname, |
1863 | (verifyCert == 1), |
1864 | (keepHostHdr == 1), |
1865 | timeoutPtr, |
1866 | expirePtr, |
1867 | &httpPtr); |
1868 | } |
1869 | |
1870 | if (result == TCL_OK0 && bodyChan != NULL((void*)0)) { |
1871 | if (HttpCutChannel(interp, bodyChan) != TCL_OK0) { |
1872 | result = TCL_ERROR1; |
1873 | } else { |
1874 | httpPtr->bodyChan = bodyChan; |
1875 | } |
1876 | } |
1877 | |
1878 | if (result == TCL_OK0 && spoolChan != NULL((void*)0)) { |
1879 | if (HttpCutChannel(interp, spoolChan) != TCL_OK0) { |
1880 | result = TCL_ERROR1; |
1881 | } else { |
1882 | httpPtr->spoolChan = spoolChan; |
1883 | } |
1884 | } |
1885 | |
1886 | if (result != TCL_OK0) { |
1887 | if (httpPtr != NULL((void*)0)) { |
1888 | HttpSpliceChannels(interp, httpPtr); |
1889 | HttpClose(httpPtr); |
1890 | } |
1891 | } else { |
1892 | |
1893 | /* |
1894 | * All is fine. Fill in the rest of the task options |
1895 | */ |
1896 | if (spoolLimit > -1) { |
1897 | httpPtr->spoolLimit = spoolLimit; |
1898 | } |
1899 | if (outputFileName != NULL((void*)0)) { |
1900 | httpPtr->spoolFileName = ns_strdup(outputFileName); |
1901 | } |
1902 | if (doneCallback != NULL((void*)0)) { |
1903 | httpPtr->doneCallback = ns_strdup(doneCallback); |
1904 | } |
1905 | if (likely(decompress != 0)(__builtin_expect((decompress != 0), 1)) && likely(raw == 0)(__builtin_expect((raw == 0), 1))) { |
1906 | httpPtr->flags |= NS_HTTP_FLAG_DECOMPRESS(1u<<0); |
1907 | } else { |
1908 | httpPtr->flags = (httpPtr->flags & ~NS_HTTP_FLAG_DECOMPRESS(1u<<0)); |
1909 | } |
1910 | if (binary != 0) { |
1911 | httpPtr->flags |= NS_HTTP_FLAG_BINARY(1u<<4); |
1912 | } |
1913 | httpPtr->servPtr = itPtr->servPtr; |
1914 | |
1915 | httpPtr->task = Ns_TaskTimedCreate(httpPtr->sock, HttpProc, httpPtr, expirePtr); |
1916 | |
1917 | if (run == NS_TRUE1) { |
1918 | |
1919 | /* |
1920 | * Run the task and collect the result in one go. |
1921 | * The task is executed in the current thread. |
1922 | */ |
1923 | Ns_TaskRun(httpPtr->task); |
1924 | result = HttpGetResult(interp, httpPtr); |
1925 | HttpSpliceChannels(interp, httpPtr); |
1926 | HttpClose(httpPtr); |
1927 | |
1928 | } else { |
1929 | static Ns_TaskQueue *taskQueue = NULL((void*)0); /* MT: static variable! */ |
1930 | |
1931 | /* |
1932 | * Enqueue the task, optionally returning the taskID |
1933 | */ |
1934 | |
1935 | if (taskQueue == NULL((void*)0)) { |
1936 | Ns_MasterLock(); |
1937 | if (taskQueue == NULL((void*)0)) { |
1938 | taskQueue = Ns_CreateTaskQueue("tclhttp"); |
1939 | } |
1940 | Ns_MasterUnlock(); |
1941 | } |
1942 | |
1943 | if (Ns_TaskEnqueue(httpPtr->task, taskQueue) != NS_OK) { |
1944 | HttpSpliceChannels(interp, httpPtr); |
1945 | HttpClose(httpPtr); |
1946 | Ns_TclPrintfResult(interp, "could not queue HTTP task"); |
1947 | result = TCL_ERROR1; |
1948 | |
1949 | } else if (doneCallback != NULL((void*)0)) { |
1950 | |
1951 | /* |
1952 | * There is nothing to wait on when the doneCallback |
1953 | * was declared, since the callback garbage-collects |
1954 | * the task. Hence we do not create the taskID. |
1955 | */ |
1956 | Ns_Log(Ns_LogTaskDebug, "HttpQueue: no taskID returned"); |
1957 | |
1958 | } else { |
1959 | Tcl_HashEntry *hPtr = NULL((void*)0); |
1960 | uint32_t ii; |
1961 | int len; |
1962 | char buf[TCL_INTEGER_SPACE24 + 4]; |
1963 | |
1964 | /* |
1965 | * Create taskID to be used for [ns_http_wait] et al. |
1966 | */ |
1967 | memcpy(buf, "http", 4u); |
1968 | for (ii = (uint32_t)itPtr->httpRequests.numEntries; ; ii++) { |
1969 | int new = 0; |
1970 | |
1971 | len = ns_uint32toa(&buf[4], ii); |
1972 | hPtr = Tcl_CreateHashEntry(&itPtr->httpRequests, buf, &new)(*((&itPtr->httpRequests)->createProc))(&itPtr-> httpRequests, (const char *)(buf), &new); |
1973 | if (new != 0) { |
1974 | break; |
1975 | } |
1976 | } |
1977 | assert(hPtr != NULL)((void) (0)); |
1978 | Tcl_SetHashValue(hPtr, (ClientData)httpPtr)((hPtr)->clientData = (ClientData) ((ClientData)httpPtr)); |
1979 | Tcl_SetObjResult(interp, Tcl_NewStringObj(buf, len+4)); |
1980 | } |
1981 | } |
1982 | } |
1983 | |
1984 | return result; |
1985 | } |
1986 | |
1987 | static void |
1988 | HttpClientLogWrite( |
1989 | const NsHttpTask *httpPtr, |
1990 | const char *causeString |
1991 | ) { |
1992 | Ns_Time diff; |
1993 | NsServer *servPtr; |
1994 | |
1995 | NS_NONNULL_ASSERT(httpPtr != NULL)((void) (0)); |
1996 | NS_NONNULL_ASSERT(causeString != NULL)((void) (0)); |
1997 | |
1998 | /*fprintf(stderr, "================ HttpClientLog %d fd %d %s etime %ld cause %s\n", |
1999 | httpPtr->servPtr->httpclient.logging, |
2000 | httpPtr->servPtr->httpclient.fd, |
2001 | httpPtr->servPtr->httpclient.logFileName, |
2002 | (long)(httpPtr->etime.sec), |
2003 | causeString |
2004 | );*/ |
2005 | |
2006 | Ns_DiffTime(&httpPtr->etime, &httpPtr->stime, &diff); |
2007 | |
2008 | if (likely(httpPtr->servPtr != NULL)(__builtin_expect((httpPtr->servPtr != ((void*)0)), 1))) { |
2009 | servPtr = httpPtr->servPtr; |
2010 | } else { |
2011 | /* |
2012 | * In case, there is no server provided in httpPtr (e.g. the itPtr had |
2013 | * no servPtr set), use the configuration of the default server. |
2014 | */ |
2015 | servPtr = NsGetServer(nsconf.defaultServer); |
2016 | if (servPtr == NULL((void*)0)) { |
2017 | Ns_Log(Error, "http client log: server could not be determined, logging attempt rejected"); |
2018 | return; |
2019 | } |
2020 | } |
2021 | |
2022 | if (servPtr->httpclient.logging |
2023 | && servPtr->httpclient.fd != NS_INVALID_FD(-1) |
2024 | ) { |
2025 | Tcl_DString logString; |
2026 | char buf[41]; /* Big enough for Ns_LogTime(). */ |
2027 | |
2028 | Tcl_DStringInit(&logString); |
2029 | Ns_DStringPrintf(&logString, "%s %s %d %s %s " NS_TIME_FMT"%" "l" "d" ".%06ld" |
2030 | " %" PRIdz"zd" " %" PRIdz"zd" " %s\n", |
2031 | Ns_LogTime(buf), |
2032 | Ns_ThreadGetName(), |
2033 | httpPtr->status == 0 ? 408 : httpPtr->status, |
2034 | httpPtr->method, |
2035 | httpPtr->url, |
2036 | (int64_t)diff.sec, diff.usec, |
2037 | httpPtr->sent, |
2038 | httpPtr->received, |
2039 | causeString |
2040 | ); |
2041 | |
2042 | Ns_MutexLock(&servPtr->httpclient.lock); |
2043 | (void)NsAsyncWrite(servPtr->httpclient.fd, |
2044 | logString.string, (size_t)logString.length); |
2045 | Ns_MutexUnlock(&servPtr->httpclient.lock); |
2046 | |
2047 | Tcl_DStringFree(&logString); |
2048 | } |
2049 | } |
2050 | |
2051 | |
2052 | /* |
2053 | *---------------------------------------------------------------------- |
2054 | * |
2055 | * HttpGetResult -- |
2056 | * |
2057 | * Get the result of the Task and set it in the interp result. |
2058 | * |
2059 | * Results: |
2060 | * Standard Tcl result. |
2061 | * |
2062 | * Side effects: |
2063 | * None. |
2064 | * |
2065 | *---------------------------------------------------------------------- |
2066 | */ |
2067 | |
2068 | static int |
2069 | HttpGetResult( |
2070 | Tcl_Interp *interp, |
2071 | NsHttpTask *httpPtr |
2072 | ) { |
2073 | int result = TCL_OK0; |
2074 | Ns_Time diff; |
2075 | Tcl_Obj *statusObj = NULL((void*)0), |
2076 | *replyBodyObj = NULL((void*)0), |
2077 | *fileNameObj = NULL((void*)0), |
2078 | *resultObj = NULL((void*)0), |
2079 | *replyHeadersObj = NULL((void*)0), |
2080 | *elapsedTimeObj; |
2081 | |
2082 | NS_NONNULL_ASSERT(interp != NULL)((void) (0)); |
2083 | NS_NONNULL_ASSERT(httpPtr != NULL)((void) (0)); |
2084 | |
2085 | //fprintf(stderr, "================ HttpGetResult\n"); |
2086 | |
2087 | /* |
2088 | * In some error conditions, the endtime is not set. make sure, take the |
2089 | * current time in these cases. |
2090 | */ |
2091 | if (httpPtr->etime.sec == 0) { |
2092 | Ns_GetTime(&httpPtr->etime); |
2093 | } |
2094 | |
2095 | Ns_DiffTime(&httpPtr->etime, &httpPtr->stime, &diff); |
2096 | elapsedTimeObj = Tcl_NewObj(); |
2097 | Ns_TclSetTimeObj(elapsedTimeObj, &diff); |
2098 | |
2099 | if (httpPtr->error != NULL((void*)0)) { |
2100 | if (httpPtr->finalSockState == NS_SOCK_TIMEOUT) { |
2101 | Tcl_SetErrorCode(interp, errorCodeTimeoutString, (char *)0L); |
2102 | Ns_Log(Ns_LogTimeoutDebug, "ns_http request '%s' runs into timeout", |
2103 | httpPtr->url); |
2104 | HttpClientLogWrite(httpPtr, "socktimeout"); |
2105 | } |
2106 | Tcl_SetObjResult(interp, Tcl_NewStringObj(httpPtr->error, -1)); |
2107 | result = TCL_ERROR1; |
2108 | goto err; |
2109 | } |
2110 | |
2111 | HttpClientLogWrite(httpPtr, "ok"); |
2112 | |
2113 | if (httpPtr->recvSpoolMode == NS_FALSE0) { |
2114 | #if defined(TCLHTTP_USE_EXTERNALTOUTF1) |
2115 | Tcl_Encoding encoding = NULL((void*)0); |
2116 | #endif |
2117 | bool_Bool binary = NS_FALSE0; |
2118 | int cSize; |
2119 | char *cData; |
2120 | |
2121 | /* |
2122 | * Determine type (binary/text) of the received data |
2123 | * and decide what kind of object we should create |
2124 | * to return the content to the Tcl. |
2125 | * We have a choice between binary and string objects. |
2126 | * Unfortunately, this is mostly whole lotta guess-work... |
2127 | */ |
2128 | if (unlikely((httpPtr->flags & NS_HTTP_FLAG_GZIP_ENCODING) != 0u)(__builtin_expect(((httpPtr->flags & (1u<<1)) != 0u), 0))) { |
2129 | if (unlikely((httpPtr->flags & NS_HTTP_FLAG_DECOMPRESS) == 0u)(__builtin_expect(((httpPtr->flags & (1u<<0)) == 0u), 0))) { |
2130 | |
2131 | /* |
2132 | * Gzipped but not inflated content |
2133 | * is automatically of a binary-type. |
2134 | * This is pretty straight-forward. |
2135 | */ |
2136 | binary = NS_TRUE1; |
2137 | } |
2138 | } |
2139 | if ((httpPtr->flags & NS_HTTP_FLAG_BINARY(1u<<4)) != 0u) { |
2140 | binary = NS_TRUE1; |
2141 | } |
2142 | if (binary == NS_FALSE0) { |
2143 | const char *cType; |
2144 | |
2145 | cType = Ns_SetIGet(httpPtr->replyHeaders, contentTypeHeader); |
2146 | if (cType != NULL((void*)0)) { |
2147 | |
2148 | /* |
2149 | * "binary" actually means: just to take the data as it is, |
2150 | * i.e. to perform no charset conversion. |
2151 | */ |
2152 | binary = Ns_IsBinaryMimeType(cType); |
2153 | /* |
2154 | * When the MIME type does not indicate binary treatment, a |
2155 | * charset encoding is required. (e.g. "text/plain; |
2156 | * charset=iso-8859-2") |
2157 | */ |
2158 | #if defined(TCLHTTP_USE_EXTERNALTOUTF1) |
2159 | if (binary == NS_FALSE0) { |
2160 | encoding = Ns_GetTypeEncoding(cType); |
2161 | if (encoding == NULL((void*)0)) { |
2162 | encoding = NS_utf8Encoding; |
2163 | } |
2164 | } |
2165 | #endif |
2166 | } |
2167 | } |
2168 | |
2169 | cData = httpPtr->ds.string + httpPtr->replyHeaderSize; |
2170 | cSize = (int)httpPtr->replyBodySize; |
2171 | |
2172 | if (binary == NS_TRUE1) { |
2173 | replyBodyObj = Tcl_NewByteArrayObj((unsigned char *)cData, cSize); |
2174 | } else { |
2175 | #if defined(TCLHTTP_USE_EXTERNALTOUTF1) |
2176 | Tcl_DString ds; |
2177 | Tcl_DStringInit(&ds); |
2178 | Tcl_ExternalToUtfDString(encoding, cData, cSize, &ds); |
2179 | replyBodyObj = Tcl_NewStringObj(Tcl_DStringValue(&ds)((&ds)->string), -1); |
2180 | Tcl_DStringFree(&ds); |
2181 | #else |
2182 | replyBodyObj = Tcl_NewStringObj(cData, cSize); |
2183 | #endif |
2184 | } |
2185 | } |
2186 | |
2187 | statusObj = Tcl_NewIntObj(httpPtr->status); |
2188 | |
2189 | if (httpPtr->spoolFd != NS_INVALID_FD(-1)) { |
2190 | fileNameObj = Tcl_NewStringObj(httpPtr->spoolFileName, -1); |
2191 | } |
2192 | |
2193 | /* |
2194 | * Add reply headers set into the interp |
2195 | */ |
2196 | result = Ns_TclEnterSet(interp, httpPtr->replyHeaders, NS_TCL_SET_DYNAMIC); |
2197 | if (result != TCL_OK0) { |
2198 | goto err; |
2199 | } |
2200 | |
2201 | httpPtr->replyHeaders = NULL((void*)0); /* Prevents Ns_SetFree() in HttpClose() */ |
2202 | replyHeadersObj = Tcl_GetObjResult(interp); |
2203 | Tcl_IncrRefCount(replyHeadersObj)++(replyHeadersObj)->refCount; |
2204 | |
2205 | |
2206 | /* |
2207 | * Assemble the resulting dictionary |
2208 | */ |
2209 | resultObj = Tcl_NewDictObj(); |
2210 | |
2211 | Tcl_DictObjPut(interp, resultObj, Tcl_NewStringObj("status", 6), |
2212 | statusObj); |
2213 | |
2214 | Tcl_DictObjPut(interp, resultObj, Tcl_NewStringObj("time", 4), |
2215 | elapsedTimeObj); |
2216 | |
2217 | Tcl_DictObjPut(interp, resultObj, Tcl_NewStringObj("headers", 7), |
2218 | replyHeadersObj); |
2219 | |
2220 | if (fileNameObj != NULL((void*)0)) { |
2221 | Tcl_DictObjPut(interp, resultObj, Tcl_NewStringObj("file", 4), |
2222 | fileNameObj); |
2223 | } |
2224 | if (replyBodyObj != NULL((void*)0)) { |
2225 | Tcl_DictObjPut(interp, resultObj, Tcl_NewStringObj("body", 4), |
2226 | replyBodyObj); |
2227 | } |
2228 | if (httpPtr->infoObj != NULL((void*)0)) { |
2229 | Tcl_DictObjPut(interp, resultObj, Tcl_NewStringObj("https", 5), |
2230 | httpPtr->infoObj); |
2231 | } |
2232 | if (httpPtr->bodyChan != NULL((void*)0)) { |
2233 | const char *chanName = Tcl_GetChannelName(httpPtr->bodyChan); |
2234 | |
2235 | Tcl_DictObjPut(interp, resultObj, Tcl_NewStringObj("body_chan", 9), |
2236 | Tcl_NewStringObj(chanName, -1)); |
2237 | } |
2238 | if (httpPtr->spoolChan != NULL((void*)0)) { |
2239 | const char *chanName = Tcl_GetChannelName(httpPtr->spoolChan); |
2240 | |
2241 | Tcl_DictObjPut(interp, resultObj, Tcl_NewStringObj("outputchan", 10), |
2242 | Tcl_NewStringObj(chanName, -1)); |
2243 | } |
2244 | Tcl_SetObjResult(interp, resultObj); |
2245 | |
2246 | Tcl_DecrRefCount(replyHeadersObj)do { Tcl_Obj *_objPtr = (replyHeadersObj); if (_objPtr->refCount -- <= 1) { TclFreeObj(_objPtr); } } while(0); |
2247 | |
2248 | err: |
2249 | if (result != TCL_OK0) { |
2250 | if (statusObj != NULL((void*)0)) { |
2251 | Tcl_DecrRefCount(statusObj)do { Tcl_Obj *_objPtr = (statusObj); if (_objPtr->refCount -- <= 1) { TclFreeObj(_objPtr); } } while(0); |
2252 | } |
2253 | if (fileNameObj != NULL((void*)0)) { |
2254 | Tcl_DecrRefCount(fileNameObj)do { Tcl_Obj *_objPtr = (fileNameObj); if (_objPtr->refCount -- <= 1) { TclFreeObj(_objPtr); } } while(0); |
2255 | } |
2256 | if (elapsedTimeObj != NULL((void*)0)) { |
2257 | Tcl_DecrRefCount(elapsedTimeObj)do { Tcl_Obj *_objPtr = (elapsedTimeObj); if (_objPtr->refCount -- <= 1) { TclFreeObj(_objPtr); } } while(0); |
2258 | } |
2259 | if (replyBodyObj != NULL((void*)0)) { |
2260 | Tcl_DecrRefCount(replyBodyObj)do { Tcl_Obj *_objPtr = (replyBodyObj); if (_objPtr->refCount -- <= 1) { TclFreeObj(_objPtr); } } while(0); |
2261 | } |
2262 | } |
2263 | |
2264 | return result; |
2265 | } |
2266 | |
2267 | |
2268 | /* |
2269 | *---------------------------------------------------------------------- |
2270 | * |
2271 | * HttpCheckHeader -- |
2272 | * |
2273 | * Check whether we have full HTTP response incl. headers. |
2274 | * If yes, record the total size of the response |
2275 | * (including the lone CR/LF delimiter) in the NsHttpTask |
2276 | * structure, as to avoid subsequent checking. |
2277 | * Terminate the response string by eliminating the lone |
2278 | * CR/LF delimiter (put a NULL byte at the CR place). |
2279 | * This way it is easy to calculate size of the optional |
2280 | * body content following the response line/headers. |
2281 | * |
2282 | * Results: |
2283 | * None. |
2284 | * |
2285 | * Side effects: |
2286 | * Handles the case where server responds with invalid |
2287 | * lone LF delimiters. |
2288 | * |
2289 | *---------------------------------------------------------------------- |
2290 | */ |
2291 | |
2292 | static void |
2293 | HttpCheckHeader( |
2294 | NsHttpTask *httpPtr |
2295 | ) { |
2296 | char *eoh; |
2297 | |
2298 | NS_NONNULL_ASSERT(httpPtr != NULL)((void) (0)); |
2299 | |
2300 | eoh = strstr(httpPtr->ds.string, "\r\n\r\n"); |
2301 | if (eoh != NULL((void*)0)) { |
2302 | httpPtr->replyHeaderSize = (int)(eoh - httpPtr->ds.string) + 4; |
2303 | *(eoh + 2) = '\0'; |
2304 | } else { |
2305 | eoh = strstr(httpPtr->ds.string, "\n\n"); |
2306 | if (eoh != NULL((void*)0)) { |
2307 | Ns_Log(Warning, "HttpCheckHeader: client reply contains" |
2308 | " LF instead of CR/LF trailer which should not happen"); |
2309 | httpPtr->replyHeaderSize = (int)(eoh - httpPtr->ds.string) + 2; |
2310 | *(eoh + 1) = '\0'; |
2311 | } |
2312 | } |
2313 | } |
2314 | |
2315 | |
2316 | /* |
2317 | *---------------------------------------------------------------------- |
2318 | * |
2319 | * HttpCheckSpool -- |
2320 | * |
2321 | * Determine, whether the received data should be left in |
2322 | * the memory or whether it should be spooled to a file |
2323 | * or channel, depending on the size of the returned content |
2324 | * and the configuration settings. |
2325 | * |
2326 | * Results: |
2327 | * Ns_ReturnCode. |
2328 | * |
2329 | * Side effects: |
2330 | * Handles the partial response content located in memory. |
2331 | * |
2332 | *---------------------------------------------------------------------- |
2333 | */ |
2334 | |
2335 | static Ns_ReturnCode |
2336 | HttpCheckSpool( |
2337 | NsHttpTask *httpPtr |
2338 | ) { |
2339 | Ns_ReturnCode result = NS_OK; |
2340 | |
2341 | NS_NONNULL_ASSERT(httpPtr != NULL)((void) (0)); |
2342 | |
2343 | /* |
2344 | * At this point, we already identified the end of the |
2345 | * response/headers but haven not yet parsed it because |
2346 | * we still do not know the value of the response status. |
2347 | * |
2348 | * The Ns_DString in httpPtr->ds contains, at this point: |
2349 | * |
2350 | * 1. HTTP response line (delimited by CR/LF) |
2351 | * 2. Response header(s) (each delimited by CR/LF) |
2352 | * 3. Terminating zero byte (was \r; see HttpCheckHeader()) |
2353 | * 4. Lone \n character (see HttpCheckHeader()) |
2354 | * 5. Content (or part of it) up to the end of the DString |
2355 | * |
2356 | * The size of 1.-4. is stored in httpPtr->replyHeaderSize. |
2357 | * The 3. delimits the partial content from the response |
2358 | * status lines/headers. Note that we parse the size of |
2359 | * the response line/headers by explicitly taking the |
2360 | * length of the DString value (size of 1.-3.) and not |
2361 | * using the DString length element. |
2362 | */ |
2363 | |
2364 | if (Ns_HttpMessageParse(httpPtr->ds.string, strlen(httpPtr->ds.string), |
2365 | httpPtr->replyHeaders, |
2366 | NULL((void*)0), |
2367 | NULL((void*)0), |
2368 | &httpPtr->status, |
2369 | NULL((void*)0)) != NS_OK || httpPtr->status == 0) { |
2370 | |
2371 | Ns_Log(Warning, "ns_http: parsing reply failed"); |
2372 | result = NS_ERROR; |
2373 | |
2374 | } else { |
2375 | const char *header; |
2376 | Tcl_WideInt replyLength = 0; |
2377 | |
2378 | /* |
2379 | * Check the returned Content-Length |
2380 | */ |
2381 | header = Ns_SetIGet(httpPtr->replyHeaders, contentLengthHeader); |
2382 | if (header != NULL((void*)0)) { |
2383 | (void)Ns_StrToWideInt(header, &replyLength); |
2384 | |
2385 | /* |
2386 | * Don't get fooled by some invalid value! |
2387 | */ |
2388 | if (replyLength < 0) { |
2389 | replyLength = 0; |
2390 | } |
2391 | |
2392 | Ns_Log(Ns_LogTaskDebug, "HttpCheckSpool: %s: %" TCL_LL_MODIFIER"l" "d", |
2393 | contentLengthHeader, replyLength); |
2394 | } else { |
2395 | |
2396 | /* |
2397 | * If none, see if we have Transfer-Encoding. |
2398 | * For now, we support "chunked" encoding only. |
2399 | */ |
2400 | header = Ns_SetIGet(httpPtr->replyHeaders, transferEncodingHeader); |
2401 | if (header != NULL((void*)0) && Ns_Match(header, "chunked") != NULL((void*)0)) { |
2402 | httpPtr->flags |= NS_HTTP_FLAG_CHUNKED(1u<<2); |
2403 | httpPtr->chunk->parsers = ChunkParsers; |
2404 | Ns_Log(Ns_LogTaskDebug, "HttpCheckSpool: %s: %s", |
2405 | transferEncodingHeader, header); |
2406 | Ns_SetIDeleteKey(httpPtr->replyHeaders, transferEncodingHeader); |
2407 | } |
2408 | } |
2409 | |
2410 | /* |
2411 | * See if we are handling compressed content. |
2412 | * Turn-on auto-decompress if requested. |
2413 | */ |
2414 | header = Ns_SetIGet(httpPtr->replyHeaders, contentEncodingHeader); |
2415 | if (header != NULL((void*)0) && Ns_Match(header, "gzip") != NULL((void*)0)) { |
2416 | httpPtr->flags |= NS_HTTP_FLAG_GZIP_ENCODING(1u<<1); |
2417 | if ((httpPtr->flags & NS_HTTP_FLAG_DECOMPRESS(1u<<0)) != 0u) { |
2418 | httpPtr->compress = ns_calloc(1u, sizeof(Ns_CompressStream)); |
2419 | (void) Ns_InflateInit(httpPtr->compress); |
2420 | Ns_Log(Ns_LogTaskDebug, "HttpCheckSpool: %s: %s", |
2421 | contentEncodingHeader, header); |
2422 | } |
2423 | } |
2424 | |
2425 | Ns_MutexLock(&httpPtr->lock); |
2426 | httpPtr->replyLength = (size_t)replyLength; |
2427 | Ns_MutexUnlock(&httpPtr->lock); |
2428 | |
2429 | /* |
2430 | * See if we need to spool the response content |
2431 | * to file/channel or leave it in the memory. |
2432 | */ |
2433 | if (httpPtr->spoolLimit > -1 |
2434 | && (replyLength == 0 || replyLength >= httpPtr->spoolLimit)) { |
2435 | |
2436 | if (httpPtr->spoolChan != NULL((void*)0)) { |
2437 | httpPtr->spoolFd = NS_INVALID_FD(-1); |
2438 | httpPtr->recvSpoolMode = NS_TRUE1; |
2439 | } else { |
2440 | int fd; |
2441 | |
2442 | if (httpPtr->spoolFileName != NULL((void*)0)) { |
2443 | int flags; |
2444 | |
2445 | flags = O_WRONLY01|O_CREAT0100|O_CLOEXEC02000000; |
2446 | fd = ns_openopen(httpPtr->spoolFileName, flags, 0644); |
2447 | } else { |
2448 | const char *tmpDir, *tmpFile = "http.XXXXXX"; |
2449 | size_t tmpLen; |
2450 | |
2451 | tmpDir = nsconf.tmpDir; |
2452 | tmpLen = strlen(tmpDir) + 13; |
2453 | |
2454 | /* |
2455 | * This lock is necessary for [ns_http wait] |
2456 | * backward compatibility. It can be removed |
2457 | * once we modify [ns_http wait] to disable |
2458 | * options processing. |
2459 | */ |
2460 | Ns_MutexLock(&httpPtr->lock); |
2461 | httpPtr->spoolFileName = ns_malloc(tmpLen); |
2462 | sprintf(httpPtr->spoolFileName, "%s/%s", tmpDir, tmpFile)__builtin___sprintf_chk (httpPtr->spoolFileName, 2 - 1, __builtin_object_size (httpPtr->spoolFileName, 2 > 1), "%s/%s", tmpDir, tmpFile ); |
2463 | Ns_MutexUnlock(&httpPtr->lock); |
2464 | |
2465 | fd = ns_mkstempmkstemp(httpPtr->spoolFileName); |
2466 | } |
2467 | if (fd != NS_INVALID_FD(-1)) { |
2468 | httpPtr->spoolFd = fd; |
2469 | httpPtr->recvSpoolMode = NS_TRUE1; |
2470 | } else { |
2471 | |
2472 | /* |
2473 | * FIXME: |
2474 | * |
2475 | * The ns_mkstemp/ns_open on Unix are clear but |
2476 | * what happens with handling error on Windows? |
2477 | */ |
2478 | Ns_Log(Error, "ns_http: can't open spool file: %s:", |
2479 | httpPtr->spoolFileName); |
2480 | result = NS_ERROR; |
2481 | } |
2482 | } |
2483 | } |
2484 | } |
2485 | |
2486 | if (result == NS_OK) { |
2487 | size_t cSize; |
2488 | |
2489 | cSize = (size_t)(httpPtr->ds.length - httpPtr->replyHeaderSize); |
2490 | if (cSize > 0) { |
2491 | char buf[CHUNK_SIZE16384], *cData; |
2492 | |
2493 | /* |
2494 | * There is (a part of the) content, past headers. |
2495 | * At this point, it is important to note that we may |
2496 | * be encountering chunked or compressed content... |
2497 | * Hence we copy this part into the private buffer, |
2498 | * erase it from the memory and let the HttpAppendContent |
2499 | * do the "right thing". |
2500 | */ |
2501 | cData = httpPtr->ds.string + httpPtr->replyHeaderSize; |
2502 | if (httpPtr->replyLength > 0 && cSize > httpPtr->replyLength) { |
2503 | cSize = httpPtr->replyLength; |
2504 | } |
2505 | memcpy(buf, cData, cSize); |
2506 | Ns_DStringSetLengthTcl_DStringSetLength(&httpPtr->ds, httpPtr->replyHeaderSize); |
2507 | if (HttpAppendContent(httpPtr, buf, cSize) != TCL_OK0) { |
2508 | result = NS_ERROR; |
2509 | } |
2510 | } |
2511 | } |
2512 | |
2513 | return result; |
2514 | } |
2515 | |
2516 | |
2517 | /* |
2518 | *---------------------------------------------------------------------- |
2519 | * |
2520 | * HttpGet -- |
2521 | * |
2522 | * Locate Http struct for a given taskID. |
2523 | * |
2524 | * Results: |
2525 | * NS_TRUE on success, NS_FALSE otherwise. |
2526 | * |
2527 | * Side effects: |
2528 | * Will update given httpPtrPtr with the pointer to NsHttpTask |
2529 | * |
2530 | *---------------------------------------------------------------------- |
2531 | */ |
2532 | |
2533 | static bool_Bool |
2534 | HttpGet( |
2535 | NsInterp *itPtr, |
2536 | const char *taskID, |
2537 | NsHttpTask **httpPtrPtr, |
2538 | bool_Bool remove |
2539 | ) { |
2540 | Tcl_HashEntry *hPtr; |
2541 | bool_Bool success; |
2542 | |
2543 | NS_NONNULL_ASSERT(itPtr != NULL)((void) (0)); |
2544 | NS_NONNULL_ASSERT(taskID != NULL)((void) (0)); |
2545 | NS_NONNULL_ASSERT(httpPtrPtr != NULL)((void) (0)); |
2546 | |
2547 | hPtr = Tcl_FindHashEntry(&itPtr->httpRequests, taskID)(*((&itPtr->httpRequests)->findProc))(&itPtr-> httpRequests, (const char *)(taskID)); |
2548 | if (hPtr == NULL((void*)0)) { |
2549 | Ns_TclPrintfResult(itPtr->interp, "no such request: %s", taskID); |
2550 | success = NS_FALSE0; |
2551 | } else { |
2552 | *httpPtrPtr = (NsHttpTask *)Tcl_GetHashValue(hPtr)((hPtr)->clientData); |
2553 | if (remove) { |
2554 | Tcl_DeleteHashEntry(hPtr); |
2555 | } |
2556 | success = NS_TRUE1; |
2557 | } |
2558 | |
2559 | return success; |
2560 | } |
2561 | |
2562 | |
2563 | /* |
2564 | *---------------------------------------------------------------------- |
2565 | * |
2566 | * HttpWaitForSocketEvent -- |
2567 | * |
2568 | * Wait until the specified event on socket. |
2569 | * |
2570 | * Results: |
2571 | * Ns_ReturnCode |
2572 | * |
2573 | * Side effects: |
2574 | * None |
2575 | * |
2576 | *---------------------------------------------------------------------- |
2577 | */ |
2578 | |
2579 | static Ns_ReturnCode |
2580 | HttpWaitForSocketEvent( |
2581 | NS_SOCKETint sock, |
2582 | short events, |
2583 | Ns_Time *timeoutPtr |
2584 | ) { |
2585 | Ns_ReturnCode result; |
2586 | struct pollfd pollfd; |
2587 | int retval; |
2588 | long ms; |
2589 | |
2590 | pollfd.fd = (int)sock; |
2591 | pollfd.events = events; |
2592 | |
2593 | if (timeoutPtr == NULL((void*)0)) { |
2594 | ms = -1; |
2595 | } else { |
2596 | ms = (long)Ns_TimeToMilliseconds(timeoutPtr); |
2597 | if (ms == 0) { |
2598 | ms = 1; |
2599 | } |
2600 | } |
2601 | |
2602 | do { |
2603 | retval = ns_poll(&pollfd, (NS_POLL_NFDS_TYPEnfds_t)1, ms); |
2604 | } while (retval == -1 && errno(*__errno_location ()) == NS_EINTR4); |
2605 | |
2606 | switch (retval) { |
2607 | case 0: |
2608 | result = NS_TIMEOUT; |
2609 | break; |
2610 | case 1: |
2611 | result = NS_OK; |
2612 | break; |
2613 | default: |
2614 | result = NS_ERROR; |
2615 | break; |
2616 | } |
2617 | |
2618 | return result; |
2619 | } |
2620 | |
2621 | |
2622 | /* |
2623 | *---------------------------------------------------------------------- |
2624 | * |
2625 | * HttpConnect -- |
2626 | * |
2627 | * Open a connection to the given URL |
2628 | * and construct an NsHttpTask to handle the request. |
2629 | * |
2630 | * Results: |
2631 | * Tcl result code |
2632 | * |
2633 | * Side effects: |
2634 | * On TCL_OK, updates httpPtrPtr with allocated NsHttpTask. |
2635 | * |
2636 | *---------------------------------------------------------------------- |
2637 | */ |
2638 | |
2639 | static int |
2640 | HttpConnect( |
2641 | NsInterp *itPtr, |
2642 | const char *method, |
2643 | const char *url, |
2644 | Tcl_Obj *proxyObj, |
2645 | Ns_Set *hdrPtr, |
2646 | ssize_t bodySize, |
2647 | Tcl_Obj *bodyObj, |
2648 | const char *bodyFileName, |
2649 | const char *cert, |
2650 | const char *caFile, |
2651 | const char *caPath, |
2652 | const char *sniHostname, |
2653 | bool_Bool verifyCert, |
2654 | bool_Bool keepHostHdr, |
2655 | Ns_Time *timeoutPtr, |
2656 | Ns_Time *expirePtr, |
2657 | NsHttpTask **httpPtrPtr |
2658 | ) { |
2659 | Tcl_Interp *interp; |
2660 | NsHttpTask *httpPtr; |
2661 | Ns_DStringTcl_DString *dsPtr; |
2662 | bool_Bool haveUserAgent = NS_FALSE0, ownHeaders = NS_FALSE0; |
2663 | bool_Bool httpTunnel = NS_FALSE0, httpProxy = NS_FALSE0; |
2664 | unsigned short portNr, defPortNr, pPortNr = 0; |
2665 | char *url2, *pHost = NULL((void*)0); |
2666 | Ns_URL u; |
2667 | const char *errorMsg = NULL((void*)0); |
2668 | const char *contentType = NULL((void*)0); |
2669 | uint64_t requestCount = 0u; |
2670 | |
2671 | NS_NONNULL_ASSERT(itPtr != NULL)((void) (0)); |
2672 | NS_NONNULL_ASSERT(method != NULL)((void) (0)); |
2673 | NS_NONNULL_ASSERT(url != NULL)((void) (0)); |
2674 | NS_NONNULL_ASSERT(httpPtrPtr != NULL)((void) (0)); |
2675 | |
2676 | interp = itPtr->interp; |
2677 | |
2678 | /* |
2679 | * Setup the task structure. From this point on |
2680 | * if something goes wrong, we must HttpClose(). |
2681 | */ |
2682 | httpPtr = ns_calloc(1u, sizeof(NsHttpTask)); |
2683 | httpPtr->chunk = ns_calloc(1u, sizeof(NsHttpChunk)); |
2684 | httpPtr->bodyFileFd = NS_INVALID_FD(-1); |
2685 | httpPtr->spoolFd = NS_INVALID_FD(-1); |
2686 | httpPtr->sock = NS_INVALID_SOCKET(-1); |
2687 | httpPtr->spoolLimit = -1; |
2688 | httpPtr->url = ns_strdup(url); |
2689 | httpPtr->method = ns_strdup(method); |
2690 | httpPtr->replyHeaders = Ns_SetCreate(NS_SET_NAME_CLIENT_RESPONSE"clresp"); |
2691 | httpPtr->servPtr = itPtr->servPtr; |
2692 | |
2693 | if (timeoutPtr != NULL((void*)0)) { |
2694 | httpPtr->timeout = ns_calloc(1u, sizeof(Ns_Time)); |
2695 | *httpPtr->timeout = *timeoutPtr; |
2696 | } |
2697 | |
2698 | Ns_GetTime(&httpPtr->stime); |
2699 | |
2700 | dsPtr = &httpPtr->ds; |
2701 | Tcl_DStringInit(&httpPtr->ds); |
2702 | Tcl_DStringInit(&httpPtr->chunk->ds); |
2703 | |
2704 | Ns_MasterLock(); |
2705 | requestCount = ++httpClientRequestCount; |
2706 | Ns_MasterUnlock(); |
2707 | |
2708 | Ns_MutexInit(&httpPtr->lock); |
2709 | (void)ns_uint64toa(dsPtr->string, requestCount); |
2710 | Ns_MutexSetName2(&httpPtr->lock, "ns:httptask", dsPtr->string); |
2711 | |
2712 | /* |
2713 | * Parse given URL into pieces. Accept a fully qualified URL only. |
2714 | * Make a non-const copy of url, in which Ns_ParseUrl can replace |
2715 | * the item separating characters with '\0' characters. |
2716 | */ |
2717 | url2 = ns_strdup(url); |
2718 | if (Ns_ParseUrl(url2, NS_FALSE0, &u, &errorMsg) != NS_OK |
2719 | || u.protocol == NULL((void*)0) |
2720 | || u.host == NULL((void*)0) |
2721 | || u.path == NULL((void*)0) |
2722 | || u.tail == NULL((void*)0)) { |
2723 | |
2724 | Ns_TclPrintfResult(interp, "invalid URL \"%s\": %s", url, errorMsg); |
2725 | goto fail; |
2726 | } |
2727 | |
2728 | if (u.userinfo != NULL((void*)0)) { |
2729 | Ns_Log(Warning, "ns_http: userinfo '%s' ignored: %s", u.userinfo, url); |
2730 | } |
2731 | |
2732 | /* |
2733 | * If "-keep_host_header" option set |
2734 | * then "Host:" header must be given. |
2735 | */ |
2736 | if (keepHostHdr == NS_TRUE1) { |
2737 | if (hdrPtr == NULL((void*)0) || Ns_SetIFind(hdrPtr, hostHeader) == -1) { |
2738 | Ns_TclPrintfResult(interp, "-keep_host_header specified" |
2739 | " but no Host header given"); |
2740 | goto fail; |
2741 | } |
2742 | } |
2743 | |
2744 | /* |
2745 | * Check used protocol and protocol-specific parameters |
2746 | * and determine the default port (80 for HTTP, 443 for HTTPS) |
2747 | */ |
2748 | if (STREQ("http", u.protocol)(((*("http")) == (*(u.protocol))) && (strcmp(("http") ,(u.protocol)) == 0))) { |
2749 | if (cert != NULL((void*)0) |
2750 | || caFile != NULL((void*)0) |
2751 | || caPath != NULL((void*)0) |
2752 | || verifyCert == NS_TRUE1) { |
2753 | |
2754 | Ns_TclPrintfResult(interp, "HTTPS options allowed for HTTPS only"); |
2755 | goto fail; |
2756 | } |
2757 | defPortNr = 80u; |
2758 | } |
2759 | #ifdef HAVE_OPENSSL_EVP_H1 |
2760 | else if (STREQ("https", u.protocol)(((*("https")) == (*(u.protocol))) && (strcmp(("https" ),(u.protocol)) == 0))) { |
2761 | defPortNr = 443u; |
2762 | } |
2763 | #endif |
2764 | else { |
2765 | Ns_TclPrintfResult(interp, "invalid URL \"%s\"", url); |
2766 | goto fail; |
2767 | } |
2768 | |
2769 | /* |
2770 | * Connect to specified port or to the default port. |
2771 | */ |
2772 | if (u.port != NULL((void*)0)) { |
2773 | portNr = (unsigned short) strtol(u.port, NULL((void*)0), 10); |
2774 | } else { |
2775 | portNr = defPortNr; |
2776 | } |
2777 | |
2778 | /* |
2779 | * For request body optionally open the backing file. |
2780 | */ |
2781 | if (bodySize > 0 && bodyFileName != NULL((void*)0)) { |
2782 | httpPtr->bodyFileFd = ns_openopen(bodyFileName, O_RDONLY00|O_CLOEXEC02000000, 0); |
2783 | if (unlikely(httpPtr->bodyFileFd == NS_INVALID_FD)(__builtin_expect((httpPtr->bodyFileFd == (-1)), 0))) { |
2784 | Ns_TclPrintfResult(interp, "cannot open file %s", bodyFileName); |
2785 | goto fail; |
2786 | } |
2787 | } |
2788 | |
2789 | /* |
2790 | * If content decompression allowed and no encodings explicitly set |
2791 | * we tell remote what we would accept per-default. |
2792 | */ |
2793 | #ifdef HAVE_ZLIB_H1 |
2794 | if (likely((httpPtr->flags & NS_HTTP_FLAG_DECOMPRESS) != 0u)(__builtin_expect(((httpPtr->flags & (1u<<0)) != 0u), 1))) { |
2795 | if (hdrPtr == NULL((void*)0) || Ns_SetIFind(hdrPtr, acceptEncodingHeader) == -1) { |
2796 | const char *acceptEncodings = "gzip, deflate"; |
2797 | |
2798 | if (hdrPtr == NULL((void*)0)) { |
2799 | hdrPtr = Ns_SetCreate(NULL((void*)0)); |
2800 | ownHeaders = NS_TRUE1; |
2801 | } |
2802 | |
2803 | Ns_SetPutSz(hdrPtr, acceptEncodingHeader, acceptEncodingHeaderLength, |
2804 | acceptEncodings, 13); |
2805 | } |
2806 | } |
2807 | #endif |
2808 | |
2809 | /* |
2810 | * Check if we need to connect to the proxy server first. |
2811 | * If the passed dictionary contains "host" key, we expect |
2812 | * to find the "port" and (optionally) "tunnel" keys. |
2813 | * If host is found, we will proxy. |
2814 | * For https connections we will tunnel, otherwise we will |
2815 | * cache-proxy. We will tunnel always if optional "tunnel" |
2816 | * key is true. |
2817 | */ |
2818 | if (proxyObj != NULL((void*)0)) { |
2819 | Tcl_Obj *keyObj, *valObj; |
2820 | |
2821 | keyObj = Tcl_NewStringObj("host", 4); |
2822 | valObj = NULL((void*)0); |
2823 | if (Tcl_DictObjGet(interp, proxyObj, keyObj, &valObj) != TCL_OK0) { |
2824 | Tcl_DecrRefCount(keyObj)do { Tcl_Obj *_objPtr = (keyObj); if (_objPtr->refCount-- <= 1) { TclFreeObj(_objPtr); } } while(0); |
2825 | goto fail; /* proxyObj is not a dictionary? */ |
2826 | } |
2827 | Tcl_DecrRefCount(keyObj)do { Tcl_Obj *_objPtr = (keyObj); if (_objPtr->refCount-- <= 1) { TclFreeObj(_objPtr); } } while(0); |
2828 | pHost = (valObj != NULL((void*)0)) ? Tcl_GetString(valObj) : NULL((void*)0); |
2829 | if (pHost != NULL((void*)0)) { |
2830 | int portval = 0; |
2831 | |
2832 | keyObj = Tcl_NewStringObj("port", 4); |
2833 | valObj = NULL((void*)0); |
2834 | Tcl_DictObjGet(interp, proxyObj, keyObj, &valObj); |
2835 | Tcl_DecrRefCount(keyObj)do { Tcl_Obj *_objPtr = (keyObj); if (_objPtr->refCount-- <= 1) { TclFreeObj(_objPtr); } } while(0); |
2836 | if (valObj == NULL((void*)0)) { |
2837 | Ns_TclPrintfResult(interp, "missing proxy port"); |
2838 | goto fail; |
2839 | } |
2840 | if (Tcl_GetIntFromObj(interp, valObj, &portval) != TCL_OK0) { |
2841 | goto fail; |
2842 | } |
2843 | if (portval <= 0) { |
2844 | Ns_TclPrintfResult(interp, "invalid proxy port"); |
2845 | } |
2846 | pPortNr = (unsigned short)portval; |
2847 | if (defPortNr == 443u) { |
2848 | httpTunnel = NS_TRUE1; |
2849 | } else { |
2850 | keyObj = Tcl_NewStringObj("tunnel", 6); |
2851 | valObj = NULL((void*)0); |
2852 | Tcl_DictObjGet(interp, proxyObj, keyObj, &valObj); |
2853 | Tcl_DecrRefCount(keyObj)do { Tcl_Obj *_objPtr = (keyObj); if (_objPtr->refCount-- <= 1) { TclFreeObj(_objPtr); } } while(0); |
2854 | if (valObj == NULL((void*)0)) { |
2855 | httpTunnel = NS_FALSE0; |
2856 | } else { |
2857 | int tunnel; |
2858 | |
2859 | if (Tcl_GetBooleanFromObj(interp, valObj, &tunnel) != TCL_OK0) { |
2860 | goto fail; |
2861 | } |
2862 | httpTunnel = (tunnel == 1) ? NS_TRUE1 : NS_FALSE0; |
2863 | } |
2864 | } |
2865 | httpProxy = (defPortNr == 80u) && (httpTunnel == NS_FALSE0); |
2866 | } |
2867 | } |
2868 | |
2869 | /* |
2870 | * Now we are ready to attempt the connection. |
2871 | * If no timeout given, assume 30 seconds. |
2872 | */ |
2873 | |
2874 | { |
2875 | Ns_ReturnCode rc; |
2876 | Ns_Time defaultTimout = {30, 0}, *toPtr = NULL((void*)0); |
2877 | |
2878 | Ns_Log(Ns_LogTaskDebug, "HttpConnect: connecting to [%s]:%hu", u.host, portNr); |
2879 | |
2880 | /* |
2881 | * Open the socket to remote, assure it is writable |
2882 | */ |
2883 | if (timeoutPtr != NULL((void*)0) && expirePtr != NULL((void*)0)) { |
2884 | if (Ns_DiffTime(timeoutPtr, expirePtr, NULL((void*)0)) < 0) { |
2885 | toPtr = timeoutPtr; |
2886 | } else { |
2887 | toPtr = expirePtr; |
2888 | } |
2889 | } else if (timeoutPtr != NULL((void*)0)) { |
2890 | toPtr = timeoutPtr; |
2891 | } else if (expirePtr != NULL((void*)0)) { |
2892 | toPtr = expirePtr; |
2893 | } else { |
2894 | toPtr = &defaultTimout; |
2895 | } |
2896 | if (httpTunnel == NS_TRUE1) { |
2897 | httpPtr->sock = HttpTunnel(itPtr, pHost, pPortNr, u.host, portNr, toPtr); |
2898 | if (httpPtr->sock == NS_INVALID_SOCKET(-1)) { |
2899 | goto fail; |
2900 | } |
2901 | } else { |
2902 | char *rhost = u.host; |
2903 | unsigned short rport = portNr; |
2904 | |
2905 | if (httpProxy == NS_TRUE1) { |
2906 | rhost = pHost; |
2907 | rport = pPortNr; |
2908 | } |
2909 | httpPtr->sock = Ns_SockTimedConnect2(rhost, rport, NULL((void*)0), 0, toPtr, &rc); |
2910 | if (httpPtr->sock == NS_INVALID_SOCKET(-1)) { |
2911 | Ns_SockConnectError(interp, rhost, rport, rc); |
2912 | if (rc == NS_TIMEOUT) { |
2913 | HttpClientLogWrite(httpPtr, "connecttimeout"); |
2914 | } |
2915 | goto fail; |
2916 | } |
2917 | if (Ns_SockSetNonBlocking(httpPtr->sock) != NS_OK) { |
2918 | Ns_TclPrintfResult(interp, "can't set socket nonblocking mode"); |
2919 | goto fail; |
2920 | } |
2921 | rc = HttpWaitForSocketEvent(httpPtr->sock, POLLOUT0x004, toPtr); |
2922 | if (rc != NS_OK) { |
Duplicate code detected | |
2923 | if (rc == NS_TIMEOUT) { |
2924 | Ns_TclPrintfResult(interp, "timeout waiting for writable socket"); |
2925 | HttpClientLogWrite(httpPtr, "writetimeout"); |
2926 | Tcl_SetErrorCode(interp, errorCodeTimeoutString, (char *)0L); |
2927 | } else { |
2928 | Ns_TclPrintfResult(interp, "waiting for writable socket: %s", |
2929 | ns_sockstrerrorstrerror(ns_sockerrno(*__errno_location ()))); |
2930 | } |
2931 | goto fail; |
2932 | } |
2933 | } |
2934 | |
2935 | /* |
2936 | * Optionally setup an SSL connection |
2937 | */ |
2938 | if (defPortNr == 443u) { |
2939 | NS_TLS_SSL_CTXSSL_CTX *ctx = NULL((void*)0); |
2940 | int result; |
2941 | |
2942 | result = Ns_TLS_CtxClientCreate(interp, cert, caFile, caPath, |
2943 | verifyCert, &ctx); |
2944 | if (likely(result == TCL_OK)(__builtin_expect((result == 0), 1))) { |
2945 | NS_TLS_SSLSSL *ssl = NULL((void*)0); |
2946 | |
2947 | httpPtr->ctx = ctx; |
2948 | result = Ns_TLS_SSLConnect(interp, httpPtr->sock, ctx, |
2949 | sniHostname, &ssl); |
2950 | if (likely(result == TCL_OK)(__builtin_expect((result == 0), 1))) { |
2951 | httpPtr->ssl = ssl; |
2952 | #ifdef HAVE_OPENSSL_EVP_H1 |
2953 | HttpAddInfo(httpPtr, "sslversion", SSL_get_version(ssl)); |
2954 | HttpAddInfo(httpPtr, "cipher", SSL_get_cipher(ssl)SSL_CIPHER_get_name(SSL_get_current_cipher(ssl))); |
2955 | SSL_set_mode(ssl, SSL_MODE_ENABLE_PARTIAL_WRITE)SSL_ctrl((ssl),33,(0x00000001U),((void*)0)); |
2956 | #endif |
2957 | } |
2958 | } |
2959 | if (unlikely(result != TCL_OK)(__builtin_expect((result != 0), 0))) { |
2960 | goto fail; |
2961 | } |
2962 | } |
2963 | } |
2964 | |
2965 | /* |
2966 | * At this point we are connected. |
2967 | * Construct HTTP request line. |
2968 | */ |
2969 | Ns_DStringSetLengthTcl_DStringSetLength(dsPtr, 0); |
2970 | Ns_DStringAppend(dsPtr, method)Tcl_DStringAppend((dsPtr), (method), -1); |
2971 | Ns_StrToUpper(Ns_DStringValue(dsPtr)((dsPtr)->string)); |
2972 | if (httpProxy == NS_TRUE1) { |
2973 | Ns_DStringNAppendTcl_DStringAppend(dsPtr, " ", 1); |
2974 | Ns_DStringNAppendTcl_DStringAppend(dsPtr, url, -1); |
2975 | } else { |
2976 | Ns_DStringNAppendTcl_DStringAppend(dsPtr, " /", 2); |
2977 | if (*u.path != '\0') { |
2978 | Ns_DStringNAppendTcl_DStringAppend(dsPtr, u.path, -1); |
2979 | Ns_DStringNAppendTcl_DStringAppend(dsPtr, "/", 1); |
2980 | } |
2981 | Ns_DStringNAppendTcl_DStringAppend(dsPtr, u.tail, -1); |
2982 | if (u.query != NULL((void*)0)) { |
2983 | Ns_DStringNAppendTcl_DStringAppend(dsPtr, "?", 1); |
2984 | Ns_DStringNAppendTcl_DStringAppend(dsPtr, u.query, -1); |
2985 | } |
2986 | if (u.fragment != NULL((void*)0)) { |
2987 | Ns_DStringNAppendTcl_DStringAppend(dsPtr, "#", 1); |
2988 | Ns_DStringNAppendTcl_DStringAppend(dsPtr, u.fragment, -1); |
2989 | } |
2990 | } |
2991 | Ns_DStringNAppendTcl_DStringAppend(dsPtr, " HTTP/1.1\r\n", 11); |
2992 | |
2993 | Ns_Log(Ns_LogTaskDebug, "HttpConnect: %s request: %s", u.protocol, dsPtr->string); |
2994 | |
2995 | /* |
2996 | * Add provided headers, remove headers we are providing explicitly, |
2997 | * check User-Agent header existence. |
2998 | */ |
2999 | if (ownHeaders == NS_FALSE0 && hdrPtr != NULL((void*)0)) { |
3000 | size_t ii; |
3001 | |
3002 | if (keepHostHdr == NS_FALSE0) { |
3003 | Ns_SetIDeleteKey(hdrPtr, hostHeader); |
3004 | } |
3005 | Ns_SetIDeleteKey(hdrPtr, contentLengthHeader); |
3006 | Ns_SetIDeleteKey(hdrPtr, connectionHeader); |
3007 | for (ii = 0u; ii < Ns_SetSize(hdrPtr)((hdrPtr)->size); ii++) { |
3008 | const char *key, *val; |
3009 | |
3010 | key = Ns_SetKey(hdrPtr, ii)((hdrPtr)->fields[(ii)].name); |
3011 | val = Ns_SetValue(hdrPtr, ii)((hdrPtr)->fields[(ii)].value); |
3012 | Ns_DStringPrintf(dsPtr, "%s: %s\r\n", key, val); |
3013 | |
3014 | if (haveUserAgent == NS_FALSE0) { |
3015 | haveUserAgent = (strcasecmp(key, userAgentHeader) == 0); |
3016 | } |
3017 | } |
3018 | } |
3019 | |
3020 | /* |
3021 | * If User-Agent header not supplied, add our own |
3022 | */ |
3023 | if (haveUserAgent == NS_FALSE0) { |
3024 | Ns_DStringPrintf(dsPtr, "%s: %s/%s\r\n", userAgentHeader, |
3025 | Ns_InfoServerName(), Ns_InfoServerVersion()); |
3026 | } |
3027 | |
3028 | /* |
3029 | * Disable keep-alive connections |
3030 | * FIXME: why? |
3031 | */ |
3032 | Ns_DStringPrintf(dsPtr, "%s: close\r\n", connectionHeader); |
3033 | |
3034 | /* |
3035 | * Optionally, add our own Host header |
3036 | */ |
3037 | if (keepHostHdr == NS_FALSE0) { |
3038 | (void)Ns_DStringVarAppend(dsPtr, hostHeader, ": ", (char *)0L); |
3039 | (void)Ns_HttpLocationString(dsPtr, NULL((void*)0), u.host, portNr, defPortNr); |
3040 | Ns_DStringNAppendTcl_DStringAppend(dsPtr, "\r\n", 2); |
3041 | } |
3042 | |
3043 | /* |
3044 | * Calculate Content-Length header, handle in-memory body |
3045 | */ |
3046 | if (bodyObj == NULL((void*)0) && bodySize == 0) { |
3047 | |
3048 | /* |
3049 | * No body provided, close request/headers part |
3050 | */ |
3051 | httpPtr->bodySize = 0u; |
3052 | Ns_DStringNAppendTcl_DStringAppend(dsPtr, "\r\n", 2); |
3053 | httpPtr->requestHeaderSize = (size_t)dsPtr->length; |
3054 | |
3055 | } else { |
3056 | |
3057 | if (ownHeaders == NS_FALSE0 && hdrPtr != NULL((void*)0)) { |
3058 | contentType = Ns_SetIGet(hdrPtr, contentTypeHeader); |
3059 | } |
3060 | |
3061 | if (contentType == NULL((void*)0)) { |
3062 | |
3063 | /* |
3064 | * Previously, we required a content-type when a body is provided, |
3065 | * which was too strong due to the following paragraph in RFC 7231: |
3066 | * |
3067 | * A sender that generates a message containing a payload body |
3068 | * SHOULD generate a Content-Type header field in that message |
3069 | * unless the intended media type of the enclosed |
3070 | * representation is unknown to the sender. If a Content-Type |
3071 | * header field is not present, the recipient MAY either assume |
3072 | * a media type of "application/octet-stream" ([RFC2046], |
3073 | * Section 4.5.1) or examine the data to determine its type. |
3074 | */ |
3075 | |
3076 | if (bodyFileName != NULL((void*)0)) { |
3077 | contentType = Ns_GetMimeType(bodyFileName); |
3078 | } else { |
3079 | /* |
3080 | * We could call Ns_GetMimeType(tail), but this does not seem |
3081 | * to be the intention of RFC2046. |
3082 | */ |
3083 | contentType = "application/octet-stream"; |
3084 | } |
3085 | } |
3086 | |
3087 | if (bodyObj != NULL((void*)0)) { |
3088 | int bodyLen = 0; |
3089 | char *bodyStr = NULL((void*)0); |
3090 | bool_Bool binary; |
3091 | |
3092 | /* |
3093 | * Append in-memory body to the requests string |
3094 | * and calculate correct Content-Length header. |
3095 | * We do not anticipate in-memory body to be |
3096 | * 2GB+ hence the signed int type suffices. |
3097 | */ |
3098 | binary = NsTclObjIsByteArray(bodyObj); |
3099 | if (binary == NS_FALSE0) { |
3100 | if (contentType != NULL((void*)0)) { |
3101 | |
3102 | /* |
3103 | * Caveat Emptor: |
3104 | * This call may return true even for |
3105 | * completely regular text formats. |
3106 | */ |
3107 | binary = Ns_IsBinaryMimeType(contentType); |
3108 | } |
3109 | } |
3110 | if (binary == NS_TRUE1) { |
3111 | bodyStr = (char *)Tcl_GetByteArrayFromObj(bodyObj, &bodyLen); |
3112 | } else { |
3113 | bodyStr = Tcl_GetStringFromObj(bodyObj, &bodyLen); |
3114 | } |
3115 | |
3116 | httpPtr->bodySize = (size_t)bodyLen; |
3117 | Ns_DStringPrintf(dsPtr, "%s: %d\r\n\r\n", contentLengthHeader, |
3118 | bodyLen); |
3119 | |
3120 | httpPtr->requestHeaderSize = (size_t)dsPtr->length; |
3121 | Ns_DStringNAppendTcl_DStringAppend(dsPtr, bodyStr, bodyLen); |
3122 | |
3123 | } else if (bodySize > 0) { |
3124 | |
3125 | /* |
3126 | * Body will be passed over file/channel and the caller |
3127 | * has already determined the correct content size. |
3128 | * Note: body size may be way over 2GB! |
3129 | */ |
3130 | httpPtr->bodySize = (size_t)bodySize; |
3131 | Ns_DStringPrintf(dsPtr, "%s: %" PRIdz"zd" "\r\n\r\n", |
3132 | contentLengthHeader, bodySize); |
3133 | httpPtr->requestHeaderSize = (size_t)dsPtr->length; |
3134 | } |
3135 | } |
3136 | |
3137 | httpPtr->requestLength = (size_t)dsPtr->length; |
3138 | httpPtr->next = dsPtr->string; |
3139 | |
3140 | *httpPtrPtr = httpPtr; |
3141 | ns_free((void *)url2); |
3142 | |
3143 | if (Ns_LogSeverityEnabled(Ns_LogRequestDebug)) { |
3144 | Tcl_DString d; |
3145 | |
3146 | Tcl_DStringInit(&d); |
3147 | Ns_Log(Ns_LogRequestDebug, "full request (len %d) <%s>", |
3148 | dsPtr->length, |
3149 | Ns_DStringAppendPrintable(&d, NS_TRUE1, dsPtr->string, |
3150 | (size_t)dsPtr->length)); |
3151 | Tcl_DStringFree(&d); |
3152 | } |
3153 | |
3154 | |
3155 | if (ownHeaders == NS_TRUE1) { |
3156 | Ns_SetFree(hdrPtr); |
3157 | } |
3158 | |
3159 | return TCL_OK0; |
3160 | |
3161 | fail: |
3162 | if (ownHeaders == NS_TRUE1) { |
3163 | Ns_SetFree(hdrPtr); |
3164 | } |
3165 | ns_free((void *)url2); |
3166 | HttpClose(httpPtr); |
3167 | |
3168 | return TCL_ERROR1; |
3169 | } |
3170 | |
3171 | |
3172 | /* |
3173 | *---------------------------------------------------------------------- |
3174 | * |
3175 | * HttpAppendRawBuffer -- |
3176 | * |
3177 | * Append data to a spool file, a Tcl channel or memory. |
3178 | * |
3179 | * Results: |
3180 | * Tcl result code. |
3181 | * |
3182 | * Side effects: |
3183 | * None. |
3184 | * |
3185 | *---------------------------------------------------------------------- |
3186 | */ |
3187 | |
3188 | static int |
3189 | HttpAppendRawBuffer( |
3190 | NsHttpTask *httpPtr, |
3191 | const char *buffer, |
3192 | size_t size |
3193 | ) { |
3194 | int result = TCL_OK0; |
3195 | ssize_t written; |
3196 | |
3197 | NS_NONNULL_ASSERT(httpPtr != NULL)((void) (0)); |
3198 | NS_NONNULL_ASSERT(buffer != NULL)((void) (0)); |
3199 | |
3200 | if (httpPtr->recvSpoolMode == NS_TRUE1) { |
3201 | if (httpPtr->spoolFd != NS_INVALID_FD(-1)) { |
3202 | written = ns_writewrite(httpPtr->spoolFd, buffer, size); |
3203 | } else if (httpPtr->spoolChan != NULL((void*)0)) { |
3204 | written = (ssize_t)Tcl_Write(httpPtr->spoolChan, buffer, (int)size); |
3205 | } else { |
3206 | written = -1; |
3207 | } |
3208 | } else { |
3209 | Tcl_DStringAppend(&httpPtr->ds, buffer, (int)size); |
3210 | written = (ssize_t)size; |
3211 | } |
3212 | |
3213 | if (written > -1) { |
3214 | result = TCL_OK0; |
3215 | } else { |
3216 | Ns_Log(Error, "HttpAppendRawBuffer: spooling of received content failed"); |
3217 | result = TCL_ERROR1; |
3218 | } |
3219 | |
3220 | return result; |
3221 | } |
3222 | |
3223 | |
3224 | /* |
3225 | *---------------------------------------------------------------------- |
3226 | * |
3227 | * HttpAppendBuffer |
3228 | * |
3229 | * Append data (w/ or w/o compression) to the spool file |
3230 | * or Tcl channel or memory. |
3231 | * |
3232 | * Results: |
3233 | * Tcl result code |
3234 | * |
3235 | * Side effects: |
3236 | * May uncompress datat in the passed buffer. |
3237 | * |
3238 | *---------------------------------------------------------------------- |
3239 | */ |
3240 | |
3241 | static int |
3242 | HttpAppendBuffer( |
3243 | NsHttpTask *httpPtr, |
3244 | const char *buffer, |
3245 | size_t size |
3246 | ) { |
3247 | int result = TCL_OK0; |
3248 | size_t bodySize = 0u; |
3249 | |
3250 | NS_NONNULL_ASSERT(httpPtr != NULL)((void) (0)); |
3251 | NS_NONNULL_ASSERT(buffer != NULL)((void) (0)); |
3252 | |
3253 | Ns_Log(Ns_LogTaskDebug, "HttpAppendBuffer: got %" PRIuz"zu" " bytes flags:%.6x", |
3254 | size, httpPtr->flags); |
3255 | |
3256 | if (unlikely((httpPtr->flags & NS_HTTP_FLAG_DECOMPRESS) == 0u)(__builtin_expect(((httpPtr->flags & (1u<<0)) == 0u), 0)) |
3257 | || likely((httpPtr->flags & NS_HTTP_FLAG_GZIP_ENCODING) == 0u)(__builtin_expect(((httpPtr->flags & (1u<<1)) == 0u), 1))) { |
3258 | |
3259 | /* |
3260 | * Output raw content |
3261 | */ |
3262 | result = HttpAppendRawBuffer(httpPtr, buffer, size); |
3263 | if (result == TCL_OK0) { |
3264 | bodySize = size; |
3265 | } |
3266 | |
3267 | } else { |
3268 | char out[CHUNK_SIZE16384]; |
3269 | |
3270 | out[0] = '\0'; |
3271 | |
3272 | /* |
3273 | * Decompress content |
3274 | */ |
3275 | (void) Ns_InflateBufferInit(httpPtr->compress, buffer, size); |
3276 | do { |
3277 | size_t ul = 0u; |
3278 | |
3279 | result = Ns_InflateBuffer(httpPtr->compress, out, CHUNK_SIZE16384, &ul); |
3280 | if (HttpAppendRawBuffer(httpPtr, out, ul) == TCL_OK0) { |
3281 | bodySize += ul; |
3282 | } else { |
3283 | result = TCL_ERROR1; |
3284 | } |
3285 | } while(result == TCL_CONTINUE4); |
3286 | } |
3287 | |
3288 | if (result == TCL_OK0) { |
3289 | if (httpPtr->replyHeaderSize > 0 && httpPtr->status > 0) { |
3290 | |
3291 | /* |
3292 | * Headers and status have been parsed so all the |
3293 | * data coming from this point are counted up as |
3294 | * being the (uncompressed, decoded) reply content. |
3295 | */ |
3296 | Ns_MutexLock(&httpPtr->lock); |
3297 | httpPtr->replyBodySize += bodySize; |
3298 | httpPtr->replySize += size; |
3299 | Ns_MutexUnlock(&httpPtr->lock); |
3300 | } |
3301 | } |
3302 | |
3303 | return result; |
3304 | } |
3305 | |
3306 | |
3307 | /* |
3308 | *---------------------------------------------------------------------- |
3309 | * |
3310 | * HttpAppendContent |
3311 | * |
3312 | * Append reply content to where it belongs, |
3313 | * potentially decoding the chunked reply format. |
3314 | * |
3315 | * Results: |
3316 | * Tcl result code |
3317 | * |
3318 | * Side effects: |
3319 | * None. |
3320 | * |
3321 | *---------------------------------------------------------------------- |
3322 | */ |
3323 | |
3324 | static int |
3325 | HttpAppendContent( |
3326 | NsHttpTask *httpPtr, |
3327 | const char *buffer, |
3328 | size_t size |
3329 | ) { |
3330 | int result; |
3331 | |
3332 | NS_NONNULL_ASSERT(httpPtr != NULL)((void) (0)); |
3333 | |
3334 | if ((httpPtr->flags & NS_HTTP_FLAG_CHUNKED(1u<<2)) == 0u) { |
3335 | result = HttpAppendBuffer(httpPtr, buffer, size); |
3336 | } else { |
3337 | result = HttpAppendChunked(httpPtr, buffer, size); |
3338 | } |
3339 | |
3340 | return result; |
3341 | } |
3342 | |
3343 | |
3344 | /* |
3345 | *---------------------------------------------------------------------- |
3346 | * |
3347 | * HttpAppendChunked |
3348 | * |
3349 | * Parse chunked content. |
3350 | * |
3351 | * This implements a simple state machine that parses data |
3352 | * delivered blockwise. As the chunked-format may be |
3353 | * sliced on an arbitrary point between the blocks, we must |
3354 | * operate character-wise and maintain the internal state. |
3355 | * In order not to write yet-another completely closed and |
3356 | * fixed parser for the format, here is the implementation |
3357 | * of a simple state machine that can be easily programmed |
3358 | * to parse any character sequence, including the chunked. |
3359 | * |
3360 | * The machine consists of a set of callbacks. Each callback |
3361 | * operates on the passed buffer and size of data in the |
3362 | * buffer. Callbacks are invoked in the order how they are |
3363 | * specified in the array. Each callback returns signals |
3364 | * that influence the order of callback invocation. |
3365 | * Also each callback can replace the callback-set during |
3366 | * its operation and adjust the pointer to the next in row. |
3367 | * The signals returned by each callback include: |
3368 | * |
3369 | * TCL_OK done regularly, go to the next one |
3370 | * TCL_BREAK re-start from the first callback |
3371 | * TCL_ERROR stops parsing |
3372 | * |
3373 | * Callbacks are invoked one after another until there is |
3374 | * unrocessed data in the buffer. |
3375 | * The last callback is marked as NULL. After reaching it |
3376 | * all is repeated from the beginning. When all data is |
3377 | * consumed the callback that encountered that state |
3378 | * usually returns TCL_BREAK which stops the machine and |
3379 | * gives the control back to the user. |
3380 | * Each callback adjusts the number of bytes left in the |
3381 | * buffer and repositions the buffer to skip consumed |
3382 | * characters. |
3383 | * |
3384 | * Writing a parser requires writing one or more |
3385 | * HttpParserProcs, stuffing them in an array |
3386 | * terminated by the NULL parser and starting the |
3387 | * machine by simply invoking the registered procs. |
3388 | * |
3389 | * Due to its universal nature, this code can be made |
3390 | * independent of NsHttp and re-used elsewhere. |
3391 | * |
3392 | * Results: |
3393 | * Tcl result code |
3394 | * |
3395 | * Side effects: |
3396 | * None. |
3397 | * |
3398 | *---------------------------------------------------------------------- |
3399 | */ |
3400 | |
3401 | static int |
3402 | HttpAppendChunked( |
3403 | NsHttpTask *httpPtr, |
3404 | const char *buffer, |
3405 | size_t size |
3406 | ) { |
3407 | int result = TCL_OK0; |
3408 | char *buf = (char *)buffer; |
3409 | size_t len = size; |
3410 | NsHttpChunk *chunkPtr; |
3411 | |
3412 | NS_NONNULL_ASSERT(httpPtr != NULL)((void) (0)); |
3413 | chunkPtr = httpPtr->chunk; |
3414 | NS_NONNULL_ASSERT(chunkPtr != NULL)((void) (0)); |
3415 | |
3416 | Ns_Log(Ns_LogTaskDebug, "HttpAppendChunked: http:%p, task:%p", |
3417 | (void*)httpPtr, (void*)httpPtr->task); |
3418 | |
3419 | while (len > 0 && result != TCL_ERROR1) { |
3420 | NsHttpParseProc *parseProcPtr; |
3421 | |
3422 | Ns_Log(Ns_LogTaskDebug, "... len %lu ", len); |
3423 | |
3424 | parseProcPtr = *(chunkPtr->parsers + chunkPtr->callx); |
3425 | while (len > 0 && parseProcPtr != NULL((void*)0)) { |
3426 | result = (*parseProcPtr)(httpPtr, &buf, &len); |
3427 | Ns_Log(Ns_LogTaskDebug, "... parseproc returns %d ", result); |
3428 | if (result != TCL_OK0) { |
3429 | break; |
3430 | } |
3431 | chunkPtr->callx++; |
3432 | parseProcPtr = *(chunkPtr->parsers + chunkPtr->callx); |
3433 | } |
3434 | if (parseProcPtr == NULL((void*)0)) { |
3435 | chunkPtr->callx = 0; /* Repeat from the first proc */ |
3436 | } |
3437 | } |
3438 | |
3439 | if (result != TCL_ERROR1) { |
3440 | result = TCL_OK0; |
3441 | } |
3442 | |
3443 | return result; |
3444 | } |
3445 | |
3446 | |
3447 | /* |
3448 | *---------------------------------------------------------------------- |
3449 | * |
3450 | * HttpClose -- |
3451 | * |
3452 | * Finish task and cleanup memory |
3453 | * |
3454 | * Results: |
3455 | * None |
3456 | * |
3457 | * Side effects: |
3458 | * Free up memory |
3459 | * |
3460 | *---------------------------------------------------------------------- |
3461 | */ |
3462 | |
3463 | static void |
3464 | HttpClose( |
3465 | NsHttpTask *httpPtr |
3466 | ) { |
3467 | NS_NONNULL_ASSERT(httpPtr != NULL)((void) (0)); |
3468 | |
3469 | Ns_Log(Ns_LogTaskDebug, "HttpClose: http:%p, task:%p", |
3470 | (void*)httpPtr, (void*)httpPtr->task); |
3471 | |
3472 | /* |
3473 | * When HttpConnect runs into a failure, it might not have httpPtr->task |
3474 | * set. We cannot be sure, the task is always set. |
3475 | */ |
3476 | |
3477 | if (httpPtr->task != NULL((void*)0)) { |
3478 | (void) Ns_TaskFree(httpPtr->task); |
3479 | httpPtr->task = NULL((void*)0); |
3480 | } |
3481 | #ifdef HAVE_OPENSSL_EVP_H1 |
3482 | if (httpPtr->ssl != NULL((void*)0)) { |
3483 | SSL_shutdown(httpPtr->ssl); |
3484 | SSL_free(httpPtr->ssl); |
3485 | } |
3486 | if (httpPtr->ctx != NULL((void*)0)) { |
3487 | SSL_CTX_free(httpPtr->ctx); |
3488 | } |
3489 | #endif |
3490 | if (httpPtr->sock != NS_INVALID_SOCKET(-1)) { |
3491 | ns_sockcloseclose(httpPtr->sock); |
3492 | } |
3493 | if (httpPtr->spoolFileName != NULL((void*)0)) { |
3494 | ns_free((void *)httpPtr->spoolFileName); |
3495 | } |
3496 | if (httpPtr->doneCallback != NULL((void*)0)) { |
3497 | ns_free((void *)httpPtr->doneCallback); |
3498 | } |
3499 | if (httpPtr->spoolFd != NS_INVALID_FD(-1)) { |
3500 | (void)ns_closeclose(httpPtr->spoolFd); |
3501 | } |
3502 | if (httpPtr->bodyFileFd != NS_INVALID_FD(-1)) { |
3503 | (void)ns_closeclose(httpPtr->bodyFileFd); |
3504 | } |
3505 | if (httpPtr->bodyChan != NULL((void*)0)) { |
3506 | (void)Tcl_Close(NULL((void*)0), httpPtr->bodyChan); |
3507 | } |
3508 | if (httpPtr->spoolChan != NULL((void*)0)) { |
3509 | (void)Tcl_Close(NULL((void*)0), httpPtr->spoolChan); |
3510 | } |
3511 | if (httpPtr->compress != NULL((void*)0)) { |
3512 | (void)Ns_InflateEnd(httpPtr->compress); |
3513 | ns_free((void *)httpPtr->compress); |
3514 | } |
3515 | if (httpPtr->infoObj != NULL((void*)0)) { |
3516 | Tcl_DecrRefCount(httpPtr->infoObj)do { Tcl_Obj *_objPtr = (httpPtr->infoObj); if (_objPtr-> refCount-- <= 1) { TclFreeObj(_objPtr); } } while(0); |
3517 | httpPtr->infoObj = NULL((void*)0); |
3518 | } |
3519 | if (httpPtr->replyHeaders != NULL((void*)0)) { |
3520 | Ns_SetFree(httpPtr->replyHeaders); |
3521 | } |
3522 | if (httpPtr->timeout != NULL((void*)0)) { |
3523 | ns_free((void *)httpPtr->timeout); |
3524 | } |
3525 | |
3526 | ns_free((void *)httpPtr->url); |
3527 | ns_free((void *)httpPtr->method); |
3528 | |
3529 | Ns_MutexDestroy(&httpPtr->lock); /* Should not be held locked here! */ |
3530 | Tcl_DStringFree(&httpPtr->ds); |
3531 | |
3532 | if (httpPtr->chunk != NULL((void*)0)) { |
3533 | Tcl_DStringFree(&httpPtr->chunk->ds); |
3534 | ns_free((void *)httpPtr->chunk); |
3535 | } |
3536 | |
3537 | ns_free((void *)httpPtr); |
3538 | } |
3539 | |
3540 | |
3541 | /* |
3542 | *---------------------------------------------------------------------- |
3543 | * |
3544 | * HttpCancel -- |
3545 | * |
3546 | * Mark the task as cancelled and wait (indefinitely) |
3547 | * for the task to finish. |
3548 | * |
3549 | * Results: |
3550 | * None. |
3551 | * |
3552 | * Side effects: |
3553 | * May free task-related memory |
3554 | * |
3555 | *---------------------------------------------------------------------- |
3556 | */ |
3557 | |
3558 | static void |
3559 | HttpCancel( |
3560 | NsHttpTask *httpPtr |
3561 | ) { |
3562 | NS_NONNULL_ASSERT(httpPtr != NULL)((void) (0)); |
3563 | NS_NONNULL_ASSERT(httpPtr->task != NULL)((void) (0)); |
3564 | |
3565 | (void) Ns_TaskCancel(httpPtr->task); |
3566 | Ns_TaskWaitCompleted(httpPtr->task); |
3567 | } |
3568 | |
3569 | |
3570 | /* |
3571 | *---------------------------------------------------------------------- |
3572 | * |
3573 | * HttpAddInfo -- |
3574 | * |
3575 | * Adds some task-related information |
3576 | * in form of a Tcl dictionary. |
3577 | * |
3578 | * Results: |
3579 | * None. |
3580 | * |
3581 | * Side effects: |
3582 | * None. |
3583 | * |
3584 | *---------------------------------------------------------------------- |
3585 | */ |
3586 | |
3587 | static void |
3588 | HttpAddInfo( |
3589 | NsHttpTask *httpPtr, |
3590 | const char *key, |
3591 | const char *value |
3592 | ) { |
3593 | Tcl_Obj *keyObj, *valObj; |
3594 | |
3595 | if (httpPtr->infoObj == NULL((void*)0)) { |
3596 | httpPtr->infoObj = Tcl_NewDictObj(); |
3597 | Tcl_IncrRefCount(httpPtr->infoObj)++(httpPtr->infoObj)->refCount; |
3598 | } |
3599 | |
3600 | keyObj = Tcl_NewStringObj(key, -1); |
3601 | valObj = Tcl_NewStringObj(value, -1); |
3602 | |
3603 | Tcl_DictObjPut(NULL((void*)0), httpPtr->infoObj, keyObj, valObj); |
3604 | } |
3605 | |
3606 | |
3607 | /* |
3608 | *---------------------------------------------------------------------- |
3609 | * |
3610 | * HttpTaskSend -- |
3611 | * |
3612 | * Send data via plain TCP or via OpenSSL. |
3613 | * May send less data then requested. |
3614 | * |
3615 | * Results: |
3616 | * Number of bytes sent or -1 on error. |
3617 | * |
3618 | * Side effects: |
3619 | * If passed length of 0, will do nothing (and return 0). |
3620 | * Otherwise, if unable to send data, will return 0, |
3621 | * if the underlying socket is (still) not writable. |
3622 | * In such cases, caller must repeat the operation after |
3623 | * making sure (by whatever means) the socket is writable. |
3624 | * |
3625 | *---------------------------------------------------------------------- |
3626 | */ |
3627 | |
3628 | static ssize_t |
3629 | HttpTaskSend( |
3630 | const NsHttpTask *httpPtr, |
3631 | const void *buffer, |
3632 | size_t length |
3633 | ) { |
3634 | ssize_t sent; |
3635 | struct iovec iov, *bufs = &iov; |
3636 | int nbufs = 1; |
3637 | |
3638 | NS_NONNULL_ASSERT(httpPtr != NULL)((void) (0)); |
3639 | NS_NONNULL_ASSERT(buffer != NULL)((void) (0)); |
3640 | |
3641 | iov.iov_base = (char*)buffer; |
3642 | iov.iov_len = length; |
3643 | |
3644 | if (httpPtr->ssl == NULL((void*)0)) { |
3645 | sent = Ns_SockSendBufs2(httpPtr->sock, bufs, nbufs, 0); |
3646 | } else { |
3647 | #ifndef HAVE_OPENSSL_EVP_H1 |
3648 | sent = -1; |
3649 | #else |
3650 | sent = Ns_SSLSendBufs2(httpPtr->ssl, bufs, nbufs); |
3651 | #endif |
3652 | } |
3653 | |
3654 | Ns_Log(Ns_LogTaskDebug, "HttpTaskSend: sent %" PRIdz"zd" |
3655 | " bytes (out of %" PRIuz"zu" ")", sent, length); |
3656 | |
3657 | return sent; |
3658 | } |
3659 | |
3660 | |
3661 | /* |
3662 | *---------------------------------------------------------------------- |
3663 | * |
3664 | * HttpTaskRecv -- |
3665 | * |
3666 | * Receive data via plain TCP or via OpenSSL. |
3667 | * |
3668 | * Results: |
3669 | * Number of bytes received or -1 on error |
3670 | * |
3671 | * Side effects: |
3672 | * None. |
3673 | * |
3674 | *---------------------------------------------------------------------- |
3675 | */ |
3676 | |
3677 | static ssize_t |
3678 | HttpTaskRecv( |
3679 | const NsHttpTask *httpPtr, |
3680 | char *buffer, |
3681 | size_t length, |
3682 | Ns_SockState *statePtr |
3683 | ) { |
3684 | ssize_t recv; |
3685 | int nbufs = 1; |
3686 | unsigned long recvErrorCode; |
3687 | struct iovec iov, *bufs = &iov; |
3688 | |
3689 | NS_NONNULL_ASSERT(httpPtr != NULL)((void) (0)); |
3690 | NS_NONNULL_ASSERT(buffer != NULL)((void) (0)); |
3691 | |
3692 | iov.iov_base = (void *)buffer; |
3693 | iov.iov_len = length; |
3694 | |
3695 | if (httpPtr->ssl == NULL((void*)0)) { |
3696 | recv = Ns_SockRecvBufs2(httpPtr->sock, bufs, nbufs, 0u, statePtr, &recvErrorCode); |
3697 | } else { |
3698 | #ifndef HAVE_OPENSSL_EVP_H1 |
3699 | recv = -1; |
3700 | #else |
3701 | recv = Ns_SSLRecvBufs2(httpPtr->ssl, bufs, nbufs, statePtr, &recvErrorCode); |
3702 | #endif |
3703 | } |
3704 | |
3705 | Ns_Log(Ns_LogTaskDebug, "HttpTaskRecv: received %" PRIdz"zd" |
3706 | " bytes (buffer size %" PRIuz"zu" ")", recv, length); |
3707 | |
3708 | return recv; |
3709 | } |
3710 | |
3711 | |
3712 | /* |
3713 | *---------------------------------------------------------------------- |
3714 | * |
3715 | * HttpDoneCallback -- |
3716 | * |
3717 | * Evaluate the doneCallback. For the time being, this is |
3718 | * executed in the default server context (may not be right!). |
3719 | * |
3720 | * Results: |
3721 | * None |
3722 | * |
3723 | * Side effects: |
3724 | * Many, depending on the callback. |
3725 | * Http task is garbage collected. |
3726 | * |
3727 | *---------------------------------------------------------------------- |
3728 | */ |
3729 | |
3730 | static void |
3731 | HttpDoneCallback( |
3732 | NsHttpTask *httpPtr |
3733 | ) { |
3734 | int result; |
3735 | Tcl_Interp *interp; |
3736 | Tcl_DString script; |
3737 | |
3738 | NS_NONNULL_ASSERT(httpPtr != NULL)((void) (0)); |
3739 | |
3740 | Ns_Log(Ns_LogTaskDebug, "HttpDoneCallback: finalSockState:%.2x, err:(%s)", |
3741 | httpPtr->finalSockState, |
3742 | (httpPtr->error != NULL((void*)0)) ? httpPtr->error : "none"); |
3743 | |
3744 | interp = NsTclAllocateInterp( httpPtr->servPtr); |
3745 | |
3746 | result = HttpGetResult(interp, httpPtr); |
3747 | |
3748 | Tcl_DStringInit(&script); |
3749 | Tcl_DStringAppend(&script, httpPtr->doneCallback, -1); |
3750 | Ns_DStringPrintf(&script, " %d ", result); |
3751 | Tcl_DStringAppendElement(&script, Tcl_GetStringResult(interp)); |
3752 | |
3753 | /* |
3754 | * Splice body/spool channels into the callback interp. |
3755 | * All supplied channels must be closed by the callback. |
3756 | * Alternatively, the Tcl will close them at the point |
3757 | * of interp de-allocation, which might not be safe. |
3758 | */ |
3759 | HttpSpliceChannels(interp, httpPtr); |
3760 | |
3761 | result = Tcl_EvalEx(interp, script.string, script.length, 0); |
3762 | |
3763 | if (result != TCL_OK0) { |
3764 | (void) Ns_TclLogErrorInfo(interp, "\n(context: httptask)"); |
3765 | } |
3766 | |
3767 | Tcl_DStringFree(&script); |
3768 | Ns_TclDeAllocateInterp(interp); |
3769 | |
3770 | HttpClose(httpPtr); /* This frees the httpPtr! */ |
3771 | } |
3772 | |
3773 | |
3774 | /* |
3775 | *---------------------------------------------------------------------- |
3776 | * |
3777 | * HttpProc -- |
3778 | * |
3779 | * Task callback for ns_http connections. |
3780 | * This is a state-machine that Ns_Task is repeatedly |
3781 | * calling to process various socket states. |
3782 | * |
3783 | * Results: |
3784 | * None. |
3785 | * |
3786 | * Side effects: |
3787 | * Calls Ns_TaskCallback and Ns_TaskDone to manage task state |
3788 | * |
3789 | *---------------------------------------------------------------------- |
3790 | */ |
3791 | |
3792 | static void |
3793 | HttpProc( |
3794 | Ns_Task *task, |
3795 | NS_SOCKETint UNUSED(sock)UNUSED_sock __attribute__((__unused__)), |
3796 | void *arg, |
3797 | Ns_SockState why |
3798 | ) { |
3799 | NsHttpTask *httpPtr; |
3800 | ssize_t n = 0; |
3801 | bool_Bool taskDone = NS_TRUE1; |
3802 | Ns_SockState nextState; |
3803 | |
3804 | NS_NONNULL_ASSERT(task != NULL)((void) (0)); |
3805 | NS_NONNULL_ASSERT(arg != NULL)((void) (0)); |
3806 | |
3807 | httpPtr = (NsHttpTask *)arg; |
3808 | |
3809 | Ns_Log(Ns_LogTaskDebug, "HttpProc: enter socket state %.2x", why); |
3810 | |
3811 | switch (why) { |
3812 | case NS_SOCK_INIT: |
3813 | |
3814 | Ns_Log(Ns_LogTaskDebug, "HttpProc: NS_SOCK_INIT"); |
3815 | |
3816 | if (httpPtr->bodyChan != NULL((void*)0)) { |
3817 | HttpSpliceChannel(NULL((void*)0), httpPtr->bodyChan); |
3818 | } |
3819 | if (httpPtr->spoolChan != NULL((void*)0)) { |
3820 | HttpSpliceChannel(NULL((void*)0), httpPtr->spoolChan); |
3821 | } |
3822 | Ns_TaskCallback(task, NS_SOCK_WRITE, httpPtr->timeout); |
3823 | taskDone = NS_FALSE0; |
3824 | |
3825 | break; |
3826 | |
3827 | case NS_SOCK_WRITE: |
3828 | |
3829 | nextState = why; /* We may switch to read state below */ |
3830 | |
3831 | Ns_Log(Ns_LogTaskDebug, "HttpProc: NS_SOCK_WRITE sendSpoolMode:%d," |
3832 | " fd:%d, chan:%s", httpPtr->sendSpoolMode, httpPtr->bodyFileFd, |
3833 | httpPtr->bodyChan ? Tcl_GetChannelName(httpPtr->bodyChan) : "(none)"); |
3834 | |
3835 | if (httpPtr->sendSpoolMode == NS_FALSE0) { |
3836 | size_t remain; |
3837 | |
3838 | /* |
3839 | * Send (next part of) the request from memory. |
3840 | * This may not include the request body, as it may have |
3841 | * to be spooled from the passed file or Tcl channel. |
3842 | * Decision whether to do this or not is done when we have |
3843 | * finished sending request line + all of the headers. |
3844 | */ |
3845 | remain = (size_t)(httpPtr->requestLength - httpPtr->sent); |
3846 | |
3847 | Ns_Log(Ns_LogTaskDebug, "HttpProc: NS_SOCK_WRITE" |
3848 | " will send dsPtr:%p, next:%p, remain:%" PRIuz"zu", |
3849 | (void*)httpPtr->ds.string, (void*)httpPtr->next, remain); |
3850 | |
3851 | if (remain > 0) { |
3852 | n = HttpTaskSend(httpPtr, httpPtr->next, remain); |
3853 | } else { |
3854 | n = 0; |
3855 | } |
3856 | |
3857 | if (n == -1) { |
3858 | httpPtr->error = "http send failed"; |
3859 | Ns_Log(Ns_LogTaskDebug, "HttpProc: NS_SOCK_WRITE send failed"); |
3860 | |
3861 | } else { |
3862 | ssize_t nb = 0; |
3863 | |
3864 | httpPtr->next += n; |
3865 | Ns_MutexLock(&httpPtr->lock); |
3866 | httpPtr->sent += (size_t)n; |
3867 | nb = (ssize_t)(httpPtr->sent - httpPtr->requestHeaderSize); |
3868 | if (nb > 0) { |
3869 | httpPtr->sendBodySize = (size_t)nb; |
3870 | } |
3871 | Ns_MutexUnlock(&httpPtr->lock); |
3872 | remain = (size_t)(httpPtr->requestLength - httpPtr->sent); |
3873 | if (remain > 0) { |
3874 | |
3875 | /* |
3876 | * We still have something to be sent |
3877 | * left in memory. |
3878 | */ |
3879 | Ns_Log(Ns_LogTaskDebug, "HttpProc: NS_SOCK_WRITE" |
3880 | " sent:%" PRIdz"zd" " bytes from memory," |
3881 | " remain:%" PRIuz"zu", n, remain); |
3882 | } else { |
3883 | const char *logMsg; |
3884 | |
3885 | /* |
3886 | * At this point we sent the line/headers |
3887 | * and can now switch to sending the request |
3888 | * body if any expected, or switch to the next |
3889 | * socket state (read stuff from the remote). |
3890 | */ |
3891 | logMsg = "HttpProc: NS_SOCK_WRITE headers sent"; |
3892 | httpPtr->next = NULL((void*)0); |
3893 | Tcl_DStringSetLength(&httpPtr->ds, 0); |
3894 | |
3895 | if (httpPtr->bodyFileFd != NS_INVALID_FD(-1)) { |
3896 | httpPtr->sendSpoolMode = NS_TRUE1; |
3897 | Ns_Log(Ns_LogTaskDebug, "%s, spool using fd:%d, size:%" |
3898 | PRIuz"zu", logMsg, httpPtr->bodyFileFd, |
3899 | httpPtr->bodySize); |
3900 | |
3901 | } else if (httpPtr->bodyChan != NULL((void*)0)) { |
3902 | httpPtr->sendSpoolMode = NS_TRUE1; |
3903 | Ns_Log(Ns_LogTaskDebug, "%s, spool using chan:%s, size:%" |
3904 | PRIuz"zu", logMsg, Tcl_GetChannelName(httpPtr->bodyChan), |
3905 | httpPtr->bodySize); |
3906 | |
3907 | } else { |
3908 | httpPtr->sendSpoolMode = NS_FALSE0; |
3909 | Ns_Log(Ns_LogTaskDebug, "%s, switch to read", logMsg); |
3910 | nextState = NS_SOCK_READ; |
3911 | } |
3912 | } |
3913 | |
3914 | taskDone = NS_FALSE0; |
3915 | } |
3916 | |
3917 | } else { |
3918 | size_t toRead = CHUNK_SIZE16384; |
3919 | bool_Bool onEof = NS_FALSE0; |
3920 | |
3921 | /* |
3922 | * Send the request body from a file or from a Tcl channel. |
3923 | */ |
3924 | |
3925 | Ns_Log(Ns_LogTaskDebug, "HttpProc: NS_SOCK_WRITE sendSpoolMode" |
3926 | " buffersize:%d buffer:%p next:%p sent:%" PRIuz"zu", |
3927 | httpPtr->ds.length, (void *)httpPtr->ds.string, |
3928 | (void *)httpPtr->next, httpPtr->sent); |
3929 | |
3930 | if (httpPtr->next == NULL((void*)0)) { |
3931 | |
3932 | /* |
3933 | * Read remaining body data in chunks |
3934 | */ |
3935 | Tcl_DStringSetLength(&httpPtr->ds, (int)toRead); |
3936 | httpPtr->next = httpPtr->ds.string; |
3937 | if (toRead > httpPtr->bodySize) { |
3938 | toRead = httpPtr->bodySize; /* At end of the body! */ |
3939 | } |
3940 | if (toRead == 0) { |
3941 | n = 0; |
3942 | } else if (httpPtr->bodyFileFd != NS_INVALID_FD(-1)) { |
3943 | n = ns_readread(httpPtr->bodyFileFd, httpPtr->next, toRead); |
3944 | } else if (httpPtr->bodyChan != NULL((void*)0)) { |
3945 | n = Tcl_Read(httpPtr->bodyChan, httpPtr->next, (int)toRead); |
3946 | } else { |
3947 | n = -1; /* Here we could read only from file or chan! */ |
3948 | } |
3949 | |
3950 | if (toRead == 0 || (n > -1 && n < (ssize_t)toRead)) { |
3951 | |
3952 | /* |
3953 | * We have a short file/chan read which can only mean |
3954 | * we are at the EOF (we are reading in blocking mode!). |
3955 | */ |
3956 | onEof = NS_TRUE1; |
3957 | Tcl_DStringSetLength(&httpPtr->ds, (int)n); |
3958 | } |
3959 | |
3960 | if (n > 0) { |
3961 | assert((size_t)n <= httpPtr->bodySize)((void) (0)); |
3962 | httpPtr->bodySize -= (size_t)n; |
3963 | } |
3964 | |
3965 | Ns_Log(Ns_LogTaskDebug, "HttpProc: NS_SOCK_WRITE sendSpoolMode" |
3966 | " got:%" PRIdz"zd" " wanted:%" PRIuz"zu" " bytes, eof:%d", |
3967 | n, toRead, onEof); |
3968 | |
3969 | } else { |
3970 | |
3971 | /* |
3972 | * The buffer has still some content left |
3973 | */ |
3974 | n = httpPtr->ds.length - (httpPtr->next - httpPtr->ds.string); |
3975 | |
3976 | Ns_Log(Ns_LogTaskDebug, "HttpProc: NS_SOCK_WRITE" |
3977 | " remaining buffersize:%" PRIdz"zd", n); |
3978 | } |
3979 | |
3980 | /* |
3981 | * We got some bytes from file/channel/memory |
3982 | * so send them to the remote. |
3983 | */ |
3984 | |
3985 | if (unlikely(n == -1)(__builtin_expect((n == -1), 0))) { |
3986 | httpPtr->error = "http read failed"; |
3987 | Ns_Log(Ns_LogTaskDebug, "HttpProc: NS_SOCK_WRITE read failed"); |
3988 | |
3989 | } else { |
3990 | ssize_t toSend = n, sent = 0; |
3991 | |
3992 | if (toSend > 0) { |
3993 | sent = HttpTaskSend(httpPtr, httpPtr->next, (size_t)toSend); |
3994 | } |
3995 | |
3996 | Ns_Log(Ns_LogTaskDebug, "HttpProc: NS_SOCK_WRITE sent" |
3997 | " %" PRIdz"zd" " of %" PRIuz"zu" " bytes", sent, toSend); |
3998 | |
3999 | if (unlikely(sent == -1)(__builtin_expect((sent == -1), 0))) { |
4000 | httpPtr->error = "http send failed"; |
4001 | Ns_Log(Ns_LogTaskDebug, "HttpProc: NS_SOCK_WRITE send failed"); |
4002 | |
4003 | } else if (sent < toSend) { |
4004 | |
4005 | /* |
4006 | * We have sent less bytes than available |
4007 | * in the buffer. At this point we may have |
4008 | * sent zero bytes as well but this is very |
4009 | * unlikely and would mean we were somehow |
4010 | * wrongly signaled from the task handler |
4011 | * (we wrote nothing to a writable sink?). |
4012 | */ |
4013 | |
4014 | if (likely(sent > 0)(__builtin_expect((sent > 0), 1))) { |
4015 | ssize_t nb = 0; |
4016 | |
4017 | httpPtr->next += sent; |
4018 | Ns_MutexLock(&httpPtr->lock); |
4019 | httpPtr->sent += (size_t)sent; |
4020 | nb = (ssize_t)(httpPtr->sent - httpPtr->requestHeaderSize); |
4021 | if (nb > 0) { |
4022 | httpPtr->sendBodySize = (size_t)nb; |
4023 | } |
4024 | Ns_MutexUnlock(&httpPtr->lock); |
4025 | } |
4026 | Ns_Log(Ns_LogTaskDebug, "HttpProc: NS_SOCK_WRITE partial" |
4027 | " send, remain:%ld", (long)(toSend - sent)); |
4028 | |
4029 | taskDone = NS_FALSE0; |
4030 | |
4031 | } else if (sent == toSend) { |
4032 | |
4033 | /* |
4034 | * We have sent the whole buffer |
4035 | */ |
4036 | if (sent > 0) { |
4037 | ssize_t nb = 0; |
4038 | |
4039 | Ns_MutexLock(&httpPtr->lock); |
4040 | httpPtr->sent += (size_t)sent; |
4041 | nb = (ssize_t)(httpPtr->sent - httpPtr->requestHeaderSize); |
4042 | if (nb > 0) { |
4043 | httpPtr->sendBodySize = (size_t)nb; |
4044 | } |
4045 | Ns_MutexUnlock(&httpPtr->lock); |
4046 | Ns_Log(Ns_LogTaskDebug, "HttpProc: NS_SOCK_WRITE sent" |
4047 | " full chunk, bytes:%" PRIdz"zd", sent); |
4048 | } |
4049 | |
4050 | Tcl_DStringSetLength(&httpPtr->ds, 0); |
4051 | httpPtr->next = NULL((void*)0); |
4052 | |
4053 | taskDone = NS_FALSE0; |
4054 | |
4055 | /* |
4056 | * Check if on the last chunk, |
4057 | * or on the premature EOF. |
4058 | */ |
4059 | if (toRead < CHUNK_SIZE16384 || onEof == NS_TRUE1) { |
4060 | if (httpPtr->bodySize == 0) { |
4061 | |
4062 | /* |
4063 | * That was the last chunk. |
4064 | * All of the body was sent, switch state |
4065 | */ |
4066 | Ns_Log(Ns_LogTaskDebug, "HttpProc: NS_SOCK_WRITE" |
4067 | " whole body sent, switch to read"); |
4068 | nextState = NS_SOCK_READ; |
4069 | |
4070 | } else { |
4071 | |
4072 | /* |
4073 | * We read less than chunksize bytes, the source |
4074 | * is on EOF, so what to do? Since we can't |
4075 | * rectify Content-Length, receiver expects us |
4076 | * to send more... |
4077 | * This situation can only happen: |
4078 | * WHEN fed with the wrong (too large) bodySize |
4079 | * OR when the file got truncated while we read it |
4080 | * OR somebody tossed wrongly positioned channel. |
4081 | * What can we do? |
4082 | * We can pretend all is fine and go to reading |
4083 | * state, expecting that either the peer's or |
4084 | * our own timeout expires. |
4085 | * Or, we can trigger the error immediately. |
4086 | * We opt for the latter. |
4087 | */ |
4088 | httpPtr->error = "http read failed"; |
4089 | taskDone = NS_TRUE1; |
4090 | Ns_Log(Ns_LogTaskDebug, "HttpProc: NS_SOCK_WRITE" |
4091 | " short read, left:%" PRIuz"zu", httpPtr->bodySize); |
4092 | } |
4093 | } |
4094 | |
4095 | } else { |
4096 | |
4097 | /* |
4098 | * This is completely unexpected: we have send more |
4099 | * then requested? There is something entirely wrong! |
4100 | * I have no idea what would be the best to do here. |
4101 | */ |
4102 | Ns_Log(Error, "HttpProc: NS_SOCK_WRITE bad state?"); |
4103 | } |
4104 | } |
4105 | } |
4106 | |
4107 | /* |
4108 | * If the request is not finished, re-apply the timeout |
4109 | * for the next task iteration. |
4110 | */ |
4111 | if (taskDone != NS_TRUE1) { |
4112 | Ns_TaskCallback(task, nextState, httpPtr->timeout); |
4113 | } |
4114 | |
4115 | break; |
4116 | |
4117 | case NS_SOCK_READ: |
4118 | |
4119 | Ns_Log(Ns_LogTaskDebug, "HttpProc: NS_SOCK_READ"); |
4120 | |
4121 | nextState = why; |
4122 | |
4123 | if (httpPtr->sent == 0u) { |
4124 | Ns_Log(Ns_LogTaskDebug, "HttpProc: NS_SOCK_READ nothing sent?"); |
4125 | |
4126 | } else { |
4127 | char buf[CHUNK_SIZE16384]; |
4128 | size_t len = CHUNK_SIZE16384; |
4129 | Ns_SockState sockState = NS_SOCK_NONE; |
4130 | |
4131 | /* |
4132 | * FIXME: |
4133 | * |
4134 | * This part can be optimized to read the reply data |
4135 | * directly into DString instead in the stack buffer. |
4136 | */ |
4137 | |
4138 | if (httpPtr->replyLength > 0) { |
4139 | size_t remain; |
4140 | |
4141 | remain = httpPtr->replyLength - httpPtr->replySize; |
4142 | if (len > remain) { |
4143 | len = remain; |
4144 | } |
4145 | } |
4146 | |
4147 | if (len > 0) { |
4148 | n = HttpTaskRecv(httpPtr, buf, len, &sockState); |
4149 | } else { |
4150 | n = 0; |
4151 | } |
4152 | |
4153 | if (unlikely(n == -1)(__builtin_expect((n == -1), 0))) { |
4154 | |
4155 | /* |
4156 | * Terminal case, some unexpected error. |
4157 | * At this point we do not really know |
4158 | * what kind of error it was. |
4159 | */ |
4160 | httpPtr->error = "http read failed"; |
4161 | Ns_Log(Ns_LogTaskDebug, "HttpProc: NS_SOCK_READ receive failed"); |
4162 | |
4163 | } else if (n > 0) { |
4164 | int result; |
4165 | |
4166 | /* |
4167 | * Most likely case: we got some bytes. |
4168 | */ |
4169 | Ns_MutexLock(&httpPtr->lock); |
4170 | httpPtr->received += (size_t)n; |
4171 | Ns_MutexUnlock(&httpPtr->lock); |
4172 | |
4173 | result = HttpAppendContent(httpPtr, buf, (size_t)n); |
4174 | if (unlikely(result != TCL_OK)(__builtin_expect((result != 0), 0))) { |
4175 | httpPtr->error = "http read failed"; |
4176 | Ns_Log(Ns_LogTaskDebug, "HttpProc: NS_SOCK_READ append failed"); |
4177 | } else { |
4178 | Ns_ReturnCode rc = NS_OK; |
4179 | if (httpPtr->replyHeaderSize == 0) { |
4180 | |
4181 | /* |
4182 | * Still not done receiving status/headers |
4183 | */ |
4184 | HttpCheckHeader(httpPtr); |
4185 | } |
4186 | if (httpPtr->replyHeaderSize > 0 && httpPtr->status == 0) { |
4187 | |
4188 | /* |
4189 | * Parses received status/headers, |
4190 | * decides where to spool content. |
4191 | */ |
4192 | rc = HttpCheckSpool(httpPtr); |
4193 | } |
4194 | if (unlikely(rc != NS_OK)(__builtin_expect((rc != NS_OK), 0))) { |
4195 | httpPtr->error = "http read failed"; |
4196 | Ns_Log(Ns_LogTaskDebug, "HttpProc: NS_SOCK_READ spool failed"); |
4197 | } else { |
4198 | |
4199 | /* |
4200 | * At the point of reading response content (if any). |
4201 | * Continue reading if any of the following is true: |
4202 | * |
4203 | * o. remote tells content length |
4204 | * o. chunked content not fully parsed |
4205 | * o. caller tells it expects content |
4206 | */ |
4207 | if (httpPtr->replyLength > 0 |
4208 | || ((httpPtr->flags & NS_HTTP_FLAG_CHUNKED(1u<<2)) != 0u |
4209 | && (httpPtr->flags & NS_HTTP_FLAG_CHUNKED_END(1u<<3)) == 0u) |
4210 | || (httpPtr->flags & NS_HTTP_FLAG_EMPTY(1u<<5)) == 0u) { |
4211 | |
4212 | taskDone = NS_FALSE0; |
4213 | } |
4214 | } |
4215 | } |
4216 | |
4217 | } else if (len > 0 && sockState == NS_SOCK_AGAIN) { |
4218 | |
4219 | /* |
4220 | * Received zero bytes on a readable socket |
4221 | * but it is not on EOD, it wants us to read more. |
4222 | */ |
4223 | taskDone = NS_FALSE0; |
4224 | |
4225 | } else if (len == 0 /* Consumed all of the replyLength bytes */ |
4226 | || sockState == NS_SOCK_DONE /* EOD on read */ |
4227 | || ((httpPtr->flags & (NS_HTTP_FLAG_CHUNKED(1u<<2) |
4228 | | NS_HTTP_FLAG_CHUNKED_END(1u<<3))) != 0u)) { |
4229 | |
4230 | taskDone = NS_TRUE1; /* Just for illustrative purposes */ |
4231 | |
4232 | } else { |
4233 | |
4234 | /* |
4235 | * Some terminal error state |
4236 | */ |
4237 | Ns_Log(Ns_LogTaskDebug, "HttpProc: NS_SOCK_READ error," |
4238 | " sockState:%.2x", sockState); |
4239 | } |
4240 | } |
4241 | |
4242 | /* |
4243 | * If the request is not finished, re-apply the timeout |
4244 | * for the next task iteration. |
4245 | */ |
4246 | if (taskDone != NS_TRUE1) { |
4247 | Ns_TaskCallback(task, nextState, httpPtr->timeout); |
4248 | } |
4249 | |
4250 | break; |
4251 | |
4252 | case NS_SOCK_TIMEOUT: |
4253 | |
4254 | Ns_Log(Ns_LogTaskDebug, "HttpProc: NS_SOCK_TIMEOUT"); |
4255 | |
4256 | /* |
4257 | * Without a doneCallback, NS_SOCK_DONE must be handled |
4258 | * by the caller (normally, caller would cancel the task) |
4259 | * hence we leave the task in processing. |
4260 | * |
4261 | * With doneCallback, the caller is cut-off of the task ID |
4262 | * (i.e. there is no chance for cancel) hence we must mark |
4263 | * the task as completed (done) right here. |
4264 | */ |
4265 | taskDone = (httpPtr->doneCallback != NULL((void*)0)); |
4266 | httpPtr->error = "http request timeout"; |
4267 | |
4268 | break; |
4269 | |
4270 | case NS_SOCK_EXIT: |
4271 | |
4272 | Ns_Log(Ns_LogTaskDebug, "HttpProc: NS_SOCK_EXIT"); |
4273 | httpPtr->error = "http task queue shutdown"; |
4274 | |
4275 | break; |
4276 | |
4277 | case NS_SOCK_CANCEL: |
4278 | |
4279 | Ns_Log(Ns_LogTaskDebug, "HttpProc: NS_SOCK_CANCEL"); |
4280 | httpPtr->error = "http request cancelled"; |
4281 | |
4282 | break; |
4283 | |
4284 | case NS_SOCK_EXCEPTION: |
4285 | |
4286 | Ns_Log(Ns_LogTaskDebug, "HttpProc: NS_SOCK_EXCEPTION"); |
4287 | httpPtr->error = "unexpected http socket exception"; |
4288 | |
4289 | break; |
4290 | |
4291 | case NS_SOCK_AGAIN: |
4292 | |
4293 | Ns_Log(Ns_LogTaskDebug, "HttpProc: NS_SOCK_AGAIN"); |
4294 | httpPtr->error = "unexpected http EOD"; |
4295 | |
4296 | break; |
4297 | |
4298 | case NS_SOCK_DONE: |
4299 | |
4300 | Ns_Log(Ns_LogTaskDebug, "HttpProc: NS_SOCK_DONE doneCallback:(%s)", |
4301 | httpPtr->doneCallback != NULL((void*)0) ? httpPtr->doneCallback : "none"); |
4302 | |
4303 | if (httpPtr->bodyChan != NULL((void*)0)) { |
4304 | HttpCutChannel(NULL((void*)0), httpPtr->bodyChan); |
4305 | } |
4306 | if (httpPtr->spoolChan != NULL((void*)0)) { |
4307 | HttpCutChannel(NULL((void*)0), httpPtr->spoolChan); |
4308 | } |
4309 | if (httpPtr->doneCallback != NULL((void*)0)) { |
4310 | HttpDoneCallback(httpPtr); /* Does free on the httpPtr */ |
4311 | httpPtr = NULL((void*)0); |
4312 | } |
4313 | |
4314 | break; |
4315 | |
4316 | case NS_SOCK_NONE: |
4317 | |
4318 | Ns_Log(Ns_LogTaskDebug, "HttpProc: NS_SOCK_NONE"); |
4319 | httpPtr->error = "unexpected http socket state"; |
4320 | |
4321 | break; |
4322 | } |
4323 | |
4324 | if (httpPtr != NULL((void*)0)) { |
4325 | httpPtr->finalSockState = why; |
4326 | Ns_Log(Ns_LogTaskDebug, "HttpProc: exit taskDone:%d, finalSockState:%.2x," |
4327 | " error:(%s)", taskDone, httpPtr->finalSockState, |
4328 | httpPtr->error != NULL((void*)0) ? httpPtr->error : "none"); |
4329 | if (taskDone == NS_TRUE1) { |
4330 | Ns_GetTime(&httpPtr->etime); |
4331 | Ns_TaskDone(httpPtr->task); |
4332 | } |
4333 | } |
4334 | } |
4335 | |
4336 | |
4337 | /* |
4338 | *---------------------------------------------------------------------- |
4339 | * |
4340 | * HttpSpliceChannels -- |
4341 | * |
4342 | * Convenience wrapper to splice-in body/spool channels |
4343 | * in the given interp. |
4344 | * |
4345 | * Results: |
4346 | * None. |
4347 | * |
4348 | * Side effects: |
4349 | * None. |
4350 | * |
4351 | *---------------------------------------------------------------------- |
4352 | */ |
4353 | |
4354 | static void |
4355 | HttpSpliceChannels( |
4356 | Tcl_Interp *interp, |
4357 | NsHttpTask *httpPtr |
4358 | ) { |
4359 | if (httpPtr->bodyChan != NULL((void*)0)) { |
4360 | HttpSpliceChannel(interp, httpPtr->bodyChan); |
4361 | httpPtr->bodyChan = NULL((void*)0); |
4362 | } |
4363 | if (httpPtr->spoolChan != NULL((void*)0)) { |
4364 | HttpSpliceChannel(interp, httpPtr->spoolChan); |
4365 | httpPtr->spoolChan = NULL((void*)0); |
4366 | } |
4367 | } |
4368 | |
4369 | |
4370 | /* |
4371 | *---------------------------------------------------------------------- |
4372 | * |
4373 | * HttpSpliceChannel -- |
4374 | * |
4375 | * Splice-in the channel in the given interp. |
4376 | * |
4377 | * Results: |
4378 | * Nothing. |
4379 | * |
4380 | * Side effects: |
4381 | * None. |
4382 | * |
4383 | *---------------------------------------------------------------------- |
4384 | */ |
4385 | |
4386 | static void |
4387 | HttpSpliceChannel( |
4388 | Tcl_Interp *interp, |
4389 | Tcl_Channel chan |
4390 | ) { |
4391 | Tcl_SpliceChannel(chan); |
4392 | |
4393 | if (interp != NULL((void*)0)) { |
4394 | Tcl_RegisterChannel(interp, chan); |
4395 | Tcl_UnregisterChannel((Tcl_Interp *)NULL((void*)0), chan); |
4396 | } |
4397 | |
4398 | Ns_Log(Ns_LogTaskDebug, "HttpSpliceChannel: interp:%p chan:%s", |
4399 | (void *)interp, Tcl_GetChannelName(chan)); |
4400 | } |
4401 | |
4402 | |
4403 | /* |
4404 | *---------------------------------------------------------------------- |
4405 | * |
4406 | * HttpCutChannel -- |
4407 | * |
4408 | * Wrapper to cut-out the given channel from the interp/thread. |
4409 | * |
4410 | * Results: |
4411 | * Standard Tcl result. |
4412 | * |
4413 | * Side effects: |
4414 | * None. |
4415 | * |
4416 | *---------------------------------------------------------------------- |
4417 | */ |
4418 | |
4419 | static int |
4420 | HttpCutChannel( |
4421 | Tcl_Interp *interp, |
4422 | Tcl_Channel chan |
4423 | ) { |
4424 | int result = TCL_OK0; |
4425 | |
4426 | if (interp != NULL((void*)0)) { |
4427 | if (Tcl_IsChannelShared(chan)) { |
4428 | const char *errorMsg = "channel is shared"; |
4429 | |
4430 | Tcl_SetResult(interp, (char*)errorMsg, TCL_STATIC((Tcl_FreeProc *) 0)); |
4431 | result = TCL_ERROR1; |
4432 | } else { |
4433 | Tcl_DriverWatchProc *watchProc; |
4434 | |
4435 | /* |
4436 | * This effectively disables processing of pending |
4437 | * events which are ready to fire for the given |
4438 | * channel. If we do not do this, events will hit |
4439 | * the detached channel which is potentially being |
4440 | * owned by some other thread. This will wreck havoc |
4441 | * on our memory and eventually badly hurt us... |
4442 | */ |
4443 | Tcl_ClearChannelHandlers(chan); |
4444 | watchProc = Tcl_ChannelWatchProc(Tcl_GetChannelType(chan)); |
4445 | if (watchProc != NULL((void*)0)) { |
4446 | (*watchProc)(Tcl_GetChannelInstanceData(chan), 0); |
4447 | } |
4448 | |
4449 | /* |
4450 | * Artificially bump the channel reference count |
4451 | * which protects us from channel being closed |
4452 | * during the Tcl_UnregisterChannel(). |
4453 | */ |
4454 | Tcl_RegisterChannel((Tcl_Interp *) NULL((void*)0), chan); |
4455 | Tcl_UnregisterChannel(interp, chan); |
4456 | } |
4457 | } |
4458 | |
4459 | if (result == TCL_OK0) { |
4460 | Ns_Log(Ns_LogTaskDebug, "HttpCutChannel: interp:%p chan:%s", |
4461 | (void *)interp, Tcl_GetChannelName(chan)); |
4462 | Tcl_CutChannel(chan); |
4463 | } |
4464 | |
4465 | return result; |
4466 | } |
4467 | |
4468 | |
4469 | /* |
4470 | *---------------------------------------------------------------------- |
4471 | * |
4472 | * HttpTunnel -- |
4473 | * |
4474 | * Dig a tunnel to the remote host over the given proxy. |
4475 | * |
4476 | * Results: |
4477 | * Socket tunneled to the remote host/port. |
4478 | * Should behave as a regular directly connected socket. |
4479 | * |
4480 | * Side effects: |
4481 | * Runs an HTTP task for HTTP/1.1 connection to proxy. |
4482 | * |
4483 | *---------------------------------------------------------------------- |
4484 | */ |
4485 | |
4486 | static NS_SOCKETint |
4487 | HttpTunnel( |
4488 | NsInterp *itPtr, |
4489 | const char *proxyhost, |
4490 | unsigned short proxyport, |
4491 | const char *host, |
4492 | unsigned short port, |
4493 | const Ns_Time *timeout |
4494 | ) { |
4495 | NsHttpTask *httpPtr; |
4496 | Ns_DStringTcl_DString *dsPtr; |
4497 | Tcl_Interp *interp; |
4498 | |
4499 | NS_SOCKETint result = NS_INVALID_SOCKET(-1); |
4500 | const char *url = "proxy-tunnel"; /* Not relevant; for logging purposes only */ |
4501 | uint64_t requestCount = 0u; |
4502 | |
4503 | NS_NONNULL_ASSERT(itPtr != NULL)((void) (0)); |
4504 | NS_NONNULL_ASSERT(proxyhost != NULL)((void) (0)); |
4505 | NS_NONNULL_ASSERT(host != NULL)((void) (0)); |
4506 | |
4507 | assert(proxyport > 0)((void) (0)); |
4508 | assert(port > 0)((void) (0)); |
4509 | /* |
4510 | * Setup the task structure. From this point on |
4511 | * if something goes wrong, we must HttpClose(). |
4512 | */ |
4513 | httpPtr = ns_calloc(1u, sizeof(NsHttpTask)); |
4514 | httpPtr->chunk = ns_calloc(1u, sizeof(NsHttpChunk)); |
4515 | httpPtr->bodyFileFd = NS_INVALID_FD(-1); |
4516 | httpPtr->spoolFd = NS_INVALID_FD(-1); |
4517 | httpPtr->sock = NS_INVALID_SOCKET(-1); |
4518 | httpPtr->spoolLimit = -1; |
4519 | httpPtr->url = ns_strdup(url); |
4520 | httpPtr->flags |= NS_HTTP_FLAG_EMPTY(1u<<5); /* Do not expect response content */ |
4521 | httpPtr->method = ns_strdup(connectMethod); |
4522 | httpPtr->replyHeaders = Ns_SetCreate(NS_SET_NAME_CLIENT_RESPONSE"clresp"); /* Ignored */ |
4523 | httpPtr->servPtr = itPtr->servPtr; |
4524 | |
4525 | if (timeout != NULL((void*)0)) { |
4526 | httpPtr->timeout = ns_calloc(1u, sizeof(Ns_Time)); |
4527 | *httpPtr->timeout = *timeout; |
4528 | } |
4529 | |
4530 | Ns_GetTime(&httpPtr->stime); |
4531 | |
4532 | interp = itPtr->interp; |
4533 | dsPtr = &httpPtr->ds; |
4534 | Ns_DStringInitTcl_DStringInit(&httpPtr->ds); |
4535 | Ns_DStringInitTcl_DStringInit(&httpPtr->chunk->ds); |
4536 | |
4537 | Ns_MasterLock(); |
4538 | requestCount = ++httpClientRequestCount; |
4539 | Ns_MasterUnlock(); |
4540 | |
4541 | Ns_MutexInit(&httpPtr->lock); |
4542 | (void)ns_uint64toa(dsPtr->string, requestCount); |
4543 | Ns_MutexSetName2(&httpPtr->lock, "ns:httptask", dsPtr->string); |
4544 | |
4545 | /* |
4546 | * Now we are ready to attempt the connection. |
4547 | * If no timeout given, assume 10 seconds. |
4548 | */ |
4549 | |
4550 | { |
4551 | Ns_ReturnCode rc; |
4552 | Ns_Time def = {10, 0}, *toPtr = NULL((void*)0); |
4553 | |
4554 | Ns_Log(Ns_LogTaskDebug, "HttpTunnel: connecting to proxy [%s]:%hu", |
4555 | proxyhost, proxyport); |
4556 | |
4557 | toPtr = (httpPtr->timeout != NULL((void*)0)) ? httpPtr->timeout : &def; |
4558 | httpPtr->sock = Ns_SockTimedConnect2(proxyhost, proxyport, NULL((void*)0), 0, toPtr, &rc); |
4559 | if (httpPtr->sock == NS_INVALID_SOCKET(-1)) { |
4560 | Ns_SockConnectError(interp, proxyhost, proxyport, rc); |
4561 | if (rc == NS_TIMEOUT) { |
4562 | HttpClientLogWrite(httpPtr, "connecttimeout"); |
4563 | } |
4564 | goto fail; |
4565 | } |
4566 | if (Ns_SockSetNonBlocking(httpPtr->sock) != NS_OK) { |
4567 | Ns_TclPrintfResult(interp, "can't set socket nonblocking mode"); |
4568 | goto fail; |
4569 | } |
4570 | rc = HttpWaitForSocketEvent(httpPtr->sock, POLLOUT0x004, httpPtr->timeout); |
4571 | if (rc != NS_OK) { |
Similar code here | |
4572 | if (rc == NS_TIMEOUT) { |
4573 | Ns_TclPrintfResult(interp, "timeout waiting for writable socket"); |
4574 | HttpClientLogWrite(httpPtr, "writetimeout"); |
4575 | Tcl_SetErrorCode(interp, errorCodeTimeoutString, (char *)0L); |
4576 | } else { |
4577 | Ns_TclPrintfResult(interp, "waiting for writable socket: %s", |
4578 | ns_sockstrerrorstrerror(ns_sockerrno(*__errno_location ()))); |
4579 | } |
4580 | goto fail; |
4581 | } |
4582 | } |
4583 | |
4584 | /* |
4585 | * At this point we are connected. |
4586 | * Construct CONNECT request line. |
4587 | */ |
4588 | Ns_DStringSetLengthTcl_DStringSetLength(dsPtr, 0); |
4589 | Ns_DStringPrintf(dsPtr, "%s %s:%d HTTP/1.1\r\n", httpPtr->method, host, port); |
4590 | Ns_DStringPrintf(dsPtr, "%s: %s:%d\r\n", hostHeader, host, port); |
4591 | Ns_DStringNAppendTcl_DStringAppend(dsPtr, "\r\n", 2); |
4592 | |
4593 | httpPtr->requestLength = (size_t)dsPtr->length; |
4594 | httpPtr->next = dsPtr->string; |
4595 | |
4596 | /* |
4597 | * Run the task, on success hijack the socket. |
4598 | */ |
4599 | httpPtr->task = Ns_TaskCreate(httpPtr->sock, HttpProc, httpPtr); |
4600 | Ns_TaskRun(httpPtr->task); |
4601 | if (httpPtr->status == 200) { |
4602 | result = httpPtr->sock; |
4603 | httpPtr->sock = NS_INVALID_SOCKET(-1); |
4604 | } else { |
4605 | Ns_TclPrintfResult(interp, "can't open http tunnel, response status: %d", |
4606 | httpPtr->status); |
4607 | } |
4608 | |
4609 | fail: |
4610 | HttpClose(httpPtr); |
4611 | return result; |
4612 | } |
4613 | |
4614 | |
4615 | /* |
4616 | *---------------------------------------------------------------------- |
4617 | * |
4618 | * ParseCRProc -- |
4619 | * |
4620 | * Handler for chunked-encoding state machine that parses |
4621 | * the chunk framing element CR. |
4622 | * |
4623 | * Results: |
4624 | * TCL_OK: CR element parsed OK |
4625 | * TCL_ERROR: error in chunked format |
4626 | * TCL_BREAK; not enough data (stop parsing but remain in state) |
4627 | * |
4628 | * Side effects: |
4629 | * None. |
4630 | * |
4631 | *---------------------------------------------------------------------- |
4632 | */ |
4633 | |
4634 | static int |
4635 | ParseCRProc( |
4636 | NsHttpTask *UNUSED(httpPtr)UNUSED_httpPtr __attribute__((__unused__)), |
4637 | char **buffer, |
4638 | size_t *size |
4639 | ) { |
4640 | char *buf = *buffer; |
4641 | int result = TCL_OK0; |
4642 | size_t len = *size; |
4643 | |
4644 | Ns_Log(Ns_LogTaskDebug, "--- ParseCRProc char %c len %lu", *buf, len); |
4645 | |
4646 | if (len == 0) { |
4647 | result = TCL_BREAK3; |
4648 | } else if (*(buf) == '\r') { |
4649 | len--; |
4650 | buf++; |
4651 | } else { |
4652 | result = TCL_ERROR1; |
4653 | } |
4654 | |
4655 | if (result != TCL_ERROR1) { |
4656 | *buffer = buf; |
4657 | *size = len; |
4658 | } |
4659 | |
4660 | return result; |
4661 | } |
4662 | |
4663 | |
4664 | /* |
4665 | *---------------------------------------------------------------------- |
4666 | * |
4667 | * ParseLFProc |
4668 | * |
4669 | * Handler for chunked-encoding state machine that parses |
4670 | * the chunk framing element LF. |
4671 | * |
4672 | * Results: |
4673 | * TCL_OK: CR element parsed OK |
4674 | * TCL_ERROR: error in chunked format |
4675 | * TCL_BREAK; not enough data (stop parsing but remain in state) |
4676 | * |
4677 | * Side effects: |
4678 | * None. |
4679 | * |
4680 | *---------------------------------------------------------------------- |
4681 | */ |
4682 | |
4683 | static int |
4684 | ParseLFProc( |
4685 | NsHttpTask *UNUSED(httpPtr)UNUSED_httpPtr __attribute__((__unused__)), |
4686 | char **buffer, |
4687 | size_t *size |
4688 | ) { |
4689 | char *buf = *buffer; |
4690 | int result = TCL_OK0; |
4691 | size_t len = *size; |
4692 | |
4693 | Ns_Log(Ns_LogTaskDebug, "--- ParseLFProc"); |
4694 | |
4695 | if (len == 0) { |
4696 | result = TCL_BREAK3; |
4697 | } else if (*(buf) == '\n') { |
4698 | len--; |
4699 | buf++; |
4700 | } else { |
4701 | result = TCL_ERROR1; |
4702 | } |
4703 | |
4704 | if (result != TCL_ERROR1) { |
4705 | *buffer = buf; |
4706 | *size = len; |
4707 | } |
4708 | |
4709 | return result; |
4710 | } |
4711 | |
4712 | |
4713 | /* |
4714 | *---------------------------------------------------------------------- |
4715 | * |
4716 | * ParseLengthProc -- |
4717 | * |
4718 | * Handler for chunked-encoding state machine that parses |
4719 | * the chunk length/size element. |
4720 | * |
4721 | * Results: |
4722 | * TCL_OK: size element parsed OK |
4723 | * TCL_ERROR: error in chunked format |
4724 | * TCL_BREAK; not enough data (stop parsing but remain in state) |
4725 | * |
4726 | * Side effects: |
4727 | * None. |
4728 | * |
4729 | *---------------------------------------------------------------------- |
4730 | */ |
4731 | |
4732 | static int |
4733 | ParseLengthProc( |
4734 | NsHttpTask *httpPtr, |
4735 | char **buffer, |
4736 | size_t *size |
4737 | ) { |
4738 | char *buf = *buffer; |
4739 | int result = TCL_OK0; |
4740 | size_t len = *size; |
4741 | NsHttpChunk *chunkPtr = httpPtr->chunk; |
4742 | Tcl_DString *dsPtr = &chunkPtr->ds; |
4743 | |
4744 | Ns_Log(Ns_LogTaskDebug, "--- ParseLengthProc"); |
4745 | |
4746 | /* |
4747 | * Collect all that looks as a hex digit |
4748 | */ |
4749 | while (len > 0 && *(buf) > 0 && isxdigit(*(buf))((*__ctype_b_loc ())[(int) ((*(buf)))] & (unsigned short int ) _ISxdigit)) { |
4750 | Tcl_DStringAppend(dsPtr, buf, 1); |
4751 | len--; |
4752 | buf++; |
4753 | } |
4754 | |
4755 | if (len == 0) { |
4756 | result = TCL_BREAK3; |
4757 | } else { |
4758 | Tcl_WideInt cl = 0; |
4759 | if (Ns_StrToWideInt(dsPtr->string, &cl) != NS_OK || cl < 0) { |
4760 | result = TCL_ERROR1; |
4761 | } else { |
4762 | chunkPtr->length = (size_t)cl; |
4763 | |
4764 | /* |
4765 | * According to the RFC, the chunk size may be followed |
4766 | * by a variable number of chunk extensions, separated |
4767 | * by a semicolon, up to the terminating frame delimiter. |
4768 | * For the time being, we simply discard extensions. |
4769 | * We might possibly declare a special parser proc for this. |
4770 | */ |
4771 | while (len > 0 && *(buf) > 0 && *(buf) != '\r') { |
4772 | len--; |
4773 | buf++; |
4774 | } |
4775 | } |
4776 | } |
4777 | |
4778 | if (result != TCL_ERROR1) { |
4779 | *buffer = buf; |
4780 | *size = len; |
4781 | } |
4782 | |
4783 | return result; |
4784 | } |
4785 | |
4786 | |
4787 | /* |
4788 | *---------------------------------------------------------------------- |
4789 | * |
4790 | * ParseBodyProc -- |
4791 | * |
4792 | * Handler for chunked-encoding state machine that parses |
4793 | * the chunk body. |
4794 | * |
4795 | * Results: |
4796 | * TCL_OK: body parsed OK |
4797 | * TCL_ERROR: error in chunked format |
4798 | * TCL_BREAK: stop/reset state machine (no data or on last chunk) |
4799 | * |
4800 | * Side effects: |
4801 | * May change state of the parsing state machine. |
4802 | * |
4803 | *---------------------------------------------------------------------- |
4804 | */ |
4805 | |
4806 | static int |
4807 | ParseBodyProc( |
4808 | NsHttpTask *httpPtr, |
4809 | char **buffer, |
4810 | size_t *size |
4811 | ) { |
4812 | char *buf = *buffer; |
4813 | int result = TCL_OK0; |
4814 | size_t len = *size; |
4815 | NsHttpChunk *chunkPtr = httpPtr->chunk; |
4816 | |
4817 | Ns_Log(Ns_LogTaskDebug, "--- ParseBodyProc"); |
4818 | |
4819 | if (chunkPtr->length == 0) { |
4820 | Ns_Set *headersPtr; |
4821 | const char *trailer; |
4822 | |
4823 | /* |
4824 | * We are on the last chunk. Check if we will get some |
4825 | * trailers and switch the state accordingly. |
4826 | */ |
4827 | headersPtr = httpPtr->replyHeaders; |
4828 | trailer = Ns_SetIGet(headersPtr, trailersHeader); |
4829 | if (trailer != NULL((void*)0)) { |
4830 | chunkPtr->parsers = TrailerParsers; |
4831 | } else { |
4832 | chunkPtr->parsers = EndParsers; |
4833 | } |
4834 | |
4835 | chunkPtr->callx = 0; |
4836 | result = TCL_BREAK3; |
4837 | |
4838 | } else if (len == 0) { |
4839 | result = TCL_BREAK3; |
4840 | } else { |
4841 | size_t remain, append; |
4842 | |
4843 | remain = chunkPtr->length - chunkPtr->got; |
4844 | append = remain < len ? remain : len; |
4845 | |
4846 | if (append > 0) { |
4847 | HttpAppendBuffer(httpPtr, (const char*)buf, append); |
4848 | chunkPtr->got += append; |
4849 | len -= append; |
4850 | buf += append; |
4851 | remain -= append; |
4852 | } |
4853 | |
4854 | if (remain > 0 && len == 0) { |
4855 | |
4856 | /* |
4857 | * Not enough data in the passed buffer to |
4858 | * consume whole chunk, break state parsing |
4859 | * but remain in the current state and go |
4860 | * and get new blocks from the source. |
4861 | */ |
4862 | result = TCL_BREAK3; |
4863 | } |
4864 | } |
4865 | |
4866 | if (result != TCL_ERROR1) { |
4867 | *buffer = buf; |
4868 | *size = len; |
4869 | } |
4870 | |
4871 | return result; |
4872 | } |
4873 | |
4874 | |
4875 | /* |
4876 | *---------------------------------------------------------------------- |
4877 | * |
4878 | * ParseTrailerProc -- |
4879 | * |
4880 | * Handler for chunked-encoding state machine that parses |
4881 | * optional trailers. Trailers look like regular headers |
4882 | * (string)(crlf). |
4883 | * |
4884 | * |
4885 | * Results: |
4886 | * TCL_OK: trailer parsed OK |
4887 | * TCL_ERROR: error in chunked format |
4888 | * TCL_BREAK: stop state machine, last trailer encountered |
4889 | * |
4890 | * Side effects: |
4891 | * None. |
4892 | * |
4893 | *---------------------------------------------------------------------- |
4894 | */ |
4895 | |
4896 | static int |
4897 | ParseTrailerProc( |
4898 | NsHttpTask *httpPtr, |
4899 | char **buffer, |
4900 | size_t *size |
4901 | ) { |
4902 | char *buf = *buffer; |
4903 | int result = TCL_OK0; |
4904 | size_t len = *size; |
4905 | NsHttpChunk *chunkPtr = httpPtr->chunk; |
4906 | Tcl_DString *dsPtr = &chunkPtr->ds; |
4907 | |
4908 | while (len > 0 && *(buf) > 0 && *(buf) != '\r') { |
4909 | Tcl_DStringAppend(dsPtr, buf, 1); |
4910 | len--; |
4911 | buf++; |
4912 | } |
4913 | |
4914 | if (len == 0) { |
4915 | result = TCL_BREAK3; |
4916 | } else if (*(buf) == '\r') { |
4917 | if (dsPtr->length == 0) { |
4918 | |
4919 | /* |
4920 | * This was the last header (== o header, zero-size) |
4921 | */ |
4922 | chunkPtr->parsers = EndParsers; |
4923 | chunkPtr->callx = 0; |
4924 | result = TCL_BREAK3; |
4925 | } else { |
4926 | Ns_Set *headersPtr = httpPtr->replyHeaders; |
4927 | char *trailer = dsPtr->string; |
4928 | |
4929 | if (Ns_ParseHeader(headersPtr, trailer, NULL((void*)0), ToLower, NULL((void*)0)) != NS_OK) { |
4930 | result = TCL_ERROR1; |
4931 | } |
4932 | } |
4933 | } else { |
4934 | result = TCL_ERROR1; |
4935 | } |
4936 | |
4937 | if (result != TCL_ERROR1) { |
4938 | *buffer = buf; |
4939 | *size = len; |
4940 | } |
4941 | |
4942 | return result; |
4943 | } |
4944 | |
4945 | |
4946 | /* |
4947 | *---------------------------------------------------------------------- |
4948 | * |
4949 | * ParseEndProc -- |
4950 | * |
4951 | * Handler for chunked-encoding state machine that terminates |
4952 | * chunk parsing state. |
4953 | * |
4954 | * Results: |
4955 | * TCL_BREAK |
4956 | * |
4957 | * Side effects: |
4958 | * None. |
4959 | * |
4960 | *---------------------------------------------------------------------- |
4961 | */ |
4962 | |
4963 | static int |
4964 | ParseEndProc( |
4965 | NsHttpTask *httpPtr, |
4966 | char **UNUSED(buffer)UNUSED_buffer __attribute__((__unused__)), |
4967 | size_t *size |
4968 | ) { |
4969 | *size = 0; |
4970 | httpPtr->flags |= NS_HTTP_FLAG_CHUNKED_END(1u<<3); |
4971 | |
4972 | return TCL_BREAK3; |
4973 | } |
4974 | |
4975 | |
4976 | /* |
4977 | *---------------------------------------------------------------------- |
4978 | * |
4979 | * ChunkInitProc -- |
4980 | * |
4981 | * Handler for chunked-encoding state machine that initializes |
4982 | * chunk parsing state. |
4983 | * |
4984 | * Results: |
4985 | * TCL_OK |
4986 | * |
4987 | * Side effects: |
4988 | * None. |
4989 | * |
4990 | *---------------------------------------------------------------------- |
4991 | */ |
4992 | |
4993 | static int |
4994 | ChunkInitProc( |
4995 | NsHttpTask *httpPtr, |
4996 | char **UNUSED(buffer)UNUSED_buffer __attribute__((__unused__)), |
4997 | size_t *UNUSED(size)UNUSED_size __attribute__((__unused__)) |
4998 | ) { |
4999 | NsHttpChunk *chunkPtr = httpPtr->chunk; |
5000 | Tcl_DString *dsPtr = &chunkPtr->ds; |
5001 | |
5002 | Ns_Log(Ns_LogTaskDebug, "--- ChunkInitProc"); |
5003 | |
5004 | chunkPtr->length = 0; |
5005 | chunkPtr->got = 0; |
5006 | Tcl_DStringSetLength(dsPtr, 0); |
5007 | Tcl_DStringAppend(dsPtr, "0x", 2); |
5008 | |
5009 | return TCL_OK0; |
5010 | } |
5011 | |
5012 | |
5013 | /* |
5014 | *---------------------------------------------------------------------- |
5015 | * |
5016 | * TrailerInitProc -- |
5017 | * |
5018 | * Handler for chunked-encoding state machine that initializes |
5019 | * trailers parsing |
5020 | * |
5021 | * Results: |
5022 | * TCL_OK always |
5023 | * |
5024 | * Side effects: |
5025 | * None. |
5026 | * |
5027 | *---------------------------------------------------------------------- |
5028 | */ |
5029 | |
5030 | static int |
5031 | TrailerInitProc( |
5032 | NsHttpTask *httpPtr, |
5033 | char **UNUSED(buffer)UNUSED_buffer __attribute__((__unused__)), |
5034 | size_t *UNUSED(size)UNUSED_size __attribute__((__unused__)) |
5035 | ) { |
5036 | NsHttpChunk *chunkPtr = httpPtr->chunk; |
5037 | Tcl_DString *dsPtr = &chunkPtr->ds; |
5038 | |
5039 | Tcl_DStringSetLength(dsPtr, 0); |
5040 | |
5041 | return TCL_OK0; |
5042 | } |
5043 | |
5044 | /* |
5045 | * Local Variables: |
5046 | * mode: c |
5047 | * c-basic-offset: 4 |
5048 | * fill-column: 78 |
5049 | * indent-tabs-mode: nil |
5050 | * eval: (c-guess) |
5051 | * End: |
5052 | */ |