Bug Summary

File:d/tclhttp.c
Warning:line 2922, column 13
Duplicate code detected
Note:line 4571, column 9
Similar code here

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-unknown-linux-gnu -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name tclhttp.c -analyzer-store=region -analyzer-opt-analyze-nested-blocks -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 2 -fhalf-no-semantic-interposition -mframe-pointer=none -fmath-errno -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fcoverage-compilation-dir=/home/isvv/naviserver/nsd -resource-dir /usr/local/lib/clang/15.0.0 -D _FORTIFY_SOURCE=2 -D NDEBUG -D SYSTEM_MALLOC -I ../include -I /usr/include/tcl8.6 -D HAVE_CONFIG_H -internal-isystem /usr/local/lib/clang/15.0.0/include -internal-isystem /usr/local/include -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/11/../../../../x86_64-linux-gnu/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -O2 -std=c99 -fdebug-compilation-dir=/home/isvv/naviserver/nsd -ferror-limit 19 -stack-protector 2 -fgnuc-version=4.2.1 -vectorize-loops -vectorize-slp -analyzer-checker alpha -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /tmp/scan-build-2022-07-23-130959-11103-1 -x c tclhttp.c
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 */
59static const char *transferEncodingHeader = "Transfer-Encoding";
60static const char *acceptEncodingHeader = "Accept-Encoding";
61static const char *contentEncodingHeader = "Content-Encoding";
62static const char *contentTypeHeader = "Content-Type";
63static const char *contentLengthHeader = "Content-Length";
64static const char *connectionHeader = "Connection";
65static const char *trailersHeader = "Trailers";
66static const char *hostHeader = "Host";
67static const char *userAgentHeader = "User-Agent";
68static const char *connectMethod = "CONNECT";
69
70static const int acceptEncodingHeaderLength = 15;
71
72/*
73 * Attempt to maintain Tcl errorCode variable.
74 * This is still not done thoroughly through the code.
75 */
76static const char *errorCodeTimeoutString = "NS_TIMEOUT";
77
78/*
79 * For http task mutex naming
80 */
81static uint64_t httpClientRequestCount = 0u; /* MT: static variable! */
82
83/*
84 * Local functions defined in this file
85 */
86
87static 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
95static 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
115static 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
122static void HttpClose(
123 NsHttpTask *httpPtr
124) NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1)));
125
126static void HttpCancel(
127 NsHttpTask *httpPtr
128) NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1)));
129
130static 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
136static 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
142static 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
148static 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
154static Ns_ReturnCode HttpWaitForSocketEvent(
155 NS_SOCKETint sock,
156 short events,
157 Ns_Time *timeoutPtr
158);
159
160static 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
166static void HttpCheckHeader(
167 NsHttpTask *httpPtr
168) NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1)));
169
170static Ns_ReturnCode HttpCheckSpool(
171 NsHttpTask *httpPtr
172) NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1)));
173
174static 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
180static 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
187static int HttpCutChannel(
188 Tcl_Interp *interp,
189 Tcl_Channel chan
190) NS_GNUC_NONNULL(2)__attribute__((__nonnull__(2)));
191
192static void HttpSpliceChannel(
193 Tcl_Interp *interp,
194 Tcl_Channel chan
195) NS_GNUC_NONNULL(2)__attribute__((__nonnull__(2)));
196
197static 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
202static 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
207static void HttpDoneCallback(
208 NsHttpTask *httpPtr
209) NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1)));
210
211static 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
216static 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
225static Ns_LogCallbackProc HttpClientLogOpen;
226static Ns_LogCallbackProc HttpClientLogClose;
227static Ns_LogCallbackProc HttpClientLogRoll;
228static Ns_SchedProc SchedLogRollCallback;
229static Ns_ArgProc SchedLogArg;
230
231static Ns_TaskProc HttpProc;
232
233/*
234 * Function implementing the Tcl interface.
235 */
236static Tcl_ObjCmdProc HttpCancelObjCmd;
237static Tcl_ObjCmdProc HttpCleanupObjCmd;
238static Tcl_ObjCmdProc HttpListObjCmd;
239static Tcl_ObjCmdProc HttpStatsObjCmd;
240static Tcl_ObjCmdProc HttpQueueObjCmd;
241static Tcl_ObjCmdProc HttpRunObjCmd;
242static Tcl_ObjCmdProc HttpWaitObjCmd;
243
244static NsHttpParseProc ParseCRProc;
245static NsHttpParseProc ParseLFProc;
246static NsHttpParseProc ParseLengthProc;
247static NsHttpParseProc ChunkInitProc;
248static NsHttpParseProc ParseBodyProc;
249static NsHttpParseProc TrailerInitProc;
250static NsHttpParseProc ParseTrailerProc;
251static NsHttpParseProc ParseEndProc;
252
253static 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 */
259static 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 */
274static 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 */
286static 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
310void
311NsInitHttp(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 */
389static void
390SchedLogRollCallback(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
416static void
417SchedLogArg(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 */
439static Ns_ReturnCode
440HttpClientLogRoll(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 */
473static Ns_ReturnCode
474HttpClientLogOpen(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 */
509static Ns_ReturnCode
510HttpClientLogClose(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 */
543void
544NsStopHttp(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 */
567static char*
568SkipDigits(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 */
616void
617Ns_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
637bool_Bool
638Ns_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
878char *
879Ns_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
932Ns_ReturnCode
933Ns_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
1024int
1025NsTclHttpObjCmd(
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
1062static int
1063HttpRunObjCmd(
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
1089static int
1090HttpQueueObjCmd(
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
1166static int
1167HttpWaitObjCmd(
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
1364static int
1365HttpCancelObjCmd(
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
1416static int
1417HttpCleanupObjCmd(
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
1502static int
1503HttpListObjCmd(
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
1577static int
1578HttpStatsObjCmd(
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
1733static int
1734HttpQueue(
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
1987static void
1988HttpClientLogWrite(
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
2068static int
2069HttpGetResult(
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
2292static void
2293HttpCheckHeader(
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
2335static Ns_ReturnCode
2336HttpCheckSpool(
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
2533static bool_Bool
2534HttpGet(
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
2579static Ns_ReturnCode
2580HttpWaitForSocketEvent(
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
2639static int
2640HttpConnect(
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
3188static int
3189HttpAppendRawBuffer(
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
3241static int
3242HttpAppendBuffer(
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
3324static int
3325HttpAppendContent(
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
3401static int
3402HttpAppendChunked(
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
3463static void
3464HttpClose(
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
3558static void
3559HttpCancel(
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
3587static void
3588HttpAddInfo(
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
3628static ssize_t
3629HttpTaskSend(
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
3677static ssize_t
3678HttpTaskRecv(
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
3730static void
3731HttpDoneCallback(
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
3792static void
3793HttpProc(
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
4354static void
4355HttpSpliceChannels(
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
4386static void
4387HttpSpliceChannel(
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
4419static int
4420HttpCutChannel(
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
4486static NS_SOCKETint
4487HttpTunnel(
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
4609fail:
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
4634static int
4635ParseCRProc(
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
4683static int
4684ParseLFProc(
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
4732static int
4733ParseLengthProc(
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
4806static int
4807ParseBodyProc(
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
4896static int
4897ParseTrailerProc(
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
4963static int
4964ParseEndProc(
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
4993static int
4994ChunkInitProc(
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
5030static int
5031TrailerInitProc(
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 */