On 28/04/2014 2:39 p.m., Amos Jeffries wrote:
> On 28/04/2014 3:51 a.m., Kinkie wrote:
>> On Sun, Apr 27, 2014 at 4:52 PM, Amos Jeffries wrote:
>>> This continues the bug 1961 redesign of URL handling.
>>
>> Hi,
>>
>> +    SBuf tmp = checklist->request->url.userInfo();
>> +    // NOTE: unescape works in-place and may decrease the c-string
>> length, never increase.
>> +    rfc1738_unescape(const_cast<char*>(tmp.c_str()));
>>
>> is troublesome: you are most likely trampling the userinfo data as
>> rfc1738_unescape is acting on a shared MemBlob (shared by tmp and
>> userInfo at least) and c_str() doesn't guarantee a COW. The best way
>> to do this is by defining a SBuf rfc1738_unescape (const SBuf &) which
>> copies the param if needed.
>>
>>
>> + if (colonPos != 0) {
>>
>> shouldn't this be if (colonPos != SBuf::npos) ?
> 
> No. The edge cases of user-info is that the colon may be the first
> character to signal missing username, and if ':' is not found at all the
> entire string is username.
>  So if colonPos==0 then we dont extract one, but all other values
> regardless including npos we do.
> 
> The edges case for password is that if colon is missing entirely we have
> none, all other cases have a password. So the password extraction only
> happens on colonPos!=npos.
> 
> Special case is that 0-length username is invalid (unlike absent
> password). The colonPos==0 case also helps omit replacing any existing
> username with an empty string - whereas the empty password case *does*
> replace the pasword with empty string.
> 

To clarify the cases are:

case u:p@...
 - gives new username 'u' and new password 'p'

case u:@...
 - gives new username 'u' and clears previous password (sets new 0-length)

case u@...
 - gives new username 'u' and retains previous password

case :p@...
 - retains previous username and gives new password 'p'

case :@...
 - retains previous username and clears previous password (sets new
0-length)

case @...
 - invalid. retain previous username and password.

> 
>>
>> +        memcpy(user, userName.rawContent(), userName.length());
>> +        user[userName.length()] = '\0';
>>
>> you could use
>> SBuf::size_type upTo = min(userName.length(), sizeof(user));
>> userName.copy(user, upto);
>> user[upto]='\0';
>>
>> Same for
>> +                    memcpy(loginbuf,
>> request->url.userInfo().rawContent(), len);
>>
> 
> Thank you for tha pointer. Adding a slight variant, copy() does min()
> internally and returs the size used so we can drop another line.
> 
>>
>>
>> +            static char result[MAX_URL*2]; // should be big enough
>> for a single URI segment
>> That conditional is worrisome. Is there any risk of the result not
>> being big enough?
>>
> 
> Oops, small bug there should have been " < sizeof(result)-1" instead of
> "> 0".
> 

I have a better answer for this now...

 The URL parser used a MAX_URL length array to parse the originally
received user-info/login field so the raw data cannot exceed MAX_URL.
The *2 is because base64 may double the length on us.

Patch attached that covers all the above.

Amos
=== modified file 'src/HttpRequest.cc'
--- src/HttpRequest.cc  2014-04-27 07:59:17 +0000
+++ src/HttpRequest.cc  2014-05-09 17:45:08 +0000
@@ -75,41 +75,40 @@
 HttpRequest::~HttpRequest()
 {
     clean();
     debugs(93,7, HERE << "destructed, this=" << this);
 }
 
 void
 HttpRequest::initHTTP(const HttpRequestMethod& aMethod, AnyP::ProtocolType 
aProtocol, const char *aUrlpath)
 {
     method = aMethod;
     url.setScheme(aProtocol);
     urlpath = aUrlpath;
 }
 
 void
 HttpRequest::init()
 {
     method = Http::METHOD_NONE;
     url.clear();
     urlpath = NULL;
-    login[0] = '\0';
     host[0] = '\0';
     host_is_numeric = -1;
 #if USE_AUTH
     auth_user_request = NULL;
 #endif
     port = 0;
     canonical = NULL;
     memset(&flags, '\0', sizeof(flags));
     range = NULL;
     ims = -1;
     imslen = 0;
     lastmod = -1;
     client_addr.setEmpty();
     my_addr.setEmpty();
     body_pipe = NULL;
     // hier
     dnsWait = -1;
     errType = ERR_NONE;
     errDetail = ERR_DETAIL_NONE;
     peer_login = NULL;         // not allocated/deallocated by this class
@@ -190,41 +189,41 @@
 
 void
 HttpRequest::reset()
 {
     clean();
     init();
 }
 
 HttpRequest *
 HttpRequest::clone() const
 {
     HttpRequest *copy = new HttpRequest(method, url.getScheme(), 
urlpath.termedBuf());
     // TODO: move common cloning clone to Msg::copyTo() or copy ctor
     copy->header.append(&header);
     copy->hdrCacheInit();
     copy->hdr_sz = hdr_sz;
     copy->http_ver = http_ver;
     copy->pstate = pstate; // TODO: should we assert a specific state here?
     copy->body_pipe = body_pipe;
 
-    strncpy(copy->login, login, sizeof(login)); // MAX_LOGIN_SZ
+    copy->url.userInfo(url.userInfo());
     strncpy(copy->host, host, sizeof(host)); // SQUIDHOSTNAMELEN
     copy->host_addr = host_addr;
 
     copy->port = port;
     // urlPath handled in ctor
     copy->canonical = canonical ? xstrdup(canonical) : NULL;
 
     // range handled in hdrCacheInit()
     copy->ims = ims;
     copy->imslen = imslen;
     copy->hier = hier; // Is it safe to copy? Should we?
 
     copy->errType = errType;
 
     // XXX: what to do with copy->peer_login?
 
     copy->lastmod = lastmod;
     copy->etag = etag;
     copy->vary_headers = vary_headers ? xstrdup(vary_headers) : NULL;
     // XXX: what to do with copy->peer_domain?

=== modified file 'src/HttpRequest.h'
--- src/HttpRequest.h   2014-04-27 07:59:17 +0000
+++ src/HttpRequest.h   2014-05-09 17:45:08 +0000
@@ -121,43 +121,41 @@
     /// Returns possibly nil history, creating it if icap logging is enabled
     Adaptation::Icap::History::Pointer icapHistory() const;
 #endif
 
     void recordLookup(const DnsLookupDetails &detail);
 
     /// sets error detail if no earlier detail was available
     void detailError(err_type aType, int aDetail);
     /// clear error details, useful for retries/repeats
     void clearError();
 
 protected:
     void clean();
 
     void init();
 
 public:
     HttpRequestMethod method;
 
     // TODO expand to include all URI parts
-    URL url; ///< the request URI (scheme only)
-
-    char login[MAX_LOGIN_SZ];
+    URL url; ///< the request URI (scheme and userinfo only)
 
 private:
     char host[SQUIDHOSTNAMELEN];
     int host_is_numeric;
 
 #if USE_ADAPTATION
     mutable Adaptation::History::Pointer adaptHistory_; ///< per-HTTP 
transaction info
 #endif
 #if ICAP_CLIENT
     mutable Adaptation::Icap::History::Pointer icapHistory_; ///< per-HTTP 
transaction info
 #endif
 
 public:
     Ip::Address host_addr;
 #if USE_AUTH
     Auth::UserRequest::Pointer auth_user_request;
 #endif
     unsigned short port;
 
     String urlpath;

=== modified file 'src/URL.h'
--- src/URL.h   2014-04-27 07:59:17 +0000
+++ src/URL.h   2014-05-09 17:45:08 +0000
@@ -16,84 +16,88 @@
  *  it under the terms of the GNU General Public License as published by
  *  the Free Software Foundation; either version 2 of the License, or
  *  (at your option) any later version.
  *
  *  This program is distributed in the hope that it will be useful,
  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *  GNU General Public License for more details.
  *
  *  You should have received a copy of the GNU General Public License
  *  along with this program; if not, write to the Free Software
  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
  *
  */
 
 #ifndef SQUID_SRC_URL_H
 #define SQUID_SRC_URL_H
 
 #include "anyp/UriScheme.h"
 #include "MemPool.h"
+#include "SBuf.h"
 
 /**
- \ingroup POD
- *
  * The URL class represents a Uniform Resource Location
  */
 class URL
 {
 public:
     MEMPROXY_CLASS(URL);
     URL() : scheme_() {}
     URL(AnyP::UriScheme const &aScheme) : scheme_(aScheme) {}
 
     void clear() {
         scheme_=AnyP::PROTO_NONE;
     }
 
     AnyP::UriScheme const & getScheme() const {return scheme_;}
 
     /// convert the URL scheme to that given
     void setScheme(const AnyP::ProtocolType &p) {scheme_=p;}
 
+    void userInfo(const SBuf &s) {userInfo_=s;}
+    const SBuf &userInfo(void) const {return userInfo_;}
+
 private:
     /**
      \par
      * The scheme of this URL. This has the 'type code' smell about it.
      * In future we may want to make the methods that dispatch based on
      * the scheme virtual and have a class per protocol.
      \par
      * On the other hand, having Protocol as an explicit concept is useful,
      * see for instance the ACLProtocol acl type. One way to represent this
      * is to have one prototype URL with no host etc for each scheme,
      * another is to have an explicit scheme class, and then each URL class
      * could be a subclass of the scheme. Another way is one instance of
      * a AnyP::UriScheme class instance for each URL scheme we support, and 
one URL
      * class for each manner of treating the scheme : a Hierarchical URL, a
      * non-hierarchical URL etc.
      \par
      * Deferring the decision, its a type code for now. RBC 20060507.
      \par
      * In order to make taking any of these routes easy, scheme is private
      * and immutable, only settable at construction time,
      */
     AnyP::UriScheme scheme_;
+
+    SBuf userInfo_; // aka 'URL-login'
 };
 
 MEMPROXY_CLASS_INLINE(URL);
 
 class HttpRequest;
 class HttpRequestMethod;
 
 AnyP::ProtocolType urlParseProtocol(const char *, const char *e = NULL);
 void urlInitialize(void);
 HttpRequest *urlParse(const HttpRequestMethod&, char *, HttpRequest *request = 
NULL);
 const char *urlCanonical(HttpRequest *);
 char *urlCanonicalClean(const HttpRequest *);
 const char *urlCanonicalFakeHttps(const HttpRequest * request);
 bool urlIsRelative(const char *);
 char *urlMakeAbsolute(const HttpRequest *, const char *);
 char *urlRInternal(const char *host, unsigned short port, const char *dir, 
const char *name);
 char *urlInternal(const char *dir, const char *name);
 int matchDomainName(const char *host, const char *domain);
 int urlCheckRequest(const HttpRequest *);
 int urlDefaultPort(AnyP::ProtocolType p);

=== modified file 'src/acl/UrlLogin.cc'
--- src/acl/UrlLogin.cc 2013-10-25 00:13:46 +0000
+++ src/acl/UrlLogin.cc 2014-05-09 17:45:08 +0000
@@ -21,36 +21,43 @@
  *
  *  This program is distributed in the hope that it will be useful,
  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *  GNU General Public License for more details.
  *
  *  You should have received a copy of the GNU General Public License
  *  along with this program; if not, write to the Free Software
  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
  *
  */
 
 #include "squid.h"
 #include "acl/Checklist.h"
 #include "acl/RegexData.h"
 #include "acl/UrlLogin.h"
 #include "HttpRequest.h"
 #include "rfc1738.h"
 
 int
-ACLUrlLoginStrategy::match (ACLData<char const *> * &data, ACLFilledChecklist 
*checklist, ACLFlags &)
+ACLUrlLoginStrategy::match(ACLData<char const *> * &data, ACLFilledChecklist 
*checklist, ACLFlags &)
 {
-    char *esc_buf = xstrdup(checklist->request->login);
-    rfc1738_unescape(esc_buf);
-    int result = data->match(esc_buf);
-    safe_free(esc_buf);
+    if (checklist->request->url.userInfo().isEmpty()) {
+        debugs(28, 5, "URL has no user-info details. cannot match");
+        return 0; // nothing can match
+    }
+
+    static char str[MAX_URL]; // should be big enough for a single URI segment
+
+    SBuf::size_type len = checklist->request->url.userInfo().copy(str, 
sizeof(str)-1);
+    str[len] = '\0';
+    rfc1738_unescape(str);
+    int result = data->match(str);
     return result;
 }
 
 ACLUrlLoginStrategy *
 ACLUrlLoginStrategy::Instance()
 {
     return &Instance_;
 }
 
 ACLUrlLoginStrategy ACLUrlLoginStrategy::Instance_;

=== modified file 'src/client_side.cc'
--- src/client_side.cc  2014-04-27 07:59:17 +0000
+++ src/client_side.cc  2014-05-09 17:45:08 +0000
@@ -2718,43 +2718,40 @@
         } else
             request->flags.spoofClientIp = false;
     }
 
     if (internalCheck(request->urlpath.termedBuf())) {
         if (internalHostnameIs(request->GetHost()) && request->port == 
getMyPort()) {
             debugs(33, 2, "internal URL found: " << request->url.getScheme() 
<< "://" << request->GetHost() <<
                    ':' << request->port);
             http->flags.internal = true;
         } else if (Config.onoff.global_internal_static && 
internalStaticCheck(request->urlpath.termedBuf())) {
             debugs(33, 2, "internal URL found: " << request->url.getScheme() 
<< "://" << request->GetHost() <<
                    ':' << request->port << " (global_internal_static on)");
             request->SetHost(internalHostname());
             request->port = getMyPort();
             http->flags.internal = true;
         } else
             debugs(33, 2, "internal URL found: " << request->url.getScheme() 
<< "://" << request->GetHost() <<
                    ':' << request->port << " (not this proxy)");
     }
 
-    if (http->flags.internal)
-        request->login[0] = '\0';
-
     request->flags.internal = http->flags.internal;
     setLogUri (http, urlCanonicalClean(request.getRaw()));
     request->client_addr = conn->clientConnection->remote; // XXX: remove 
reuest->client_addr member.
 #if FOLLOW_X_FORWARDED_FOR
     // indirect client gets stored here because it is an HTTP header result 
(from X-Forwarded-For:)
     // not a details about teh TCP connection itself
     request->indirect_client_addr = conn->clientConnection->remote;
 #endif /* FOLLOW_X_FORWARDED_FOR */
     request->my_addr = conn->clientConnection->local;
     request->myportname = conn->port->name;
     request->http_ver = http_ver;
 
     // Link this HttpRequest to ConnStateData relatively early so the 
following complex handling can use it
     // TODO: this effectively obsoletes a lot of conn->FOO copying. That needs 
cleaning up later.
     request->clientConnectionManager = conn;
 
     if (request->header.chunked()) {
         chunked = true;
     } else if (request->header.has(HDR_TRANSFER_ENCODING)) {
         const String te = request->header.getList(HDR_TRANSFER_ENCODING);

=== modified file 'src/client_side_request.cc'
--- src/client_side_request.cc  2014-04-27 07:59:17 +0000
+++ src/client_side_request.cc  2014-05-09 17:45:08 +0000
@@ -1141,41 +1141,41 @@
             http->range_iter.pos = request->range->begin();
             http->range_iter.end = request->range->end();
             http->range_iter.valid = true;
         }
     }
 
     /* Only HEAD and GET requests permit a Range or Request-Range header.
      * If these headers appear on any other type of request, delete them now.
      */
     else {
         req_hdr->delById(HDR_RANGE);
         req_hdr->delById(HDR_REQUEST_RANGE);
         request->ignoreRange("neither HEAD nor GET");
     }
 
     if (req_hdr->has(HDR_AUTHORIZATION))
         request->flags.auth = true;
 
     clientCheckPinning(http);
 
