This continues the bug 1961 redesign of URL handling.

Move the HttpRequest::login detail into class URL as an SBuf. Renaming
to userInfo inline with the RFC defined name for this URI piece.

Converted the details to SBuf gaining several minor str*() removals in
the process and a simpler FTP login parser based on SBuf capabilities.

Also, updated the base64 encoder API used for converting between URI
userInfo and Basic authentication header token. Gaining better control
over the length of maximum token size and a small speedup from pre-known
input length.

Amos
=== modified file 'src/HttpRequest.cc'
--- src/HttpRequest.cc  2014-04-27 07:59:17 +0000
+++ src/HttpRequest.cc  2014-04-27 13:20:32 +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-04-27 08:49:32 +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-04-27 13:21:53 +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-04-27 14:36:25 +0000
@@ -21,36 +21,36 @@
  *
  *  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);
+    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()));
+    int result = data->match(tmp.c_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-04-27 14:36:51 +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-04-27 10:11:28 +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-04-27 07:59:17 +0000
+++ src/ftp.cc  2014-04-27 12:50:27 +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,84 @@
     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 */
+    if (colonPos != 0) {
+        SBuf userName = login.substr(0, colonPos);
+        if (userName.length() >= sizeof(user))
+            userName.chop(0, sizeof(user)-1);
+        memcpy(user, userName.rawContent(), userName.length());
+        user[userName.length()] = '\0';
+        debugs(9, 9, "found user=" << userName << " (" << userName.length() << 
"), 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 */
+    if (colonPos != SBuf::npos) {
+        SBuf pass = login.substr(colonPos+1, SBuf::npos);
+        if (pass.length() >= sizeof(pass))
+            pass.chop(0, sizeof(pass)-1);
+        memcpy(password, pass.rawContent(), pass.length());
+        password[pass.length()] = '\0';
+        debugs(9, 9, "found password=" << pass << " (" << pass.length() << "), 
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 +1344,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-04-27 13:17:58 +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()) > 0)
+                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-04-27 07:59:17 +0000
+++ src/url.cc  2014-04-27 14:14:27 +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)
 {
@@ -498,93 +498,93 @@
     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(':');
+                    if (len == SBuf::npos)
+                        len = request->url.userInfo().length();
+
+                    memcpy(loginbuf, request->url.userInfo().rawContent(), 
len);
+                    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;
@@ -652,52 +652,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