#include "fqdncache.h"
#include "globals.h"
#include "http.h"
+#include "HttpHdrCc.h"
#include "HttpHdrContRange.h"
#include "HttpHeaderTools.h"
#include "HttpReply.h"
static void FtpCloseDataConnection(ConnStateData *conn);
static void FtpWriteGreeting(ConnStateData *conn);
static ClientSocketContext *FtpParseRequest(ConnStateData *connState, HttpRequestMethod *method_p, Http::ProtocolVersion *http_ver);
+static bool FtpHandleUserRequest(ConnStateData *connState, const String &cmd, String ¶ms);
static void FtpHandleReply(ClientSocketContext *context, HttpReply *reply, StoreIOBuffer data);
typedef void FtpReplyHandler(ClientSocketContext *context, const HttpReply *reply, StoreIOBuffer data);
static IOCB FtpWroteReply;
static IOCB FtpWroteReplyData;
-typedef bool FtpRequestHandler(ConnStateData *connState, String &cmd, String ¶ms);
+typedef bool FtpRequestHandler(ClientSocketContext *context, String &cmd, String ¶ms);
static FtpRequestHandler FtpHandleRequest;
-static FtpRequestHandler FtpHandleUserRequest;
static FtpRequestHandler FtpHandlePasvRequest;
static FtpRequestHandler FtpHandlePortRequest;
static FtpRequestHandler FtpHandleDataRequest;
-static bool FtpCheckDataConnection(ConnStateData *connState);
+static bool FtpCheckDataConnection(ClientSocketContext *context);
+static void FtpSetReply(ClientSocketContext *context, const int code, const char *msg);
clientStreamNode *
ClientSocketContext::getTail() const
/* We have an initial client stream in place should it be needed */
/* setup our private context */
- context->registerWithConn();
+ if (!conn->isFtp)
+ context->registerWithConn();
if (context->flags.parsed_ok == 0) {
clientStreamNode *node = context->getClientReplyContext();
if (conn->isFtp) {
assert(http->request);
request = http->request;
+ notedUseOfBuffer = true;
} else
if ((request = HttpRequest::CreateFromUrlAndMethod(http->uri, method)) == NULL) {
clientStreamNode *node = context->getClientReplyContext();
request->body_pipe = conn->expectRequestBody(
chunked ? -1 : request->content_length);
- // consume header early so that body pipe gets just the body
- connNoteUseOfBuffer(conn, http->req_sz);
- notedUseOfBuffer = true;
+ if (!notedUseOfBuffer) {
+ // consume header early so that body pipe gets just the body
+ connNoteUseOfBuffer(conn, http->req_sz);
+ notedUseOfBuffer = true;
+ }
/* Is it too large? */
if (!chunked && // if chunked, we will check as we accumulate
}
}
+void
+ConnStateData::processFtpRequest(ClientSocketContext *const context)
+{
+ ClientHttpRequest *const http = context->http;
+ assert(http != NULL);
+ HttpRequest *const request = http->request;
+ assert(request != NULL);
+ HttpHeader &header = request->header;
+ assert(header.has(HDR_FTP_COMMAND));
+ String &cmd = header.findEntry(HDR_FTP_COMMAND)->value;
+ assert(header.has(HDR_FTP_ARGUMENTS));
+ String ¶ms = header.findEntry(HDR_FTP_ARGUMENTS)->value;
+
+ if (!FtpHandleRequest(context, cmd, params)) {
+ assert(http->storeEntry() != NULL);
+ clientSetKeepaliveFlag(http);
+ context->pullData();
+ return;
+ }
+
+ assert(http->storeEntry() == NULL);
+ clientProcessRequest(this, &parser_, context, request->method,
+ request->http_ver);
+}
+
static void
connStripBufferWhitespace (ConnStateData * conn)
{
CommTimeoutCbPtrFun(clientLifetimeTimeout, context->http));
commSetConnTimeout(clientConnection, Config.Timeout.lifetime, timeoutCall);
- clientProcessRequest(this, &parser_, context, method, http_ver);
+ if (!isFtp)
+ clientProcessRequest(this, &parser_, context, method, http_ver);
+ else {
+ // Process FTP request asynchronously to make sure FTP
+ // data connection accept callback is fired first.
+ CallJobHere1(33, 4, CbcPointer<ConnStateData>(this),
+ ConnStateData, ConnStateData::processFtpRequest, context);
+ }
parsed_req = true; // XXX: do we really need to parse everything right NOW ?
const char *const eor =
static_cast<const char *>(memchr(connState->in.buf, '\n',
min(connState->in.notYetUsed, Config.maxRequestHeaderSize)));
+ const size_t req_sz = eor + 1 - connState->in.buf;
if (eor == NULL && connState->in.notYetUsed >= Config.maxRequestHeaderSize) {
connState->ftp.state = ConnStateData::FTP_ERROR;
return NULL;
}
+ connNoteUseOfBuffer(connState, req_sz);
+
// skip leading whitespaces
const char *boc = connState->in.buf;
while (boc < eor && isspace(*boc)) ++boc;
if (boc >= eor) {
debugs(33, 5, HERE << "Empty request, ignoring");
- connNoteUseOfBuffer(connState, eor + 1 - connState->in.buf);
return NULL;
}
(bop == NULL ? "no " : "") << "parameters" <<
(bop != NULL ? ": " : "") << bop);
- String cmd = boc;
+ const String cmd = boc;
String params = bop;
- // the first command must be USER
- if (connState->ftp.uri.size() == 0 && cmd.caseCmp("USER") != 0) {
- debugs(33, 5, HERE << "Unexpected FTP command: expected USER, got " <<
- boc);
- FtpWriteEarlyReply(connState, 530, "Must login first");
- connNoteUseOfBuffer(connState, eor + 1 - connState->in.buf);
- return NULL;
+ if (connState->ftp.uri.size() == 0) {
+ // the first command must be USER
+ if (cmd.caseCmp("USER") != 0) {
+ FtpWriteEarlyReply(connState, 530, "Must login first");
+ return NULL;
+ }
+
+ if (params.size() == 0) {
+ FtpWriteEarlyReply(connState, 501, "Missing username");
+ return NULL;
+ }
}
- if (!FtpHandleRequest(connState, cmd, params)) {
- connNoteUseOfBuffer(connState, eor + 1 - connState->in.buf);
+ // We need to process USER request now because it sets request URI.
+ if (cmd.caseCmp("USER") == 0 &&
+ !FtpHandleUserRequest(connState, cmd, params))
return NULL;
- }
+ assert(connState->ftp.uri.size() > 0);
char *uri = xstrdup(connState->ftp.uri.termedBuf());
HttpRequest *const request =
HttpRequest::CreateFromUrlAndMethod(uri, *method_p);
debugs(33, 5, HERE << "Invalid FTP URL: " << connState->ftp.uri);
FtpWriteEarlyReply(connState, 501, "Invalid host");
connState->ftp.uri.clean();
- connNoteUseOfBuffer(connState, eor + 1 - connState->in.buf);
safe_free(uri);
return NULL;
}
+ request->http_ver = *http_ver;
request->header.putStr(HDR_FTP_COMMAND, cmd.termedBuf());
- if (params.size() > 0)
- request->header.putStr(HDR_FTP_ARGUMENTS, params.termedBuf());
+ request->header.putStr(HDR_FTP_ARGUMENTS, params.termedBuf() != NULL ?
+ params.termedBuf() : "");
ClientHttpRequest *const http = new ClientHttpRequest(connState);
http->request = request;
HTTPMSGLOCK(http->request);
- http->req_sz = eor - connState->in.buf + 1;
- http->uri = xstrdup(connState->ftp.uri.termedBuf());
+ http->req_sz = req_sz;
+ http->uri = uri;
ClientSocketContext *const result =
ClientSocketContextNew(connState->clientConnection, http);
clientReplyStatus, newServer, clientSocketRecipient,
clientSocketDetach, newClient, tempBuffer);
+ result->registerWithConn();
result->flags.parsed_ok = 1;
connState->flags.readMore = false;
return result;
}
bool
-FtpHandleRequest(ConnStateData *connState, String &cmd, String ¶ms) {
+FtpHandleRequest(ClientSocketContext *context, String &cmd, String ¶ms) {
static std::pair<const char *, FtpRequestHandler *> handlers[] = {
std::make_pair("PASV", FtpHandlePasvRequest),
std::make_pair("PORT", FtpHandlePortRequest),
std::make_pair("RETR", FtpHandleDataRequest),
std::make_pair("LIST", FtpHandleDataRequest),
- std::make_pair("NLST", FtpHandleDataRequest),
- std::make_pair("USER", FtpHandleUserRequest)
+ std::make_pair("NLST", FtpHandleDataRequest)
};
FtpRequestHandler *handler = NULL;
}
}
- return handler != NULL ? (*handler)(connState, cmd, params) : true;
+ return handler != NULL ? (*handler)(context, cmd, params) : true;
}
bool
-FtpHandleUserRequest(ConnStateData *connState, String &cmd, String ¶ms)
+FtpHandleUserRequest(ConnStateData *connState, const String &cmd, String ¶ms)
{
if (params.size() == 0) {
FtpWriteEarlyReply(connState, 501, "Missing username");
}
bool
-FtpHandlePasvRequest(ConnStateData *connState, String &cmd, String ¶ms)
+FtpHandlePasvRequest(ClientSocketContext *context, String &cmd, String ¶ms)
{
if (params.size() > 0) {
- FtpWriteEarlyReply(connState, 501, "Unexpected parameter");
+ FtpSetReply(context, 501, "Unexpected parameter");
return false;
}
- connState->ftp.state = ConnStateData::FTP_HANDLE_PASV;
+ context->getConn()->ftp.state = ConnStateData::FTP_HANDLE_PASV;
return true;
}
bool
-FtpHandlePortRequest(ConnStateData *connState, String &cmd, String ¶ms)
+FtpHandlePortRequest(ClientSocketContext *context, String &cmd, String ¶ms)
{
- FtpWriteEarlyReply(connState, 502, "Command not supported");
+ FtpSetReply(context, 502, "Command not supported");
return false;
}
bool
-FtpHandleDataRequest(ConnStateData *connState, String &cmd, String ¶ms)
+FtpHandleDataRequest(ClientSocketContext *context, String &cmd, String ¶ms)
{
- if (!FtpCheckDataConnection(connState))
+ if (!FtpCheckDataConnection(context))
return false;
- connState->ftp.state = ConnStateData::FTP_HANDLE_DATA_REQUEST;
+ context->getConn()->ftp.state = ConnStateData::FTP_HANDLE_DATA_REQUEST;
return true;
}
bool
-FtpCheckDataConnection(ConnStateData *connState)
+FtpCheckDataConnection(ClientSocketContext *context)
{
+ const ConnStateData *const connState = context->getConn();
if (Comm::IsConnOpen(connState->ftp.dataConn))
return true;
if (!Comm::IsConnOpen(connState->ftp.dataListenConn))
- FtpWriteEarlyReply(connState, 425, "Use PASV first");
+ FtpSetReply(context, 425, "Use PASV first");
else
- FtpWriteEarlyReply(connState, 425, "Data connection is not established");
+ FtpSetReply(context, 425, "Data connection is not established");
return false;
}
+
+void
+FtpSetReply(ClientSocketContext *context, const int code, const char *msg)
+{
+ ClientHttpRequest *const http = context->http;
+ assert(http != NULL);
+ assert(http->storeEntry() == NULL);
+
+ HttpReply *const reply = new HttpReply;
+ reply->sline.set(Http::ProtocolVersion(1, 1), Http::scNoContent);
+ HttpHeader &header = reply->header;
+ header.putTime(HDR_DATE, squid_curtime);
+ {
+ HttpHdrCc cc;
+ cc.Private();
+ header.putCc(&cc);
+ }
+ header.putInt64(HDR_CONTENT_LENGTH, 0);
+ header.putInt(HDR_FTP_STATUS, code);
+ header.putStr(HDR_FTP_REASON, msg);
+ reply->hdrCacheInit();
+
+ setLogUri(http, http->uri, true);
+
+ clientStreamNode *const node = context->getClientReplyContext();
+ clientReplyContext *const repContext =
+ dynamic_cast<clientReplyContext *>(node->data.getRaw());
+ assert(repContext != NULL);
+ repContext->createStoreEntry(http->request->method, RequestFlags());
+ http->storeEntry()->replaceHttpReply(reply);
+}