-    if (request->login[0] != '\0')
+    if (!request->url.userInfo().isEmpty())
         request->flags.auth = true;
 
     if (req_hdr->has(HDR_VIA)) {
         String s = req_hdr->getList(HDR_VIA);
         /*
          * ThisCache cannot be a member of Via header, "1.1 ThisCache" can.
          * Note ThisCache2 has a space prepended to the hostname so we don't
          * accidentally match super-domains.
          */
 
         if (strListIsSubstr(&s, ThisCache2, ',')) {
             debugObj(33, 1, "WARNING: Forwarding loop detected for:\n",
                      request, (ObjPackMethod) & httpRequestPack);
             request->flags.loopDetected = true;
         }
 
 #if USE_FORW_VIA_DB
         fvdbCountVia(s.termedBuf());
 
 #endif

=== modified file 'src/ftp.cc'
--- src/ftp.cc  2014-05-07 14:40:05 +0000
+++ src/ftp.cc  2014-05-09 17:45:08 +0000
@@ -43,41 +43,40 @@
 #include "fde.h"
 #include "FwdState.h"
 #include "html_quote.h"
 #include "HttpHdrContRange.h"
 #include "HttpHeader.h"
 #include "HttpHeaderRange.h"
 #include "HttpReply.h"
 #include "HttpRequest.h"
 #include "ip/tools.h"
 #include "Mem.h"
 #include "MemBuf.h"
 #include "mime.h"
 #include "rfc1738.h"
 #include "Server.h"
 #include "SquidConfig.h"
 #include "SquidString.h"
 #include "SquidTime.h"
 #include "StatCounters.h"
 #include "Store.h"
 #include "tools.h"
-#include "URL.h"
 #include "wordlist.h"
 
 #if USE_DELAY_POOLS
 #include "DelayPools.h"
 #include "MemObject.h"
 #endif
 
 #if HAVE_ERRNO_H
 #include <errno.h>
 #endif
 
 /**
  \defgroup ServerProtocolFTPInternal Server-Side FTP Internals
  \ingroup ServerProtocolFTPAPI
  */
 
 /// \ingroup ServerProtocolFTPInternal
 static const char *const crlf = "\r\n";
 
 #define CTRL_BUFLEN 1024
@@ -219,41 +218,40 @@
         size_t offset;
         wordlist *message;
         char *last_command;
         char *last_reply;
         int replycode;
     } ctrl;
 
     /// FTP data channel info; the channel may be opened/closed a few times
     struct DataChannel: public FtpChannel {
         MemBuf *readBuf;
         char *host;
         unsigned short port;
         bool read_pending;
     } data;
 
     struct _ftp_flags flags;
 
 public:
     // these should all be private
     virtual void start();
