From d61a4c17579c4a064d692e670e06c98c45baff80 Mon Sep 17 00:00:00 2001 From: "jan.nijtmans" Date: Tue, 10 Jan 2017 13:56:10 +0000 Subject: Experimental follow-up: Change internal TclCreateSocketAddress() signature, from using "int port" to "const char *service". --- generic/tclIOSock.c | 13 +++++-------- generic/tclInt.h | 2 +- unix/tclUnixSock.c | 10 +++++++--- win/tclWinSock.c | 11 ++++++++--- 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/generic/tclIOSock.c b/generic/tclIOSock.c index 8ad268a..b4a3df4 100644 --- a/generic/tclIOSock.c +++ b/generic/tclIOSock.c @@ -158,7 +158,7 @@ TclCreateSocketAddress( * family */ struct addrinfo **addrlist, /* Socket address list */ const char *host, /* Host. NULL implies INADDR_ANY */ - int port, /* Port number */ + const char *service, /* Service */ int willBind, /* Is this an address to bind() to or to * connect() to? */ const char **errorMsgPtr) /* Place to store the error message detail, if @@ -168,7 +168,7 @@ TclCreateSocketAddress( struct addrinfo *p; struct addrinfo *v4head = NULL, *v4ptr = NULL; struct addrinfo *v6head = NULL, *v6ptr = NULL; - char *native = NULL, portbuf[TCL_INTEGER_SPACE], *portstring; + char *native = NULL; const char *family = NULL; Tcl_DString ds; int result; @@ -182,11 +182,8 @@ TclCreateSocketAddress( * when the loopback device is the only available network interface. */ - if (host != NULL && port == 0) { - portstring = NULL; - } else { - TclFormatInt(portbuf, port); - portstring = portbuf; + if (host != NULL && service != NULL && !strcmp(service, "0")) { + service = NULL; } (void) memset(&hints, 0, sizeof(hints)); @@ -231,7 +228,7 @@ TclCreateSocketAddress( hints.ai_flags |= AI_PASSIVE; } - result = getaddrinfo(native, portstring, &hints, addrlist); + result = getaddrinfo(native, service, &hints, addrlist); if (host != NULL) { Tcl_DStringFree(&ds); diff --git a/generic/tclInt.h b/generic/tclInt.h index dd0c11a..5faa275 100644 --- a/generic/tclInt.h +++ b/generic/tclInt.h @@ -3068,7 +3068,7 @@ MODULE_SCOPE void TclpFinalizePipes(void); MODULE_SCOPE void TclpFinalizeSockets(void); MODULE_SCOPE int TclCreateSocketAddress(Tcl_Interp *interp, struct addrinfo **addrlist, - const char *host, int port, int willBind, + const char *host, const char *service, int willBind, const char **errorMsgPtr); MODULE_SCOPE int TclpThreadCreate(Tcl_ThreadId *idPtr, Tcl_ThreadCreateProc *proc, ClientData clientData, diff --git a/unix/tclUnixSock.c b/unix/tclUnixSock.c index 8e97543..63bccae 100644 --- a/unix/tclUnixSock.c +++ b/unix/tclUnixSock.c @@ -1287,13 +1287,17 @@ Tcl_OpenTcpClient( const char *errorMsg = NULL; struct addrinfo *addrlist = NULL, *myaddrlist = NULL; char channelName[SOCK_CHAN_LENGTH]; + char service[TCL_INTEGER_SPACE], myservice[TCL_INTEGER_SPACE]; /* * Do the name lookups for the local and remote addresses. */ - if (!TclCreateSocketAddress(interp, &addrlist, host, port, 0, &errorMsg) - || !TclCreateSocketAddress(interp, &myaddrlist, myaddr, myport, 1, + TclFormatInt(service, port); + TclFormatInt(myservice, myport); + + if (!TclCreateSocketAddress(interp, &addrlist, host, service, 0, &errorMsg) + || !TclCreateSocketAddress(interp, &myaddrlist, myaddr, myservice, 1, &errorMsg)) { if (addrlist != NULL) { freeaddrinfo(addrlist); @@ -1481,7 +1485,7 @@ Tcl_OpenTcpServerEx( goto error; } - if (!TclCreateSocketAddress(interp, &addrlist, myHost, port, 1, &errorMsg)) { + if (!TclCreateSocketAddress(interp, &addrlist, myHost, service, 1, &errorMsg)) { my_errno = errno; goto error; } diff --git a/win/tclWinSock.c b/win/tclWinSock.c index 5e0d7c8..b2d77a1 100644 --- a/win/tclWinSock.c +++ b/win/tclWinSock.c @@ -1902,6 +1902,11 @@ Tcl_OpenTcpClient( const char *errorMsg = NULL; struct addrinfo *addrlist = NULL, *myaddrlist = NULL; char channelName[SOCK_CHAN_LENGTH]; + char service[TCL_INTEGER_SPACE], myservice[TCL_INTEGER_SPACE]; + + TclFormatInt(service, port); + TclFormatInt(myservice, myport); + if (TclpHasSockets(interp) != TCL_OK) { return NULL; @@ -1921,8 +1926,8 @@ Tcl_OpenTcpClient( * Do the name lookups for the local and remote addresses. */ - if (!TclCreateSocketAddress(interp, &addrlist, host, port, 0, &errorMsg) - || !TclCreateSocketAddress(interp, &myaddrlist, myaddr, myport, 1, + if (!TclCreateSocketAddress(interp, &addrlist, host, service, 0, &errorMsg) + || !TclCreateSocketAddress(interp, &myaddrlist, myaddr, myservice, 1, &errorMsg)) { if (addrlist != NULL) { freeaddrinfo(addrlist); @@ -2078,7 +2083,7 @@ Tcl_OpenTcpServerEx( goto error; } - if (!TclCreateSocketAddress(interp, &addrlist, myHost, port, 1, &errorMsg)) { + if (!TclCreateSocketAddress(interp, &addrlist, myHost, service, 1, &errorMsg)) { goto error; } -- cgit v0.12 From 8c52e2d45db4862de7e7506197321a6a111c65f6 Mon Sep 17 00:00:00 2001 From: "jan.nijtmans" Date: Tue, 10 Jan 2017 14:35:13 +0000 Subject: Further experimental follow-up: Add internal function TclOpenTcpClientEx(), as companion to Tcl_OpenTcpServerEx(). Should be exported through new TIP. --- generic/tclIOCmd.c | 19 ++++--------------- generic/tclInt.h | 4 ++++ unix/tclUnixSock.c | 29 ++++++++++++++++++++--------- win/tclWinSock.c | 26 ++++++++++++++++++++------ 4 files changed, 48 insertions(+), 30 deletions(-) diff --git a/generic/tclIOCmd.c b/generic/tclIOCmd.c index 1bd3fe7..a5038b7 100644 --- a/generic/tclIOCmd.c +++ b/generic/tclIOCmd.c @@ -1492,10 +1492,10 @@ Tcl_SocketObjCmd( SKT_ASYNC, SKT_MYADDR, SKT_MYPORT, SKT_REUSEADDR, SKT_REUSEPORT, SKT_SERVER }; - int optionIndex, a, server = 0, myport = 0, async = 0, reusep = -1, + int optionIndex, a, server = 0, async = 0, reusep = -1, reusea = -1; unsigned int flags = 0; - const char *host, *port, *myaddr = NULL; + const char *host, *port, *myaddr = NULL, *myport = NULL; Tcl_Obj *script = NULL; Tcl_Channel chan; @@ -1532,18 +1532,13 @@ Tcl_SocketObjCmd( myaddr = TclGetString(objv[a]); break; case SKT_MYPORT: { - const char *myPortName; - a++; if (a >= objc) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "no argument given for -myport option", -1)); return TCL_ERROR; } - myPortName = TclGetString(objv[a]); - if (TclSockGetPort(interp, myPortName, "tcp", &myport) != TCL_OK) { - return TCL_ERROR; - } + myport = TclGetString(objv[a]); break; } case SKT_SERVER: @@ -1670,13 +1665,7 @@ Tcl_SocketObjCmd( Tcl_CreateCloseHandler(chan, TcpServerCloseProc, acceptCallbackPtr); } else { - int portNum; - - if (TclSockGetPort(interp, port, "tcp", &portNum) != TCL_OK) { - return TCL_ERROR; - } - - chan = Tcl_OpenTcpClient(interp, portNum, host, myaddr, myport, async); + chan = TclOpenTcpClientEx(interp, port, host, myaddr, myport, async); if (chan == NULL) { return TCL_ERROR; } diff --git a/generic/tclInt.h b/generic/tclInt.h index 5faa275..e42bcfb 100644 --- a/generic/tclInt.h +++ b/generic/tclInt.h @@ -3070,6 +3070,10 @@ MODULE_SCOPE int TclCreateSocketAddress(Tcl_Interp *interp, struct addrinfo **addrlist, const char *host, const char *service, int willBind, const char **errorMsgPtr); +Tcl_Channel TclOpenTcpClientEx(Tcl_Interp *interp, + const char *service, const char *host, + const char *myaddr, const char *myservice, + unsigned int flags); MODULE_SCOPE int TclpThreadCreate(Tcl_ThreadId *idPtr, Tcl_ThreadCreateProc *proc, ClientData clientData, int stackSize, int flags); diff --git a/unix/tclUnixSock.c b/unix/tclUnixSock.c index 63bccae..50452e9 100644 --- a/unix/tclUnixSock.c +++ b/unix/tclUnixSock.c @@ -1283,19 +1283,30 @@ Tcl_OpenTcpClient( * connect. Otherwise we do a blocking * connect. */ { - TcpState *statePtr; - const char *errorMsg = NULL; - struct addrinfo *addrlist = NULL, *myaddrlist = NULL; - char channelName[SOCK_CHAN_LENGTH]; char service[TCL_INTEGER_SPACE], myservice[TCL_INTEGER_SPACE]; - /* - * Do the name lookups for the local and remote addresses. - */ - TclFormatInt(service, port); TclFormatInt(myservice, myport); + return TclOpenTcpClientEx(interp, service, host, myaddr, myservice, async!=0); +} + +Tcl_Channel +TclOpenTcpClientEx( + Tcl_Interp *interp, /* For error reporting; can be NULL. */ + const char *service, /* Port number to open. */ + const char *host, /* Host on which to open port. */ + const char *myaddr, /* Client-side address */ + const char *myservice, /* Client-side port */ + unsigned int flags) /* If nonzero, attempt to do an asynchronous + * connect. Otherwise we do a blocking + * connect. */ +{ + TcpState *statePtr; + const char *errorMsg = NULL; + struct addrinfo *addrlist = NULL, *myaddrlist = NULL; + char channelName[SOCK_CHAN_LENGTH]; + if (!TclCreateSocketAddress(interp, &addrlist, host, service, 0, &errorMsg) || !TclCreateSocketAddress(interp, &myaddrlist, myaddr, myservice, 1, &errorMsg)) { @@ -1314,7 +1325,7 @@ Tcl_OpenTcpClient( */ statePtr = ckalloc(sizeof(TcpState)); memset(statePtr, 0, sizeof(TcpState)); - statePtr->flags = async ? TCP_ASYNC_CONNECT : 0; + statePtr->flags = (flags&1) ? TCP_ASYNC_CONNECT : 0; statePtr->cachedBlocking = TCL_MODE_BLOCKING; statePtr->addrlist = addrlist; statePtr->myaddrlist = myaddrlist; diff --git a/win/tclWinSock.c b/win/tclWinSock.c index b2d77a1..8545cdd 100644 --- a/win/tclWinSock.c +++ b/win/tclWinSock.c @@ -1873,7 +1873,7 @@ out: /* *---------------------------------------------------------------------- * - * Tcl_OpenTcpClient -- + * Tcl_OpenTcpClient, TclOpenTcpClientEx -- * * Opens a TCP client socket and creates a channel around it. * @@ -1898,15 +1898,29 @@ Tcl_OpenTcpClient( * connect. Otherwise we do a blocking * connect. */ { - TcpState *statePtr; - const char *errorMsg = NULL; - struct addrinfo *addrlist = NULL, *myaddrlist = NULL; - char channelName[SOCK_CHAN_LENGTH]; char service[TCL_INTEGER_SPACE], myservice[TCL_INTEGER_SPACE]; TclFormatInt(service, port); TclFormatInt(myservice, myport); + return TclOpenTcpClientEx(interp, service, host, myaddr, myservice, async!=0); +} + +Tcl_Channel +TclOpenTcpClientEx( + Tcl_Interp *interp, /* For error reporting; can be NULL. */ + const char *service, /* Port number to open. */ + const char *host, /* Host on which to open port. */ + const char *myaddr, /* Client-side address */ + const char *myservice, /* Client-side port */ + unsigned int flags) /* If nonzero, attempt to do an asynchronous + * connect. Otherwise we do a blocking + * connect. */ +{ + TcpState *statePtr; + const char *errorMsg = NULL; + struct addrinfo *addrlist = NULL, *myaddrlist = NULL; + char channelName[SOCK_CHAN_LENGTH]; if (TclpHasSockets(interp) != TCL_OK) { return NULL; @@ -1942,7 +1956,7 @@ Tcl_OpenTcpClient( statePtr = NewSocketInfo(INVALID_SOCKET); statePtr->addrlist = addrlist; statePtr->myaddrlist = myaddrlist; - if (async) { + if (flags&1) { statePtr->flags |= TCP_ASYNC_CONNECT; } -- cgit v0.12 From d64611a414a4e8609b17ae27dc02969200d2fa37 Mon Sep 17 00:00:00 2001 From: dkf Date: Sun, 9 Apr 2017 14:32:26 +0000 Subject: TIP 468 implementation from Shannon Noe. --- generic/tcl.decls | 4 ++-- generic/tclIOCmd.c | 36 +++++++++++++++++++++++------------- generic/tclIOSock.c | 5 ++--- unix/tclUnixSock.c | 6 +++++- win/tclWinSock.c | 7 ++++++- 5 files changed, 38 insertions(+), 20 deletions(-) diff --git a/generic/tcl.decls b/generic/tcl.decls index b2b91a9..c7ca44f 100644 --- a/generic/tcl.decls +++ b/generic/tcl.decls @@ -2329,8 +2329,8 @@ declare 630 { # TIP #456 declare 631 { Tcl_Channel Tcl_OpenTcpServerEx(Tcl_Interp *interp, const char *service, - const char *host, unsigned int flags, Tcl_TcpAcceptProc *acceptProc, - ClientData callbackData) + const char *host, unsigned int flags, int backlog, + Tcl_TcpAcceptProc *acceptProc, ClientData callbackData) } # ----- BASELINE -- FOR -- 8.7.0 ----- # diff --git a/generic/tclIOCmd.c b/generic/tclIOCmd.c index 1bd3fe7..55685e3 100644 --- a/generic/tclIOCmd.c +++ b/generic/tclIOCmd.c @@ -1485,15 +1485,15 @@ Tcl_SocketObjCmd( Tcl_Obj *const objv[]) /* Argument objects. */ { static const char *const socketOptions[] = { - "-async", "-myaddr", "-myport", "-reuseaddr", "-reuseport", "-server", - NULL + "-async", "-backlog", "-myaddr", "-myport", "-reuseaddr", + "-reuseport", "-server", NULL }; enum socketOptions { - SKT_ASYNC, SKT_MYADDR, SKT_MYPORT, SKT_REUSEADDR, SKT_REUSEPORT, - SKT_SERVER + SKT_ASYNC, SKT_BACKLOG, SKT_MYADDR, SKT_MYPORT, SKT_REUSEADDR, + SKT_REUSEPORT, SKT_SERVER }; int optionIndex, a, server = 0, myport = 0, async = 0, reusep = -1, - reusea = -1; + reusea = -1, backlog = -1; unsigned int flags = 0; const char *host, *port, *myaddr = NULL; Tcl_Obj *script = NULL; @@ -1583,6 +1583,17 @@ Tcl_SocketObjCmd( return TCL_ERROR; } break; + case SKT_BACKLOG: + a++; + if (a >= objc) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "no argument given for -backlog option", -1)); + return TCL_ERROR; + } + if (Tcl_GetIntFromObj(interp, objv[a], &backlog) != TCL_OK) { + return TCL_ERROR; + } + break; default: Tcl_Panic("Tcl_SocketObjCmd: bad option index to SocketOptions"); } @@ -1607,14 +1618,14 @@ Tcl_SocketObjCmd( iPtr->flags |= INTERP_ALTERNATE_WRONG_ARGS; Tcl_WrongNumArgs(interp, 1, objv, "-server command ?-reuseaddr boolean? ?-reuseport boolean? " - "?-myaddr addr? port"); + "?-myaddr addr? ?-backlog count? port"); return TCL_ERROR; } - if (!server && (reusea != -1 || reusep != -1)) { + if (!server && (reusea != -1 || reusep != -1 || backlog != -1)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( - "options -reuseaddr and -reuseport are only valid for servers", - -1)); + "options -backlog, -reuseaddr and -reuseport are only valid " + "for servers", -1)); return TCL_ERROR; } @@ -1638,15 +1649,14 @@ Tcl_SocketObjCmd( port = TclGetString(objv[a]); if (server) { - AcceptCallback *acceptCallbackPtr = - ckalloc(sizeof(AcceptCallback)); + AcceptCallback *acceptCallbackPtr = ckalloc(sizeof(AcceptCallback)); Tcl_IncrRefCount(script); acceptCallbackPtr->script = script; acceptCallbackPtr->interp = interp; - chan = Tcl_OpenTcpServerEx(interp, port, host, flags, AcceptCallbackProc, - acceptCallbackPtr); + chan = Tcl_OpenTcpServerEx(interp, port, host, flags, backlog, + AcceptCallbackProc, acceptCallbackPtr); if (chan == NULL) { Tcl_DecrRefCount(script); ckfree(acceptCallbackPtr); diff --git a/generic/tclIOSock.c b/generic/tclIOSock.c index 8ad268a..858c58e 100644 --- a/generic/tclIOSock.c +++ b/generic/tclIOSock.c @@ -307,9 +307,8 @@ Tcl_Channel Tcl_OpenTcpServer(Tcl_Interp *interp, int port, char portbuf[TCL_INTEGER_SPACE]; TclFormatInt(portbuf, port); - - return Tcl_OpenTcpServerEx(interp, portbuf, host, TCL_TCPSERVER_REUSEADDR, - acceptProc, callbackData); + return Tcl_OpenTcpServerEx(interp, portbuf, host, -1, + TCL_TCPSERVER_REUSEADDR, acceptProc, callbackData); } /* diff --git a/unix/tclUnixSock.c b/unix/tclUnixSock.c index 9387d05..1e80799 100644 --- a/unix/tclUnixSock.c +++ b/unix/tclUnixSock.c @@ -1425,6 +1425,7 @@ Tcl_OpenTcpServerEx( const char *service, /* Port number to open. */ const char *myHost, /* Name of local host. */ unsigned int flags, /* Flags. */ + int backlog, /* Length of OS listen backlog queue. */ Tcl_TcpAcceptProc *acceptProc, /* Callback for accepting connections from new * clients. */ @@ -1584,7 +1585,10 @@ Tcl_OpenTcpServerEx( chosenport = ntohs(sockname.sa4.sin_port); } } - status = listen(sock, SOMAXCONN); + if (backlog < 0) { + backlog = SOMAXCONN; + } + status = listen(sock, backlog); if (status < 0) { if (howfar < LISTEN) { howfar = LISTEN; diff --git a/win/tclWinSock.c b/win/tclWinSock.c index 81a5449..a580a8d 100644 --- a/win/tclWinSock.c +++ b/win/tclWinSock.c @@ -2040,6 +2040,8 @@ Tcl_OpenTcpServerEx( const char *service, /* Port number to open. */ const char *myHost, /* Name of local host. */ unsigned int flags, /* Flags. */ + int backlog, /* Length of OS listen backlog queue, or -1 + * for default. */ Tcl_TcpAcceptProc *acceptProc, /* Callback for accepting connections from new * clients. */ @@ -2160,7 +2162,10 @@ Tcl_OpenTcpServerEx( * different, and there may be differences between TCP/IP stacks). */ - if (listen(sock, SOMAXCONN) == SOCKET_ERROR) { + if (backlog < 0) { + backlog = SOMAXCONN; + } + if (listen(sock, backlog) == SOCKET_ERROR) { TclWinConvertError((DWORD) WSAGetLastError()); closesocket(sock); continue; -- cgit v0.12 From ba62d5de6e8d0818da84501ef2dd6cd9a635b27b Mon Sep 17 00:00:00 2001 From: kjnash Date: Tue, 25 Oct 2022 16:23:54 +0000 Subject: Fix bug 1173760 (proxy server for https). Add http::config options -proxynot, -proxyauth. --- doc/http.n | 51 +++- library/http/http.tcl | 335 ++++++++++++++++++++--- tests/http.test | 6 +- tests/httpProxy.test | 456 ++++++++++++++++++++++++++++++++ tests/httpProxySquidConfigForEL8.tar.gz | Bin 0 -> 2266 bytes 5 files changed, 801 insertions(+), 47 deletions(-) create mode 100644 tests/httpProxy.test create mode 100644 tests/httpProxySquidConfigForEL8.tar.gz diff --git a/doc/http.n b/doc/http.n index 59f15b6..ff2307e 100644 --- a/doc/http.n +++ b/doc/http.n @@ -172,14 +172,15 @@ fresh socket, overriding the \fB\-keepalive\fR option of command \fBhttp::geturl\fR. See the \fBPERSISTENT SOCKETS\fR section for details. The default is 0. .TP -\fB\-proxyhost\fR \fIhostname\fR -. -The name of the proxy host, if any. If this value is the -empty string, the URL host is contacted directly. -.TP -\fB\-proxyport\fR \fInumber\fR +\fB\-proxyauth\fR \fIstring\fR . -The proxy port number. +If non-empty, the string is supplied to the proxy server as the value of the +request header Proxy-Authorization. This option can be used for HTTP Basic +Authentication. If the proxy server requires authentication by another +technique, e.g. Digest Authentication, the \fB\-proxyauth\fR option is not +useful. In that case the caller must expect a 407 response from the proxy, +compute the authentication value to be supplied, and use the \fB\-headers\fR +option to supply it as the value of the Proxy-Authorization header. .TP \fB\-proxyfilter\fR \fIcommand\fR . @@ -188,18 +189,46 @@ The command is a callback that is made during to determine if a proxy is required for a given host. One argument, a host name, is added to \fIcommand\fR when it is invoked. If a proxy is required, the callback should return a two-element list containing -the proxy server and proxy port. Otherwise the filter should return -an empty list. The default filter returns the values of the -\fB\-proxyhost\fR and \fB\-proxyport\fR settings if they are -non-empty. +the proxy server and proxy port. Otherwise the filter command should return +an empty list. .RS .PP +The default value of \fB\-proxyfilter\fR is \fBhttp::ProxyRequired\fR, and +this command returns the values of the \fB\-proxyhost\fR and +\fB\-proxyport\fR settings if they are non-empty. The options +\fB\-proxyhost\fR, \fB\-proxyport\fR, and \fB\-proxynot\fR are used only +by \fBhttp::ProxyRequired\fR, and nowhere else in \fB::http::geturl\fR. +A user-supplied \fB\-proxyfilter\fR command may use these options, or +alternatively it may obtain values from elsewhere in the calling script. +In the latter case, any values provided for \fB\-proxyhost\fR, +\fB\-proxyport\fR, and \fB\-proxynot\fR are unused. +.PP The \fB::http::geturl\fR command runs the \fB\-proxyfilter\fR callback inside a \fBcatch\fR command. Therefore an error in the callback command does not call the \fBbgerror\fR handler. See the \fBERRORS\fR section for details. .RE .TP +\fB\-proxyhost\fR \fIhostname\fR +. +The host name or IP address of the proxy server, if any. If this value is +the empty string, the URL host is contacted directly. See +\fB\-proxyfilter\fR for how the value is used. +.TP +\fB\-proxynot\fR \fIlist\fR +. +A Tcl list of domain names and IP addresses that should be accessed directly, +not through the proxy server. The target hostname is compared with each list +element using a case-insensitive \fBstring match\fR. It is often convenient +to use the wildcard "*" at the start of a domain name (e.g. *.example.com) or +at the end of an IP address (e.g. 192.168.0.*). See \fB\-proxyfilter\fR for +how the value is used. +.TP +\fB\-proxyport\fR \fInumber\fR +. +The port number of the proxy server. See \fB\-proxyfilter\fR for how the +value is used. +.TP \fB\-repost\fR \fIboolean\fR . Specifies what to do if a POST request over a persistent connection fails diff --git a/library/http/http.tcl b/library/http/http.tcl index 88685ec..fcb03e1 100644 --- a/library/http/http.tcl +++ b/library/http/http.tcl @@ -26,6 +26,8 @@ namespace eval http { -proxyhost {} -proxyport {} -proxyfilter http::ProxyRequired + -proxynot {} + -proxyauth {} -repost 0 -threadlevel 0 -urlencoding utf-8 @@ -470,7 +472,9 @@ proc http::Finish {token {errormsg ""} {skipCB 0}} { if {[info exists state(-command)] && (!$skipCB) && (![info exists state(done-command-cb)])} { set state(done-command-cb) yes - if {[catch {namespace eval :: $state(-command) $token} err] && $errormsg eq ""} { + if { [catch {namespace eval :: $state(-command) $token} err] + && ($errormsg eq "") + } { set state(error) [list $err $errorInfo $errorCode] set state(status) error } @@ -886,20 +890,22 @@ proc http::reset {token {why reset}} { proc http::geturl {url args} { variable urlTypes - # The value is set in the namespace header of this file. If the file has - # not been modified the value is "::http::socket". - set socketCmd [lindex $urlTypes(http) 1] - # - If ::tls::socketCmd has its default value "::socket", change it to the - # new value $socketCmd. + # new value ::http::socketForTls. # - If the old value is different, then it has been modified either by the # script or by the Tcl installation, and replaced by a new command. The # script or installation that modified ::tls::socketCmd is also - # responsible for integrating ::http::socket into its own "new" command, - # if it wishes to do so. + # responsible for integrating ::http::socketForTls into its own "new" + # command, if it wishes to do so. + # - Commands that open a socket: + # - ::socket - basic + # - ::http::socket - can use a thread to avoid blockage by slow DNS + # lookup. See http::config option -threadlevel. + # - ::http::socketForTls - as ::http::socket, but can also open a socket + # for HTTPS/TLS through a proxy. if {[info exists ::tls::socketCmd] && ($::tls::socketCmd eq {::socket})} { - set ::tls::socketCmd $socketCmd + set ::tls::socketCmd ::http::socketForTls } set token [CreateToken $url {*}$args] @@ -1023,6 +1029,7 @@ proc http::CreateToken {url args} { requestHeaders {} requestLine {} transfer {} + proxyUsed none } set state(-keepalive) $defaultKeepalive set state(-strict) $strict @@ -1299,11 +1306,16 @@ proc http::CreateToken {url args} { set state(-keepalive) 0 } - # If we are using the proxy, we must pass in the full URL that includes - # the server name. - if {$phost ne ""} { + # Handle proxy requests here for http:// but not for https:// + # The proxying for https is done in the ::http::socketForTls command. + # A proxy request for http:// needs the full URL in the HTTP request line, + # including the server name. + # The *tls* test below attempts to describe protocols in addition to + # "https on port 443" that use HTTP over TLS. + if {($phost ne "") && (![string match -nocase *tls* $defcmd])} { set srvurl $url set targetAddr [list $phost $pport] + set state(proxyUsed) HttpProxy } else { set targetAddr [list $host $port] } @@ -1316,7 +1328,7 @@ proc http::CreateToken {url args} { } set state(connArgs) [list $proto $phost $srvurl] - set state(openCmd) [list {*}$defcmd {*}$sockopts {*}$targetAddr] + set state(openCmd) [list {*}$defcmd {*}$sockopts -type $token {*}$targetAddr] # See if we are supposed to use a previously opened channel. # - In principle, ANY call to http::geturl could use a previously opened @@ -1663,12 +1675,14 @@ proc http::OpenSocket {token DoLater} { ##Log pre socket opened, - token $token ##Log $state(openCmd) - token $token set sock [namespace eval :: $state(openCmd)] - + set state(sock) $sock # Normal return from $state(openCmd) always returns a valid socket. + # A TLS proxy connection with 407 or other failure from the + # proxy server raises an error. + # Initialisation of a new socket. ##Log post socket opened, - token $token ##Log socket opened, now fconfigure - token $token - set state(sock) $sock set delay [expr {[clock milliseconds] - $pre}] if {$delay > 3000} { Log socket delay $delay - token $token @@ -1684,7 +1698,15 @@ proc http::OpenSocket {token DoLater} { # Code above has set state(sock) $sock ConfigureNewSocket $token $sockOld $DoLater } result errdict]} { - Finish $token $result + if {[string range $result 0 20] eq {proxy connect failed:}} { + # The socket can be persistent: if so it is identified with + # the https target host, and will be kept open. + # Results of the failed proxy CONNECT have been copied to $token and + # are available to the caller. + Eot $token + } else { + Finish $token $result + } } ##Log Leaving http::OpenSocket coroutine [info coroutine] - token $token return @@ -1715,7 +1737,8 @@ proc http::OpenSocket {token DoLater} { # # Arguments: # token - connection token (name of an array) -# sockOld - handle or placeholder used for a socket before the call to OpenSocket +# sockOld - handle or placeholder used for a socket before the call to +# OpenSocket # DoLater - dictionary of boolean values listing unfinished tasks # # Return Value: none @@ -2083,9 +2106,15 @@ proc http::Connected {token proto phost srvurl} { Log ^B$tk begin sending request - token $token if {[catch { - set state(method) $how - set state(requestHeaders) {} - set state(requestLine) "$how $srvurl HTTP/$state(-protocol)" + if {[info exists state(bypass)]} { + set state(method) [lindex [split $state(bypass) { }] 0] + set state(requestHeaders) {} + set state(requestLine) $state(bypass) + } else { + set state(method) $how + set state(requestHeaders) {} + set state(requestLine) "$how $srvurl HTTP/$state(-protocol)" + } puts $sock $state(requestLine) set hostValue [GetFieldValue $state(-headers) Host] if {$hostValue ne {}} { @@ -2119,6 +2148,11 @@ proc http::Connected {token proto phost srvurl} { # and "state(-keepalive) 0". set ConnVal close } + # Proxy authorisation (cf. mod by Anders Ramdahl to autoproxy by + # Pat Thoyts). + if {($http(-proxyauth) ne {}) && ($state(proxyUsed) eq {HttpProxy})} { + SendHeader $token Proxy-Authorization $http(-proxyauth) + } # RFC7230 A.1 - "clients are encouraged not to send the # Proxy-Connection header field in any requests" set accept_encoding_seen 0 @@ -2143,7 +2177,12 @@ proc http::Connected {token proto phost srvurl} { set contDone 1 set state(querylength) $value } - if {[string equal -nocase $key "connection"]} { + if { [string equal -nocase $key "connection"] + && [info exists state(bypass)] + } { + # Value supplied in -headers overrides $ConnVal. + set connection_seen 1 + } elseif {[string equal -nocase $key "connection"]} { # Remove "close" or "keep-alive" and use our own value. # In an upgrade request, the upgrade is not guaranteed. # Value "close" or "keep-alive" tells the server what to do @@ -3121,6 +3160,7 @@ proc http::responseInfo {token} { currentPost STATE queryoffset totalSize STATE totalsize currentSize STATE currentsize + proxyUsed STATE proxyUsed } { if {$origin eq {STATE}} { if {[info exists state($name)]} { @@ -3604,6 +3644,45 @@ proc http::Event {sock token} { set state(state) complete Eot $token return + } elseif { + ($state(method) eq {CONNECT}) + && [string is integer -strict $state(responseCode)] + && ($state(responseCode) >= 200) + && ($state(responseCode) < 300) + } { + # A successful CONNECT response has no body. + # (An unsuccessful CONNECT has headers and body.) + # The code below is abstracted from Eot/Finish, but + # keeps the socket open. + catch {fileevent $state(sock) readable {}} + catch {fileevent $state(sock) writable {}} + set state(state) complete + set state(status) ok + if {[info commands ${token}--EventCoroutine] ne {}} { + rename ${token}--EventCoroutine {} + } + if {[info commands ${token}--SocketCoroutine] ne {}} { + rename ${token}--SocketCoroutine {} + } + if {[info exists state(socketcoro)]} { + Log $token Cancel socket after-idle event (Finish) + after cancel $state(socketcoro) + unset state(socketcoro) + } + if {[info exists state(after)]} { + after cancel $state(after) + unset state(after) + } + if { [info exists state(-command)] + && (![info exists state(done-command-cb)]) + } { + set state(done-command-cb) yes + if {[catch {namespace eval :: $state(-command) $token} err]} { + set state(error) [list $err $errorInfo $errorCode] + set state(status) error + } + } + return } else { } @@ -4305,7 +4384,7 @@ proc http::CopyDone {token count {error {}}} { # reason - "eof" means premature EOF (not EOF as the natural end of # the response) # - "" means completion of response, with or without EOF -# - anything else describes an error confition other than +# - anything else describes an error condition other than # premature EOF. # # Side Effects @@ -4537,17 +4616,23 @@ proc http::quoteString {string} { proc http::ProxyRequired {host} { variable http - if {[info exists http(-proxyhost)] && [string length $http(-proxyhost)]} { - if { - ![info exists http(-proxyport)] || - ![string length $http(-proxyport)] - } { - set http(-proxyport) 8080 - } - return [list $http(-proxyhost) $http(-proxyport)] + if {(![info exists http(-proxyhost)]) || ($http(-proxyhost) eq {})} { + return + } + if {![info exists http(-proxyport)] || ($http(-proxyport) eq {})} { + set port 8080 } else { - return + set port $http(-proxyport) + } + + # Simple test (cf. autoproxy) for hosts that must be accessed directly, + # not through the proxy server. + foreach domain $http(-proxynot) { + if {[string match -nocase $domain $host]} { + return {} + } } + return [list $http(-proxyhost) $port] } # http::CharsetToEncoding -- @@ -4730,6 +4815,190 @@ interp alias {} http::meta {} http::responseHeaders interp alias {} http::metaValue {} http::responseHeaderValue interp alias {} http::ncode {} http::responseCode + +# ------------------------------------------------------------------------------ +# Proc http::socketForTls +# ------------------------------------------------------------------------------ +# Command to use in place of ::socket as the value of ::tls::socketCmd. +# This command does the same as http::socket, and also handles https connections +# through a proxy server. +# +# Notes. +# - The proxy server works differently for https and http. This implementation +# is for https. The proxy for http is implemented in http::CreateToken (in +# code that was previously part of http::geturl). +# - This code implicitly uses the tls options set for https in a call to +# http::register, and does not need to call commands tls::*. This simple +# implementation is possible because tls uses a callback to ::socket that can +# be redirected by changing the value of ::tls::socketCmd. +# +# Arguments: +# args - as for ::socket +# +# Return Value: a socket identifier +# ------------------------------------------------------------------------------ + +proc http::socketForTls {args} { + variable http + set host [lindex $args end-1] + set port [lindex $args end] + if { ($http(-proxyfilter) ne {}) + && (![catch {$http(-proxyfilter) $host} proxy]) + } { + set phost [lindex $proxy 0] + set pport [lindex $proxy 1] + } else { + set phost {} + set pport {} + } + if {$phost eq ""} { + set sock [::http::socket {*}$args] + } else { + set sock [::http::SecureProxyConnect {*}$args $phost $pport] + } + return $sock +} + + +# ------------------------------------------------------------------------------ +# Proc http::SecureProxyConnect +# ------------------------------------------------------------------------------ +# Command to open a socket through a proxy server to a remote server for use by +# tls. The caller must perform the tls handshake. +# +# Notes +# - Based on patch supplied by Melissa Chawla in ticket 1173760, and +# Proxy-Authorization header cf. autoproxy by Pat Thoyts. +# - Rewritten as a call to http::geturl, because response headers and body are +# needed if the CONNECT request fails. CONNECT is implemented for this case +# only, by state(bypass). +# - FUTURE WORK: give http::geturl a -connect option for a general CONNECT. +# - The request header Proxy-Connection is discouraged in RFC 7230 (June 2014), +# RFC 9112 (June 2022). +# +# Arguments: +# args - as for ::socket, ending in host, port; with proxy host, proxy +# port appended. +# +# Return Value: a socket identifier +# ------------------------------------------------------------------------------ +proc http::AllDone {varName args} { + set $varName done + return +} + +proc http::SecureProxyConnect {args} { + variable http + variable ConnectVar + variable ConnectCounter + set varName ::http::ConnectVar([incr ConnectCounter]) + + # Extract (non-proxy) target from args. + set host [lindex $args end-3] + set port [lindex $args end-2] + set args [lremove $args end-3 end-2] + + # Proxy server URL for connection. + # This determines where the socket is opened. + set phost [lindex $args end-1] + set pport [lindex $args end] + if {[string first : $phost] != -1} { + # IPv6 address, wrap it in [] so we can append :pport + set phost "\[${phost}\]" + } + set url http://${phost}:${pport} + # Elements of args other than host and port are not used when + # AsyncTransaction opens a socket. Those elements are -async and the + # -type $tokenName for the https transaction. Option -async is used by + # AsyncTransaction anyway, and -type $tokenName should not be propagated: + # the proxy request adds its own -type value. + + set targ [lsearch -exact $args -type] + if {$targ != -1} { + # Record in the token that this is a proxy call. + set token [lindex $args $targ+1] + upvar 0 ${token} state + set state(proxyUsed) SecureProxy + set tim $state(-timeout) + } else { + set tim 0 + } + if {$tim == 0} { + # Do not use infinite timeout for the proxy. + set tim 30000 + } + + # Prepare and send a CONNECT request to the proxy, using + # code similar to http::geturl. + set requestHeaders [list Host $host] + lappend requestHeaders Connection keep-alive + if {$http(-proxyauth) != {}} { + lappend requestHeaders Proxy-Authorization $http(-proxyauth) + } + + set token2 [CreateToken $url -keepalive 0 -timeout $tim \ + -headers $requestHeaders -command [list http::AllDone $varName]] + variable $token2 + upvar 0 $token2 state2 + + # Setting this variable overrides the HTTP request line and allows + # -headers to override the Connection: header set by -keepalive. + set state2(bypass) "CONNECT $host:$port HTTP/1.1" + + AsyncTransaction $token2 + + if {[info coroutine] ne {}} { + # All callers in the http package are coroutines launched by + # the event loop. + # The cwait command requires a coroutine because it yields + # to the caller; $varName is traced and the coroutine resumes + # when the variable is written. + cwait $varName + } else { + return -code error {code must run in a coroutine} + # For testing with a non-coroutine caller outside the http package. + # vwait $varName + } + unset $varName + + set sock $state2(sock) + set code $state2(responseCode) + if {[string is integer -strict $code] && ($code >= 200) && ($code < 300)} { + # All OK. The caller in tls will now call "tls::import $sock". + # Do not use Finish, which will close (sock). + # Other tidying done in http::Event. + array unset state2 + } elseif {$targ != -1} { + # Bad HTTP status code; token is known. + # Copy from state2 to state, including (sock). + foreach name [array names state2] { + set state($name) $state2($name) + } + set state(proxyUsed) SecureProxy + set state(proxyFail) failed + + # Do not use Finish, which will close (sock). + # Other tidying done in http::Event. + array unset state2 + + # Error message detected by http::OpenSocket. + return -code error "proxy connect failed: $code" + } else { + # Bad HTTP status code; token is not known because option -type + # (cf. targ) was not passed through tcltls, and so the script + # cannot write to state(*). + # Do not use Finish, which will close (sock). + # Other tidying done in http::Event. + array unset state2 + + # Error message detected by http::OpenSocket. + return -code error "proxy connect failed: $code\n$block" + } + + return $sock +} + + # ------------------------------------------------------------------------------ # Proc http::socket # ------------------------------------------------------------------------------ @@ -4767,7 +5036,7 @@ proc http::socket {args} { LoadThreadIfNeeded - set targ [lsearch -exact $args -token] + set targ [lsearch -exact $args -type] if {$targ != -1} { set token [lindex $args $targ+1] set args [lreplace $args $targ $targ+1] @@ -4831,7 +5100,7 @@ proc http::socket {args} { } # The commands below are dependencies of http::socket and -# are not used elsewhere. +# http::SecureProxyConnect and are not used elsewhere. # ------------------------------------------------------------------------------ # Proc http::LoadThreadIfNeeded diff --git a/tests/http.test b/tests/http.test index 1218536..18850d9 100644 --- a/tests/http.test +++ b/tests/http.test @@ -89,7 +89,7 @@ http::config -threadlevel $ThreadLevel test http-1.1 {http::config} { http::config -useragent UserAgent http::config -} [list -accept */* -cookiejar {} -pipeline 1 -postfresh 0 -proxyfilter http::ProxyRequired -proxyhost {} -proxyport {} -repost 0 -threadlevel $ThreadLevel -urlencoding utf-8 -useragent UserAgent -zip 1] +} [list -accept */* -cookiejar {} -pipeline 1 -postfresh 0 -proxyauth {} -proxyfilter http::ProxyRequired -proxyhost {} -proxynot {} -proxyport {} -repost 0 -threadlevel $ThreadLevel -urlencoding utf-8 -useragent UserAgent -zip 1] test http-1.2 {http::config} { http::config -proxyfilter } http::ProxyRequired @@ -104,10 +104,10 @@ test http-1.4 {http::config} { set x [http::config] http::config {*}$savedconf set x -} [list -accept */* -cookiejar {} -pipeline 1 -postfresh 0 -proxyfilter myFilter -proxyhost nowhere.come -proxyport 8080 -repost 0 -threadlevel $ThreadLevel -urlencoding iso8859-1 -useragent {Tcl Test Suite} -zip 1] +} [list -accept */* -cookiejar {} -pipeline 1 -postfresh 0 -proxyauth {} -proxyfilter myFilter -proxyhost nowhere.come -proxynot {} -proxyport 8080 -repost 0 -threadlevel $ThreadLevel -urlencoding iso8859-1 -useragent {Tcl Test Suite} -zip 1] test http-1.5 {http::config} -returnCodes error -body { http::config -proxyhost {} -junk 8080 -} -result {Unknown option -junk, must be: -accept, -cookiejar, -pipeline, -postfresh, -proxyfilter, -proxyhost, -proxyport, -repost, -threadlevel, -urlencoding, -useragent, -zip} +} -result {Unknown option -junk, must be: -accept, -cookiejar, -pipeline, -postfresh, -proxyauth, -proxyfilter, -proxyhost, -proxynot, -proxyport, -repost, -threadlevel, -urlencoding, -useragent, -zip} test http-1.6 {http::config} -setup { set oldenc [http::config -urlencoding] } -body { diff --git a/tests/httpProxy.test b/tests/httpProxy.test new file mode 100644 index 0000000..2d0bea2 --- /dev/null +++ b/tests/httpProxy.test @@ -0,0 +1,456 @@ +# Commands covered: http::geturl when using a proxy server. +# +# This file contains a collection of tests for the http script library. +# Sourcing this file into Tcl runs the tests and generates output for errors. +# No output means no errors were found. +# +# Copyright © 1991-1993 The Regents of the University of California. +# Copyright © 1994-1996 Sun Microsystems, Inc. +# Copyright © 1998-2000 Ajuba Solutions. +# Copyright © 2022 Keith Nash. +# +# See the file "license.terms" for information on usage and redistribution of +# this file, and for a DISCLAIMER OF ALL WARRANTIES. + +if {"::tcltest" ni [namespace children]} { + package require tcltest 2.5 + namespace import -force ::tcltest::* +} + +package require http 2.10 + +proc bgerror {args} { + global errorInfo + puts stderr "httpProxy.test bgerror" + puts stderr [join $args] + puts stderr $errorInfo +} + +if {![info exists ThreadLevel]} { + if {[catch {package require Thread}] == 0} { + set ValueRange {0 1 2} + } else { + set ValueRange {0 1} + } + + # For each value of ThreadLevel, source this file recursively in the + # same interpreter. + foreach ThreadLevel $ValueRange { + source [info script] + } + catch {unset ThreadLevel} + catch {unset ValueRange} + return +} + +catch {puts "==== Test with ThreadLevel $ThreadLevel ===="} +http::config -threadlevel $ThreadLevel + + +#testConstraint needsSquid 1 +#testConstraint needsTls 1 + +if {[testConstraint needsTls]} { + package require tls + http::register https 443 [list ::tls::socket -ssl2 0 -ssl3 0 \ + -tls1 0 -tls1.1 0 -tls1.2 1 -tls1.3 0 -autoservername 1] +} + +# Testing with Squid +# - Example Squid configuration for Enterprise Linux 8 (Red Hat, Oracle, Rocky, +# Alma, ...) is in file tests/httpProxySquidConfigForEL8.tar.gz. +# - Two instances of Squid are launched, one that needs authentication and one +# that does not. +# - Each instance of Squid listens on IPv4 and IPv6, on different ports. + +# Instance of Squid that does not need authentication. +set n4host 127.0.0.1 +set n6host ::1 +set n4port 3128 +set n6port 3130 + +# Instance of Squid that needs authentication. +set a4host 127.0.0.1 +set a6host ::1 +set a4port 3129 +set a6port 3131 + +# concat Basic [base64::encode alice:alicia] +set aliceCreds {Basic YWxpY2U6YWxpY2lh} + +# concat Basic [base64::encode intruder:intruder] +set badCreds {Basic aW50cnVkZXI6aW50cnVkZXI=} + +test httpProxy-1.1 {squid is running - ipv4 noauth} -constraints {needsSquid} -setup { +} -body { + set token [http::geturl http://$n4host:$n4port/] + set ri [http::responseInfo $token] + set res "[dict get $ri stage] [dict get $ri status] [dict get $ri responseCode] [dict get $ri proxyUsed]" +} -result {complete ok 400 none} -cleanup { + http::cleanup $token + unset -nocomplain ri res +} + +test httpProxy-1.2 {squid is running - ipv6 noauth} -constraints {needsSquid} -setup { +} -body { + set token [http::geturl http://\[$n6host\]:$n6port/] + set ri [http::responseInfo $token] + set res "[dict get $ri stage] [dict get $ri status] [dict get $ri responseCode] [dict get $ri proxyUsed]" +} -result {complete ok 400 none} -cleanup { + http::cleanup $token + unset -nocomplain ri res +} + +test httpProxy-1.3 {squid is running - ipv4 auth} -constraints {needsSquid} -setup { +} -body { + set token [http::geturl http://$a4host:$a4port/] + set ri [http::responseInfo $token] + set res "[dict get $ri stage] [dict get $ri status] [dict get $ri responseCode] [dict get $ri proxyUsed]" +} -result {complete ok 400 none} -cleanup { + http::cleanup $token + unset -nocomplain ri res +} + +test httpProxy-1.4 {squid is running - ipv6 auth} -constraints {needsSquid} -setup { +} -body { + set token [http::geturl http://\[$a6host\]:$a6port/] + set ri [http::responseInfo $token] + set res "[dict get $ri stage] [dict get $ri status] [dict get $ri responseCode] [dict get $ri proxyUsed]" +} -result {complete ok 400 none} -cleanup { + http::cleanup $token + unset -nocomplain ri res +} + +test httpProxy-2.1 {http no-proxy no-auth} -constraints {needsSquid} -setup { + http::config -proxyhost {} -proxyport {} -proxynot {} -proxyauth {} +} -body { + set token [http::geturl http://www.google.com/] + set ri [http::responseInfo $token] + set res "[dict get $ri stage] [dict get $ri status] [dict get $ri responseCode] [dict get $ri proxyUsed]" +} -result {complete ok 200 none} -cleanup { + http::cleanup $token + unset -nocomplain ri res +} + +test httpProxy-2.2 {https no-proxy no-auth} -constraints {needsSquid needsTls} -setup { + http::config -proxyhost {} -proxyport {} -proxynot {} -proxyauth {} +} -body { + set token [http::geturl https://www.google.com/] + set ri [http::responseInfo $token] + set res "[dict get $ri stage] [dict get $ri status] [dict get $ri responseCode] [dict get $ri proxyUsed]" +} -result {complete ok 200 none} -cleanup { + http::cleanup $token + unset -nocomplain ri res +} + +test httpProxy-2.3 {http with-proxy ipv4 no-auth} -constraints {needsSquid} -setup { + http::config -proxyhost $n4host -proxyport $n4port -proxynot {127.0.0.1 localhost} -proxyauth {} +} -body { + set token [http::geturl http://www.google.com/] + set ri [http::responseInfo $token] + set res "[dict get $ri stage] [dict get $ri status] [dict get $ri responseCode] [dict get $ri proxyUsed]" +} -result {complete ok 200 HttpProxy} -cleanup { + http::cleanup $token + unset -nocomplain ri res + http::config -proxyhost {} -proxyport {} -proxynot {} +} + +test httpProxy-2.4 {https with-proxy ipv4 no-auth} -constraints {needsSquid needsTls} -setup { + http::config -proxyhost $n4host -proxyport $n4port -proxynot {127.0.0.1 localhost} -proxyauth {} +} -body { + set token [http::geturl https://www.google.com/] + set ri [http::responseInfo $token] + set res "[dict get $ri stage] [dict get $ri status] [dict get $ri responseCode] [dict get $ri proxyUsed]" +} -result {complete ok 200 SecureProxy} -cleanup { + http::cleanup $token + unset -nocomplain ri res + http::config -proxyhost {} -proxyport {} -proxynot {} +} + +test httpProxy-2.5 {http with-proxy ipv6 no-auth} -constraints {needsSquid} -setup { + http::config -proxyhost $n6host -proxyport $n6port -proxynot {127.0.0.1 localhost} -proxyauth {} +} -body { + set token [http::geturl http://www.google.com/] + set ri [http::responseInfo $token] + set res "[dict get $ri stage] [dict get $ri status] [dict get $ri responseCode] [dict get $ri proxyUsed]" +} -result {complete ok 200 HttpProxy} -cleanup { + http::cleanup $token + unset -nocomplain ri res + http::config -proxyhost {} -proxyport {} -proxynot {} +} + +test httpProxy-2.6 {https with-proxy ipv6 no-auth} -constraints {needsSquid needsTls} -setup { + http::config -proxyhost $n6host -proxyport $n6port -proxynot {127.0.0.1 localhost} -proxyauth {} +} -body { + set token [http::geturl https://www.google.com/] + set ri [http::responseInfo $token] + set res "[dict get $ri stage] [dict get $ri status] [dict get $ri responseCode] [dict get $ri proxyUsed]" +} -result {complete ok 200 SecureProxy} -cleanup { + http::cleanup $token + unset -nocomplain ri res + http::config -proxyhost {} -proxyport {} -proxynot {} +} + +test httpProxy-3.1 {http no-proxy with-auth valid-creds-provided} -constraints {needsSquid} -setup { + http::config -proxyhost {} -proxyport $a4port -proxynot {127.0.0.1 localhost} -proxyauth $aliceCreds +} -body { + set token [http::geturl http://www.google.com/] + set ri [http::responseInfo $token] + set pos1 [lsearch -exact [string tolower [set ${token}(requestHeaders)]] proxy-authorization] + set pos2 [lsearch -exact [set ${token}(requestHeaders)] $aliceCreds] + set res "[dict get $ri stage] [dict get $ri status] [dict get $ri responseCode] [dict get $ri proxyUsed] [expr {$pos1 > -1}] [expr {$pos2 > -1}]" +} -result {complete ok 200 none 0 0} -cleanup { + http::cleanup $token + unset -nocomplain ri res pos1 pos2 + http::config -proxyhost {} -proxyport {} -proxynot {} -proxyauth {} +} + +test httpProxy-3.2 {https no-proxy with-auth valid-creds-provided} -constraints {needsSquid needsTls} -setup { + http::config -proxyhost {} -proxyport $a4port -proxynot {127.0.0.1 localhost} -proxyauth $aliceCreds +} -body { + set token [http::geturl https://www.google.com/] + set ri [http::responseInfo $token] + set pos1 [lsearch -exact [string tolower [set ${token}(requestHeaders)]] proxy-authorization] + set pos2 [lsearch -exact [set ${token}(requestHeaders)] $aliceCreds] + set res "[dict get $ri stage] [dict get $ri status] [dict get $ri responseCode] [dict get $ri proxyUsed] [expr {$pos1 > -1}] [expr {$pos2 > -1}]" +} -result {complete ok 200 none 0 0} -cleanup { + http::cleanup $token + unset -nocomplain ri res pos1 pos2 + http::config -proxyhost {} -proxyport {} -proxynot {} -proxyauth {} +} + +test httpProxy-3.3 {http with-proxy ipv4 with-auth valid-creds-provided} -constraints {needsSquid} -setup { + http::config -proxyhost $a4host -proxyport $a4port -proxynot {127.0.0.1 localhost} -proxyauth $aliceCreds +} -body { + set token [http::geturl http://www.google.com/] + set ri [http::responseInfo $token] + set pos1 [lsearch -exact [string tolower [set ${token}(requestHeaders)]] proxy-authorization] + set pos2 [lsearch -exact [set ${token}(requestHeaders)] $aliceCreds] + set res "[dict get $ri stage] [dict get $ri status] [dict get $ri responseCode] [dict get $ri proxyUsed] [expr {$pos1 > -1}] [expr {$pos2 > -1}]" +} -result {complete ok 200 HttpProxy 1 1} -cleanup { + http::cleanup $token + unset -nocomplain ri res pos1 pos2 + http::config -proxyhost {} -proxyport {} -proxynot {} -proxyauth {} +} + +test httpProxy-3.4 {https with-proxy ipv4 with-auth valid-creds-provided} -constraints {needsSquid needsTls} -setup { + http::config -proxyhost $a4host -proxyport $a4port -proxynot {127.0.0.1 localhost} -proxyauth $aliceCreds +} -body { + set token [http::geturl https://www.google.com/] + set ri [http::responseInfo $token] + set pos1 [lsearch -exact [string tolower [set ${token}(requestHeaders)]] proxy-authorization] + set pos2 [lsearch -exact [set ${token}(requestHeaders)] $aliceCreds] + set res "[dict get $ri stage] [dict get $ri status] [dict get $ri responseCode] [dict get $ri proxyUsed] [expr {$pos1 > -1}] [expr {$pos2 > -1}]" +} -result {complete ok 200 SecureProxy 0 0} -cleanup { + http::cleanup $token + unset -nocomplain ri res pos1 pos2 + http::config -proxyhost {} -proxyport {} -proxynot {} -proxyauth {} +} + +test httpProxy-3.5 {http with-proxy ipv6 with-auth valid-creds-provided} -constraints {needsSquid} -setup { + http::config -proxyhost $a6host -proxyport $a6port -proxynot {127.0.0.1 localhost} -proxyauth $aliceCreds +} -body { + set token [http::geturl http://www.google.com/] + set ri [http::responseInfo $token] + set pos1 [lsearch -exact [string tolower [set ${token}(requestHeaders)]] proxy-authorization] + set pos2 [lsearch -exact [set ${token}(requestHeaders)] $aliceCreds] + set res "[dict get $ri stage] [dict get $ri status] [dict get $ri responseCode] [dict get $ri proxyUsed] [expr {$pos1 > -1}] [expr {$pos2 > -1}]" +} -result {complete ok 200 HttpProxy 1 1} -cleanup { + http::cleanup $token + unset -nocomplain ri res pos1 pos2 + http::config -proxyhost {} -proxyport {} -proxynot {} -proxyauth {} +} + +test httpProxy-3.6 {https with-proxy ipv6 with-auth valid-creds-provided} -constraints {needsSquid needsTls} -setup { + http::config -proxyhost $a6host -proxyport $a6port -proxynot {127.0.0.1 localhost} -proxyauth $aliceCreds +} -body { + set token [http::geturl https://www.google.com/] + set ri [http::responseInfo $token] + set pos1 [lsearch -exact [string tolower [set ${token}(requestHeaders)]] proxy-authorization] + set pos2 [lsearch -exact [set ${token}(requestHeaders)] $aliceCreds] + set res "[dict get $ri stage] [dict get $ri status] [dict get $ri responseCode] [dict get $ri proxyUsed] [expr {$pos1 > -1}] [expr {$pos2 > -1}]" +} -result {complete ok 200 SecureProxy 0 0} -cleanup { + http::cleanup $token + unset -nocomplain ri res pos1 pos2 + http::config -proxyhost {} -proxyport {} -proxynot {} -proxyauth {} +} + +test httpProxy-4.1 {http no-proxy with-auth no-creds-provided} -constraints {needsSquid} -setup { + http::config -proxyhost {} -proxyport $a4port -proxynot {127.0.0.1 localhost} -proxyauth {} +} -body { + set token [http::geturl http://www.google.com/] + set ri [http::responseInfo $token] + set pos1 [lsearch -exact [string tolower [set ${token}(requestHeaders)]] proxy-authorization] + set pos2 [lsearch -exact [set ${token}(requestHeaders)] $aliceCreds] + set res "[dict get $ri stage] [dict get $ri status] [dict get $ri responseCode] [dict get $ri proxyUsed] [expr {$pos1 > -1}] [expr {$pos2 > -1}]" +} -result {complete ok 200 none 0 0} -cleanup { + http::cleanup $token + unset -nocomplain ri res pos1 pos2 + http::config -proxyhost {} -proxyport {} -proxynot {} -proxyauth {} +} + +test httpProxy-4.2 {https no-proxy with-auth no-creds-provided} -constraints {needsSquid needsTls} -setup { + http::config -proxyhost {} -proxyport $a4port -proxynot {127.0.0.1 localhost} -proxyauth {} +} -body { + set token [http::geturl https://www.google.com/] + set ri [http::responseInfo $token] + set pos1 [lsearch -exact [string tolower [set ${token}(requestHeaders)]] proxy-authorization] + set pos2 [lsearch -exact [set ${token}(requestHeaders)] $aliceCreds] + set res "[dict get $ri stage] [dict get $ri status] [dict get $ri responseCode] [dict get $ri proxyUsed] [expr {$pos1 > -1}] [expr {$pos2 > -1}]" +} -result {complete ok 200 none 0 0} -cleanup { + http::cleanup $token + unset -nocomplain ri res pos1 pos2 + http::config -proxyhost {} -proxyport {} -proxynot {} -proxyauth {} +} + +test httpProxy-4.3 {http with-proxy ipv4 with-auth no-creds-provided} -constraints {needsSquid} -setup { + http::config -proxyhost $a4host -proxyport $a4port -proxynot {127.0.0.1 localhost} -proxyauth {} +} -body { + set token [http::geturl http://www.google.com/] + set ri [http::responseInfo $token] + set pos1 [lsearch -exact [string tolower [set ${token}(requestHeaders)]] proxy-authorization] + set pos2 [lsearch -exact [set ${token}(requestHeaders)] $aliceCreds] + set res "[dict get $ri stage] [dict get $ri status] [dict get $ri responseCode] [dict get $ri proxyUsed] [expr {$pos1 > -1}] [expr {$pos2 > -1}]" +} -result {complete ok 407 HttpProxy 0 0} -cleanup { + http::cleanup $token + unset -nocomplain ri res pos1 pos2 + http::config -proxyhost {} -proxyport {} -proxynot {} -proxyauth {} +} + +test httpProxy-4.4 {https with-proxy ipv4 with-auth no-creds-provided} -constraints {needsSquid needsTls} -setup { + http::config -proxyhost $a4host -proxyport $a4port -proxynot {127.0.0.1 localhost} -proxyauth {} +} -body { + set token [http::geturl https://www.google.com/] + set ri [http::responseInfo $token] + set pos1 [lsearch -exact [string tolower [set ${token}(requestHeaders)]] proxy-authorization] + set pos2 [lsearch -exact [set ${token}(requestHeaders)] $aliceCreds] + set res "[dict get $ri stage] [dict get $ri status] [dict get $ri responseCode] [dict get $ri proxyUsed] [expr {$pos1 > -1}] [expr {$pos2 > -1}]" +} -result {complete ok 407 SecureProxy 0 0} -cleanup { + http::cleanup $token + unset -nocomplain ri res pos1 pos2 + http::config -proxyhost {} -proxyport {} -proxynot {} -proxyauth {} +} + +test httpProxy-4.5 {http with-proxy ipv6 with-auth no-creds-provided} -constraints {needsSquid} -setup { + http::config -proxyhost $a6host -proxyport $a6port -proxynot {127.0.0.1 localhost} -proxyauth {} +} -body { + set token [http::geturl http://www.google.com/] + set ri [http::responseInfo $token] + set pos1 [lsearch -exact [string tolower [set ${token}(requestHeaders)]] proxy-authorization] + set pos2 [lsearch -exact [set ${token}(requestHeaders)] $aliceCreds] + set res "[dict get $ri stage] [dict get $ri status] [dict get $ri responseCode] [dict get $ri proxyUsed] [expr {$pos1 > -1}] [expr {$pos2 > -1}]" +} -result {complete ok 407 HttpProxy 0 0} -cleanup { + http::cleanup $token + unset -nocomplain ri res pos1 pos2 + http::config -proxyhost {} -proxyport {} -proxynot {} -proxyauth {} +} + +test httpProxy-4.6 {https with-proxy ipv6 with-auth no-creds-provided} -constraints {needsSquid needsTls} -setup { + http::config -proxyhost $a6host -proxyport $a6port -proxynot {127.0.0.1 localhost} -proxyauth {} +} -body { + set token [http::geturl https://www.google.com/] + set ri [http::responseInfo $token] + set pos1 [lsearch -exact [string tolower [set ${token}(requestHeaders)]] proxy-authorization] + set pos2 [lsearch -exact [set ${token}(requestHeaders)] $aliceCreds] + set res "[dict get $ri stage] [dict get $ri status] [dict get $ri responseCode] [dict get $ri proxyUsed] [expr {$pos1 > -1}] [expr {$pos2 > -1}]" +} -result {complete ok 407 SecureProxy 0 0} -cleanup { + http::cleanup $token + unset -nocomplain ri res pos1 pos2 + http::config -proxyhost {} -proxyport {} -proxynot {} -proxyauth {} +} + +test httpProxy-5.1 {http no-proxy with-auth bad-creds-provided} -constraints {needsSquid} -setup { + http::config -proxyhost {} -proxyport $a4port -proxynot {127.0.0.1 localhost} -proxyauth $badCreds +} -body { + set token [http::geturl http://www.google.com/] + set ri [http::responseInfo $token] + set pos1 [lsearch -exact [string tolower [set ${token}(requestHeaders)]] proxy-authorization] + set pos2 [lsearch -exact [set ${token}(requestHeaders)] $badCreds] + set res "[dict get $ri stage] [dict get $ri status] [dict get $ri responseCode] [dict get $ri proxyUsed] [expr {$pos1 > -1}] [expr {$pos2 > -1}]" +} -result {complete ok 200 none 0 0} -cleanup { + http::cleanup $token + unset -nocomplain ri res pos1 pos2 + http::config -proxyhost {} -proxyport {} -proxynot {} -proxyauth {} +} + +test httpProxy-5.2 {https no-proxy with-auth bad-creds-provided} -constraints {needsSquid needsTls} -setup { + http::config -proxyhost {} -proxyport $a4port -proxynot {127.0.0.1 localhost} -proxyauth $badCreds +} -body { + set token [http::geturl https://www.google.com/] + set ri [http::responseInfo $token] + set pos1 [lsearch -exact [string tolower [set ${token}(requestHeaders)]] proxy-authorization] + set pos2 [lsearch -exact [set ${token}(requestHeaders)] $badCreds] + set res "[dict get $ri stage] [dict get $ri status] [dict get $ri responseCode] [dict get $ri proxyUsed] [expr {$pos1 > -1}] [expr {$pos2 > -1}]" +} -result {complete ok 200 none 0 0} -cleanup { + http::cleanup $token + unset -nocomplain ri res pos1 pos2 + http::config -proxyhost {} -proxyport {} -proxynot {} -proxyauth {} +} + +test httpProxy-5.3 {http with-proxy ipv4 with-auth bad-creds-provided} -constraints {needsSquid} -setup { + http::config -proxyhost $a4host -proxyport $a4port -proxynot {127.0.0.1 localhost} -proxyauth $badCreds +} -body { + set token [http::geturl http://www.google.com/] + set ri [http::responseInfo $token] + set pos1 [lsearch -exact [string tolower [set ${token}(requestHeaders)]] proxy-authorization] + set pos2 [lsearch -exact [set ${token}(requestHeaders)] $badCreds] + set res "[dict get $ri stage] [dict get $ri status] [dict get $ri responseCode] [dict get $ri proxyUsed] [expr {$pos1 > -1}] [expr {$pos2 > -1}]" +} -result {complete ok 407 HttpProxy 1 1} -cleanup { + http::cleanup $token + unset -nocomplain ri res pos1 pos2 + http::config -proxyhost {} -proxyport {} -proxynot {} -proxyauth {} +} + +test httpProxy-5.4 {https with-proxy ipv4 with-auth bad-creds-provided} -constraints {needsSquid needsTls} -setup { + http::config -proxyhost $a4host -proxyport $a4port -proxynot {127.0.0.1 localhost} -proxyauth $badCreds +} -body { + set token [http::geturl https://www.google.com/] + set ri [http::responseInfo $token] + set pos1 [lsearch -exact [string tolower [set ${token}(requestHeaders)]] proxy-authorization] + set pos2 [lsearch -exact [set ${token}(requestHeaders)] $badCreds] + set res "[dict get $ri stage] [dict get $ri status] [dict get $ri responseCode] [dict get $ri proxyUsed] [expr {$pos1 > -1}] [expr {$pos2 > -1}]" +} -result {complete ok 407 SecureProxy 1 1} -cleanup { + http::cleanup $token + unset -nocomplain ri res pos1 pos2 + http::config -proxyhost {} -proxyport {} -proxynot {} -proxyauth {} +} + +test httpProxy-5.5 {http with-proxy ipv6 with-auth bad-creds-provided} -constraints {needsSquid} -setup { + http::config -proxyhost $a6host -proxyport $a6port -proxynot {127.0.0.1 localhost} -proxyauth $badCreds +} -body { + set token [http::geturl http://www.google.com/] + set ri [http::responseInfo $token] + set pos1 [lsearch -exact [string tolower [set ${token}(requestHeaders)]] proxy-authorization] + set pos2 [lsearch -exact [set ${token}(requestHeaders)] $badCreds] + set res "[dict get $ri stage] [dict get $ri status] [dict get $ri responseCode] [dict get $ri proxyUsed] [expr {$pos1 > -1}] [expr {$pos2 > -1}]" +} -result {complete ok 407 HttpProxy 1 1} -cleanup { + http::cleanup $token + unset -nocomplain ri res pos1 pos2 + http::config -proxyhost {} -proxyport {} -proxynot {} -proxyauth {} +} + +test httpProxy-5.6 {https with-proxy ipv6 with-auth bad-creds-provided} -constraints {needsSquid needsTls} -setup { + http::config -proxyhost $a6host -proxyport $a6port -proxynot {127.0.0.1 localhost} -proxyauth $badCreds +} -body { + set token [http::geturl https://www.google.com/] + set ri [http::responseInfo $token] + set pos1 [lsearch -exact [string tolower [set ${token}(requestHeaders)]] proxy-authorization] + set pos2 [lsearch -exact [set ${token}(requestHeaders)] $badCreds] + set res "[dict get $ri stage] [dict get $ri status] [dict get $ri responseCode] [dict get $ri proxyUsed] [expr {$pos1 > -1}] [expr {$pos2 > -1}]" +} -result {complete ok 407 SecureProxy 1 1} -cleanup { + http::cleanup $token + unset -nocomplain ri res pos1 pos2 + http::config -proxyhost {} -proxyport {} -proxynot {} -proxyauth {} +} + +# cleanup +unset -nocomplain n4host n6host n4port n6port a4host a6host a4port a6port + +rename bgerror {} + +::tcltest::cleanupTests + +# Local variables: +# mode: tcl +# End: + diff --git a/tests/httpProxySquidConfigForEL8.tar.gz b/tests/httpProxySquidConfigForEL8.tar.gz new file mode 100644 index 0000000..a94dbdb Binary files /dev/null and b/tests/httpProxySquidConfigForEL8.tar.gz differ -- cgit v0.12 From 2ecf92f50b4fad000f8cf4b368ce47c6035bdf4c Mon Sep 17 00:00:00 2001 From: kjnash Date: Wed, 26 Oct 2022 11:28:29 +0000 Subject: Minor changes to http tests. --- tests/http.test | 5 ++++- tests/httpProxy.test | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/http.test b/tests/http.test index 18850d9..6826448 100644 --- a/tests/http.test +++ b/tests/http.test @@ -631,7 +631,10 @@ test http-4.14 {http::Event} -body { test http-4.15 {http::Event} -body { # This test may fail if you use a proxy server. That is to be # expected and is not a problem with Tcl. - set token [http::geturl //not_a_host.tcl.tk -timeout 3000 -command \#] + # With http::config -threadlevel 1 or 2, the script enters the event loop + # during the DNS lookup, and has the opportunity to time out. + # Increase -timeout from 3000 to 10000 to prevent this. + set token [http::geturl //not_a_host.tcl.tk -timeout 10000 -command \#] http::wait $token set result "[http::status $token] -- [lindex [http::error $token] 0]" # error codes vary among platforms. diff --git a/tests/httpProxy.test b/tests/httpProxy.test index 2d0bea2..42ad574 100644 --- a/tests/httpProxy.test +++ b/tests/httpProxy.test @@ -444,7 +444,7 @@ test httpProxy-5.6 {https with-proxy ipv6 with-auth bad-creds-provided} -constra } # cleanup -unset -nocomplain n4host n6host n4port n6port a4host a6host a4port a6port +unset -nocomplain n4host n6host n4port n6port a4host a6host a4port a6port aliceCreds badCreds rename bgerror {} -- cgit v0.12