[squid-dev] [PATCH] Refactor path strings into class URL

Amos Jeffries squid3 at treenet.co.nz
Sun Jun 28 11:21:54 UTC 2015


This takes another step towards bug 1961 closure by shuffling the
HttpRequest::urlpath member into class URL.

These changes appear to work nicely. But I have not been able to test
all code paths that have logic change so a second pair of eyes on it
would be appreciated.

Amos
-------------- next part --------------
=== modified file 'src/HttpRequest.cc'
--- src/HttpRequest.cc	2015-06-09 06:14:43 +0000
+++ src/HttpRequest.cc	2015-06-28 06:32:00 +0000
@@ -44,49 +44,48 @@
 HttpRequest::HttpRequest(const HttpRequestMethod& aMethod, AnyP::ProtocolType aProtocol, const char *aUrlpath) :
     HttpMsg(hoRequest)
 {
     static unsigned int id = 1;
     debugs(93,7, HERE << "constructed, this=" << this << " id=" << ++id);
     init();
     initHTTP(aMethod, aProtocol, aUrlpath);
 }
 
 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;
+    url.path(aUrlpath);
 }
 
 void
 HttpRequest::init()
 {
     method = Http::METHOD_NONE;
     url.clear();
-    urlpath = NULL;
 #if USE_AUTH
     auth_user_request = NULL;
 #endif
     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
     peer_domain = NULL;     // not allocated/deallocated by this class
     peer_host = NULL;
     vary_headers = NULL;
@@ -109,41 +108,40 @@
     icapHistory_ = NULL;
 #endif
     rangeOffsetLimit = -2; //a value of -2 means not checked yet
     forcedBodyContinuation = false;
 }
 
 void
 HttpRequest::clean()
 {
     // we used to assert that the pipe is NULL, but now the request only
     // points to a pipe that is owned and initiated by another object.
     body_pipe = NULL;
 #if USE_AUTH
     auth_user_request = NULL;
 #endif
     safe_free(canonical);
 
     safe_free(vary_headers);
 
     url.clear();
-    urlpath.clean();
 
     header.clean();
 
     if (cache_control) {
         delete cache_control;
         cache_control = NULL;
     }
 
     if (range) {
         delete range;
         range = NULL;
     }
 
     myportname.clean();
 
     notes = NULL;
 
     tag.clean();
 #if USE_AUTH
     extacl_user.clean();
@@ -156,52 +154,55 @@
     etag.clean();
 
 #if USE_ADAPTATION
     adaptHistory_ = NULL;
 #endif
 #if ICAP_CLIENT
     icapHistory_ = NULL;
 #endif
 }
 
 void
 HttpRequest::reset()
 {
     clean();
     init();
 }
 
 HttpRequest *
 HttpRequest::clone() const
 {
-    HttpRequest *copy = new HttpRequest(method, url.getScheme(), urlpath.termedBuf());
+    HttpRequest *copy = new HttpRequest();
+    copy->method = method;
     // 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;
 
+    copy->url.setScheme(url.getScheme());
     copy->url.userInfo(url.userInfo());
     copy->url.host(url.host());
     copy->url.port(url.port());
+    copy->url.path(url.path());
 
     // 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?
 
     copy->tag = tag;
     copy->extacl_log = extacl_log;
@@ -355,65 +356,65 @@
         hdrCacheInit();
 
     return result;
 }
 
 /* swaps out request using httpRequestPack */
 void
 HttpRequest::swapOut(StoreEntry * e)
 {
     assert(e);
     e->buffer();
     pack(e);
 }
 
 /* packs request-line and headers, appends <crlf> terminator */
 void
 HttpRequest::pack(Packable * p)
 {
     assert(p);
     /* pack request-line */
-    p->appendf(SQUIDSBUFPH " " SQUIDSTRINGPH " HTTP/%d.%d\r\n",
-               SQUIDSBUFPRINT(method.image()), SQUIDSTRINGPRINT(urlpath),
+    p->appendf(SQUIDSBUFPH " " SQUIDSBUFPH " HTTP/%d.%d\r\n",
+               SQUIDSBUFPRINT(method.image()), SQUIDSBUFPRINT(url.path()),
                http_ver.major, http_ver.minor);
     /* headers */
     header.packInto(p);
     /* trailer */
     p->append("\r\n", 2);
 }
 
 /*
  * A wrapper for debugObj()
  */
 void
 httpRequestPack(void *obj, Packable *p)
 {
     HttpRequest *request = static_cast<HttpRequest*>(obj);
     request->pack(p);
 }
 
 /* returns the length of request line + headers + crlf */
 int
-HttpRequest::prefixLen()
+HttpRequest::prefixLen() const
 {
     return method.image().length() + 1 +
-           urlpath.size() + 1 +
+           url.path().length() + 1 +
            4 + 1 + 3 + 2 +
            header.len + 2;
 }
 
 /* sync this routine when you update HttpRequest struct */
 void
 HttpRequest::hdrCacheInit()
 {
     HttpMsg::hdrCacheInit();
 
     assert(!range);
     range = header.getRange();
 }
 
 #if ICAP_CLIENT
 Adaptation::Icap::History::Pointer
 HttpRequest::icapHistory() const
 {
     if (!icapHistory_) {
         if (Log::TheConfig.hasIcapToken || IcapLogfileStatus == LOG_ENABLE) {
@@ -474,57 +475,53 @@
 {
     if (errType || errDetail)
         debugs(11, 5, HERE << "old error details: " << errType << '/' << errDetail);
     debugs(11, 5, HERE << "current error details: " << aType << '/' << aDetail);
     // checking type and detail separately may cause inconsistency, but
     // may result in more details available if they only become available later
     if (!errType)
         errType = aType;
     if (!errDetail)
         errDetail = aDetail;
 }
 
 void
 HttpRequest::clearError()
 {
     debugs(11, 7, HERE << "old error details: " << errType << '/' << errDetail);
     errType = ERR_NONE;
     errDetail = ERR_DETAIL_NONE;
 }
 
-const char *HttpRequest::packableURI(bool full_uri) const
+void
+HttpRequest::packFirstLineInto(Packable * p, bool full_uri) const
 {
+    SBuf tmp;
     if (full_uri)
-        return urlCanonical((HttpRequest*)this);
-
-    if (urlpath.size())
-        return urlpath.termedBuf();
+        tmp = urlCanonical((HttpRequest*)this);
+    else
+        tmp = url.path();
 
-    return "/";
-}
-
-void HttpRequest::packFirstLineInto(Packable * p, bool full_uri) const
-{
     // form HTTP request-line
-    p->appendf(SQUIDSBUFPH " %s HTTP/%d.%d\r\n",
+    p->appendf(SQUIDSBUFPH " " SQUIDSBUFPH " HTTP/%d.%d\r\n",
                SQUIDSBUFPRINT(method.image()),
-               packableURI(full_uri),
+               SQUIDSBUFPRINT(tmp),
                http_ver.major, http_ver.minor);
 }
 
 /*
  * Indicate whether or not we would expect an entity-body
  * along with this request
  */
 bool
 HttpRequest::expectingBody(const HttpRequestMethod &, int64_t &theSize) const
 {
     bool expectBody = false;
 
     /*
      * Note: Checks for message validity is in clientIsContentLengthValid().
      * this just checks if a entity-body is expected based on HTTP message syntax
      */
     if (header.chunked()) {
         expectBody = true;
         theSize = -1;
     } else if (content_length >= 0) {

=== modified file 'src/HttpRequest.h'
--- src/HttpRequest.h	2015-06-09 06:14:43 +0000
+++ src/HttpRequest.h	2015-06-15 03:50:46 +0000
@@ -99,42 +99,40 @@
 
     void init();
 
 public:
     HttpRequestMethod method;
     URL url; ///< the request URI
 
 private:
 #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:
 #if USE_AUTH
     Auth::UserRequest::Pointer auth_user_request;
 #endif
 
-    String urlpath;
-
     char *canonical;
 
     /**
      * If defined, store_id_program mapped the request URL to this ID.
      * Store uses this ID (and not the URL) to find and store entries,
      * avoiding caching duplicate entries when different URLs point to
      * "essentially the same" cachable resource.
      */
     String store_id;
 
     RequestFlags flags;
 
     HttpHdrRange *range;
 
     time_t ims;
 
     int imslen;
 
     Ip::Address client_addr;
 
@@ -179,70 +177,68 @@
     String x_forwarded_for_iterator; /* XXX a list of IP addresses */
 #endif /* FOLLOW_X_FORWARDED_FOR */
 
     /// A strong etag of the cached entry. Used for refreshing that entry.
     String etag;
 
     /// whether we have responded with HTTP 100 or FTP 150 already
     bool forcedBodyContinuation;
 
 public:
     bool multipartRangeRequest() const;
 
     bool parseFirstLine(const char *start, const char *end);
 
     bool parseHeader(Http1::RequestParser &hp); // TODO move this function to the parser
 
     virtual bool expectingBody(const HttpRequestMethod& unused, int64_t&) const;
 
     bool bodyNibbled() const; // the request has a [partially] consumed body
 
-    int prefixLen();
+    int prefixLen() const;
 
     void swapOut(StoreEntry * e);
 
     void pack(Packable * p);
 
     static void httpRequestPack(void *obj, Packable *p);
 
     static HttpRequest * CreateFromUrlAndMethod(char * url, const HttpRequestMethod& method);
 
     static HttpRequest * CreateFromUrl(char * url);
 
     ConnStateData *pinnedConnection();
 
     /**
      * Returns the current StoreID for the request as a nul-terminated char*.
      * Always returns the current id for the request
      * (either the request canonical url or modified ID by the helper).
      * Does not return NULL.
      */
     const char *storeId();
 
     /**
      * The client connection manager, if known;
      * Used for any response actions needed directly to the client.
      * ie 1xx forwarding or connection pinning state changes
      */
     CbcPointer<ConnStateData> clientConnectionManager;
 
     /// forgets about the cached Range header (for a reason)
     void ignoreRange(const char *reason);
     int64_t getRangeOffsetLimit(); /* the result of this function gets cached in rangeOffsetLimit */
 
 private:
-    const char *packableURI(bool full_uri) const;
-
     mutable int64_t rangeOffsetLimit;  /* caches the result of getRangeOffsetLimit */
 
 protected:
     virtual void packFirstLineInto(Packable * p, bool full_uri) const;
 
     virtual bool sanityCheckStartLine(const char *buf, const size_t hdr_len, Http::StatusCode *error);
 
     virtual void hdrCacheInit();
 
     virtual bool inheritProperties(const HttpMsg *aMsg);
 };
 
 #endif /* SQUID_HTTPREQUEST_H */
 

=== modified file 'src/URL.h'
--- src/URL.h	2015-06-09 06:14:43 +0000
+++ src/URL.h	2015-06-28 06:35:45 +0000
@@ -1,75 +1,84 @@
 /*
  * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
 #ifndef SQUID_SRC_URL_H
 #define SQUID_SRC_URL_H
 
 #include "anyp/UriScheme.h"
 #include "ip/Address.h"
 #include "rfc2181.h"
 #include "SBuf.h"
 
+#include <iosfwd>
+
 /**
  * The URL class represents a Uniform Resource Location
  *
  * Governed by RFC 3986
  */
 class URL
 {
     MEMPROXY_CLASS(URL);
 
 public:
     URL() : scheme_(), hostIsNumeric_(false), port_(0) {*host_=0;}
     URL(AnyP::UriScheme const &aScheme) : scheme_(aScheme), hostIsNumeric_(false), port_(0) {*host_=0;}
 
     void clear() {
         scheme_=AnyP::PROTO_NONE;
         hostIsNumeric_ = false;
         *host_ = 0;
         hostAddr_.setEmpty();
         port_ = 0;
         touch();
     }
     void touch(); ///< clear the cached URI display forms
 
     AnyP::UriScheme const & getScheme() const {return scheme_;}
 
     /// convert the URL scheme to that given
     void setScheme(const AnyP::ProtocolType &p) {scheme_=p; touch();}
 
     void userInfo(const SBuf &s) {userInfo_=s; touch();}
     const SBuf &userInfo() const {return userInfo_;}
 
     void host(const char *src);
     const char *host(void) const {return host_;}
     int hostIsNumeric(void) const {return hostIsNumeric_;}
     Ip::Address const & hostIP(void) const {return hostAddr_;}
 
     void port(unsigned short p) {port_=p; touch();}
     unsigned short port() const {return port_;}
 
+    void path(const char *p) {path_=p; touch();}
+    void path(const SBuf &p) {path_=p; touch();}
+    const SBuf &path() const;
+
+    /// the static '/' default URL-path
+    static const SBuf &SlashPath();
+
     /// the static '*' pseudo-URL
     static const SBuf &Asterisk();
 
     /**
      * The authority-form URI for currently stored values.
      *
      * As defined by RFC 7230 section 5.3.3 this form omits the
      * userinfo@ field from RFC 3986 defined authority segment.
      *
      * \param requirePort when true the port will be included, otherwise
      *                    port will be elided when it is the default for
      *                    the current scheme.
      */
     SBuf &authority(bool requirePort = false) const;
 
 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
@@ -84,45 +93,58 @@
      * 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'
 
     // XXX: uses char[] instead of SBUf to reduce performance regressions
     //      from c_str() since most code using this is not yet using SBuf
     char host_[SQUIDHOSTNAMELEN];   ///< string representation of the URI authority name or IP
     bool hostIsNumeric_;            ///< whether the authority 'host' is a raw-IP
     Ip::Address hostAddr_;          ///< binary representation of the URI authority if it is a raw-IP
 
     unsigned short port_;   ///< URL port
 
+    // XXX: for now includes query-string.
+    SBuf path_;     ///< URL path segment
+
     // pre-assembled URL forms
     mutable SBuf authorityHttp_;     ///< RFC 7230 section 5.3.3 authority, maybe without default-port
     mutable SBuf authorityWithPort_; ///< RFC 7230 section 5.3.3 authority with explicit port
+    mutable SBuf canonical_;         ///< full absolute-URI
 };
 
+inline std::ostream &
+operator <<(std::ostream &os, const URL &url)
+{
+    if (const char *sc = url.getScheme().c_str())
+        os << sc << ":";
+    os << "//" << url.authority() << url.path();
+    return os;
+}
+
 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);
 
 /**
  * matchDomainName() compares a hostname (usually extracted from traffic)
  * with a domainname (usually from an ACL) according to the following rules:
  *
  *    HOST      |   DOMAIN    |   MATCH?
  * -------------|-------------|------

=== modified file 'src/acl/UrlPath.cc'
--- src/acl/UrlPath.cc	2015-01-13 07:25:36 +0000
+++ src/acl/UrlPath.cc	2015-06-09 11:52:48 +0000
@@ -1,38 +1,39 @@
 /*
  * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
 /* DEBUG: section 28    Access Control */
 
 #include "squid.h"
 #include "acl/Checklist.h"
 #include "acl/RegexData.h"
 #include "acl/UrlPath.h"
 #include "HttpRequest.h"
 #include "rfc1738.h"
 
 int
 ACLUrlPathStrategy::match (ACLData<char const *> * &data, ACLFilledChecklist *checklist, ACLFlags &)
 {
-    if (!checklist->request->urlpath.size())
+    if (checklist->request->url.path().isEmpty())
         return -1;
 
-    char *esc_buf = xstrdup(checklist->request->urlpath.termedBuf());
+    SBuf tmp = checklist->request->url.path();
+    char *esc_buf = xstrndup(tmp.rawContent(), tmp.length());
     rfc1738_unescape(esc_buf);
     int result = data->match(esc_buf);
-    safe_free(esc_buf);
+    xfree(esc_buf);
     return result;
 }
 
 ACLUrlPathStrategy *
 ACLUrlPathStrategy::Instance()
 {
     return &Instance_;
 }
 
 ACLUrlPathStrategy ACLUrlPathStrategy::Instance_;
 

=== modified file 'src/adaptation/Service.cc'
--- src/adaptation/Service.cc	2015-01-13 07:25:36 +0000
+++ src/adaptation/Service.cc	2015-06-14 19:02:01 +0000
@@ -32,41 +32,41 @@
     return probed() && !up();
 }
 
 bool
 Adaptation::Service::wants(const ServiceFilter &filter) const
 {
     if (cfg().method != filter.method)
         return false;
 
     if (cfg().point != filter.point)
         return false;
 
     // sending a message to a broken service is likely to cause errors
     if (cfg().bypass && broken())
         return false;
 
     if (up()) {
         // Sending a message to a service that does not want it is useless.
         // note that we cannot check wantsUrl for service that is not "up"
         // note that even essential services are skipped on unwanted URLs!
-        return wantsUrl(filter.request->urlpath);
+        return wantsUrl(filter.request->url.path());
     }
 
     // The service is down and is either not bypassable or not probed due
     // to the bypass && broken() test above. Thus, we want to use it!
     return true;
 }
 
 Adaptation::Services &
 Adaptation::AllServices()
 {
     static Services *TheServices = new Services;
     return *TheServices;
 }
 
 Adaptation::ServicePointer
 Adaptation::FindService(const Service::Id& key)
 {
     typedef Services::iterator SI;
     for (SI i = AllServices().begin(); i != AllServices().end(); ++i) {
         if ((*i)->cfg().key == key)

=== modified file 'src/adaptation/Service.h'
--- src/adaptation/Service.h	2015-01-13 07:25:36 +0000
+++ src/adaptation/Service.h	2015-06-14 19:14:27 +0000
@@ -28,41 +28,41 @@
 // specific adaptation mechanisms extend this class
 class Service: public RefCountable
 {
 public:
     typedef RefCount<Service> Pointer;
     typedef String Id;
 
 public:
     explicit Service(const ServiceConfigPointer &aConfig);
     virtual ~Service();
 
     virtual bool probed() const = 0; // see comments above
     virtual bool broken() const;
     virtual bool up() const = 0; // see comments above
 
     virtual Initiate *makeXactLauncher(HttpMsg *virginHeader, HttpRequest *virginCause, AccessLogEntry::Pointer &alp) = 0;
 
     bool wants(const ServiceFilter &filter) const;
 
     // the methods below can only be called on an up() service
-    virtual bool wantsUrl(const String &urlPath) const = 0;
+    virtual bool wantsUrl(const SBuf &urlPath) const = 0;
 
     // called by transactions to report service failure
     virtual void noteFailure() = 0;
 
     const ServiceConfig &cfg() const { return *theConfig; }
 
     virtual void finalize(); // called after creation
 
     /// called when removed from the config; the service will be
     /// auto-destroyed when the last refcounting user leaves
     virtual void detach() = 0;
     /// whether detached() was called
     virtual bool detached() const = 0;
 
 protected:
     ServiceConfig &writeableCfg() { return *theConfig; }
 
 private:
     ServiceConfigPointer theConfig;
 };

=== modified file 'src/adaptation/ecap/ServiceRep.cc'
--- src/adaptation/ecap/ServiceRep.cc	2015-01-13 07:25:36 +0000
+++ src/adaptation/ecap/ServiceRep.cc	2015-06-14 19:24:48 +0000
@@ -220,44 +220,46 @@
         return false; // we cannot handle the problem; the caller may escalate
 
     // make up() false, preventing new adaptation requests and enabling bypass
     theService.reset();
     debugs(93, level, "WARNING: " << kind << " eCAP service is " <<
            "down after initialization failure: " << cfg().uri);
 
     return true; // tell the caller to ignore the problem because we handled it
 }
 
 bool Adaptation::Ecap::ServiceRep::probed() const
 {
     return true; // we "probe" the adapter in finalize().
 }
 
 bool Adaptation::Ecap::ServiceRep::up() const
 {
     return theService != NULL;
 }
 
-bool Adaptation::Ecap::ServiceRep::wantsUrl(const String &urlPath) const
+bool Adaptation::Ecap::ServiceRep::wantsUrl(const SBuf &urlPath) const
 {
     Must(up());
-    return theService->wantsUrl(urlPath.termedBuf());
+    SBuf nonConstUrlPath = urlPath;
+    // c_str() reallocates and terminates for libecap API
+    return theService->wantsUrl(nonConstUrlPath.c_str());
 }
 
 Adaptation::Initiate *
 Adaptation::Ecap::ServiceRep::makeXactLauncher(HttpMsg *virgin,
         HttpRequest *cause, AccessLogEntry::Pointer &alp)
 {
     Must(up());
 
     // register now because (a) we need EventLoop::Running and (b) we do not
     // want to add more main loop overheads unless an async service is used.
     static AsyncEngine *TheEngine = NULL;
     if (AsyncServices.size() && !TheEngine && EventLoop::Running) {
         TheEngine = new Engine;
         EventLoop::Running->registerEngine(TheEngine);
         debugs(93, 3, "asyncs: " << AsyncServices.size() << ' ' << TheEngine);
     }
 
     XactionRep *rep = new XactionRep(virgin, cause, alp, Pointer(this));
     XactionRep::AdapterXaction x(theService->makeXaction(rep));
     rep->master(x);

=== modified file 'src/adaptation/ecap/ServiceRep.h'
--- src/adaptation/ecap/ServiceRep.h	2015-01-13 07:25:36 +0000
+++ src/adaptation/ecap/ServiceRep.h	2015-06-14 19:07:28 +0000
@@ -21,41 +21,41 @@
 namespace Ecap
 {
 
 /* The eCAP service representative maintains information about a single eCAP
    service that Squid communicates with. One eCAP module may register many
    eCAP services. */
 
 class ServiceRep : public Adaptation::Service
 {
 public:
     explicit ServiceRep(const ServiceConfigPointer &aConfig);
     virtual ~ServiceRep();
 
     typedef libecap::shared_ptr<libecap::adapter::Service> AdapterService;
 
     /* Adaptation::Service API */
     virtual void finalize();
     virtual bool probed() const;
     virtual bool up() const;
     virtual Adaptation::Initiate *makeXactLauncher(HttpMsg *virginHeader, HttpRequest *virginCause, AccessLogEntry::Pointer &alp);
-    virtual bool wantsUrl(const String &urlPath) const;
+    virtual bool wantsUrl(const SBuf &urlPath) const;
     virtual void noteFailure();
     virtual const char *status() const;
     virtual void detach();
     virtual bool detached() const;
 
 protected:
     void tryConfigureAndStart();
     bool handleFinalizeFailure(const char *error);
 
 private:
     AdapterService theService; // the actual adaptation service we represent
     bool           isDetached;
 };
 
 /// register loaded eCAP module service
 void RegisterAdapterService(const ServiceRep::AdapterService& adapterService);
 /// unregister loaded eCAP module service by service uri
 void UnregisterAdapterService(const String& serviceUri);
 
 /// returns loaded eCAP module service by service uri

=== modified file 'src/adaptation/icap/ModXact.cc'
--- src/adaptation/icap/ModXact.cc	2015-06-01 21:41:37 +0000
+++ src/adaptation/icap/ModXact.cc	2015-06-28 06:37:43 +0000
@@ -1372,43 +1372,41 @@
         Adaptation::History::Pointer ah = request->adaptHistory(false);
         if (ah != NULL) {
             String name, value;
             if (ah->getXxRecord(name, value)) {
                 buf.appendf(SQUIDSTRINGPH ": " SQUIDSTRINGPH "\r\n", SQUIDSTRINGPRINT(name), SQUIDSTRINGPRINT(value));
             }
         }
     }
 
     buf.append("Encapsulated: ", 14);
 
     MemBuf httpBuf;
 
     httpBuf.init();
 
     // build HTTP request header, if any
     ICAP::Method m = s.method;
 
     // to simplify, we could assume that request is always available
 
-    String urlPath;
     if (request) {
-        urlPath = request->urlpath;
         if (ICAP::methodRespmod == m)
             encapsulateHead(buf, "req-hdr", httpBuf, request);
         else if (ICAP::methodReqmod == m)
             encapsulateHead(buf, "req-hdr", httpBuf, virgin.header);
     }
 
     if (ICAP::methodRespmod == m)
         if (const HttpMsg *prime = virgin.header)
             encapsulateHead(buf, "res-hdr", httpBuf, prime);
 
     if (!virginBody.expected())
         buf.appendf("null-body=%d", (int) httpBuf.contentSize());
     else if (ICAP::methodReqmod == m)
         buf.appendf("req-body=%d", (int) httpBuf.contentSize());
     else
         buf.appendf("res-body=%d", (int) httpBuf.contentSize());
 
     buf.append(ICAP::crlf, 2); // terminate Encapsulated line
 
     if (preview.enabled()) {
@@ -1566,44 +1564,44 @@
 
     // pack polished HTTP header
     packHead(httpBuf, headClone.getRaw());
 
     // headClone unlocks and, hence, deletes the message we packed
 }
 
 void Adaptation::Icap::ModXact::packHead(MemBuf &httpBuf, const HttpMsg *head)
 {
     head->packInto(&httpBuf, true);
 }
 
 // decides whether to offer a preview and calculates its size
 void Adaptation::Icap::ModXact::decideOnPreview()
 {
     if (!TheConfig.preview_enable) {
         debugs(93, 5, HERE << "preview disabled by squid.conf");
         return;
     }
 
-    const String urlPath = virginRequest().urlpath;
+    const SBuf urlPath(virginRequest().url.path());
     size_t wantedSize;
     if (!service().wantsPreview(urlPath, wantedSize)) {
-        debugs(93, 5, HERE << "should not offer preview for " << urlPath);
+        debugs(93, 5, "should not offer preview for " << urlPath);
         return;
     }
 
     // we decided to do preview, now compute its size
 
     // cannot preview more than we can backup
     size_t ad = min(wantedSize, TheBackupLimit);
 
     if (!virginBody.expected())
         ad = 0;
     else if (virginBody.knownSize())
         ad = min(static_cast<uint64_t>(ad), virginBody.size()); // not more than we have
 
     debugs(93, 5, HERE << "should offer " << ad << "-byte preview " <<
            "(service wanted " << wantedSize << ")");
 
     preview.enable(ad);
     Must(preview.enabled());
 }
 

=== modified file 'src/adaptation/icap/Options.cc'
--- src/adaptation/icap/Options.cc	2015-01-13 07:25:36 +0000
+++ src/adaptation/icap/Options.cc	2015-06-14 19:06:59 +0000
@@ -26,52 +26,53 @@
 {
     theTransfers.preview.name = "Transfer-Preview";
     theTransfers.preview.kind = xferPreview;
     theTransfers.ignore.name = "Transfer-Ignore";
     theTransfers.ignore.kind = xferIgnore;
     theTransfers.complete.name = "Transfer-Complete";
     theTransfers.complete.kind = xferComplete;
 
     // Section 4.10.2 of RFC 3507 says that default is no Preview
     // TODO: provide a squid.conf option to overwrite the default
     theTransfers.byDefault = &theTransfers.complete;
 }
 
 Adaptation::Icap::Options::~Options()
 {
 }
 
 // future optimization note: this method is called by ICAP ACL code at least
 // twice for each HTTP message to see if the message should be ignored. For any
 // non-ignored HTTP message, ICAP calls to check whether a preview is needed.
-Adaptation::Icap::Options::TransferKind Adaptation::Icap::Options::transferKind(const String &urlPath) const
+Adaptation::Icap::Options::TransferKind
+Adaptation::Icap::Options::transferKind(const SBuf &urlPath) const
 {
     if (theTransfers.preview.matches(urlPath))
         return xferPreview;
 
     if (theTransfers.complete.matches(urlPath))
         return xferComplete;
 
     if (theTransfers.ignore.matches(urlPath))
         return xferIgnore;
 
-    debugs(93,7, HERE << "url " << urlPath << " matches no extensions; " <<
+    debugs(93,7, "url " << urlPath << " matches no extensions; " <<
            "using default: " << theTransfers.byDefault->name);
     return theTransfers.byDefault->kind;
 }
 
 bool Adaptation::Icap::Options::valid() const
 {
     return !error;
 }
 
 bool Adaptation::Icap::Options::fresh() const
 {
     return squid_curtime <= expire();
 }
 
 int Adaptation::Icap::Options::ttl() const
 {
     Must(valid());
     return theTTL >= 0 ? theTTL : TheConfig.default_options_ttl;
 }
 
@@ -167,60 +168,58 @@
     list.report(5, "Adaptation::Icap::Options::cfgTransferList: ");
 }
 
 /* Adaptation::Icap::Options::TransferList */
 
 Adaptation::Icap::Options::TransferList::TransferList(): extensions(NULL), name(NULL),
     kind(xferNone)
 {
 };
 
 Adaptation::Icap::Options::TransferList::~TransferList()
 {
     wordlistDestroy(&extensions);
 };
 
 void Adaptation::Icap::Options::TransferList::add(const char *extension)
 {
     wordlistAdd(&extensions, extension);
 };
 
-bool Adaptation::Icap::Options::TransferList::matches(const String &urlPath) const
+bool Adaptation::Icap::Options::TransferList::matches(const SBuf &urlPath) const
 {
-    const int urlLen = urlPath.size();
+    const SBuf::size_type urlLen = urlPath.length();
     for (wordlist *e = extensions; e; e = e->next) {
         // optimize: store extension lengths
-        const int eLen = strlen(e->key);
+        const size_t eLen = strlen(e->key);
 
         // assume URL contains at least '/' before the extension
         if (eLen < urlLen) {
-            const int eOff = urlLen - eLen;
+            const size_t eOff = urlLen - eLen;
             // RFC 3507 examples imply that extensions come without leading '.'
-            if (urlPath[eOff-1] == '.' &&
-                    strcmp(urlPath.termedBuf() + eOff, e->key) == 0) {
-                debugs(93,7, HERE << "url " << urlPath << " matches " <<
-                       name << " extension " << e->key);
+            if (urlPath[eOff-1] == '.' && urlPath.substr(eOff).cmp(e->key, eLen) == 0) {
+                debugs(93,7, "url " << urlPath << " matches " << name << " extension " << e->key);
                 return true;
             }
         }
     }
-    debugs(93,8, HERE << "url " << urlPath << " matches no " << name << " extensions");
+    debugs(93,8, "url " << urlPath << " matches no " << name << " extensions");
     return false;
 }
 
 void Adaptation::Icap::Options::TransferList::parse(const String &buf, bool &foundStar)
 {
     foundStar = false;
 
     const char *item;
     const char *pos = NULL;
     int ilen;
     while (strListGetItem(&buf, ',', &item, &ilen, &pos)) {
         if (ilen == 1 && *item == '*')
             foundStar = true;
         else {
             const char *tmp = xstrndup(item, ilen+1);
             add(tmp);
             xfree(tmp);
         }
     }
 }

=== modified file 'src/adaptation/icap/Options.h'
--- src/adaptation/icap/Options.h	2015-01-13 07:25:36 +0000
+++ src/adaptation/icap/Options.h	2015-06-14 19:16:30 +0000
@@ -25,67 +25,67 @@
 class Options
 {
 
 public:
     typedef void GetCallback(void *data, Options *options);
     static void Get(ServiceRep::Pointer &service, GetCallback *cb, void *data);
 
 public:
     Options();
     ~Options();
 
     void configure(const HttpReply *reply);
 
     bool valid() const;
     bool fresh() const;
     int ttl() const;
     time_t expire() const;
     time_t timestamp() const { return theTimestamp; };
 
     typedef enum { xferNone, xferPreview, xferIgnore, xferComplete } TransferKind;
-    TransferKind transferKind(const String &urlPath) const;
+    TransferKind transferKind(const SBuf &urlPath) const;
 
 public:
     const char *error; // human-readable information; set iff !valid()
 
     // ICAP server MUST supply this info
     std::vector<ICAP::Method> methods;
     String istag;
 
     // ICAP server MAY supply this info. If not, Squid supplies defaults.
     String service;
     String serviceId;
     int max_connections;
     bool allow204;
     bool allow206;
     int preview;
 
 protected:
     // Transfer-* extension list representation
     // maintains wordlist and does parsing/matching
     class TransferList
     {
     public:
         TransferList();
         ~TransferList();
 
-        bool matches(const String &urlPath) const;
+        bool matches(const SBuf &urlPath) const;
 
         void parse(const String &buf, bool &foundStar);
         void add(const char *extension);
         void report(int level, const char *prefix) const;
 
     public:
         wordlist *extensions; // TODO: optimize with a hash of some sort
         const char *name;  // header name, mostly for debugging
         TransferKind kind; // to simplify caller's life
     };
 
     // varios Transfer-* lists
     struct Transfers {
         TransferList preview;
         TransferList ignore;
         TransferList complete;
         TransferList *byDefault;  // Transfer-X that has '*'
     } theTransfers;
 
     int theTTL;

=== modified file 'src/adaptation/icap/ServiceRep.cc'
--- src/adaptation/icap/ServiceRep.cc	2015-05-26 17:25:04 +0000
+++ src/adaptation/icap/ServiceRep.cc	2015-06-14 19:10:43 +0000
@@ -304,47 +304,47 @@
 }
 
 bool Adaptation::Icap::ServiceRep::availableForNew() const
 {
     Must(up());
     int available = availableConnections();
     if (available < 0)
         return true;
     else
         return (available - theAllWaiters > 0);
 }
 
 bool Adaptation::Icap::ServiceRep::availableForOld() const
 {
     Must(up());
 
     int available = availableConnections();
     return (available != 0); // it is -1 (no limit) or has available slots
 }
 
-bool Adaptation::Icap::ServiceRep::wantsUrl(const String &urlPath) const
+bool Adaptation::Icap::ServiceRep::wantsUrl(const SBuf &urlPath) const
 {
     Must(hasOptions());
     return theOptions->transferKind(urlPath) != Adaptation::Icap::Options::xferIgnore;
 }
 
-bool Adaptation::Icap::ServiceRep::wantsPreview(const String &urlPath, size_t &wantedSize) const
+bool Adaptation::Icap::ServiceRep::wantsPreview(const SBuf &urlPath, size_t &wantedSize) const
 {
     Must(hasOptions());
 
     if (theOptions->preview < 0)
         return false;
 
     if (theOptions->transferKind(urlPath) != Adaptation::Icap::Options::xferPreview)
         return false;
 
     wantedSize = theOptions->preview;
 
     return true;
 }
 
 bool Adaptation::Icap::ServiceRep::allows204() const
 {
     Must(hasOptions());
     return true; // in the future, we may have ACLs to prevent 204s
 }
 

=== modified file 'src/adaptation/icap/ServiceRep.h'
--- src/adaptation/icap/ServiceRep.h	2015-05-05 09:09:27 +0000
+++ src/adaptation/icap/ServiceRep.h	2015-06-14 19:10:55 +0000
@@ -64,42 +64,42 @@
 public:
     typedef RefCount<ServiceRep> Pointer;
 
 public:
     explicit ServiceRep(const ServiceConfigPointer &aConfig);
     virtual ~ServiceRep();
 
     virtual void finalize();
 
     virtual bool probed() const; // see comments above
     virtual bool up() const; // see comments above
     bool availableForNew() const; ///< a new transaction may start communicating with the service
     bool availableForOld() const; ///< a transaction notified about connection slot availability may start communicating with the service
 
     virtual Initiate *makeXactLauncher(HttpMsg *virginHeader, HttpRequest *virginCause, AccessLogEntry::Pointer &alp);
 
     void callWhenAvailable(AsyncCall::Pointer &cb, bool priority = false);
     void callWhenReady(AsyncCall::Pointer &cb);
 
     // the methods below can only be called on an up() service
-    bool wantsUrl(const String &urlPath) const;
-    bool wantsPreview(const String &urlPath, size_t &wantedSize) const;
+    bool wantsUrl(const SBuf &urlPath) const;
+    bool wantsPreview(const SBuf &urlPath, size_t &wantedSize) const;
     bool allows204() const;
     bool allows206() const;
     Comm::ConnectionPointer getConnection(bool isRetriable, bool &isReused);
     void putConnection(const Comm::ConnectionPointer &conn, bool isReusable, bool sendReset, const char *comment);
     void noteConnectionUse(const Comm::ConnectionPointer &conn);
     void noteConnectionFailed(const char *comment);
 
     void noteFailure(); // called by transactions to report service failure
 
     void noteNewWaiter() {theAllWaiters++;} ///< New xaction waiting for service to be up or available
     void noteGoneWaiter(); ///< An xaction is not waiting any more for service to be available
     bool existWaiters() const {return (theAllWaiters > 0);} ///< if there are xactions waiting for the service to be available
 
     //AsyncJob virtual methods
     virtual bool doneAll() const { return Adaptation::Initiator::doneAll() && false;}
     virtual void callException(const std::exception &e);
 
     virtual void detach();
     virtual bool detached() const;
 

=== modified file 'src/carp.cc'
--- src/carp.cc	2015-06-09 06:14:43 +0000
+++ src/carp.cc	2015-06-28 06:43:45 +0000
@@ -161,50 +161,48 @@
 
     /* select CachePeer */
     for (k = 0; k < n_carp_peers; ++k) {
         SBuf key;
         tp = carp_peers[k];
         if (tp->options.carp_key.set) {
             //this code follows urlCanonical's pattern.
             //   corner cases should use the canonical URL
             if (tp->options.carp_key.scheme) {
                 key.append(request->url.getScheme().c_str());
                 if (key.length()) //if the scheme is not empty
                     key.append("://");
             }
             if (tp->options.carp_key.host) {
                 key.append(request->url.host());
             }
             if (tp->options.carp_key.port) {
                 key.appendf(":%u", request->url.port());
             }
             if (tp->options.carp_key.path) {
-                String::size_type pos;
-                if ((pos=request->urlpath.find('?'))!=String::npos)
-                    key.append(SBuf(request->urlpath.substr(0,pos)));
-                else
-                    key.append(SBuf(request->urlpath));
+                // XXX: fix when path and query are separate
+                key.append(request->url.path().substr(0,request->url.path().find('?'))); // 0..N
             }
             if (tp->options.carp_key.params) {
-                String::size_type pos;
-                if ((pos=request->urlpath.find('?'))!=String::npos)
-                    key.append(SBuf(request->urlpath.substr(pos,request->urlpath.size())));
+                // XXX: fix when path and query are separate
+                SBuf::size_type pos;
+                if ((pos=request->url.path().find('?')) != SBuf::npos)
+                    key.append(request->url.path().substr(pos)); // N..npos
             }
         }
         // if the url-based key is empty, e.g. because the user is
         // asking to balance on the path but the request doesn't supply any,
         // then fall back to canonical URL
 
         if (key.isEmpty())
             key=SBuf(urlCanonical(request));
 
         for (const char *c = key.rawContent(), *e=key.rawContent()+key.length(); c < e; ++c)
             user_hash += ROTATE_LEFT(user_hash, 19) + *c;
         combined_hash = (user_hash ^ tp->carp.hash);
         combined_hash += combined_hash * 0x62531965;
         combined_hash = ROTATE_LEFT(combined_hash, 21);
         score = combined_hash * tp->carp.load_multiplier;
         debugs(39, 3, "carpSelectParent: key=" << key << " name=" << tp->name << " combined_hash=" << combined_hash  <<
                " score=" << std::setprecision(0) << score);
 
         if ((score > high_score) && peerHTTPOkay(tp, request)) {
             p = tp;

=== modified file 'src/client_side.cc'
--- src/client_side.cc	2015-06-09 06:14:43 +0000
+++ src/client_side.cc	2015-06-18 09:50:39 +0000
@@ -2186,83 +2186,80 @@
            ", mime-header-size=" << hp->headerBlockSize() <<
            ", mime header block:\n" << hp->mimeHeader() << "\n----------");
 
     /* Ok, all headers are received */
     ClientHttpRequest *http = new ClientHttpRequest(csd);
 
     http->req_sz = hp->messageHeaderSize();
     ClientSocketContext *result = new ClientSocketContext(csd->clientConnection, http);
 
     StoreIOBuffer tempBuffer;
     tempBuffer.data = result->reqbuf;
     tempBuffer.length = HTTP_REQBUF_SZ;
 
     ClientStreamData newServer = new clientReplyContext(http);
     ClientStreamData newClient = result;
     clientStreamInit(&http->client_stream, clientGetMoreData, clientReplyDetach,
                      clientReplyStatus, newServer, clientSocketRecipient,
                      clientSocketDetach, newClient, tempBuffer);
 
     /* set url */
-    // XXX: c_str() does re-allocate but here replaces explicit malloc/free.
-    // when internalCheck() accepts SBuf removing this will be a net gain for performance.
-    SBuf tmp(hp->requestUri());
-    const char *url = tmp.c_str();
-
     debugs(33,5, "Prepare absolute URL from " <<
            (csd->transparent()?"intercept":(csd->port->flags.accelSurrogate ? "accel":"")));
     /* Rewrite the URL in transparent or accelerator mode */
     /* NP: there are several cases to traverse here:
      *  - standard mode (forward proxy)
      *  - transparent mode (TPROXY)
      *  - transparent mode with failures
      *  - intercept mode (NAT)
      *  - intercept mode with failures
      *  - accelerator mode (reverse proxy)
-     *  - internal URL
+     *  - internal relative-URL
      *  - mixed combos of the above with internal URL
      *  - remote interception with PROXY protocol
      *  - remote reverse-proxy with PROXY protocol
      */
     if (csd->transparent()) {
         /* intercept or transparent mode, properly working with no failures */
         prepareTransparentURL(csd, http, hp);
 
-    } else if (internalCheck(url)) {
+    } else if (internalCheck(hp->requestUri())) { // NP: only matches relative-URI
         /* internal URL mode */
         /* prepend our name & port */
-        http->uri = xstrdup(internalLocalUri(NULL, url));
+        http->uri = xstrdup(internalLocalUri(NULL, hp->requestUri()));
         // We just re-wrote the URL. Must replace the Host: header.
         //  But have not parsed there yet!! flag for local-only handling.
         http->flags.internal = true;
 
     } else if (csd->port->flags.accelSurrogate || csd->switchedToHttps()) {
         /* accelerator mode */
         prepareAcceleratedURL(csd, http, hp);
     }
 
     if (!http->uri) {
         /* No special rewrites have been applied above, use the
          * requested url. may be rewritten later, so make extra room */
         int url_sz = hp->requestUri().length() + Config.appendDomainLen + 5;
         http->uri = (char *)xcalloc(url_sz, 1);
-        strcpy(http->uri, url);
+        // XXX: c_str() does re-allocate but here replaces explicit malloc/free.
+        SBuf tmp(hp->requestUri());
+        strcpy(http->uri, tmp.c_str());
     }
 
     result->flags.parsed_ok = 1;
     return result;
 }
 
 bool
 ConnStateData::In::maybeMakeSpaceAvailable()
 {
     if (buf.spaceSize() < 2) {
         const SBuf::size_type haveCapacity = buf.length() + buf.spaceSize();
         if (haveCapacity >= Config.maxRequestBufferSize) {
             debugs(33, 4, "request buffer full: client_request_buffer_max_size=" << Config.maxRequestBufferSize);
             return false;
         }
         if (haveCapacity == 0) {
             // haveCapacity is based on the SBuf visible window of the MemBlob buffer, which may fill up.
             // at which point bump the buffer back to default. This allocates a new MemBlob with any un-parsed bytes.
             buf.reserveCapacity(CLIENT_REQ_BUF_SZ);
         } else {
@@ -2546,45 +2543,45 @@
 
     /** \par
      * If transparent or interception mode is working clone the transparent and interception flags
      * from the port settings to the request.
      */
     if (http->clientConnection != NULL) {
         request->flags.intercepted = ((http->clientConnection->flags & COMM_INTERCEPTION) != 0);
         request->flags.interceptTproxy = ((http->clientConnection->flags & COMM_TRANSPARENT) != 0 ) ;
         static const bool proxyProtocolPort = (conn->port != NULL) ? conn->port->flags.proxySurrogate : false;
         if (request->flags.interceptTproxy && !proxyProtocolPort) {
             if (Config.accessList.spoof_client_ip) {
                 ACLFilledChecklist *checklist = clientAclChecklistCreate(Config.accessList.spoof_client_ip, http);
                 request->flags.spoofClientIp = (checklist->fastCheck() == ACCESS_ALLOWED);
                 delete checklist;
             } else
                 request->flags.spoofClientIp = true;
         } else
             request->flags.spoofClientIp = false;
     }
 
-    if (internalCheck(request->urlpath.termedBuf())) {
+    if (internalCheck(request->url.path())) {
         if (internalHostnameIs(request->url.host()) && request->url.port() == getMyPort()) {
             debugs(33, 2, "internal URL found: " << request->url.getScheme() << "://" << request->url.authority(true));
             http->flags.internal = true;
-        } else if (Config.onoff.global_internal_static && internalStaticCheck(request->urlpath.termedBuf())) {
+        } else if (Config.onoff.global_internal_static && internalStaticCheck(request->url.path())) {
             debugs(33, 2, "internal URL found: " << request->url.getScheme() << "://" << request->url.authority(true) << " (global_internal_static on)");
             request->url.setScheme(AnyP::PROTO_HTTP);
             request->SetHost(internalHostname());
             request->url.port(getMyPort());
             http->flags.internal = true;
         } else
             debugs(33, 2, "internal URL found: " << request->url.getScheme() << "://" << request->url.authority(true) << " (not this proxy)");
     }
 
     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;
 

=== modified file 'src/clients/FtpGateway.cc'
--- src/clients/FtpGateway.cc	2015-06-22 11:52:31 +0000
+++ src/clients/FtpGateway.cc	2015-06-28 06:51:22 +0000
@@ -1064,127 +1064,125 @@
     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;
         }
     }
 
     return 0;           /* different username */
 }
 
-static String str_type_eq;
 void
 Ftp::Gateway::checkUrlpath()
 {
-    int l;
-    size_t t;
+    static SBuf str_type_eq("type=");
+    auto t = request->url.path().rfind(';');
 
-    if (str_type_eq.size()==0) //hack. String doesn't support global-static
-        str_type_eq="type=";
-
-    if ((t = request->urlpath.rfind(';')) != String::npos) {
-        if (request->urlpath.substr(t+1,t+1+str_type_eq.size())==str_type_eq) {
-            typecode = (char)xtoupper(request->urlpath[t+str_type_eq.size()+1]);
-            request->urlpath.cut(t);
+    if (t != SBuf::npos) {
+        auto filenameEnd = t-1;
+        if (request->url.path().substr(++t).cmp(str_type_eq, str_type_eq.length()) == 0) {
+            t += str_type_eq.length();
+            typecode = (char)xtoupper(request->url.path()[t]);
+            request->url.path(request->url.path().substr(0,filenameEnd));
         }
     }
 
-    l = request->urlpath.size();
+    int l = request->url.path().length();
     /* check for null path */
 
     if (!l) {
         flags.isdir = 1;
         flags.root_dir = 1;
         flags.need_base_href = 1;   /* Work around broken browsers */
-    } else if (!request->urlpath.cmp("/%2f/")) {
+    } else if (!request->url.path().cmp("/%2f/")) {
         /* UNIX root directory */
         flags.isdir = 1;
         flags.root_dir = 1;
-    } else if ((l >= 1) && (request->urlpath[l - 1] == '/')) {
+    } else if ((l >= 1) && (request->url.path()[l-1] == '/')) {
         /* Directory URL, ending in / */
         flags.isdir = 1;
 
         if (l == 1)
             flags.root_dir = 1;
     } else {
         flags.dir_slash = 1;
     }
 }
 
 void
 Ftp::Gateway::buildTitleUrl()
 {
     title_url = "ftp://";
 
     if (strcmp(user, "anonymous")) {
         title_url.append(user);
         title_url.append("@");
     }
 
     SBuf authority = request->url.authority(request->url.getScheme() != AnyP::PROTO_FTP);
 
     title_url.append(authority.rawContent(), authority.length());
-    title_url.append(request->urlpath);
+    title_url.append(request->url.path().rawContent(), request->url.path().length());
 
     base_href = "ftp://";
 
     if (strcmp(user, "anonymous") != 0) {
         base_href.append(rfc1738_escape_part(user));
 
         if (password_url) {
             base_href.append(":");
             base_href.append(rfc1738_escape_part(password));
         }
 
         base_href.append("@");
     }
 
     base_href.append(authority.rawContent(), authority.length());
-    base_href.append(request->urlpath);
+    base_href.append(request->url.path().rawContent(), request->url.path().length());
     base_href.append("/");
 }
 
 void
 Ftp::Gateway::start()
 {
     if (!checkAuth(&request->header)) {
         /* create appropriate reply */
         HttpReply *reply = ftpAuthRequired(request, ftpRealm());
         entry->replaceHttpReply(reply);
         serverComplete();
         return;
     }
 
     checkUrlpath();
     buildTitleUrl();
-    debugs(9, 5, HERE << "FD " << ctrl.conn->fd << " : host=" << request->url.host() <<
-           ", path=" << request->urlpath << ", user=" << user << ", passwd=" << password);
+    debugs(9, 5, "FD " << ctrl.conn->fd << " : host=" << request->url.host() <<
+           ", path=" << request->url.path() << ", user=" << user << ", passwd=" << password);
     state = BEGIN;
     Ftp::Client::start();
 }
 
 /* ====================================================================== */
 
 void
 Ftp::Gateway::handleControlReply()
 {
     Ftp::Client::handleControlReply();
     if (ctrl.message == NULL)
         return; // didn't get complete reply yet
 
     /* Copy the message except for the last line to cwd_message to be
      * printed in error messages.
      */
     for (wordlist *w = ctrl.message; w && w->next; w = w->next) {
         cwd_message.append('\n');
         cwd_message.append(w->key);
     }
@@ -1348,99 +1346,98 @@
     ftpState->writeCommand(cbuf);
     ftpState->state = Ftp::Client::SENT_PASS;
 }
 
 static void
 ftpReadPass(Ftp::Gateway * ftpState)
 {
     int code = ftpState->ctrl.replycode;
     debugs(9, 3, HERE << "code=" << code);
 
     if (code == 230) {
         ftpSendType(ftpState);
     } else {
         ftpState->loginFailed();
     }
 }
 
 static void
 ftpSendType(Ftp::Gateway * ftpState)
 {
-    const char *t;
-    const char *filename;
-    char mode;
-
     /* check the server control channel is still available */
     if (!ftpState || !ftpState->haveControlChannel("ftpSendType"))
         return;
 
     /*
      * Ref section 3.2.2 of RFC 1738
      */
-    mode = ftpState->typecode;
+    char mode = ftpState->typecode;
 
     switch (mode) {
 
     case 'D':
         mode = 'A';
         break;
 
     case 'A':
 
     case 'I':
         break;
 
     default:
 
         if (ftpState->flags.isdir) {
             mode = 'A';
         } else {
-            t = ftpState->request->urlpath.rpos('/');
-            filename = t ? t + 1 : ftpState->request->urlpath.termedBuf();
-            mode = mimeGetTransferMode(filename);
+            auto t = ftpState->request->url.path().rfind('/');
+            // XXX: performance regression, c_str() may reallocate
+            SBuf filename = ftpState->request->url.path().substr(t != SBuf::npos ? t + 1 : 0);
+            mode = mimeGetTransferMode(filename.c_str());
         }
 
         break;
     }
 
     if (mode == 'I')
         ftpState->flags.binary = 1;
     else
         ftpState->flags.binary = 0;
 
     snprintf(cbuf, CTRL_BUFLEN, "TYPE %c\r\n", mode);
 
     ftpState->writeCommand(cbuf);
 
     ftpState->state = Ftp::Client::SENT_TYPE;
 }
 
 static void
 ftpReadType(Ftp::Gateway * ftpState)
 {
     int code = ftpState->ctrl.replycode;
     char *path;
     char *d, *p;
     debugs(9, 3, HERE << "code=" << code);
 
     if (code == 200) {
-        p = path = xstrdup(ftpState->request->urlpath.termedBuf());
+        // XXX: performance regression, c_str() may reallocate
+        SBuf tmp = ftpState->request->url.path();
+        p = path = xstrndup(tmp.c_str(),tmp.length());
 
         if (*p == '/')
             ++p;
 
         while (*p) {
             d = p;
             p += strcspn(p, "/");
 
             if (*p) {
                 *p = '\0';
                 ++p;
             }
 
             rfc1738_unescape(d);
 
             if (*d)
                 wordlistAdd(&ftpState->pathcomps, d);
         }
 
         xfree(path);
@@ -2351,41 +2348,43 @@
 ftpReadQuit(Ftp::Gateway * ftpState)
 {
     ftpState->serverComplete();
 }
 
 static void
 ftpTrySlashHack(Ftp::Gateway * ftpState)
 {
     char *path;
     ftpState->flags.try_slash_hack = 1;
     /* Free old paths */
 
     debugs(9, 3, HERE);
 
     if (ftpState->pathcomps)
         wordlistDestroy(&ftpState->pathcomps);
 
     safe_free(ftpState->filepath);
 
     /* Build the new path (urlpath begins with /) */
-    path = xstrdup(ftpState->request->urlpath.termedBuf());
+    // XXX: performance regression. c_str() may reallocate, then xstrdup repeats.
+    SBuf tmp = ftpState->request->url.path();
+    path = xstrdup(tmp.c_str());
 
     rfc1738_unescape(path);
 
     ftpState->filepath = path;
 
     /* And off we go */
     ftpGetFile(ftpState);
 }
 
 /**
  * Forget hack status. Next error is shown to the user
  */
 void
 Ftp::Gateway::unhack()
 {
     debugs(9, 3, HERE);
 
     if (old_request != NULL) {
         safe_free(old_request);
         safe_free(old_reply);
@@ -2402,51 +2401,51 @@
 
     debugs(9, 3, HERE);
 
     if (old_request == NULL) {
         old_request = ctrl.last_command;
         ctrl.last_command = NULL;
         old_reply = ctrl.last_reply;
         ctrl.last_reply = NULL;
 
         if (pathcomps == NULL && filepath != NULL)
             old_filepath = xstrdup(filepath);
     }
 
     /* Jump to the "hack" state */
     nextState(this);
 }
 
 static void
 ftpFail(Ftp::Gateway *ftpState)
 {
-    debugs(9, 6, HERE << "flags(" <<
+    const bool slashHack = ftpState->request->url.path().caseCmp("/%2f", 4)==0;
+    debugs(9, 6, "flags(" <<
            (ftpState->flags.isdir?"IS_DIR,":"") <<
            (ftpState->flags.try_slash_hack?"TRY_SLASH_HACK":"") << "), " <<
            "mdtm=" << ftpState->mdtm << ", size=" << ftpState->theSize <<
-           "slashhack=" << (ftpState->request->urlpath.caseCmp("/%2f", 4)==0? "T":"F") );
+           "slashhack=" << (slashHack? "T":"F") );
 
     /* Try the / hack to support "Netscape" FTP URL's for retreiving files */
     if (!ftpState->flags.isdir &&   /* Not a directory */
-            !ftpState->flags.try_slash_hack &&  /* Not in slash hack */
-            ftpState->mdtm <= 0 && ftpState->theSize < 0 && /* Not known as a file */
-            ftpState->request->urlpath.caseCmp("/%2f", 4) != 0) {   /* No slash encoded */
+            !ftpState->flags.try_slash_hack && !slashHack && /* Not doing slash hack */
+            ftpState->mdtm <= 0 && ftpState->theSize < 0) { /* Not known as a file */
 
         switch (ftpState->state) {
 
         case Ftp::Client::SENT_CWD:
 
         case Ftp::Client::SENT_RETR:
             /* Try the / hack */
             ftpState->hackShortcut(ftpTrySlashHack);
             return;
 
         default:
             break;
         }
     }
 
     ftpState->failed(ERR_NONE, 0);
     /* failed() closes ctrl.conn and frees this */
 }
 
 Http::StatusCode
@@ -2515,80 +2514,81 @@
         err.ftp.request = xstrdup(ftpState->ctrl.last_command);
 
     if (ftpState->old_reply)
         err.ftp.reply = xstrdup(ftpState->old_reply);
     else if (ftpState->ctrl.last_reply)
         err.ftp.reply = xstrdup(ftpState->ctrl.last_reply);
     else
         err.ftp.reply = xstrdup("");
 
     // TODO: interpret as FTP-specific error code
     err.detailError(code);
 
     ftpState->entry->replaceHttpReply( err.BuildHttpReply() );
 
     ftpSendQuit(ftpState);
 }
 
 void
 Ftp::Gateway::appendSuccessHeader()
 {
-    const char *mime_type = NULL;
-    const char *mime_enc = NULL;
-    String urlpath = request->urlpath;
-    const char *filename = NULL;
-    const char *t = NULL;
-
     debugs(9, 3, HERE);
 
     if (flags.http_header_sent)
         return;
 
     HttpReply *reply = new HttpReply;
 
     flags.http_header_sent = 1;
 
     assert(entry->isEmpty());
 
     EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT);
 
     entry->buffer();    /* released when done processing current data payload */
 
-    filename = (t = urlpath.rpos('/')) ? t + 1 : urlpath.termedBuf();
+    SBuf urlPath = request->url.path();
+    auto t = urlPath.rfind('/');
+    SBuf filename = urlPath.substr(t != SBuf::npos ? t : 0);
+
+    const char *mime_type = NULL;
+    const char *mime_enc = NULL;
 
     if (flags.isdir) {
         mime_type = "text/html";
     } else {
         switch (typecode) {
 
         case 'I':
             mime_type = "application/octet-stream";
-            mime_enc = mimeGetContentEncoding(filename);
+            // XXX: performance regression, c_str() may reallocate
+            mime_enc = mimeGetContentEncoding(filename.c_str());
             break;
 
         case 'A':
             mime_type = "text/plain";
             break;
 
         default:
-            mime_type = mimeGetContentType(filename);
-            mime_enc = mimeGetContentEncoding(filename);
+            // XXX: performance regression, c_str() may reallocate
+            mime_type = mimeGetContentType(filename.c_str());
+            mime_enc = mimeGetContentEncoding(filename.c_str());
             break;
         }
     }
 
     /* set standard stuff */
 
     if (0 == getCurrentOffset()) {
         /* Full reply */
         reply->setHeaders(Http::scOkay, "Gatewaying", mime_type, theSize, mdtm, -2);
     } else if (theSize < getCurrentOffset()) {
         /*
          * DPW 2007-05-04
          * offset should not be larger than theSize.  We should
          * not be seeing this condition any more because we'll only
          * send REST if we know the theSize and if it is less than theSize.
          */
         debugs(0,DBG_CRITICAL,HERE << "Whoops! " <<
                " current offset=" << getCurrentOffset() <<
                ", but theSize=" << theSize <<
                ".  assuming full content response");
@@ -2629,52 +2629,52 @@
     } else {
         e->release();
     }
 }
 
 HttpReply *
 Ftp::Gateway::ftpAuthRequired(HttpRequest * request, const char *realm)
 {
     ErrorState err(ERR_CACHE_ACCESS_DENIED, Http::scUnauthorized, request);
     HttpReply *newrep = err.BuildHttpReply();
 #if HAVE_AUTH_MODULE_BASIC
     /* add Authenticate header */
     newrep->header.putAuth("Basic", realm);
 #endif
     return newrep;
 }
 
 const char *
 Ftp::UrlWith2f(HttpRequest * request)
 {
-    String newbuf = "%2f";
+    SBuf newbuf("%2f");
 
     if (request->url.getScheme() != AnyP::PROTO_FTP)
         return NULL;
 
-    if ( request->urlpath[0]=='/' ) {
-        newbuf.append(request->urlpath);
-        request->urlpath.absorb(newbuf);
+    if (request->url.path()[0] == '/') {
+        newbuf.append(request->url.path());
+        request->url.path(newbuf);
         safe_free(request->canonical);
-    } else if ( !strncmp(request->urlpath.termedBuf(), "%2f", 3) ) {
-        newbuf.append(request->urlpath.substr(1,request->urlpath.size()));
-        request->urlpath.absorb(newbuf);
+    } else if (!request->url.path().cmp(newbuf, 3)) {
+        newbuf.append(request->url.path().substr(1));
+        request->url.path(newbuf);
         safe_free(request->canonical);
     }
 
     return urlCanonical(request);
 }
 
 void
 Ftp::Gateway::printfReplyBody(const char *fmt, ...)
 {
     va_list args;
     va_start (args, fmt);
     static char buf[4096];
     buf[0] = '\0';
     vsnprintf(buf, 4096, fmt, args);
     writeReplyBody(buf, strlen(buf));
     va_end(args);
 }
 
 /**
  * Call this when there is data from the origin server

=== modified file 'src/errorpage.cc'
--- src/errorpage.cc	2015-06-09 06:14:43 +0000
+++ src/errorpage.cc	2015-06-28 07:04:07 +0000
@@ -718,52 +718,44 @@
 #if USE_AUTH
     if (auth_user_request.getRaw() && auth_user_request->denyMessage())
         str.appendf("Auth ErrMsg: %s\r\n", auth_user_request->denyMessage());
 #endif
     if (dnsError.size() > 0)
         str.appendf("DNS ErrMsg: %s\r\n", dnsError.termedBuf());
 
     /* - TimeStamp */
     str.appendf("TimeStamp: %s\r\n\r\n", mkrfc1123(squid_curtime));
 
     /* - IP stuff */
     str.appendf("ClientIP: %s\r\n", src_addr.toStr(ntoabuf,MAX_IPSTRLEN));
 
     if (request && request->hier.host[0] != '\0') {
         str.appendf("ServerIP: %s\r\n", request->hier.host);
     }
 
     str.append("\r\n", 2);
     /* - HTTP stuff */
     str.append("HTTP Request:\r\n", 15);
-
-    if (NULL != request) {
-        String urlpath_or_slash;
-
-        if (request->urlpath.size() != 0)
-            urlpath_or_slash = request->urlpath;
-        else
-            urlpath_or_slash = "/";
-
-        str.appendf(SQUIDSBUFPH " " SQUIDSTRINGPH " %s/%d.%d\n",
+    if (request) {
+        str.appendf(SQUIDSBUFPH " " SQUIDSBUFPH " %s/%d.%d\n",
                     SQUIDSBUFPRINT(request->method.image()),
-                    SQUIDSTRINGPRINT(urlpath_or_slash),
+                    SQUIDSBUFPRINT(request->url.path()),
                     AnyP::ProtocolType_str[request->http_ver.protocol],
                     request->http_ver.major, request->http_ver.minor);
         request->header.packInto(&str);
     }
 
     str.append("\r\n", 2);
     /* - FTP stuff */
 
     if (ftp.request) {
         str.appendf("FTP Request: %s\r\n", ftp.request);
         str.appendf("FTP Reply: %s\r\n", (ftp.reply? ftp.reply:"[none]"));
         str.append("FTP Msg: ", 9);
         wordlistCat(ftp.server_msg, &str);
         str.append("\r\n", 2);
     }
 
     str.append("\r\n", 2);
     mb->appendf("&body=%s", rfc1738_escape_part(str.buf));
     str.clean();
     return 0;
@@ -936,57 +928,51 @@
 
     case 'p':
         if (request) {
             mb.appendf("%u", request->url.port());
         } else if (!building_deny_info_url) {
             p = "[unknown port]";
         }
         break;
 
     case 'P':
         if (request) {
             p = request->url.getScheme().c_str();
         } else if (!building_deny_info_url) {
             p = "[unknown protocol]";
         }
         break;
 
     case 'R':
         if (building_deny_info_url) {
             if (request != NULL) {
-                p = (request->urlpath.size() != 0 ? request->urlpath.termedBuf() : "/");
+                SBuf tmp = request->url.path();
+                p = tmp.c_str();
                 no_urlescape = 1;
             } else
                 p = "[no request]";
             break;
         }
-        if (NULL != request) {
-            String urlpath_or_slash;
-
-            if (request->urlpath.size() != 0)
-                urlpath_or_slash = request->urlpath;
-            else
-                urlpath_or_slash = "/";
-
-            mb.appendf(SQUIDSBUFPH " " SQUIDSTRINGPH " %s/%d.%d\n",
+        if (request != NULL) {
+            mb.appendf(SQUIDSBUFPH " " SQUIDSBUFPH " %s/%d.%d\n",
                        SQUIDSBUFPRINT(request->method.image()),
-                       SQUIDSTRINGPRINT(urlpath_or_slash),
+                       SQUIDSBUFPRINT(request->url.path()),
                        AnyP::ProtocolType_str[request->http_ver.protocol],
                        request->http_ver.major, request->http_ver.minor);
             request->header.packInto(&mb, true); //hide authorization data
         } else if (request_hdrs) {
             p = request_hdrs;
         } else {
             p = "[no request]";
         }
         break;
 
     case 's':
         /* for backward compat we make %s show the full URL. Drop this in some future release. */
         if (building_deny_info_url) {
             p = request ? urlCanonical(request) : url;
             debugs(0, DBG_CRITICAL, "WARNING: deny_info now accepts coded tags. Use %u to get the full URL instead of %s");
         } else
             p = visible_appname_string;
         break;
 
     case 'S':

=== modified file 'src/external_acl.cc'
--- src/external_acl.cc	2015-06-09 06:14:43 +0000
+++ src/external_acl.cc	2015-06-28 07:03:42 +0000
@@ -961,42 +961,44 @@
             str = buf;
             break;
 
         case Format::LFT_CLIENT_REQ_URI:
             str = urlCanonical(request);
             break;
 
         case Format::LFT_CLIENT_REQ_URLDOMAIN:
             str = request->url.host();
             break;
 
         case Format::LFT_CLIENT_REQ_URLSCHEME:
             str = request->url.getScheme().c_str();
             break;
 
         case Format::LFT_CLIENT_REQ_URLPORT:
             snprintf(buf, sizeof(buf), "%u", request->url.port());
             str = buf;
             break;
 
-        case Format::LFT_CLIENT_REQ_URLPATH:
-            str = request->urlpath.termedBuf();
+        case Format::LFT_CLIENT_REQ_URLPATH: {
+                SBuf tmp = request->url.path();
+                str = tmp.c_str();
+            }
             break;
 
         case Format::LFT_CLIENT_REQ_METHOD: {
             const SBuf &s = request->method.image();
             sb.append(s.rawContent(), s.length());
         }
         str = sb.termedBuf();
         break;
 
         case Format::LFT_ADAPTED_REQUEST_HEADER:
             if (format->header_id == -1)
                 sb = request->header.getByName(format->header);
             else
                 sb = request->header.getStrOrList(format->header_id);
             str = sb.termedBuf();
             break;
 
         case Format::LFT_ADAPTED_REQUEST_HEADER_ELEM:
             if (format->header_id == -1)
                 sb = request->header.getByNameListMember(format->header, format->member, format->separator);

=== modified file 'src/format/Format.cc'
--- src/format/Format.cc	2015-06-09 06:14:43 +0000
+++ src/format/Format.cc	2015-06-09 12:13:49 +0000
@@ -954,41 +954,42 @@
             }
             break;
 
         case LFT_CLIENT_REQ_URLDOMAIN:
             if (al->request) {
                 out = al->request->url.host();
                 quote = 1;
             }
             break;
 
         case LFT_CLIENT_REQ_URLPORT:
             if (al->request) {
                 outint = al->request->url.port();
                 doint = 1;
             }
             break;
 
         case LFT_REQUEST_URLPATH_OLD_31:
         case LFT_CLIENT_REQ_URLPATH:
             if (al->request) {
-                out = al->request->urlpath.termedBuf();
+                SBuf s = al->request->url.path();
+                out = s.c_str();
                 quote = 1;
             }
             break;
 
         case LFT_CLIENT_REQ_VERSION:
             if (al->request) {
                 snprintf(tmp, sizeof(tmp), "%d.%d", (int) al->request->http_ver.major, (int) al->request->http_ver.minor);
                 out = tmp;
             }
             break;
 
         case LFT_REQUEST_METHOD:
             if (al->_private.method_str) // ICP, HTCP method code
                 out = al->_private.method_str;
             else {
                 const SBuf &s = al->http.method.image();
                 sb.append(s.rawContent(), s.length());
                 out = sb.termedBuf();
                 quote = 1;
             }
@@ -1027,41 +1028,42 @@
                 quote = 1;
             }
             break;
 
         case LFT_SERVER_REQ_URLDOMAIN:
             if (al->adapted_request) {
                 out = al->adapted_request->url.host();
                 quote = 1;
             }
             break;
 
         case LFT_SERVER_REQ_URLPORT:
             if (al->adapted_request) {
                 outint = al->adapted_request->url.port();
                 doint = 1;
             }
             break;
 
         case LFT_SERVER_REQ_URLPATH:
             if (al->adapted_request) {
-                out = al->adapted_request->urlpath.termedBuf();
+                SBuf s = al->adapted_request->url.path();
+                out = s.c_str();
                 quote = 1;
             }
             break;
 
         case LFT_SERVER_REQ_VERSION:
             if (al->adapted_request) {
                 snprintf(tmp, sizeof(tmp), "%d.%d",
                          (int) al->adapted_request->http_ver.major,
                          (int) al->adapted_request->http_ver.minor);
                 out = tmp;
             }
             break;
 
         case LFT_CLIENT_REQUEST_SIZE_TOTAL:
             outoff = al->http.clientRequestSz.messageTotal();
             dooff = 1;
             break;
 
         case LFT_CLIENT_REQUEST_SIZE_HEADERS:
             outoff = al->http.clientRequestSz.header;

=== modified file 'src/gopher.cc'
--- src/gopher.cc	2015-06-09 06:14:43 +0000
+++ src/gopher.cc	2015-06-14 22:04:38 +0000
@@ -4,40 +4,41 @@
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
 /* DEBUG: section 10    Gopher */
 
 #include "squid.h"
 #include "comm.h"
 #include "comm/Read.h"
 #include "comm/Write.h"
 #include "errorpage.h"
 #include "fd.h"
 #include "FwdState.h"
 #include "globals.h"
 #include "html_quote.h"
 #include "HttpReply.h"
 #include "HttpRequest.h"
 #include "MemBuf.h"
 #include "mime.h"
+#include "parser/Tokenizer.h"
 #include "rfc1738.h"
 #include "SquidConfig.h"
 #include "SquidTime.h"
 #include "StatCounters.h"
 #include "Store.h"
 #include "tools.h"
 
 #if USE_DELAY_POOLS
 #include "DelayPools.h"
 #include "MemObject.h"
 #endif
 
 /* gopher type code from rfc. Anawat. */
 #define GOPHER_FILE         '0'
 #define GOPHER_DIRECTORY    '1'
 #define GOPHER_CSO          '2'
 #define GOPHER_ERROR        '3'
 #define GOPHER_MACBINHEX    '4'
 #define GOPHER_DOSBIN       '5'
 #define GOPHER_UUENCODED    '6'
@@ -236,57 +237,61 @@
     }
 
     assert(entry->isEmpty());
     EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT);
 
     HttpReply *reply = new HttpReply;
     entry->buffer();
     reply->setHeaders(Http::scOkay, "Gatewaying", mime_type, -1, -1, -2);
     if (mime_enc)
         reply->header.putStr(HDR_CONTENT_ENCODING, mime_enc);
 
     entry->replaceHttpReply(reply);
 }
 
 /**
  * Parse a gopher request into components.  By Anawat.
  */
 static void
 gopher_request_parse(const HttpRequest * req, char *type_id, char *request)
 {
-    const char *path = req->urlpath.termedBuf();
+    ::Parser::Tokenizer tok(req->url.path());
 
     if (request)
-        request[0] = '\0';
+        *request = 0;
 
-    if (path && (*path == '/'))
-        ++path;
+    tok.skip('/'); // ignore failures? path could be ab-empty
 
-    if (!path || !*path) {
+    if (tok.atEnd()) {
         *type_id = GOPHER_DIRECTORY;
         return;
     }
 
-    *type_id = path[0];
+    static const CharacterSet anyByte("UTF-8",0x00, 0xFF);
+
+    SBuf typeId;
+    (void)tok.prefix(typeId, anyByte, 1); // never fails since !atEnd()
+    *type_id = typeId[0];
 
     if (request) {
-        xstrncpy(request, path + 1, MAX_URL);
+        SBuf path = tok.remaining();
+        xstrncpy(request, path.c_str(), MAX_URL);
         /* convert %xx to char */
         rfc1738_unescape(request);
     }
 }
 
 /**
  * Parse the request to determine whether it is cachable.
  *
  * \param req   Request data.
  * \retval 0    Not cachable.
  * \retval 1    Cachable.
  */
 int
 gopherCachable(const HttpRequest * req)
 {
     int cachable = 1;
     char type_id;
     /* parse to see type */
     gopher_request_parse(req,
                          &type_id,

=== modified file 'src/http.cc'
--- src/http.cc	2015-06-19 07:13:57 +0000
+++ src/http.cc	2015-06-19 07:20:11 +0000
@@ -1292,62 +1292,61 @@
     if (!flags.headers_parsed && !eof) {
         debugs(11, 9, "needs more at " << inBuf.length());
         flags.do_next_read = true;
         /** \retval false If we have not finished parsing the headers and may get more data.
          *                Schedules more reads to retrieve the missing data.
          */
         maybeReadVirginBody(); // schedules all kinds of reads; TODO: rename
         return false;
     }
 
     /** If we are done with parsing, check for errors */
 
     err_type error = ERR_NONE;
 
     if (flags.headers_parsed) { // parsed headers, possibly with errors
         // check for header parsing errors
         if (HttpReply *vrep = virginReply()) {
             const Http::StatusCode s = vrep->sline.status();
             const AnyP::ProtocolVersion &v = vrep->sline.version;
             if (s == Http::scInvalidHeader && v != Http::ProtocolVersion(0,9)) {
-                debugs(11, DBG_IMPORTANT, "WARNING: HTTP: Invalid Response: Bad header encountered from " << entry->url() << " AKA " << request->url.host() << request->urlpath.termedBuf());
+                debugs(11, DBG_IMPORTANT, "WARNING: HTTP: Invalid Response: Bad header encountered from " << entry->url() << " AKA " << request->url);
                 error = ERR_INVALID_RESP;
             } else if (s == Http::scHeaderTooLarge) {
                 fwd->dontRetry(true);
                 error = ERR_TOO_BIG;
             } else {
                 return true; // done parsing, got reply, and no error
             }
         } else {
             // parsed headers but got no reply
-            debugs(11, DBG_IMPORTANT, "WARNING: HTTP: Invalid Response: No reply at all for " << entry->url() << " AKA " << request->url.host() << request->urlpath.termedBuf());
+            debugs(11, DBG_IMPORTANT, "WARNING: HTTP: Invalid Response: No reply at all for " << entry->url() << " AKA " << request->url);
             error = ERR_INVALID_RESP;
         }
     } else {
         assert(eof);
         if (inBuf.length()) {
             error = ERR_INVALID_RESP;
-            debugs(11, DBG_IMPORTANT, "WARNING: HTTP: Invalid Response: Headers did not parse at all for " << entry->url() << " AKA " << request->url.host() << request->urlpath.termedBuf());
+            debugs(11, DBG_IMPORTANT, "WARNING: HTTP: Invalid Response: Headers did not parse at all for " << entry->url() << " AKA " << request->url);
         } else {
             error = ERR_ZERO_SIZE_OBJECT;
-            debugs(11, (request->flags.accelerated?DBG_IMPORTANT:2), "WARNING: HTTP: Invalid Response: No object data received for " <<
-                   entry->url() << " AKA " << request->url.host() << request->urlpath.termedBuf());
+            debugs(11, (request->flags.accelerated?DBG_IMPORTANT:2), "WARNING: HTTP: Invalid Response: No object data received for " << entry->url() << " AKA " << request->url);
         }
     }
 
     assert(error != ERR_NONE);
     entry->reset();
     fwd->fail(new ErrorState(error, Http::scBadGateway, fwd->request));
     flags.do_next_read = false;
     serverConnection->close();
     return false; // quit on error
 }
 
 /** truncate what we read if we read too much so that writeReplyBody()
     writes no more than what we should have read */
 void
 HttpStateData::truncateVirginBody()
 {
     assert(flags.headers_parsed);
 
     HttpReply *vrep = virginReply();
     int64_t clen = -1;
@@ -2150,45 +2149,48 @@
 
     return result;
 }
 
 /* build request prefix and append it to a given MemBuf;
  * return the length of the prefix */
 mb_size_t
 HttpStateData::buildRequestPrefix(MemBuf * mb)
 {
     const int offset = mb->size;
     /* Uses a local httpver variable to print the HTTP label
      * since the HttpRequest may have an older version label.
      * XXX: This could create protocol bugs as the headers sent and
      * flow control should all be based on the HttpRequest version
      * not the one we are sending. Needs checking.
      */
     const AnyP::ProtocolVersion httpver = Http::ProtocolVersion();
     const char * url;
     if (_peer && !_peer->options.originserver)
         url = urlCanonical(request);
-    else
-        url = request->urlpath.termedBuf();
+    else {
+        // XXX: performance regression, c_str() reallocates
+        SBuf tmp = request->url.path();
+        url = tmp.c_str();
+    }
     mb->appendf(SQUIDSBUFPH " %s %s/%d.%d\r\n",
                 SQUIDSBUFPRINT(request->method.image()),
-                url && *url ? url : "/",
+                url,
                 AnyP::ProtocolType_str[httpver.protocol],
                 httpver.major,httpver.minor);
     /* build and pack headers */
     {
         HttpHeader hdr(hoRequest);
         httpBuildRequestHeader(request, entry, fwd->al, &hdr, flags);
 
         if (request->flags.pinned && request->flags.connectionAuth)
             request->flags.authSent = true;
         else if (hdr.has(HDR_AUTHORIZATION))
             request->flags.authSent = true;
 
         hdr.packInto(mb);
         hdr.clean();
     }
     /* append header terminator */
     mb->append(crlf, 2);
     return mb->size - offset;
 }
 

=== modified file 'src/icmp/net_db.cc'
--- src/icmp/net_db.cc	2015-06-09 06:14:43 +0000
+++ src/icmp/net_db.cc	2015-06-19 11:45:11 +0000
@@ -1272,41 +1272,42 @@
     }
 
     assert(0 == i);
     s->flush();
     memFree(buf, MEM_4K_BUF);
 #else
 
     reply->setHeaders(Http::scBadRequest, "Bad Request", NULL, -1, squid_curtime, -2);
     s->replaceHttpReply(reply);
     storeAppendPrintf(s, "NETDB support not compiled into this Squid cache.\n");
 #endif
 
     s->complete();
 }
 
 void
 netdbExchangeStart(void *data)
 {
 #if USE_ICMP
     CachePeer *p = (CachePeer *)data;
-    char *uri = internalRemoteUri(p->host, p->http_port, "/squid-internal-dynamic/", "netdb");
+    static const SBuf netDB("netdb");
+    char *uri = internalRemoteUri(p->host, p->http_port, "/squid-internal-dynamic/", netDB);
     debugs(38, 3, "netdbExchangeStart: Requesting '" << uri << "'");
     assert(NULL != uri);
     HttpRequest *req = HttpRequest::CreateFromUrl(uri);
 
     if (req == NULL) {
         debugs(38, DBG_IMPORTANT, "netdbExchangeStart: Bad URI " << uri);
         return;
     }
 
     netdbExchangeState *ex = new netdbExchangeState(p, req);
     ex->e = storeCreateEntry(uri, uri, RequestFlags(), Http::METHOD_GET);
     assert(NULL != ex->e);
 
     StoreIOBuffer tempBuffer;
     tempBuffer.length = ex->buf_sz;
     tempBuffer.data = ex->buf;
 
     ex->sc = storeClientListAdd(ex->e, ex);
 
     storeClientCopy(ex->sc, ex->e, tempBuffer,

=== modified file 'src/internal.cc'
--- src/internal.cc	2015-06-09 06:14:43 +0000
+++ src/internal.cc	2015-06-28 07:19:47 +0000
@@ -15,134 +15,140 @@
 #include "HttpReply.h"
 #include "HttpRequest.h"
 #include "icmp/net_db.h"
 #include "MemBuf.h"
 #include "SquidConfig.h"
 #include "SquidTime.h"
 #include "Store.h"
 #include "tools.h"
 #include "URL.h"
 #include "util.h"
 #include "wordlist.h"
 
 /* called when we "miss" on an internal object;
  * generate known dynamic objects,
  * return Http::scNotFound for others
  */
 void
 internalStart(const Comm::ConnectionPointer &clientConn, HttpRequest * request, StoreEntry * entry)
 {
     ErrorState *err;
-    const char *upath = request->urlpath.termedBuf();
-    debugs(76, 3, HERE << clientConn << " requesting '" << upath << "'");
+    const SBuf upath = request->url.path();
+    debugs(76, 3, clientConn << " requesting '" << upath << "'");
 
-    if (0 == strcmp(upath, "/squid-internal-dynamic/netdb")) {
+    static const SBuf netdbUri("/squid-internal-dynamic/netdb");
+    static const SBuf storeDigestUri("/squid-internal-periodic/store_digest");
+    static const SBuf mgrPfx("/squid-internal-mgr/");
+
+    if (upath.cmp(netdbUri) == 0) {
         netdbBinaryExchange(entry);
-    } else if (0 == strcmp(upath, "/squid-internal-periodic/store_digest")) {
+    } else if (upath.cmp(storeDigestUri) == 0) {
 #if USE_CACHE_DIGESTS
         const char *msgbuf = "This cache is currently building its digest.\n";
 #else
 
         const char *msgbuf = "This cache does not support Cache Digests.\n";
 #endif
 
         HttpReply *reply = new HttpReply;
         reply->setHeaders(Http::scNotFound, "Not Found", "text/plain", strlen(msgbuf), squid_curtime, -2);
         entry->replaceHttpReply(reply);
         entry->append(msgbuf, strlen(msgbuf));
         entry->complete();
-    } else if (0 == strncmp(upath, "/squid-internal-mgr/", 20)) {
-        debugs(17, 2, "calling CacheManager due to URL-path /squid-internal-mgr/");
+    } else if (upath.cmp(mgrPfx, mgrPfx.length()) == 0) {
+        debugs(17, 2, "calling CacheManager due to URL-path " << mgrPfx);
         CacheManager::GetInstance()->Start(clientConn, request, entry);
     } else {
         debugObj(76, 1, "internalStart: unknown request:\n",
                  request, (ObjPackMethod) & httpRequestPack);
         err = new ErrorState(ERR_INVALID_REQ, Http::scNotFound, request);
         errorAppendEntry(entry, err);
     }
 }
 
-int
-internalCheck(const char *urlpath)
+bool
+internalCheck(const SBuf &urlPath)
 {
-    return (0 == strncmp(urlpath, "/squid-internal-", 16));
+    static const SBuf InternalPfx("/squid-internal-");
+    return urlPath.cmp(InternalPfx, InternalPfx.length()) == 0;
 }
 
-int
-internalStaticCheck(const char *urlpath)
+bool
+internalStaticCheck(const SBuf &urlPath)
 {
-    return (0 == strncmp(urlpath, "/squid-internal-static", 22));
+    static const SBuf InternalStaticPfx("/squid-internal-static");
+    return urlPath.cmp(InternalStaticPfx, InternalStaticPfx.length()) == 0;
 }
 
 /*
  * makes internal url with a given host and port (remote internal url)
  */
 char *
-internalRemoteUri(const char *host, unsigned short port, const char *dir, const char *name)
+internalRemoteUri(const char *host, unsigned short port, const char *dir, const SBuf &name)
 {
     static char lc_host[SQUIDHOSTNAMELEN];
-    assert(host && name);
+    assert(host && !name.isEmpty());
     /* convert host name to lower case */
     xstrncpy(lc_host, host, SQUIDHOSTNAMELEN);
     Tolower(lc_host);
 
     /* check for an IP address and format appropriately if found */
     Ip::Address test = lc_host;
     if ( !test.isAnyAddr() ) {
         test.toHostStr(lc_host,SQUIDHOSTNAMELEN);
     }
 
     /*
      * append the domain in order to mirror the requests with appended
      * domains
      */
 
     /* For IPv6 addresses also check for a colon */
     if (Config.appendDomain && !strchr(lc_host, '.') && !strchr(lc_host, ':'))
         strncat(lc_host, Config.appendDomain, SQUIDHOSTNAMELEN -
                 strlen(lc_host) - 1);
 
     /* build URI */
     URL tmp(AnyP::PROTO_HTTP);
     tmp.host(lc_host);
     if (port)
         tmp.port(port);
 
     static MemBuf mb;
 
     mb.reset();
     mb.appendf("http://" SQUIDSBUFPH, SQUIDSBUFPRINT(tmp.authority()));
 
     if (dir)
         mb.append(dir, strlen(dir));
 
-    mb.append(name, strlen(name));
+    mb.append(name.rawContent(), name.length());
 
     /* return a pointer to a local static buffer */
     return mb.buf;
 }
 
 /*
  * makes internal url with local host and port
  */
 char *
-internalLocalUri(const char *dir, const char *name)
+internalLocalUri(const char *dir, const SBuf &name)
 {
     return internalRemoteUri(getMyHostname(),
                              getMyPort(), dir, name);
 }
 
 const char *
 internalHostname(void)
 {
     LOCAL_ARRAY(char, host, SQUIDHOSTNAMELEN + 1);
     xstrncpy(host, getMyHostname(), SQUIDHOSTNAMELEN);
 
     /* For IPv6 addresses also check for a colon */
     if (Config.appendDomain && !strchr(host, '.') && !strchr(host, ':'))
         strncat(host, Config.appendDomain, SQUIDHOSTNAMELEN -
                 strlen(host) - 1);
 
     Tolower(host);
 
     return host;
 }

=== modified file 'src/internal.h'
--- src/internal.h	2015-01-13 07:25:36 +0000
+++ src/internal.h	2015-06-19 11:44:31 +0000
@@ -1,30 +1,32 @@
 /*
  * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
 /*
  * DEBUG: section 76    Internal Squid Object handling
  * AUTHOR: Duane, Alex, Henrik
  */
 
 #ifndef SQUID_INTERNAL_H_
 #define SQUID_INTERNAL_H_
 
 #include "comm/forward.h"
+
 class HttpRequest;
+class SBuf;
 class StoreEntry;
 
 void internalStart(const Comm::ConnectionPointer &clientConn, HttpRequest *, StoreEntry *);
-int internalCheck(const char *urlpath);
-int internalStaticCheck(const char *urlpath);
-char *internalLocalUri(const char *dir, const char *name);
-char *internalRemoteUri(const char *, unsigned short, const char *, const char *);
+bool internalCheck(const SBuf &urlPath);
+bool internalStaticCheck(const SBuf &urlPath);
+char *internalLocalUri(const char *dir, const SBuf &name);
+char *internalRemoteUri(const char *, unsigned short, const char *, const SBuf &);
 const char *internalHostname(void);
 int internalHostnameIs(const char *);
 
 #endif /* SQUID_INTERNAL_H_ */
 

=== modified file 'src/mime.cc'
--- src/mime.cc	2015-06-18 15:11:24 +0000
+++ src/mime.cc	2015-06-18 16:36:22 +0000
@@ -101,89 +101,89 @@
         else if (!strcmp(m->content_encoding, dash_str))
             (void) 0;
         else {
             /* Assume we matched /\.\w$/ and cut off the last extension */
             if ((t = strrchr(name, '.'))) {
                 *t = '\0';
             } else {
                 /* What? A encoding without a extension? */
                 m = NULL;
             }
         }
     } while (t);
 
     xfree(name);
     return m;
 }
 
 MimeIcon::MimeIcon(const char *aName) :
     icon_(aName)
 {
-    url_ = xstrdup(internalLocalUri("/squid-internal-static/icons/", icon_.c_str()));
+    url_ = xstrdup(internalLocalUri("/squid-internal-static/icons/", icon_));
 }
 
 MimeIcon::~MimeIcon()
 {
     xfree(url_);
 }
 
 void
 MimeIcon::setName(char const *aString)
 {
     xfree(url_);
     icon_ = aString;
-    url_ = xstrdup(internalLocalUri("/squid-internal-static/icons/", icon_.c_str()));
+    url_ = xstrdup(internalLocalUri("/squid-internal-static/icons/", icon_));
 }
 
 SBuf
 MimeIcon::getName() const
 {
     return icon_;
 }
 
 const SBuf
 mimeGetIcon(const char *fn)
 {
     MimeEntry *m = mimeGetEntry(fn, 1);
 
     if (!m || !m->theIcon.getName().cmp(dash_str))
         return SBuf();
 
     return m->theIcon.getName();
 }
 
 const char *
 mimeGetIconURL(const char *fn)
 {
     SBuf icon(mimeGetIcon(fn));
 
     if (icon.isEmpty())
         return null_string;
 
     if (Config.icons.use_short_names) {
         static SBuf mb;
         mb.clear();
         mb.append("/squid-internal-static/icons/");
         mb.append(icon);
         return mb.c_str();
     } else {
-        return internalLocalUri("/squid-internal-static/icons/", icon.c_str());
+        return internalLocalUri("/squid-internal-static/icons/", icon);
     }
 }
 
 const char *
 mimeGetContentType(const char *fn)
 {
     MimeEntry *m = mimeGetEntry(fn, 1);
 
     if (m == NULL)
         return NULL;
 
     if (!strcmp(m->content_type, dash_str))
         return NULL;
 
     return m->content_type;
 }
 
 const char *
 mimeGetContentEncoding(const char *fn)
 {

=== modified file 'src/peer_digest.cc'
--- src/peer_digest.cc	2015-02-04 03:22:38 +0000
+++ src/peer_digest.cc	2015-06-19 11:45:55 +0000
@@ -313,41 +313,41 @@
 
 /* ask store for a digest */
 static void
 peerDigestRequest(PeerDigest * pd)
 {
     CachePeer *p = pd->peer;
     StoreEntry *e, *old_e;
     char *url = NULL;
     const cache_key *key;
     HttpRequest *req;
     StoreIOBuffer tempBuffer;
 
     pd->req_result = NULL;
     pd->flags.requested = true;
 
     /* compute future request components */
 
     if (p->digest_url)
         url = xstrdup(p->digest_url);
     else
-        url = xstrdup(internalRemoteUri(p->host, p->http_port, "/squid-internal-periodic/", StoreDigestFileName));
+        url = xstrdup(internalRemoteUri(p->host, p->http_port, "/squid-internal-periodic/", SBuf(StoreDigestFileName)));
 
     req = HttpRequest::CreateFromUrl(url);
 
     assert(req);
 
     key = storeKeyPublicByRequest(req);
 
     debugs(72, 2, "peerDigestRequest: " << url << " key: " << storeKeyText(key));
 
     /* add custom headers */
     assert(!req->header.len);
 
     req->header.putStr(HDR_ACCEPT, StoreDigestMimeStr);
 
     req->header.putStr(HDR_ACCEPT, "text/html");
 
     if (p->login &&
             p->login[0] != '*' &&
             strcmp(p->login, "PASS") != 0 &&
             strcmp(p->login, "PASSTHRU") != 0 &&

=== modified file 'src/tests/testHttpRequest.cc'
--- src/tests/testHttpRequest.cc	2015-06-09 06:14:43 +0000
+++ src/tests/testHttpRequest.cc	2015-06-23 11:16:56 +0000
@@ -32,138 +32,138 @@
 {
     Mem::Init();
     httpHeaderInitModule();
 }
 
 /*
  * Test creating an HttpRequest object from a Url and method
  */
 void
 testHttpRequest::testCreateFromUrlAndMethod()
 {
     /* vanilla url */
     unsigned short expected_port;
     char * url = xstrdup("http://foo:90/bar");
     HttpRequest *aRequest = HttpRequest::CreateFromUrlAndMethod(url, Http::METHOD_GET);
     expected_port = 90;
     HttpRequest *nullRequest = NULL;
     CPPUNIT_ASSERT_EQUAL(expected_port, aRequest->url.port());
     CPPUNIT_ASSERT(aRequest->method == Http::METHOD_GET);
     CPPUNIT_ASSERT_EQUAL(String("foo"), String(aRequest->url.host()));
-    CPPUNIT_ASSERT_EQUAL(String("/bar"), aRequest->urlpath);
+    CPPUNIT_ASSERT_EQUAL(SBuf("/bar"), aRequest->url.path());
     CPPUNIT_ASSERT_EQUAL(AnyP::PROTO_HTTP, static_cast<AnyP::ProtocolType>(aRequest->url.getScheme()));
     CPPUNIT_ASSERT_EQUAL(String("http://foo:90/bar"), String(url));
     xfree(url);
 
     /* vanilla url, different method */
     url = xstrdup("http://foo/bar");
     aRequest = HttpRequest::CreateFromUrlAndMethod(url, Http::METHOD_PUT);
     expected_port = 80;
     CPPUNIT_ASSERT_EQUAL(expected_port, aRequest->url.port());
     CPPUNIT_ASSERT(aRequest->method == Http::METHOD_PUT);
     CPPUNIT_ASSERT_EQUAL(String("foo"), String(aRequest->url.host()));
-    CPPUNIT_ASSERT_EQUAL(String("/bar"), aRequest->urlpath);
+    CPPUNIT_ASSERT_EQUAL(SBuf("/bar"), aRequest->url.path());
     CPPUNIT_ASSERT_EQUAL(AnyP::PROTO_HTTP, static_cast<AnyP::ProtocolType>(aRequest->url.getScheme()));
     CPPUNIT_ASSERT_EQUAL(String("http://foo/bar"), String(url));
     xfree(url);
 
     /* a connect url with non-CONNECT data */
     url = xstrdup(":foo/bar");
     aRequest = HttpRequest::CreateFromUrlAndMethod(url, Http::METHOD_CONNECT);
     xfree(url);
     CPPUNIT_ASSERT_EQUAL(nullRequest, aRequest);
 
     /* a CONNECT url with CONNECT data */
     url = xstrdup("foo:45");
     aRequest = HttpRequest::CreateFromUrlAndMethod(url, Http::METHOD_CONNECT);
     expected_port = 45;
     CPPUNIT_ASSERT_EQUAL(expected_port, aRequest->url.port());
     CPPUNIT_ASSERT(aRequest->method == Http::METHOD_CONNECT);
     CPPUNIT_ASSERT_EQUAL(String("foo"), String(aRequest->url.host()));
-    CPPUNIT_ASSERT_EQUAL(String(""), aRequest->urlpath);
+    CPPUNIT_ASSERT_EQUAL(SBuf(), aRequest->url.path());
     CPPUNIT_ASSERT_EQUAL(AnyP::PROTO_NONE, static_cast<AnyP::ProtocolType>(aRequest->url.getScheme()));
     CPPUNIT_ASSERT_EQUAL(String("foo:45"), String(url));
     xfree(url);
 }
 
 /*
  * Test creating an HttpRequest object from a Url alone.
  */
 void
 testHttpRequest::testCreateFromUrl()
 {
     /* vanilla url */
     unsigned short expected_port;
     char * url = xstrdup("http://foo:90/bar");
     HttpRequest *aRequest = HttpRequest::CreateFromUrl(url);
     expected_port = 90;
     CPPUNIT_ASSERT_EQUAL(expected_port, aRequest->url.port());
     CPPUNIT_ASSERT(aRequest->method == Http::METHOD_GET);
     CPPUNIT_ASSERT_EQUAL(String("foo"), String(aRequest->url.host()));
-    CPPUNIT_ASSERT_EQUAL(String("/bar"), aRequest->urlpath);
+    CPPUNIT_ASSERT_EQUAL(SBuf("/bar"), aRequest->url.path());
     CPPUNIT_ASSERT_EQUAL(AnyP::PROTO_HTTP, static_cast<AnyP::ProtocolType>(aRequest->url.getScheme()));
     CPPUNIT_ASSERT_EQUAL(String("http://foo:90/bar"), String(url));
     xfree(url);
 }
 
 /*
  * Test BUG: URL '2000:800:45' opens host 2000 port 800 !!
  */
 void
 testHttpRequest::testIPv6HostColonBug()
 {
     unsigned short expected_port;
     char * url = NULL;
     HttpRequest *aRequest = NULL;
 
     /* valid IPv6 address without port */
     url = xstrdup("http://[2000:800::45]/foo");
     aRequest = HttpRequest::CreateFromUrlAndMethod(url, Http::METHOD_GET);
     expected_port = 80;
     CPPUNIT_ASSERT_EQUAL(expected_port, aRequest->url.port());
     CPPUNIT_ASSERT(aRequest->method == Http::METHOD_GET);
     CPPUNIT_ASSERT_EQUAL(String("[2000:800::45]"), String(aRequest->url.host()));
-    CPPUNIT_ASSERT_EQUAL(String("/foo"), aRequest->urlpath);
+    CPPUNIT_ASSERT_EQUAL(SBuf("/foo"), aRequest->url.path());
     CPPUNIT_ASSERT_EQUAL(AnyP::PROTO_HTTP, static_cast<AnyP::ProtocolType>(aRequest->url.getScheme()));
     CPPUNIT_ASSERT_EQUAL(String("http://[2000:800::45]/foo"), String(url));
     xfree(url);
 
     /* valid IPv6 address with port */
     url = xstrdup("http://[2000:800::45]:90/foo");
     aRequest = HttpRequest::CreateFromUrlAndMethod(url, Http::METHOD_GET);
     expected_port = 90;
     CPPUNIT_ASSERT_EQUAL(expected_port, aRequest->url.port());
     CPPUNIT_ASSERT(aRequest->method == Http::METHOD_GET);
     CPPUNIT_ASSERT_EQUAL(String("[2000:800::45]"), String(aRequest->url.host()));
-    CPPUNIT_ASSERT_EQUAL(String("/foo"), aRequest->urlpath);
+    CPPUNIT_ASSERT_EQUAL(SBuf("/foo"), aRequest->url.path());
     CPPUNIT_ASSERT_EQUAL(AnyP::PROTO_HTTP, static_cast<AnyP::ProtocolType>(aRequest->url.getScheme()));
     CPPUNIT_ASSERT_EQUAL(String("http://[2000:800::45]:90/foo"), String(url));
     xfree(url);
 
     /* IPv6 address as invalid (bug trigger) */
     url = xstrdup("http://2000:800::45/foo");
     aRequest = HttpRequest::CreateFromUrlAndMethod(url, Http::METHOD_GET);
     expected_port = 80;
     CPPUNIT_ASSERT_EQUAL(expected_port, aRequest->url.port());
     CPPUNIT_ASSERT(aRequest->method == Http::METHOD_GET);
     CPPUNIT_ASSERT_EQUAL(String("[2000:800::45]"), String(aRequest->url.host()));
-    CPPUNIT_ASSERT_EQUAL(String("/foo"), aRequest->urlpath);
+    CPPUNIT_ASSERT_EQUAL(SBuf("/foo"), aRequest->url.path());
     CPPUNIT_ASSERT_EQUAL(AnyP::PROTO_HTTP, static_cast<AnyP::ProtocolType>(aRequest->url.getScheme()));
     CPPUNIT_ASSERT_EQUAL(String("http://2000:800::45/foo"), String(url));
     xfree(url);
 }
 
 void
 testHttpRequest::testSanityCheckStartLine()
 {
     MemBuf input;
     PrivateHttpRequest engine;
     Http::StatusCode error = Http::scNone;
     size_t hdr_len;
     input.init();
 
     // a valid request line
     input.append("GET / HTTP/1.1\n\n", 16);
     hdr_len = headersEnd(input.content(), input.contentSize());
     CPPUNIT_ASSERT(engine.doSanityCheckStartLine(input.content(), hdr_len, &error) );
     CPPUNIT_ASSERT_EQUAL(error, Http::scNone);
     input.reset();

=== modified file 'src/url.cc'
--- src/url.cc	2015-06-09 06:14:43 +0000
+++ src/url.cc	2015-06-15 10:46:18 +0000
@@ -27,56 +27,75 @@
 static const char valid_hostname_chars_u[] =
     "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
     "abcdefghijklmnopqrstuvwxyz"
     "0123456789-._"
     "[:]"
     ;
 static const char valid_hostname_chars[] =
     "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
     "abcdefghijklmnopqrstuvwxyz"
     "0123456789-."
     "[:]"
     ;
 
 const SBuf &
 URL::Asterisk()
 {
     static SBuf star("*");
     return star;
 }
 
+const SBuf &
+URL::SlashPath()
+{
+    static SBuf slash("/");
+    return slash;
+}
+
 void
 URL::host(const char *src)
 {
     hostAddr_.setEmpty();
     hostAddr_ = src;
     if (hostAddr_.isAnyAddr()) {
         xstrncpy(host_, src, sizeof(host_));
         hostIsNumeric_ = false;
     } else {
         hostAddr_.toHostStr(host_, sizeof(host_));
         debugs(23, 3, "given IP: " << hostAddr_);
         hostIsNumeric_ = 1;
     }
     touch();
 }
 
+const SBuf &
+URL::path() const
+{
+    // RFC 3986 section 3.3 says path can be empty (path-abempty).
+    // RFC 7230 sections 2.7.3, 5.3.1, 5.7.2 - says path cannot be empty, default to "/"
+    // at least when sending and using. We must still accept path-abempty as input.
+    if (path_.isEmpty() && (scheme_ == AnyP::PROTO_HTTP || scheme_ == AnyP::PROTO_HTTPS))
+        return SlashPath();
+
+    return path_;
+}
+
 void
 urlInitialize(void)
 {
     debugs(23, 5, "urlInitialize: Initializing...");
     /* this ensures that the number of protocol strings is the same as
      * the enum slots allocated because the last enum is always 'MAX'.
      */
     assert(strcmp(AnyP::ProtocolType_str[AnyP::PROTO_MAX], "MAX") == 0);
     /*
      * These test that our matchDomainName() function works the
      * way we expect it to.
      */
     assert(0 == matchDomainName("foo.com", "foo.com"));
     assert(0 == matchDomainName(".foo.com", "foo.com"));
     assert(0 == matchDomainName("foo.com", ".foo.com"));
     assert(0 == matchDomainName(".foo.com", ".foo.com"));
     assert(0 == matchDomainName("x.foo.com", ".foo.com"));
     assert(0 != matchDomainName("x.foo.com", "foo.com"));
     assert(0 != matchDomainName("foo.com", "x.foo.com"));
     assert(0 != matchDomainName("bar.com", "foo.com"));
@@ -469,96 +488,96 @@
         authorityHttp_ = authorityWithPort_;
 
         // authorityForm_ only has :port if it is non-default
         authorityWithPort_.appendf(":%u",port());
         if (port() != getScheme().defaultPort())
             authorityHttp_ = authorityWithPort_;
     }
 
     return requirePort ? authorityWithPort_ : authorityHttp_;
 }
 
 const char *
 urlCanonical(HttpRequest * request)
 {
     LOCAL_ARRAY(char, urlbuf, MAX_URL);
 
     if (request->canonical)
         return request->canonical;
 
     if (request->url.getScheme() == AnyP::PROTO_URN) {
-        snprintf(urlbuf, MAX_URL, "urn:" SQUIDSTRINGPH,
-                 SQUIDSTRINGPRINT(request->urlpath));
+        snprintf(urlbuf, MAX_URL, "urn:" SQUIDSBUFPH,
+                 SQUIDSBUFPRINT(request->url.path()));
     } else {
         SBuf authorityForm;
         switch (request->method.id()) {
 
         case Http::METHOD_CONNECT:
             authorityForm = request->url.authority(true); // host:port
             snprintf(urlbuf, MAX_URL, SQUIDSBUFPH, SQUIDSBUFPRINT(authorityForm));
             break;
 
         default: {
             authorityForm = request->url.authority(); // host[:port]
-            snprintf(urlbuf, MAX_URL, "%s://" SQUIDSBUFPH "%s" SQUIDSBUFPH SQUIDSTRINGPH,
+            snprintf(urlbuf, MAX_URL, "%s://" SQUIDSBUFPH "%s" SQUIDSBUFPH SQUIDSBUFPH,
                      request->url.getScheme().c_str(),
                      SQUIDSBUFPRINT(request->url.userInfo()),
                      !request->url.userInfo().isEmpty() ? "@" : "",
                      SQUIDSBUFPRINT(authorityForm),
-                     SQUIDSTRINGPRINT(request->urlpath));
+                     SQUIDSBUFPRINT(request->url.path()));
         }
         }
     }
 
     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);
     char *t;
 
     if (request->url.getScheme() == AnyP::PROTO_URN) {
-        snprintf(buf, MAX_URL, "urn:" SQUIDSTRINGPH,
-                 SQUIDSTRINGPRINT(request->urlpath));
+        snprintf(buf, MAX_URL, "urn:" SQUIDSBUFPH,
+                 SQUIDSBUFPRINT(request->url.path()));
     } else {
         SBuf authorityForm;
         switch (request->method.id()) {
 
         case Http::METHOD_CONNECT:
             authorityForm = request->url.authority(true); // host:port
             snprintf(buf, MAX_URL, SQUIDSBUFPH, SQUIDSBUFPRINT(authorityForm));
             break;
 
         default: {
             authorityForm = request->url.authority(); // host[:port]
-            snprintf(buf, MAX_URL, "%s://" SQUIDSBUFPH "%s" SQUIDSBUFPH SQUIDSTRINGPH,
+            snprintf(buf, MAX_URL, "%s://" SQUIDSBUFPH "%s" SQUIDSBUFPH SQUIDSBUFPH,
                      request->url.getScheme().c_str(),
                      SQUIDSBUFPRINT(request->url.userInfo()),
                      !request->url.userInfo().isEmpty() ? "@" : "",
                      SQUIDSBUFPRINT(authorityForm),
-                     SQUIDSTRINGPRINT(request->urlpath));
+                     SQUIDSBUFPRINT(request->url.path()));
 
             // strip arguments AFTER a question-mark
             if (Config.onoff.strip_query_terms)
                 if ((t = strchr(buf, '?')))
                     *(++t) = '\0';
         }
         } // switch
     }
 
     if (stringHasCntl(buf))
         xstrncpy(buf, rfc1738_escape_unescaped(buf), MAX_URL);
 
     return buf;
 }
 
 /**
  * Yet another alternative to urlCanonical.
  * This one adds the https:// parts to Http::METHOD_CONNECT URL
  * for use in error page outputs.
  * Luckily we can leverage the others instead of duplicating.
@@ -611,70 +630,75 @@
  * It is assumed that you have already ensured that the URL is relative.
  *
  * If NULL is returned it is an indication that the method in use in the
  * request does not distinguish between relative and absolute and you should
  * use the url unchanged.
  *
  * If non-NULL is returned, it is up to the caller to free the resulting
  * memory using safe_free().
  */
 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));
+        snprintf(urlbuf, MAX_URL, "urn:" SQUIDSBUFPH,
+                 SQUIDSBUFPRINT(req->url.path()));
         return (urlbuf);
     }
 
     SBuf authorityForm = req->url.authority(); // host[:port]
     size_t urllen = snprintf(urlbuf, MAX_URL, "%s://" SQUIDSBUFPH "%s" SQUIDSBUFPH,
                              req->url.getScheme().c_str(),
                              SQUIDSBUFPRINT(req->url.userInfo()),
                              !req->url.userInfo().isEmpty() ? "@" : "",
                              SQUIDSBUFPRINT(authorityForm));
 
+    // if the first char is '/' assume its a relative path
+    // XXX: this breaks on scheme-relative URLs,
+    // but we should not see those outside ESI, and rarely there.
     if (relUrl[0] == '/') {
         strncpy(&urlbuf[urllen], relUrl, MAX_URL - urllen - 1);
     } else {
-        const char *path = req->urlpath.termedBuf();
-        const char *last_slash = strrchr(path, '/');
+        SBuf path = req->url.path();
+        SBuf::size_type lastSlashPos = path.rfind('/');
 
-        if (last_slash == NULL) {
+        if (lastSlashPos == SBuf::npos) {
+            // replace the whole path with the given bit(s)
             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;
+            // replace only the last (file?) segment with the given bit(s)
+            ++lastSlashPos;
+            if (lastSlashPos > MAX_URL - urllen - 1) {
+                // XXX: crops bits in the middle of the combined URL.
+                lastSlashPos = MAX_URL - urllen - 1;
             }
-            strncpy(&urlbuf[urllen], path, pathlen);
-            urllen += pathlen;
+            strncpy(&urlbuf[urllen], path.rawContent(), lastSlashPos);
+            urllen += lastSlashPos;
             if (urllen + 1 < MAX_URL) {
                 strncpy(&urlbuf[urllen], relUrl, MAX_URL - urllen - 1);
             }
         }
     }
 
     return (urlbuf);
 }
 
 int
 matchDomainName(const char *h, const char *d, bool honorWildcards)
 {
     int dl;
     int hl;
 
     while ('.' == *h)
         ++h;
 
     hl = strlen(h);
 
@@ -753,41 +777,41 @@
 urlCheckRequest(const HttpRequest * r)
 {
     int rc = 0;
     /* protocol "independent" methods
      *
      * actually these methods are specific to HTTP:
      * they are methods we recieve on our HTTP port,
      * and if we had a FTP listener would not be relevant
      * there.
      *
      * So, we should delegate them to HTTP. The problem is that we
      * do not have a default protocol from the client side of HTTP.
      */
 
     if (r->method == Http::METHOD_CONNECT)
         return 1;
 
     // we support OPTIONS and TRACE directed at us (with a 501 reply, for now)
     // we also support forwarding OPTIONS and TRACE, except for the *-URI ones
     if (r->method == Http::METHOD_OPTIONS || r->method == Http::METHOD_TRACE)
-        return (r->header.getInt64(HDR_MAX_FORWARDS) == 0 || URL::Asterisk().cmp(r->urlpath.rawBuf(), r->urlpath.size()) != 0);
+        return (r->header.getInt64(HDR_MAX_FORWARDS) == 0 || URL::Asterisk().cmp(r->url.path()) != 0);
 
     if (r->method == Http::METHOD_PURGE)
         return 1;
 
     /* does method match the protocol? */
     switch (r->url.getScheme()) {
 
     case AnyP::PROTO_URN:
 
     case AnyP::PROTO_HTTP:
 
     case AnyP::PROTO_CACHE_OBJECT:
         rc = 1;
         break;
 
     case AnyP::PROTO_FTP:
 
         if (r->method == Http::METHOD_PUT)
             rc = 1;
 

=== modified file 'src/urn.cc'
--- src/urn.cc	2015-05-26 18:12:08 +0000
+++ src/urn.cc	2015-06-15 11:06:49 +0000
@@ -18,45 +18,43 @@
 #include "icmp/net_db.h"
 #include "MemBuf.h"
 #include "mime_header.h"
 #include "RequestFlags.h"
 #include "SquidTime.h"
 #include "Store.h"
 #include "StoreClient.h"
 #include "tools.h"
 #include "URL.h"
 #include "urn.h"
 
 #define URN_REQBUF_SZ   4096
 
 class UrnState : public StoreClient
 {
     CBDATA_CLASS(UrnState);
 
 public:
     void created (StoreEntry *newEntry);
     void start (HttpRequest *, StoreEntry *);
-    char *getHost (String &urlpath);
+    char *getHost(SBuf &urlpath);
     void setUriResFromRequest(HttpRequest *);
     bool RequestNeedsMenu(HttpRequest *r);
-    void updateRequestURL(HttpRequest *r, char const *newPath, const size_t newPath_len);
-    void createUriResRequest (String &uri);
 
     virtual ~UrnState();
 
     StoreEntry *entry;
     store_client *sc;
     StoreEntry *urlres_e;
     HttpRequest::Pointer request;
     HttpRequest::Pointer urlres_r;
 
     struct {
         bool force_menu;
     } flags;
     char reqbuf[URN_REQBUF_SZ];
     int reqofs;
 
 private:
     char *urlres;
 };
 
 typedef struct {
@@ -111,94 +109,80 @@
 
         if (u->rtt > min_rtt && min_rtt != 0)
             continue;
 
         min_rtt = u->rtt;
 
         min_u = u;
     }
 
     if (rtt_ret)
         *rtt_ret = min_rtt;
 
     debugs(52, DBG_IMPORTANT, "urnFindMinRtt: Returning '" <<
            (min_u ? min_u->url : "NONE") << "' RTT " <<
            min_rtt  );
 
     return min_u;
 }
 
 char *
-UrnState::getHost (String &urlpath)
+UrnState::getHost(SBuf &urlpath)
 {
     char * result;
     size_t p;
 
     /** FIXME: this appears to be parsing the URL. *very* badly. */
     /*   a proper encapsulated URI/URL type needs to clear this up. */
-    if ((p=urlpath.find(':')) != String::npos) {
-        result=xstrndup(urlpath.rawBuf(),p-1);
+    if ((p=urlpath.find(':')) != SBuf::npos) {
+        result=xstrndup(urlpath.rawContent(),p-1);
     } else {
-        result = xstrndup(urlpath.rawBuf(),urlpath.size());
+        result = xstrndup(urlpath.rawContent(),urlpath.length());
     }
     return result;
 }
 
 bool
 UrnState::RequestNeedsMenu(HttpRequest *r)
 {
-    if (r->urlpath.size() < 5)
+    if (r->url.path().length() < 5)
         return false;
     //now we're sure it's long enough
-    return strncasecmp(r->urlpath.rawBuf(), "menu.", 5) == 0;
+    return r->url.path().caseCmp("menu.", 5) == 0;
 }
 
 void
-UrnState::updateRequestURL(HttpRequest *r, char const *newPath, const size_t newPath_len)
+UrnState::setUriResFromRequest(HttpRequest *r)
 {
-    char *new_path = xstrndup (newPath, newPath_len);
-    r->urlpath = new_path;
-    xfree(new_path);
-}
+    if (RequestNeedsMenu(r)) {
+        r->url.path(r->url.path().substr(5)); // strip prefix "menu."
+        flags.force_menu = true;
+    }
 
-void
-UrnState::createUriResRequest (String &uri)
-{
+    SBuf uri = r->url.path();
     LOCAL_ARRAY(char, local_urlres, 4096);
-    char *host = getHost (uri);
-    snprintf(local_urlres, 4096, "http://%s/uri-res/N2L?urn:" SQUIDSTRINGPH,
-             host, SQUIDSTRINGPRINT(uri));
+    char *host = getHost(uri);
+    snprintf(local_urlres, 4096, "http://%s/uri-res/N2L?urn:" SQUIDSBUFPH, host, SQUIDSBUFPRINT(uri));
     safe_free(host);
     safe_free(urlres);
     urlres = xstrdup(local_urlres);
     urlres_r = HttpRequest::CreateFromUrl(urlres);
-}
-
-void
-UrnState::setUriResFromRequest(HttpRequest *r)
-{
-    if (RequestNeedsMenu(r)) {
-        updateRequestURL(r, r->urlpath.rawBuf() + 5, r->urlpath.size() - 5 );
-        flags.force_menu = true;
-    }
-
-    createUriResRequest (r->urlpath);
 
     if (urlres_r == NULL) {
         debugs(52, 3, "urnStart: Bad uri-res URL " << urlres);
         ErrorState *err = new ErrorState(ERR_URN_RESOLVE, Http::scNotFound, r);
         err->url = urlres;
         urlres = NULL;
         errorAppendEntry(entry, err);
         return;
     }
 
     urlres_r->header.putStr(HDR_ACCEPT, "text/plain");
 }
 
 void
 UrnState::start(HttpRequest * r, StoreEntry * e)
 {
     debugs(52, 3, "urnStart: '" << e->url() << "'" );
     entry = e;
     request = r;
 

=== modified file 'src/whois.cc'
--- src/whois.cc	2015-01-13 07:25:36 +0000
+++ src/whois.cc	2015-06-15 11:09:45 +0000
@@ -39,57 +39,54 @@
     bool dataWritten;
 };
 
 CBDATA_CLASS_INIT(WhoisState);
 
 static CLCB whoisClose;
 static CTCB whoisTimeout;
 static IOCB whoisReadReply;
 
 /* PUBLIC */
 
 static void
 whoisWriteComplete(const Comm::ConnectionPointer &, char *buf, size_t, Comm::Flag, int, void *)
 {
     xfree(buf);
 }
 
 void
 whoisStart(FwdState * fwd)
 {
-    char *buf;
-    size_t l;
     WhoisState *p = new WhoisState;
     p->request = fwd->request;
     p->entry = fwd->entry;
     p->fwd = fwd;
     p->dataWritten = false;
 
     p->entry->lock("whoisStart");
     comm_add_close_handler(fwd->serverConnection()->fd, whoisClose, p);
 
-    l = p->request->urlpath.size() + 3;
+    size_t l = p->request->url.path().length() + 3;
+    char *buf = (char *)xmalloc(l);
 
-    buf = (char *)xmalloc(l);
-
-    String str_print=p->request->urlpath.substr(1,p->request->urlpath.size());
-    snprintf(buf, l, SQUIDSTRINGPH"\r\n", SQUIDSTRINGPRINT(str_print));
+    SBuf str_print = p->request->url.path().substr(1);
+    snprintf(buf, l, SQUIDSBUFPH "\r\n", SQUIDSBUFPRINT(str_print));
 
     AsyncCall::Pointer writeCall = commCbCall(5,5, "whoisWriteComplete",
                                    CommIoCbPtrFun(whoisWriteComplete, p));
     Comm::Write(fwd->serverConnection(), buf, strlen(buf), writeCall, NULL);
     AsyncCall::Pointer readCall = commCbCall(5,4, "whoisReadReply",
                                   CommIoCbPtrFun(whoisReadReply, p));
     comm_read(fwd->serverConnection(), p->buf, BUFSIZ, readCall);
     AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "whoisTimeout",
                                      CommTimeoutCbPtrFun(whoisTimeout, p));
     commSetConnTimeout(fwd->serverConnection(), Config.Timeout.read, timeoutCall);
 }
 
 /* PRIVATE */
 
 static void
 whoisTimeout(const CommTimeoutCbParams &io)
 {
     WhoisState *p = static_cast<WhoisState *>(io.data);
     debugs(75, 3, HERE << io.conn << ", URL " << p->entry->url());
     io.conn->close();



More information about the squid-dev mailing list