-    void loginParser(const char *, int escaped);
     int restartable();
     void appendSuccessHeader();
     void hackShortcut(FTPSM * nextState);
     void failed(err_type, int xerrno);
     void failedErrorMessage(err_type, int xerrno);
     void unhack();
     void scheduleReadControlReply(int);
     void handleControlReply();
     void readStor();
     void parseListing();
     MemBuf *htmlifyListEntry(const char *line);
     void completedListing(void);
     void dataComplete();
     void dataRead(const CommIoCbParams &io);
 
     /// ignore timeout on CTRL channel. set read timeout on DATA channel.
     void switchTimeoutToDataChannel();
     /// create a data channel acceptor and start listening.
     void listenForDataChannel(const Comm::ConnectionPointer &conn, const char 
*note);
 
@@ -284,62 +282,57 @@
 
     static HttpReply *ftpAuthRequired(HttpRequest * request, const char 
*realm);
     const char *ftpRealm(void);
     void loginFailed(void);
     static wordlist *ftpParseControlReply(char *, size_t, int *, size_t *);
 
     // sending of the request body to the server
     virtual void sentRequestBody(const CommIoCbParams&);
     virtual void doneSendingRequestBody();
 
     virtual void haveParsedReplyHeaders();
 
     virtual bool doneWithServer() const;
     virtual bool haveControlChannel(const char *caller_name) const;
     AsyncCall::Pointer dataCloser(); /// creates a Comm close callback
     AsyncCall::Pointer dataOpener(); /// creates a Comm connect callback
 
 private:
     // BodyConsumer for HTTP: consume request body.
     virtual void handleRequestBodyProducerAborted();
