On 28/04/2014 2:39 p.m., Amos Jeffries wrote:
> On 28/04/2014 3:51 a.m., Kinkie wrote:
>> On Sun, Apr 27, 2014 at 4:52 PM, Amos Jeffries wrote:
>>> This continues the bug 1961 redesign of URL handling.
>>
>> Hi,
>>
>> + SBuf tmp = checklist->request->url.userInfo();
>> + // NOTE: unescape works in-place and may decrease the c-string
>> length, never increase.
>> + rfc1738_unescape(const_cast<char*>(tmp.c_str()));
>>
>> is troublesome: you are most likely trampling the userinfo data as
>> rfc1738_unescape is acting on a shared MemBlob (shared by tmp and
>> userInfo at least) and c_str() doesn't guarantee a COW. The best way
>> to do this is by defining a SBuf rfc1738_unescape (const SBuf &) which
>> copies the param if needed.
>>
>>
>> + if (colonPos != 0) {
>>
>> shouldn't this be if (colonPos != SBuf::npos) ?
>
> No. The edge cases of user-info is that the colon may be the first
> character to signal missing username, and if ':' is not found at all the
> entire string is username.
> So if colonPos==0 then we dont extract one, but all other values
> regardless including npos we do.
>
> The edges case for password is that if colon is missing entirely we have
> none, all other cases have a password. So the password extraction only
> happens on colonPos!=npos.
>
> Special case is that 0-length username is invalid (unlike absent
> password). The colonPos==0 case also helps omit replacing any existing
> username with an empty string - whereas the empty password case *does*
> replace the pasword with empty string.
>
To clarify the cases are:
case u:p@...
- gives new username 'u' and new password 'p'
case u:@...
- gives new username 'u' and clears previous password (sets new 0-length)
case u@...
- gives new username 'u' and retains previous password
case :p@...
- retains previous username and gives new password 'p'
case :@...
- retains previous username and clears previous password (sets new
0-length)
case @...
- invalid. retain previous username and password.
>
>>
>> + memcpy(user, userName.rawContent(), userName.length());
>> + user[userName.length()] = '\0';
>>
>> you could use
>> SBuf::size_type upTo = min(userName.length(), sizeof(user));
>> userName.copy(user, upto);
>> user[upto]='\0';
>>
>> Same for
>> + memcpy(loginbuf,
>> request->url.userInfo().rawContent(), len);
>>
>
> Thank you for tha pointer. Adding a slight variant, copy() does min()
> internally and returs the size used so we can drop another line.
>
>>
>>
>> + static char result[MAX_URL*2]; // should be big enough
>> for a single URI segment
>> That conditional is worrisome. Is there any risk of the result not
>> being big enough?
>>
>
> Oops, small bug there should have been " < sizeof(result)-1" instead of
> "> 0".
>
I have a better answer for this now...
The URL parser used a MAX_URL length array to parse the originally
received user-info/login field so the raw data cannot exceed MAX_URL.
The *2 is because base64 may double the length on us.
Patch attached that covers all the above.
Amos
=== modified file 'src/HttpRequest.cc'
--- src/HttpRequest.cc 2014-04-27 07:59:17 +0000
+++ src/HttpRequest.cc 2014-05-09 17:45:08 +0000
@@ -75,41 +75,40 @@
HttpRequest::~HttpRequest()
{
clean();
debugs(93,7, HERE << "destructed, this=" << this);
}
void
HttpRequest::initHTTP(const HttpRequestMethod& aMethod, AnyP::ProtocolType
aProtocol, const char *aUrlpath)
{
method = aMethod;
url.setScheme(aProtocol);
urlpath = aUrlpath;
}
void
HttpRequest::init()
{
method = Http::METHOD_NONE;
url.clear();
urlpath = NULL;
- login[0] = '\0';
host[0] = '\0';
host_is_numeric = -1;
#if USE_AUTH
auth_user_request = NULL;
#endif
port = 0;
canonical = NULL;
memset(&flags, '\0', sizeof(flags));
range = NULL;
ims = -1;
imslen = 0;
lastmod = -1;
client_addr.setEmpty();
my_addr.setEmpty();
body_pipe = NULL;
// hier
dnsWait = -1;
errType = ERR_NONE;
errDetail = ERR_DETAIL_NONE;
peer_login = NULL; // not allocated/deallocated by this class
@@ -190,41 +189,41 @@
void
HttpRequest::reset()
{
clean();
init();
}
HttpRequest *
HttpRequest::clone() const
{
HttpRequest *copy = new HttpRequest(method, url.getScheme(),
urlpath.termedBuf());
// TODO: move common cloning clone to Msg::copyTo() or copy ctor
copy->header.append(&header);
copy->hdrCacheInit();
copy->hdr_sz = hdr_sz;
copy->http_ver = http_ver;
copy->pstate = pstate; // TODO: should we assert a specific state here?
copy->body_pipe = body_pipe;
- strncpy(copy->login, login, sizeof(login)); // MAX_LOGIN_SZ
+ copy->url.userInfo(url.userInfo());
strncpy(copy->host, host, sizeof(host)); // SQUIDHOSTNAMELEN
copy->host_addr = host_addr;
copy->port = port;
// urlPath handled in ctor
copy->canonical = canonical ? xstrdup(canonical) : NULL;
// range handled in hdrCacheInit()
copy->ims = ims;
copy->imslen = imslen;
copy->hier = hier; // Is it safe to copy? Should we?
copy->errType = errType;
// XXX: what to do with copy->peer_login?
copy->lastmod = lastmod;
copy->etag = etag;
copy->vary_headers = vary_headers ? xstrdup(vary_headers) : NULL;
// XXX: what to do with copy->peer_domain?
=== modified file 'src/HttpRequest.h'
--- src/HttpRequest.h 2014-04-27 07:59:17 +0000
+++ src/HttpRequest.h 2014-05-09 17:45:08 +0000
@@ -121,43 +121,41 @@
/// Returns possibly nil history, creating it if icap logging is enabled
Adaptation::Icap::History::Pointer icapHistory() const;
#endif
void recordLookup(const DnsLookupDetails &detail);
/// sets error detail if no earlier detail was available
void detailError(err_type aType, int aDetail);
/// clear error details, useful for retries/repeats
void clearError();
protected:
void clean();
void init();
public:
HttpRequestMethod method;
// TODO expand to include all URI parts
- URL url; ///< the request URI (scheme only)
-
- char login[MAX_LOGIN_SZ];
+ URL url; ///< the request URI (scheme and userinfo only)
private:
char host[SQUIDHOSTNAMELEN];
int host_is_numeric;
#if USE_ADAPTATION
mutable Adaptation::History::Pointer adaptHistory_; ///< per-HTTP
transaction info
#endif
#if ICAP_CLIENT
mutable Adaptation::Icap::History::Pointer icapHistory_; ///< per-HTTP
transaction info
#endif
public:
Ip::Address host_addr;
#if USE_AUTH
Auth::UserRequest::Pointer auth_user_request;
#endif
unsigned short port;
String urlpath;
=== modified file 'src/URL.h'
--- src/URL.h 2014-04-27 07:59:17 +0000
+++ src/URL.h 2014-05-09 17:45:08 +0000
@@ -16,84 +16,88 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
*
*/
#ifndef SQUID_SRC_URL_H
#define SQUID_SRC_URL_H
#include "anyp/UriScheme.h"
#include "MemPool.h"
+#include "SBuf.h"
/**
- \ingroup POD
- *
* The URL class represents a Uniform Resource Location
*/
class URL
{
public:
MEMPROXY_CLASS(URL);
URL() : scheme_() {}
URL(AnyP::UriScheme const &aScheme) : scheme_(aScheme) {}
void clear() {
scheme_=AnyP::PROTO_NONE;
}
AnyP::UriScheme const & getScheme() const {return scheme_;}
/// convert the URL scheme to that given
void setScheme(const AnyP::ProtocolType &p) {scheme_=p;}
+ void userInfo(const SBuf &s) {userInfo_=s;}
+ const SBuf &userInfo(void) const {return userInfo_;}
+
private:
/**
\par
* The scheme of this URL. This has the 'type code' smell about it.
* In future we may want to make the methods that dispatch based on
* the scheme virtual and have a class per protocol.
\par
* On the other hand, having Protocol as an explicit concept is useful,
* see for instance the ACLProtocol acl type. One way to represent this
* is to have one prototype URL with no host etc for each scheme,
* another is to have an explicit scheme class, and then each URL class
* could be a subclass of the scheme. Another way is one instance of
* a AnyP::UriScheme class instance for each URL scheme we support, and
one URL
* class for each manner of treating the scheme : a Hierarchical URL, a
* non-hierarchical URL etc.
\par
* Deferring the decision, its a type code for now. RBC 20060507.
\par
* In order to make taking any of these routes easy, scheme is private
* and immutable, only settable at construction time,
*/
AnyP::UriScheme scheme_;
+
+ SBuf userInfo_; // aka 'URL-login'
};
MEMPROXY_CLASS_INLINE(URL);
class HttpRequest;
class HttpRequestMethod;
AnyP::ProtocolType urlParseProtocol(const char *, const char *e = NULL);
void urlInitialize(void);
HttpRequest *urlParse(const HttpRequestMethod&, char *, HttpRequest *request =
NULL);
const char *urlCanonical(HttpRequest *);
char *urlCanonicalClean(const HttpRequest *);
const char *urlCanonicalFakeHttps(const HttpRequest * request);
bool urlIsRelative(const char *);
char *urlMakeAbsolute(const HttpRequest *, const char *);
char *urlRInternal(const char *host, unsigned short port, const char *dir,
const char *name);
char *urlInternal(const char *dir, const char *name);
int matchDomainName(const char *host, const char *domain);
int urlCheckRequest(const HttpRequest *);
int urlDefaultPort(AnyP::ProtocolType p);
=== modified file 'src/acl/UrlLogin.cc'
--- src/acl/UrlLogin.cc 2013-10-25 00:13:46 +0000
+++ src/acl/UrlLogin.cc 2014-05-09 17:45:08 +0000
@@ -21,36 +21,43 @@
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
*
*/
#include "squid.h"
#include "acl/Checklist.h"
#include "acl/RegexData.h"
#include "acl/UrlLogin.h"
#include "HttpRequest.h"
#include "rfc1738.h"
int
-ACLUrlLoginStrategy::match (ACLData<char const *> * &data, ACLFilledChecklist
*checklist, ACLFlags &)
+ACLUrlLoginStrategy::match(ACLData<char const *> * &data, ACLFilledChecklist
*checklist, ACLFlags &)
{
- char *esc_buf = xstrdup(checklist->request->login);
- rfc1738_unescape(esc_buf);
- int result = data->match(esc_buf);
- safe_free(esc_buf);
+ if (checklist->request->url.userInfo().isEmpty()) {
+ debugs(28, 5, "URL has no user-info details. cannot match");
+ return 0; // nothing can match
+ }
+
+ static char str[MAX_URL]; // should be big enough for a single URI segment
+
+ SBuf::size_type len = checklist->request->url.userInfo().copy(str,
sizeof(str)-1);
+ str[len] = '\0';
+ rfc1738_unescape(str);
+ int result = data->match(str);
return result;
}
ACLUrlLoginStrategy *
ACLUrlLoginStrategy::Instance()
{
return &Instance_;
}
ACLUrlLoginStrategy ACLUrlLoginStrategy::Instance_;
=== modified file 'src/client_side.cc'
--- src/client_side.cc 2014-04-27 07:59:17 +0000
+++ src/client_side.cc 2014-05-09 17:45:08 +0000
@@ -2718,43 +2718,40 @@
} else
request->flags.spoofClientIp = false;
}
if (internalCheck(request->urlpath.termedBuf())) {
if (internalHostnameIs(request->GetHost()) && request->port ==
getMyPort()) {
debugs(33, 2, "internal URL found: " << request->url.getScheme()
<< "://" << request->GetHost() <<
':' << request->port);
http->flags.internal = true;
} else if (Config.onoff.global_internal_static &&
internalStaticCheck(request->urlpath.termedBuf())) {
debugs(33, 2, "internal URL found: " << request->url.getScheme()
<< "://" << request->GetHost() <<
':' << request->port << " (global_internal_static on)");
request->SetHost(internalHostname());
request->port = getMyPort();
http->flags.internal = true;
} else
debugs(33, 2, "internal URL found: " << request->url.getScheme()
<< "://" << request->GetHost() <<
':' << request->port << " (not this proxy)");
}
- if (http->flags.internal)
- request->login[0] = '\0';
-
request->flags.internal = http->flags.internal;
setLogUri (http, urlCanonicalClean(request.getRaw()));
request->client_addr = conn->clientConnection->remote; // XXX: remove
reuest->client_addr member.
#if FOLLOW_X_FORWARDED_FOR
// indirect client gets stored here because it is an HTTP header result
(from X-Forwarded-For:)
// not a details about teh TCP connection itself
request->indirect_client_addr = conn->clientConnection->remote;
#endif /* FOLLOW_X_FORWARDED_FOR */
request->my_addr = conn->clientConnection->local;
request->myportname = conn->port->name;
request->http_ver = http_ver;
// Link this HttpRequest to ConnStateData relatively early so the
following complex handling can use it
// TODO: this effectively obsoletes a lot of conn->FOO copying. That needs
cleaning up later.
request->clientConnectionManager = conn;
if (request->header.chunked()) {
chunked = true;
} else if (request->header.has(HDR_TRANSFER_ENCODING)) {
const String te = request->header.getList(HDR_TRANSFER_ENCODING);
=== modified file 'src/client_side_request.cc'
--- src/client_side_request.cc 2014-04-27 07:59:17 +0000
+++ src/client_side_request.cc 2014-05-09 17:45:08 +0000
@@ -1141,41 +1141,41 @@
http->range_iter.pos = request->range->begin();
http->range_iter.end = request->range->end();
http->range_iter.valid = true;
}
}
/* Only HEAD and GET requests permit a Range or Request-Range header.
* If these headers appear on any other type of request, delete them now.
*/
else {
req_hdr->delById(HDR_RANGE);
req_hdr->delById(HDR_REQUEST_RANGE);
request->ignoreRange("neither HEAD nor GET");
}
if (req_hdr->has(HDR_AUTHORIZATION))
request->flags.auth = true;
clientCheckPinning(http);
- if (request->login[0] != '\0')
+ if (!request->url.userInfo().isEmpty())
request->flags.auth = true;
if (req_hdr->has(HDR_VIA)) {
String s = req_hdr->getList(HDR_VIA);
/*
* ThisCache cannot be a member of Via header, "1.1 ThisCache" can.
* Note ThisCache2 has a space prepended to the hostname so we don't
* accidentally match super-domains.
*/
if (strListIsSubstr(&s, ThisCache2, ',')) {
debugObj(33, 1, "WARNING: Forwarding loop detected for:\n",
request, (ObjPackMethod) & httpRequestPack);
request->flags.loopDetected = true;
}
#if USE_FORW_VIA_DB
fvdbCountVia(s.termedBuf());
#endif
=== modified file 'src/ftp.cc'
--- src/ftp.cc 2014-05-07 14:40:05 +0000
+++ src/ftp.cc 2014-05-09 17:45:08 +0000
@@ -43,41 +43,40 @@
#include "fde.h"
#include "FwdState.h"
#include "html_quote.h"
#include "HttpHdrContRange.h"
#include "HttpHeader.h"
#include "HttpHeaderRange.h"
#include "HttpReply.h"
#include "HttpRequest.h"
#include "ip/tools.h"
#include "Mem.h"
#include "MemBuf.h"
#include "mime.h"
#include "rfc1738.h"
#include "Server.h"
#include "SquidConfig.h"
#include "SquidString.h"
#include "SquidTime.h"
#include "StatCounters.h"
#include "Store.h"
#include "tools.h"
-#include "URL.h"
#include "wordlist.h"
#if USE_DELAY_POOLS
#include "DelayPools.h"
#include "MemObject.h"
#endif
#if HAVE_ERRNO_H
#include <errno.h>
#endif
/**
\defgroup ServerProtocolFTPInternal Server-Side FTP Internals
\ingroup ServerProtocolFTPAPI
*/
/// \ingroup ServerProtocolFTPInternal
static const char *const crlf = "\r\n";
#define CTRL_BUFLEN 1024
@@ -219,41 +218,40 @@
size_t offset;
wordlist *message;
char *last_command;
char *last_reply;
int replycode;
} ctrl;
/// FTP data channel info; the channel may be opened/closed a few times
struct DataChannel: public FtpChannel {
MemBuf *readBuf;
char *host;
unsigned short port;
bool read_pending;
} data;
struct _ftp_flags flags;
public:
// these should all be private
virtual void start();
- void loginParser(const char *, int escaped);
int restartable();
void appendSuccessHeader();
void hackShortcut(FTPSM * nextState);
void failed(err_type, int xerrno);
void failedErrorMessage(err_type, int xerrno);
void unhack();
void scheduleReadControlReply(int);
void handleControlReply();
void readStor();
void parseListing();
MemBuf *htmlifyListEntry(const char *line);
void completedListing(void);
void dataComplete();
void dataRead(const CommIoCbParams &io);
/// ignore timeout on CTRL channel. set read timeout on DATA channel.
void switchTimeoutToDataChannel();
/// create a data channel acceptor and start listening.
void listenForDataChannel(const Comm::ConnectionPointer &conn, const char
*note);
@@ -284,62 +282,57 @@
static HttpReply *ftpAuthRequired(HttpRequest * request, const char
*realm);
const char *ftpRealm(void);
void loginFailed(void);
static wordlist *ftpParseControlReply(char *, size_t, int *, size_t *);
// sending of the request body to the server
virtual void sentRequestBody(const CommIoCbParams&);
virtual void doneSendingRequestBody();
virtual void haveParsedReplyHeaders();
virtual bool doneWithServer() const;
virtual bool haveControlChannel(const char *caller_name) const;
AsyncCall::Pointer dataCloser(); /// creates a Comm close callback
AsyncCall::Pointer dataOpener(); /// creates a Comm connect callback
private:
// BodyConsumer for HTTP: consume request body.
virtual void handleRequestBodyProducerAborted();
+ void loginParser(const SBuf &login, bool escaped);
CBDATA_CLASS2(FtpStateData);
};
CBDATA_CLASS_INIT(FtpStateData);
/// \ingroup ServerProtocolFTPInternal
typedef struct {
char type;
int64_t size;
char *date;
char *name;
char *showname;
char *link;
} ftpListParts;
-/// \ingroup ServerProtocolFTPInternal
-#define FTP_LOGIN_ESCAPED 1
-
-/// \ingroup ServerProtocolFTPInternal
-#define FTP_LOGIN_NOT_ESCAPED 0
-
/*
* State machine functions
* send == state transition
* read == wait for response, and select next state transition
* other == Transition logic
*/
static FTPSM ftpReadWelcome;
static FTPSM ftpSendUser;
static FTPSM ftpReadUser;
static FTPSM ftpSendPass;
static FTPSM ftpReadPass;
static FTPSM ftpSendType;
static FTPSM ftpReadType;
static FTPSM ftpSendMdtm;
static FTPSM ftpReadMdtm;
static FTPSM ftpSendSize;
static FTPSM ftpReadSize;
static FTPSM ftpSendEPRT;
static FTPSM ftpReadEPRT;
static FTPSM ftpSendPORT;
@@ -539,92 +532,88 @@
safe_free(old_reply);
safe_free(old_filepath);
title_url.clean();
base_href.clean();
safe_free(filepath);
safe_free(dirpath);
safe_free(data.host);
fwd = NULL; // refcounted
}
/**
* Parse a possible login username:password pair.
* Produces filled member variables user, password, password_url if anything
found.
+ *
+ * \param login a decoded Basic authentication credential token or URI
user-info token.
+ * \param escaped whether to URL-decode the token after extracting user and
password
*/
void
-FtpStateData::loginParser(const char *login, int escaped)
+FtpStateData::loginParser(const SBuf &login, bool escaped)
{
- const char *u = NULL; // end of the username sub-string
- int len; // length of the current sub-string to handle.
+ debugs(9, 4, "login=" << login << ", escaped=" << escaped);
+ debugs(9, 9, "IN : login=" << login << ", escaped=" << escaped << ",
user=" << user << ", password=" << password);
- int total_len = strlen(login);
+ if (login.isEmpty())
+ return;
- debugs(9, 4, HERE << ": login='" << login << "', escaped=" << escaped);
- debugs(9, 9, HERE << ": IN : login='" << login << "', escaped=" << escaped
<< ", user=" << user << ", password=" << password);
-
- if ((u = strchr(login, ':'))) {
-
- /* if there was a username part */
- if (u > login) {
- len = u - login;
- ++u; // jump off the delimiter.
- if (len > MAX_URL)
- len = MAX_URL-1;
- xstrncpy(user, login, len +1);
- debugs(9, 9, HERE << ": found user='" << user << "'(" << len <<"),
escaped=" << escaped);
- if (escaped)
- rfc1738_unescape(user);
- debugs(9, 9, HERE << ": found user='" << user << "'(" << len <<")
unescaped.");
- }
+ const SBuf::size_type colonPos = login.find(':');
- /* if there was a password part */
- len = login + total_len - u;
- if ( len > 0) {
- if (len > MAX_URL)
- len = MAX_URL -1;
- xstrncpy(password, u, len +1);
- debugs(9, 9, HERE << ": found password='" << password << "'(" <<
len <<"), escaped=" << escaped);
- if (escaped) {
- rfc1738_unescape(password);
- password_url = 1;
- }
- debugs(9, 9, HERE << ": found password='" << password << "'(" <<
len <<") unescaped.");
- }
- } else if (login[0]) {
- /* no password, just username */
- if (total_len > MAX_URL)
- total_len = MAX_URL -1;
- xstrncpy(user, login, total_len +1);
- debugs(9, 9, HERE << ": found user='" << user << "'(" << total_len
<<"), escaped=" << escaped);
+ /* If there was a username part.
+ * Ignore 0-length usernames, retain what we have already
+ */
+ if (colonPos != 0) {
+ const SBuf userName = login.substr(0, colonPos);
+ SBuf::size_type upto = userName.copy(user, sizeof(user)-1);
+ user[upto]='\0';
+ debugs(9, 9, "found user=" << userName << ' ' <<
+ (upto != userName.length() ? ", truncated-to=" : ", length=")
<< upto <<
+ ", escaped=" << escaped);
if (escaped)
rfc1738_unescape(user);
- debugs(9, 9, HERE << ": found user='" << user << "'(" << total_len
<<") unescaped.");
+ debugs(9, 9, "found user=" << user << " (" << strlen(user) << ")
unescaped.");
+ }
+
+ /* If there was a password part.
+ * For 0-length password clobber what we have already, this means
explicitly none
+ */
+ if (colonPos != SBuf::npos) {
+ const SBuf pass = login.substr(colonPos+1, SBuf::npos);
+ SBuf::size_type upto = pass.copy(password, sizeof(password)-1);
+ password[upto]='\0';
+ debugs(9, 9, "found password=" << pass << " " <<
+ (upto != pass.length() ? ", truncated-to=" : ", length=") <<
upto <<
+ ", escaped=" << escaped);
+ if (escaped) {
+ rfc1738_unescape(password);
+ password_url = 1;
+ }
+ debugs(9, 9, "found password=" << password << " (" << strlen(password)
<< ") unescaped.");
}
- debugs(9, 9, HERE << ": OUT: login='" << login << "', escaped=" << escaped
<< ", user=" << user << ", password=" << password);
+ debugs(9, 9, "OUT: login=" << login << ", escaped=" << escaped << ",
user=" << user << ", password=" << password);
}
/**
* Cancel the timeout on the Control socket and establish one
* on the data socket
*/
void
FtpStateData::switchTimeoutToDataChannel()
{
commUnsetConnTimeout(ctrl.conn);
typedef CommCbMemFunT<FtpStateData, CommTimeoutCbParams> TimeoutDialer;
AsyncCall::Pointer timeoutCall = JobCallback(9, 5, TimeoutDialer, this,
FtpStateData::ftpTimeout);
commSetConnTimeout(data.conn, Config.Timeout.read, timeoutCall);
}
void
FtpStateData::listenForDataChannel(const Comm::ConnectionPointer &conn, const
char *note)
{
assert(!Comm::IsConnOpen(data.conn));
@@ -1359,50 +1348,50 @@
* - Checks URL (ftp://user:pass@domain)
* - Authorization: Basic header
* - squid.conf anonymous-FTP settings (default: anonymous:Squid@).
*
* Special Case: A username-only may be provided in the URL and password in
the HTTP headers.
*
* TODO: we might be able to do something about locating username from other
sources:
* ie, external ACL user=* tag or ident lookup
*
\retval 1 if we have everything needed to complete this request.
\retval 0 if something is missing.
*/
int
FtpStateData::checkAuth(const HttpHeader * req_hdr)
{
/* default username */
xstrncpy(user, "anonymous", MAX_URL);
#if HAVE_AUTH_MODULE_BASIC
/* Check HTTP Authorization: headers (better than defaults, but less than
URL) */
- const char *auth;
- if ( (auth = req_hdr->getAuth(HDR_AUTHORIZATION, "Basic")) ) {
+ const SBuf auth(req_hdr->getAuth(HDR_AUTHORIZATION, "Basic"));
+ if (!auth.isEmpty()) {
flags.authenticated = 1;
- loginParser(auth, FTP_LOGIN_NOT_ESCAPED);
+ loginParser(auth, false);
}
/* we fail with authorization-required error later IFF the FTP server
requests it */
#endif
/* Test URL login syntax. Overrides any headers received. */
- loginParser(request->login, FTP_LOGIN_ESCAPED);
+ loginParser(request->url.userInfo(), true);
/* name is missing. thats fatal. */
if (!user[0])
fatal("FTP login parsing destroyed username info");
/* name + password == success */
if (password[0])
return 1;
/* Setup default FTP password settings */
/* this has to be done last so that we can have a no-password case above.
*/
if (!password[0]) {
if (strcmp(user, "anonymous") == 0 && !flags.tried_auth_anonymous) {
xstrncpy(password, Config.Ftp.anon_user, MAX_URL);
flags.tried_auth_anonymous=1;
return 1;
} else if (!flags.tried_auth_nopass) {
xstrncpy(password, null_string, MAX_URL);
flags.tried_auth_nopass=1;
return 1;
=== modified file 'src/http.cc'
--- src/http.cc 2014-04-27 07:59:17 +0000
+++ src/http.cc 2014-05-09 17:45:08 +0000
@@ -1802,43 +1802,44 @@
hdr_out->putStr(HDR_X_FORWARDED_FOR, strFwd.termedBuf());
}
/** If set to DELETE - do not copy through. */
/* append Host if not there already */
if (!hdr_out->has(HDR_HOST)) {
if (request->peer_domain) {
hdr_out->putStr(HDR_HOST, request->peer_domain);
} else if (request->port == urlDefaultPort(request->url.getScheme())) {
/* use port# only if not default */
hdr_out->putStr(HDR_HOST, request->GetHost());
} else {
httpHeaderPutStrf(hdr_out, HDR_HOST, "%s:%d",
request->GetHost(),
(int) request->port);
}
}
/* append Authorization if known in URL, not in header and going direct */
if (!hdr_out->has(HDR_AUTHORIZATION)) {
- if (!request->flags.proxying && request->login[0] != '\0') {
- httpHeaderPutStrf(hdr_out, HDR_AUTHORIZATION, "Basic %s",
- old_base64_encode(request->login));
+ if (!request->flags.proxying && !request->url.userInfo().isEmpty()) {
+ static char result[MAX_URL*2]; // should be big enough for a
single URI segment
+ if (base64_encode_str(result, sizeof(result)-1,
request->url.userInfo().rawContent(), request->url.userInfo().length()) <
static_cast<int>(sizeof(result)-1))
+ httpHeaderPutStrf(hdr_out, HDR_AUTHORIZATION, "Basic %s",
result);
}
}
/* Fixup (Proxy-)Authorization special cases. Plain relaying dealt with
above */
httpFixupAuthentication(request, hdr_in, hdr_out, flags);
/* append Cache-Control, add max-age if not there already */
{
HttpHdrCc *cc = hdr_in->getCc();
if (!cc)
cc = new HttpHdrCc();
#if 0 /* see bug 2330 */
/* Set no-cache if determined needed but not found */
if (request->flags.nocache)
EBIT_SET(cc->mask, CC_NO_CACHE);
#endif
/* Add max-age only without no-cache */
=== modified file 'src/url.cc'
--- src/url.cc 2014-05-07 14:40:05 +0000
+++ src/url.cc 2014-05-09 17:56:47 +0000
@@ -26,41 +26,41 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
*
*/
#include "squid.h"
#include "globals.h"
#include "HttpRequest.h"
#include "rfc1738.h"
#include "SquidConfig.h"
#include "SquidString.h"
#include "URL.h"
static HttpRequest *urlParseFinish(const HttpRequestMethod& method,
const AnyP::ProtocolType protocol,
const char *const urlpath,
const char *const host,
- const char *const login,
+ const SBuf &login,
const int port,
HttpRequest *request);
static HttpRequest *urnParse(const HttpRequestMethod& method, char *urn,
HttpRequest *request);
static const char valid_hostname_chars_u[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789-._"
"[:]"
;
static const char valid_hostname_chars[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789-."
"[:]"
;
void
urlInitialize(void)
{
debugs(23, 5, "urlInitialize: Initializing...");
@@ -224,41 +224,41 @@
char *dst;
proto[0] = host[0] = urlpath[0] = login[0] = '\0';
if ((l = strlen(url)) + Config.appendDomainLen > (MAX_URL - 1)) {
/* terminate so it doesn't overflow other buffers */
*(url + (MAX_URL >> 1)) = '\0';
debugs(23, DBG_IMPORTANT, "urlParse: URL too large (" << l << "
bytes)");
return NULL;
}
if (method == Http::METHOD_CONNECT) {
port = CONNECT_PORT;
if (sscanf(url, "[%[^]]]:%d", host, &port) < 1)
if (sscanf(url, "%[^:]:%d", host, &port) < 1)
return NULL;
} else if ((method == Http::METHOD_OPTIONS || method ==
Http::METHOD_TRACE) &&
strcmp(url, "*") == 0) {
protocol = AnyP::PROTO_HTTP;
port = urlDefaultPort(protocol);
- return urlParseFinish(method, protocol, url, host, login, port,
request);
+ return urlParseFinish(method, protocol, url, host, SBuf(), port,
request);
} else if (!strncmp(url, "urn:", 4)) {
return urnParse(method, url, request);
} else {
/* Parse the URL: */
src = url;
i = 0;
/* Find first : - everything before is protocol */
for (i = 0, dst = proto; i < l && *src != ':'; ++i, ++src, ++dst) {
*dst = *src;
}
if (i >= l)
return NULL;
*dst = '\0';
/* Then its :// */
if ((i+3) > l || *src != ':' || *(src + 1) != '/' || *(src + 2) != '/')
return NULL;
i += 3;
src += 3;
@@ -429,66 +429,66 @@
break;
case URI_WHITESPACE_CHOP:
*(urlpath + strcspn(urlpath, w_space)) = '\0';
break;
case URI_WHITESPACE_STRIP:
default:
t = q = urlpath;
while (*t) {
if (!xisspace(*t)) {
*q = *t;
++q;
}
++t;
}
*q = '\0';
}
}
- return urlParseFinish(method, protocol, urlpath, host, login, port,
request);
+ return urlParseFinish(method, protocol, urlpath, host, SBuf(login), port,
request);
}
/**
* Update request with parsed URI data. If the request arg is
* non-NULL, put parsed values there instead of allocating a new
* HttpRequest.
*/
static HttpRequest *
urlParseFinish(const HttpRequestMethod& method,
const AnyP::ProtocolType protocol,
const char *const urlpath,
const char *const host,
- const char *const login,
+ const SBuf &login,
const int port,
HttpRequest *request)
{
if (NULL == request)
request = new HttpRequest(method, protocol, urlpath);
else {
request->initHTTP(method, protocol, urlpath);
safe_free(request->canonical);
}
request->SetHost(host);
- xstrncpy(request->login, login, MAX_LOGIN_SZ);
+ request->url.userInfo(login);
request->port = (unsigned short) port;
return request;
}
static HttpRequest *
urnParse(const HttpRequestMethod& method, char *urn, HttpRequest *request)
{
debugs(50, 5, "urnParse: " << urn);
if (request) {
request->initHTTP(method, AnyP::PROTO_URN, urn + 4);
safe_free(request->canonical);
return request;
}
return new HttpRequest(method, AnyP::PROTO_URN, urn + 4);
}
const char *
urlCanonical(HttpRequest * request)
{
@@ -497,92 +497,90 @@
if (request->canonical)
return request->canonical;
if (request->url.getScheme() == AnyP::PROTO_URN) {
snprintf(urlbuf, MAX_URL, "urn:" SQUIDSTRINGPH,
SQUIDSTRINGPRINT(request->urlpath));
} else {
switch (request->method.id()) {
case Http::METHOD_CONNECT:
snprintf(urlbuf, MAX_URL, "%s:%d", request->GetHost(),
request->port);
break;
default: {
portbuf[0] = '\0';
if (request->port != urlDefaultPort(request->url.getScheme()))
snprintf(portbuf, 32, ":%d", request->port);
- snprintf(urlbuf, MAX_URL, "%s://%s%s%s%s" SQUIDSTRINGPH,
+ snprintf(urlbuf, MAX_URL, "%s://" SQUIDSBUFPH "%s%s%s"
SQUIDSTRINGPH,
request->url.getScheme().c_str(),
- request->login,
- *request->login ? "@" : null_string,
+ SQUIDSBUFPRINT(request->url.userInfo()),
+ !request->url.userInfo().isEmpty() ? "@" : "",
request->GetHost(),
portbuf,
SQUIDSTRINGPRINT(request->urlpath));
}
}
}
return (request->canonical = xstrdup(urlbuf));
}
/** \todo AYJ: Performance: This is an *almost* duplicate of urlCanonical. But
elides the query-string.
* After copying it on in the first place! Would be less code to merge
the two with a flag parameter.
* and never copy the query-string part in the first place
*/
char *
urlCanonicalClean(const HttpRequest * request)
{
LOCAL_ARRAY(char, buf, MAX_URL);
LOCAL_ARRAY(char, portbuf, 32);
LOCAL_ARRAY(char, loginbuf, MAX_LOGIN_SZ + 1);
char *t;
if (request->url.getScheme() == AnyP::PROTO_URN) {
snprintf(buf, MAX_URL, "urn:" SQUIDSTRINGPH,
SQUIDSTRINGPRINT(request->urlpath));
} else {
switch (request->method.id()) {
case Http::METHOD_CONNECT:
snprintf(buf, MAX_URL, "%s:%d", request->GetHost(), request->port);
break;
default: {
portbuf[0] = '\0';
if (request->port != urlDefaultPort(request->url.getScheme()))
snprintf(portbuf, 32, ":%d", request->port);
- loginbuf[0] = '\0';
-
- if ((int) strlen(request->login) > 0) {
- strcpy(loginbuf, request->login);
-
- if ((t = strchr(loginbuf, ':')))
- *t = '\0';
-
- strcat(loginbuf, "@");
- }
+ if (!request->url.userInfo().isEmpty()) {
+ SBuf::size_type len = request->url.userInfo().find(':');
+ // URI userinfo/login is deprecated by IETF so we dont care if
it crops accidentally
+ len = request->url.userInfo().copy(loginbuf, min(len,
sizeof(loginbuf)-2));
+ loginbuf[len] = '@';
+ loginbuf[len+1] = '\0';
+ } else
+ loginbuf[0] = '\0';
snprintf(buf, MAX_URL, "%s://%s%s%s" SQUIDSTRINGPH,
request->url.getScheme().c_str(),
loginbuf,
request->GetHost(),
portbuf,
SQUIDSTRINGPRINT(request->urlpath));
// strip arguments AFTER a question-mark
if (Config.onoff.strip_query_terms)
if ((t = strchr(buf, '?')))
*(++t) = '\0';
}
}
}
if (stringHasCntl(buf))
xstrncpy(buf, rfc1738_escape_unescaped(buf), MAX_URL);
return buf;
@@ -650,52 +648,52 @@
*/
char *
urlMakeAbsolute(const HttpRequest * req, const char *relUrl)
{
if (req->method.id() == Http::METHOD_CONNECT) {
return (NULL);
}
char *urlbuf = (char *)xmalloc(MAX_URL * sizeof(char));
if (req->url.getScheme() == AnyP::PROTO_URN) {
snprintf(urlbuf, MAX_URL, "urn:" SQUIDSTRINGPH,
SQUIDSTRINGPRINT(req->urlpath));
return (urlbuf);
}
size_t urllen;
if (req->port != urlDefaultPort(req->url.getScheme())) {
- urllen = snprintf(urlbuf, MAX_URL, "%s://%s%s%s:%d",
+ urllen = snprintf(urlbuf, MAX_URL, "%s://" SQUIDSBUFPH "%s%s:%d",
req->url.getScheme().c_str(),
- req->login,
- *req->login ? "@" : null_string,
+ SQUIDSBUFPRINT(req->url.userInfo()),
+ !req->url.userInfo().isEmpty() ? "@" : "",
req->GetHost(),
req->port
);
} else {
- urllen = snprintf(urlbuf, MAX_URL, "%s://%s%s%s",
+ urllen = snprintf(urlbuf, MAX_URL, "%s://" SQUIDSBUFPH "%s%s",
req->url.getScheme().c_str(),
- req->login,
- *req->login ? "@" : null_string,
+ SQUIDSBUFPRINT(req->url.userInfo()),
+ !req->url.userInfo().isEmpty() ? "@" : "",
req->GetHost()
);
}
if (relUrl[0] == '/') {
strncpy(&urlbuf[urllen], relUrl, MAX_URL - urllen - 1);
} else {
const char *path = req->urlpath.termedBuf();
const char *last_slash = strrchr(path, '/');
if (last_slash == NULL) {
urlbuf[urllen] = '/';
++urllen;
strncpy(&urlbuf[urllen], relUrl, MAX_URL - urllen - 1);
} else {
++last_slash;
size_t pathlen = last_slash - path;
if (pathlen > MAX_URL - urllen - 1) {
pathlen = MAX_URL - urllen - 1;
}