+    void loginParser(const SBuf &login, bool escaped);
 
     CBDATA_CLASS2(FtpStateData);
 };
 
 CBDATA_CLASS_INIT(FtpStateData);
 
 /// \ingroup ServerProtocolFTPInternal
 typedef struct {
     char type;
     int64_t size;
     char *date;
     char *name;
     char *showname;
     char *link;
 } ftpListParts;
 
-/// \ingroup ServerProtocolFTPInternal
-#define FTP_LOGIN_ESCAPED      1
-
-/// \ingroup ServerProtocolFTPInternal
-#define FTP_LOGIN_NOT_ESCAPED  0
-
 /*
  * State machine functions
  * send == state transition
  * read == wait for response, and select next state transition
  * other == Transition logic
  */
 static FTPSM ftpReadWelcome;
 static FTPSM ftpSendUser;
 static FTPSM ftpReadUser;
 static FTPSM ftpSendPass;
 static FTPSM ftpReadPass;
 static FTPSM ftpSendType;
 static FTPSM ftpReadType;
 static FTPSM ftpSendMdtm;
 static FTPSM ftpReadMdtm;
 static FTPSM ftpSendSize;
 static FTPSM ftpReadSize;
 static FTPSM ftpSendEPRT;
 static FTPSM ftpReadEPRT;
 static FTPSM ftpSendPORT;
@@ -539,92 +532,88 @@
     safe_free(old_reply);
 
     safe_free(old_filepath);
 
     title_url.clean();
 
     base_href.clean();
 
     safe_free(filepath);
 
     safe_free(dirpath);
 
     safe_free(data.host);
 
     fwd = NULL;        // refcounted
 }
 
 /**
  * Parse a possible login username:password pair.
  * Produces filled member variables user, password, password_url if anything 
found.
+ *
+ * \param login  a decoded Basic authentication credential token or URI 
user-info token.
+ * \param escaped  whether to URL-decode the token after extracting user and 
password
  */
 void
-FtpStateData::loginParser(const char *login, int escaped)
+FtpStateData::loginParser(const SBuf &login, bool escaped)
 {
-    const char *u = NULL; // end of the username sub-string
-    int len;              // length of the current sub-string to handle.
+    debugs(9, 4, "login=" << login << ", escaped=" << escaped);
+    debugs(9, 9, "IN : login=" << login << ", escaped=" << escaped << ", 
user=" << user << ", password=" << password);
 
-    int total_len = strlen(login);
+    if (login.isEmpty())
+        return;
 
-    debugs(9, 4, HERE << ": login='" << login << "', escaped=" << escaped);
-    debugs(9, 9, HERE << ": IN : login='" << login << "', escaped=" << escaped 
<< ", user=" << user << ", password=" << password);
-
-    if ((u = strchr(login, ':'))) {
-
-        /* if there was a username part */
-        if (u > login) {
-            len = u - login;
-            ++u; // jump off the delimiter.
-            if (len > MAX_URL)
-                len = MAX_URL-1;
-            xstrncpy(user, login, len +1);
-            debugs(9, 9, HERE << ": found user='" << user << "'(" << len <<"), 
escaped=" << escaped);
-            if (escaped)
-                rfc1738_unescape(user);
-            debugs(9, 9, HERE << ": found user='" << user << "'(" << len <<") 
unescaped.");
-        }
+    const SBuf::size_type colonPos = login.find(':');
 
-        /* if there was a password part */
-        len = login + total_len - u;
-        if ( len > 0) {
-            if (len > MAX_URL)
-                len = MAX_URL -1;
-            xstrncpy(password, u, len +1);
-            debugs(9, 9, HERE << ": found password='" << password << "'(" << 
len <<"), escaped=" << escaped);
-            if (escaped) {
-                rfc1738_unescape(password);
-                password_url = 1;
-            }
-            debugs(9, 9, HERE << ": found password='" << password << "'(" << 
len <<") unescaped.");
-        }
-    } else if (login[0]) {
-        /* no password, just username */
-        if (total_len > MAX_URL)
-            total_len = MAX_URL -1;
-        xstrncpy(user, login, total_len +1);
-        debugs(9, 9, HERE << ": found user='" << user << "'(" << total_len 
<<"), escaped=" << escaped);
+    /* If there was a username part.
+     * Ignore 0-length usernames, retain what we have already
+     */
+    if (colonPos != 0) {
+        const SBuf userName = login.substr(0, colonPos);
+        SBuf::size_type upto = userName.copy(user, sizeof(user)-1);
+        user[upto]='\0';
+        debugs(9, 9, "found user=" << userName << ' ' <<
+               (upto != userName.length() ? ", truncated-to=" : ", length=") 
<< upto <<
+               ", escaped=" << escaped);
         if (escaped)
             rfc1738_unescape(user);
-        debugs(9, 9, HERE << ": found user='" << user << "'(" << total_len 
<<") unescaped.");
+        debugs(9, 9, "found user=" << user << " (" << strlen(user) << ") 
unescaped.");
+    }
+
+    /* If there was a password part.
+     * For 0-length password clobber what we have already, this means 
explicitly none
+     */
+    if (colonPos != SBuf::npos) {
+        const SBuf pass = login.substr(colonPos+1, SBuf::npos);
+        SBuf::size_type upto = pass.copy(password, sizeof(password)-1);
+        password[upto]='\0';
+        debugs(9, 9, "found password=" << pass << " " <<
+               (upto != pass.length() ? ", truncated-to=" : ", length=") << 
upto <<
+               ", escaped=" << escaped);
+        if (escaped) {
+            rfc1738_unescape(password);
+            password_url = 1;
+        }
+        debugs(9, 9, "found password=" << password << " (" << strlen(password) 
<< ") unescaped.");
     }
 
-    debugs(9, 9, HERE << ": OUT: login='" << login << "', escaped=" << escaped 
<< ", user=" << user << ", password=" << password);
+    debugs(9, 9, "OUT: login=" << login << ", escaped=" << escaped << ", 
user=" << user << ", password=" << password);
 }
 
 /**
  * Cancel the timeout on the Control socket and establish one
  * on the data socket
  */
 void
 FtpStateData::switchTimeoutToDataChannel()
 {
     commUnsetConnTimeout(ctrl.conn);
 
     typedef CommCbMemFunT<FtpStateData, CommTimeoutCbParams> TimeoutDialer;
     AsyncCall::Pointer timeoutCall = JobCallback(9, 5, TimeoutDialer, this, 
FtpStateData::ftpTimeout);
     commSetConnTimeout(data.conn, Config.Timeout.read, timeoutCall);
 }
 
 void
 FtpStateData::listenForDataChannel(const Comm::ConnectionPointer &conn, const 
char *note)
 {
     assert(!Comm::IsConnOpen(data.conn));
@@ -1359,50 +1348,50 @@
  *  - Checks URL (ftp://user:pass@domain)
  *  - Authorization: Basic header
  *  - squid.conf anonymous-FTP settings (default: anonymous:Squid@).
  *
  * Special Case: A username-only may be provided in the URL and password in 
the HTTP headers.
  *
  * TODO: we might be able to do something about locating username from other 
sources:
  *       ie, external ACL user=* tag or ident lookup
  *
  \retval 1     if we have everything needed to complete this request.
  \retval 0     if something is missing.
  */
 int
 FtpStateData::checkAuth(const HttpHeader * req_hdr)
 {
     /* default username */
     xstrncpy(user, "anonymous", MAX_URL);
 
 #if HAVE_AUTH_MODULE_BASIC
     /* Check HTTP Authorization: headers (better than defaults, but less than 
URL) */
-    const char *auth;
-    if ( (auth = req_hdr->getAuth(HDR_AUTHORIZATION, "Basic")) ) {
+    const SBuf auth(req_hdr->getAuth(HDR_AUTHORIZATION, "Basic"));
+    if (!auth.isEmpty()) {
         flags.authenticated = 1;
-        loginParser(auth, FTP_LOGIN_NOT_ESCAPED);
+        loginParser(auth, false);
     }
     /* we fail with authorization-required error later IFF the FTP server 
requests it */
 #endif
 
     /* Test URL login syntax. Overrides any headers received. */
-    loginParser(request->login, FTP_LOGIN_ESCAPED);
+    loginParser(request->url.userInfo(), true);
 
     /* name is missing. thats fatal. */
     if (!user[0])
         fatal("FTP login parsing destroyed username info");
 
     /* name + password == success */
     if (password[0])
         return 1;
 
     /* Setup default FTP password settings */
     /* this has to be done last so that we can have a no-password case above. 
*/
     if (!password[0]) {
         if (strcmp(user, "anonymous") == 0 && !flags.tried_auth_anonymous) {
             xstrncpy(password, Config.Ftp.anon_user, MAX_URL);
             flags.tried_auth_anonymous=1;
             return 1;
         } else if (!flags.tried_auth_nopass) {
             xstrncpy(password, null_string, MAX_URL);
             flags.tried_auth_nopass=1;
             return 1;

=== modified file 'src/http.cc'
--- src/http.cc 2014-04-27 07:59:17 +0000
+++ src/http.cc 2014-05-09 17:45:08 +0000
@@ -1802,43 +1802,44 @@
             hdr_out->putStr(HDR_X_FORWARDED_FOR, strFwd.termedBuf());
     }
     /** If set to DELETE - do not copy through. */
 
     /* append Host if not there already */
     if (!hdr_out->has(HDR_HOST)) {
         if (request->peer_domain) {
             hdr_out->putStr(HDR_HOST, request->peer_domain);
         } else if (request->port == urlDefaultPort(request->url.getScheme())) {
             /* use port# only if not default */
             hdr_out->putStr(HDR_HOST, request->GetHost());
         } else {
             httpHeaderPutStrf(hdr_out, HDR_HOST, "%s:%d",
                               request->GetHost(),
                               (int) request->port);
         }
     }
 
     /* append Authorization if known in URL, not in header and going direct */
     if (!hdr_out->has(HDR_AUTHORIZATION)) {
-        if (!request->flags.proxying && request->login[0] != '\0') {
-            httpHeaderPutStrf(hdr_out, HDR_AUTHORIZATION, "Basic %s",
-                              old_base64_encode(request->login));
+        if (!request->flags.proxying && !request->url.userInfo().isEmpty()) {
+            static char result[MAX_URL*2]; // should be big enough for a 
single URI segment
+            if (base64_encode_str(result, sizeof(result)-1, 
request->url.userInfo().rawContent(), request->url.userInfo().length()) < 
static_cast<int>(sizeof(result)-1))
+                httpHeaderPutStrf(hdr_out, HDR_AUTHORIZATION, "Basic %s", 
result);
         }
     }
 
     /* Fixup (Proxy-)Authorization special cases. Plain relaying dealt with 
above */
     httpFixupAuthentication(request, hdr_in, hdr_out, flags);
 
     /* append Cache-Control, add max-age if not there already */
     {
         HttpHdrCc *cc = hdr_in->getCc();
 
         if (!cc)
             cc = new HttpHdrCc();
 
 #if 0 /* see bug 2330 */
         /* Set no-cache if determined needed but not found */
         if (request->flags.nocache)
             EBIT_SET(cc->mask, CC_NO_CACHE);
 #endif
 
         /* Add max-age only without no-cache */

=== modified file 'src/url.cc'
--- src/url.cc  2014-05-07 14:40:05 +0000
+++ src/url.cc  2014-05-09 17:56:47 +0000
@@ -26,41 +26,41 @@
  *  GNU General Public License for more details.
  *
  *  You should have received a copy of the GNU General Public License
  *  along with this program; if not, write to the Free Software
  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
  *
  */
 
 #include "squid.h"
 #include "globals.h"
 #include "HttpRequest.h"
 #include "rfc1738.h"
 #include "SquidConfig.h"
 #include "SquidString.h"
 #include "URL.h"
 
 static HttpRequest *urlParseFinish(const HttpRequestMethod& method,
                                    const AnyP::ProtocolType protocol,
                                    const char *const urlpath,
                                    const char *const host,
-                                   const char *const login,
+                                   const SBuf &login,
                                    const int port,
                                    HttpRequest *request);
 static HttpRequest *urnParse(const HttpRequestMethod& method, char *urn, 
HttpRequest *request);
 static const char valid_hostname_chars_u[] =
     "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
     "abcdefghijklmnopqrstuvwxyz"
     "0123456789-._"
     "[:]"
     ;
 static const char valid_hostname_chars[] =
     "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
     "abcdefghijklmnopqrstuvwxyz"
     "0123456789-."
     "[:]"
     ;
 
 void
 urlInitialize(void)
 {
     debugs(23, 5, "urlInitialize: Initializing...");
@@ -224,41 +224,41 @@
     char *dst;
     proto[0] = host[0] = urlpath[0] = login[0] = '\0';
 
     if ((l = strlen(url)) + Config.appendDomainLen > (MAX_URL - 1)) {
         /* terminate so it doesn't overflow other buffers */
         *(url + (MAX_URL >> 1)) = '\0';
         debugs(23, DBG_IMPORTANT, "urlParse: URL too large (" << l << " 
bytes)");
         return NULL;
     }
     if (method == Http::METHOD_CONNECT) {
         port = CONNECT_PORT;
 
         if (sscanf(url, "[%[^]]]:%d", host, &port) < 1)
             if (sscanf(url, "%[^:]:%d", host, &port) < 1)
                 return NULL;
 
     } else if ((method == Http::METHOD_OPTIONS || method == 
Http::METHOD_TRACE) &&
                strcmp(url, "*") == 0) {
         protocol = AnyP::PROTO_HTTP;
         port = urlDefaultPort(protocol);
-        return urlParseFinish(method, protocol, url, host, login, port, 
request);
+        return urlParseFinish(method, protocol, url, host, SBuf(), port, 
request);
     } else if (!strncmp(url, "urn:", 4)) {
         return urnParse(method, url, request);
     } else {
         /* Parse the URL: */
         src = url;
         i = 0;
         /* Find first : - everything before is protocol */
         for (i = 0, dst = proto; i < l && *src != ':'; ++i, ++src, ++dst) {
             *dst = *src;
         }
         if (i >= l)
             return NULL;
         *dst = '\0';
 
         /* Then its :// */
         if ((i+3) > l || *src != ':' || *(src + 1) != '/' || *(src + 2) != '/')
             return NULL;
         i += 3;
         src += 3;
 
@@ -429,66 +429,66 @@
             break;
 
         case URI_WHITESPACE_CHOP:
             *(urlpath + strcspn(urlpath, w_space)) = '\0';
             break;
 
         case URI_WHITESPACE_STRIP:
         default:
             t = q = urlpath;
             while (*t) {
                 if (!xisspace(*t)) {
                     *q = *t;
                     ++q;
                 }
                 ++t;
             }
             *q = '\0';
         }
     }
 
-    return urlParseFinish(method, protocol, urlpath, host, login, port, 
request);
+    return urlParseFinish(method, protocol, urlpath, host, SBuf(login), port, 
request);
 }
 
 /**
  * Update request with parsed URI data.  If the request arg is
  * non-NULL, put parsed values there instead of allocating a new
  * HttpRequest.
  */
 static HttpRequest *
 urlParseFinish(const HttpRequestMethod& method,
                const AnyP::ProtocolType protocol,
                const char *const urlpath,
                const char *const host,
-               const char *const login,
+               const SBuf &login,
                const int port,
                HttpRequest *request)
 {
     if (NULL == request)
         request = new HttpRequest(method, protocol, urlpath);
     else {
         request->initHTTP(method, protocol, urlpath);
         safe_free(request->canonical);
     }
 
     request->SetHost(host);
-    xstrncpy(request->login, login, MAX_LOGIN_SZ);
+    request->url.userInfo(login);
     request->port = (unsigned short) port;
     return request;
 }
 
 static HttpRequest *
 urnParse(const HttpRequestMethod& method, char *urn, HttpRequest *request)
 {
     debugs(50, 5, "urnParse: " << urn);
     if (request) {
         request->initHTTP(method, AnyP::PROTO_URN, urn + 4);
         safe_free(request->canonical);
         return request;
     }
 
     return new HttpRequest(method, AnyP::PROTO_URN, urn + 4);
 }
 
 const char *
 urlCanonical(HttpRequest * request)
 {
@@ -497,92 +497,90 @@
 
     if (request->canonical)
         return request->canonical;
 
     if (request->url.getScheme() == AnyP::PROTO_URN) {
         snprintf(urlbuf, MAX_URL, "urn:" SQUIDSTRINGPH,
                  SQUIDSTRINGPRINT(request->urlpath));
     } else {
         switch (request->method.id()) {
 
         case Http::METHOD_CONNECT:
             snprintf(urlbuf, MAX_URL, "%s:%d", request->GetHost(), 
request->port);
             break;
 
         default: {
             portbuf[0] = '\0';
 
             if (request->port != urlDefaultPort(request->url.getScheme()))
                 snprintf(portbuf, 32, ":%d", request->port);
 
-            snprintf(urlbuf, MAX_URL, "%s://%s%s%s%s" SQUIDSTRINGPH,
+            snprintf(urlbuf, MAX_URL, "%s://" SQUIDSBUFPH "%s%s%s" 
SQUIDSTRINGPH,
                      request->url.getScheme().c_str(),
-                     request->login,
-                     *request->login ? "@" : null_string,
+                     SQUIDSBUFPRINT(request->url.userInfo()),
+                     !request->url.userInfo().isEmpty() ? "@" : "",
                      request->GetHost(),
                      portbuf,
                      SQUIDSTRINGPRINT(request->urlpath));
         }
         }
     }
 
     return (request->canonical = xstrdup(urlbuf));
 }
 
 /** \todo AYJ: Performance: This is an *almost* duplicate of urlCanonical. But 
elides the query-string.
  *        After copying it on in the first place! Would be less code to merge 
the two with a flag parameter.
  *        and never copy the query-string part in the first place
  */
 char *
 urlCanonicalClean(const HttpRequest * request)
 {
     LOCAL_ARRAY(char, buf, MAX_URL);
     LOCAL_ARRAY(char, portbuf, 32);
     LOCAL_ARRAY(char, loginbuf, MAX_LOGIN_SZ + 1);
     char *t;
 
     if (request->url.getScheme() == AnyP::PROTO_URN) {
         snprintf(buf, MAX_URL, "urn:" SQUIDSTRINGPH,
                  SQUIDSTRINGPRINT(request->urlpath));
     } else {
         switch (request->method.id()) {
 
         case Http::METHOD_CONNECT:
             snprintf(buf, MAX_URL, "%s:%d", request->GetHost(), request->port);
             break;
 
         default: {
             portbuf[0] = '\0';
 
             if (request->port != urlDefaultPort(request->url.getScheme()))
                 snprintf(portbuf, 32, ":%d", request->port);
 
-            loginbuf[0] = '\0';
-
-            if ((int) strlen(request->login) > 0) {
-                strcpy(loginbuf, request->login);
-
-                if ((t = strchr(loginbuf, ':')))
-                    *t = '\0';
-
-                strcat(loginbuf, "@");
-            }
+            if (!request->url.userInfo().isEmpty()) {
+                SBuf::size_type len = request->url.userInfo().find(':');
+                // URI userinfo/login is deprecated by IETF so we dont care if 
it crops accidentally
+                len = request->url.userInfo().copy(loginbuf, min(len, 
sizeof(loginbuf)-2));
+                loginbuf[len] = '@';
+                loginbuf[len+1] = '\0';
+            } else
+                loginbuf[0] = '\0';
 
             snprintf(buf, MAX_URL, "%s://%s%s%s" SQUIDSTRINGPH,
                      request->url.getScheme().c_str(),
                      loginbuf,
                      request->GetHost(),
                      portbuf,
                      SQUIDSTRINGPRINT(request->urlpath));
 
             // strip arguments AFTER a question-mark
             if (Config.onoff.strip_query_terms)
                 if ((t = strchr(buf, '?')))
                     *(++t) = '\0';
         }
         }
     }
 
     if (stringHasCntl(buf))
         xstrncpy(buf, rfc1738_escape_unescaped(buf), MAX_URL);
 
     return buf;
@@ -650,52 +648,52 @@
  */
 char *
 urlMakeAbsolute(const HttpRequest * req, const char *relUrl)
 {
 
     if (req->method.id() == Http::METHOD_CONNECT) {
         return (NULL);
     }
 
     char *urlbuf = (char *)xmalloc(MAX_URL * sizeof(char));
 
     if (req->url.getScheme() == AnyP::PROTO_URN) {
         snprintf(urlbuf, MAX_URL, "urn:" SQUIDSTRINGPH,
                  SQUIDSTRINGPRINT(req->urlpath));
         return (urlbuf);
     }
 
     size_t urllen;
 
     if (req->port != urlDefaultPort(req->url.getScheme())) {
-        urllen = snprintf(urlbuf, MAX_URL, "%s://%s%s%s:%d",
+        urllen = snprintf(urlbuf, MAX_URL, "%s://" SQUIDSBUFPH "%s%s:%d",
                           req->url.getScheme().c_str(),
-                          req->login,
-                          *req->login ? "@" : null_string,
+                          SQUIDSBUFPRINT(req->url.userInfo()),
+                          !req->url.userInfo().isEmpty() ? "@" : "",
                           req->GetHost(),
                           req->port
                          );
     } else {
-        urllen = snprintf(urlbuf, MAX_URL, "%s://%s%s%s",
+        urllen = snprintf(urlbuf, MAX_URL, "%s://" SQUIDSBUFPH "%s%s",
                           req->url.getScheme().c_str(),
-                          req->login,
-                          *req->login ? "@" : null_string,
+                          SQUIDSBUFPRINT(req->url.userInfo()),
+                          !req->url.userInfo().isEmpty() ? "@" : "",
                           req->GetHost()
                          );
     }
 
     if (relUrl[0] == '/') {
         strncpy(&urlbuf[urllen], relUrl, MAX_URL - urllen - 1);
     } else {
         const char *path = req->urlpath.termedBuf();
         const char *last_slash = strrchr(path, '/');
 
         if (last_slash == NULL) {
             urlbuf[urllen] = '/';
             ++urllen;
             strncpy(&urlbuf[urllen], relUrl, MAX_URL - urllen - 1);
         } else {
             ++last_slash;
             size_t pathlen = last_slash - path;
             if (pathlen > MAX_URL - urllen - 1) {
                 pathlen = MAX_URL - urllen - 1;
             }

Reply via email to