/*
- * $Id: ftp.cc,v 1.187 1998/01/12 04:30:38 wessels Exp $
+ * $Id: ftp.cc,v 1.188 1998/02/02 19:50:10 wessels Exp $
*
* DEBUG: section 9 File Transfer Protocol (FTP)
* AUTHOR: Harvest Derived
FTP_NO_DOTDOT,
FTP_HTML_HEADER_SENT,
FTP_BINARY,
- FTP_TRY_SLASH_HACK
+ FTP_TRY_SLASH_HACK,
+ FTP_LISTFORMAT_UNKNOWN
};
static const char *const crlf = "\r\n";
/* Local functions */
static CNCB ftpConnectDone;
static CNCB ftpPasvCallback;
-static PF ftpReadData;
+static PF ftpDataRead;
static PF ftpStateFree;
static PF ftpTimeout;
static PF ftpReadControlReply;
static char *ftpGetBasicAuth(const char *);
static void ftpLoginParser(const char *, FtpStateData *);
static wordlist *ftpParseControlReply(char *buf, size_t len, int *code);
-static void ftpRestOrList(FtpStateData * ftpState);
-static void ftpDataTransferDone(FtpStateData * ftpState);
static void ftpAppendSuccessHeader(FtpStateData * ftpState);
static char *ftpAuthRequired(const request_t *, const char *);
static STABH ftpAbort;
/* 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 ftpReadPort;
static FTPSM ftpSendPasv;
static FTPSM ftpReadPasv;
-static FTPSM ftpTraverseDirectory; /* Selects CWD or RETR */
+static FTPSM ftpTraverseDirectory;
+static FTPSM ftpListDir;
+static FTPSM ftpGetFile;
static FTPSM ftpSendCwd;
static FTPSM ftpReadCwd;
static FTPSM ftpSendList;
static FTPSM ftpSendQuit;
static FTPSM ftpReadQuit;
static FTPSM ftpFail;
+static FTPSM ftpDataTransferDone;
+static FTPSM ftpRestOrList;
+
+/************************************************
+** State Machine Description (excluding hacks) **
+*************************************************
+From To
+---------------------------------------
+Welcome User
+User Pass
+Pass Type
+Type TraverseDirectory / GetFile
+TraverseDirectory Cwd / GetFile / ListDir
+Cwd TraverseDirectory
+GetFile Mdtm
+Mdtm Size
+Size Pasv
+ListDir Pasv
+Pasv RestOrList
+RestOrList Rest / Retr / Nlst / List
+Rest Retr
+Retr / Nlst / List (ftpDataRead on datachannel)
+(ftpDataRead) ReadTransferDone
+ReadTransferDone DataTransferDone
+DataTransferDone Quit
+Quit -
+************************************************/
FTPSM *FTP_SM_FUNCS[] =
{
StoreEntry *e = ftpState->entry;
storeBuffer(e);
storeAppendPrintf(e, "</PRE>\n");
+ if (EBIT_TEST(ftpState->flags, FTP_LISTFORMAT_UNKNOWN) && !EBIT_TEST(ftpState->flags, FTP_TRIED_NLST)) {
+ storeAppendPrintf(e, "<A HREF=\"./;type=d\">[As plain directory]</A>\n");
+ } else if (ftpState->typecode=='D') {
+ storeAppendPrintf(e, "<A HREF=\"./\">[As extended directory]</A>\n");
+ }
storeAppendPrintf(e, "<HR>\n");
storeAppendPrintf(e, "<ADDRESS>\n");
storeAppendPrintf(e, "Generated %s, by %s/%s@%s\n",
for (i = 0; i < MAX_TOKENS; i++)
tokens[i] = (char *) NULL;
xbuf = xstrdup(buf);
+ if (EBIT_TEST(flags, FTP_TRIED_NLST)) {
+ /* Machine readable format, one name per line */
+ p->name=xbuf;
+ p->type='\0';
+ return p;
+ }
for (t = strtok(xbuf, w_space); t && n_tokens < MAX_TOKENS; t = strtok(NULL, w_space))
tokens[n_tokens++] = xstrdup(t);
xfree(xbuf);
}
static char *
-ftpHtmlifyListEntry(char *line, int flags)
+ftpHtmlifyListEntry(char *line, FtpStateData * ftpState)
{
LOCAL_ARRAY(char, link, 2048 + 40);
LOCAL_ARRAY(char, link2, 2048 + 40);
LOCAL_ARRAY(char, html, 8192);
size_t width = Config.Ftp.list_width;
ftpListParts *parts;
+ int flags = ftpState->flags;
if (strlen(line) > 1024) {
snprintf(html, 8192, "%s\n", line);
return html;
}
if ((parts = ftpListParseParts(line, flags)) == NULL) {
+ char *p;
snprintf(html, 8192, "%s\n", line);
+ for(p=line;*p && isspace(*p);p++);
+ if (*p && !isspace(*p))
+ EBIT_SET(ftpState->flags, FTP_LISTFORMAT_UNKNOWN);
return html;
}
/* check .. as special case */
parts->date,
link2);
break;
+ case '\0':
+ snprintf(icon, 2048, "<IMG SRC=\"%s%s\" ALT=\"%-6s\">",
+ "http://internal.squid/icons/",
+ mimeGetIcon(parts->name),
+ "[UNKNOWN]");
+ snprintf(link, 2048, "<A HREF=\"%s\">%s</A>",
+ rfc1738_escape(parts->name),
+ parts->name);
+ snprintf(link2, 2048, "(<A HREF=\"%s/;type=d\">chdir</A>)",
+ rfc1738_escape(parts->name));
+ snprintf(html, 8192, "%s %s %s\n",
+ icon,
+ link,
+ link2);
+ break;
case '-':
default:
snprintf(icon, 2048, "<IMG SRC=\"%s%s\" ALT=\"%-6s\">",
debug(9, 7) ("%s\n", line);
if (!strncmp(line, "total", 5))
continue;
- t = ftpHtmlifyListEntry(line, ftpState->flags);
+ t = ftpHtmlifyListEntry(line, ftpState);
assert(t != NULL);
storeAppend(e, t, strlen(t));
}
}
static void
-ftpReadData(int fd, void *data)
+ftpDataRead(int fd, void *data)
{
FtpStateData *ftpState = data;
int len;
ftpState->data.buf + ftpState->data.offset,
ftpState->data.size - ftpState->data.offset);
fd_bytes(fd, len, FD_READ);
- debug(9, 5) ("ftpReadData: FD %d, Read %d bytes\n", fd, len);
+ debug(9, 5) ("ftpDataRead: FD %d, Read %d bytes\n", fd, len);
if (len > 0) {
IOStats.Ftp.reads++;
for (j = len - 1, bin = 0; j; bin++)
IOStats.Ftp.read_hist[bin]++;
}
if (len < 0) {
- debug(50, 1) ("ftpReadData: read error: %s\n", xstrerror());
+ debug(50, 1) ("ftpDataRead: read error: %s\n", xstrerror());
if (ignoreErrno(errno)) {
commSetSelect(fd,
COMM_SELECT_READ,
- ftpReadData,
+ ftpDataRead,
data,
Config.Timeout.read);
} else {
else
commSetSelect(fd,
COMM_SELECT_READ,
- ftpReadData,
+ ftpDataRead,
data,
Config.Timeout.read);
}
request_t *request = ftpState->request;
int l;
char *t;
+ if ((t = strrchr(request->urlpath, ';')) != NULL) {
+ if (strncasecmp(t + 1, "type=", 5) == 0) {
+ ftpState->typecode = (char) toupper((int) *(t + 6));
+ *t = '\0';
+ }
+ }
l = strlen(request->urlpath);
EBIT_SET(ftpState->flags, FTP_USE_BASE);
/* check for null path */
if (l == 1)
EBIT_SET(ftpState->flags, FTP_ROOT_DIR);
}
- if ((t = strrchr(request->urlpath, ';')) != NULL) {
- if (strncasecmp(t + 1, "type=", 5) == 0) {
- ftpState->typecode = (char) toupper((int) *(t + 6));
- *t = '\0';
- }
- }
}
static void
case 'I':
break;
default:
- t = strrchr(ftpState->request->urlpath, '/');
- filename = t ? t + 1 : ftpState->request->urlpath;
- mode = mimeGetTransferMode(filename);
+ if (EBIT_TEST(ftpState->flags, FTP_ISDIR)) {
+ mode = 'A';
+ } else {
+ t = strrchr(ftpState->request->urlpath, '/');
+ filename = t ? t + 1 : ftpState->request->urlpath;
+ mode = mimeGetTransferMode(filename);
+ }
break;
}
if (mode == 'I')
if (ftpState->pathcomps)
ftpTraverseDirectory(ftpState);
else
- ftpSendPasv(ftpState);
+ ftpListDir(ftpState);
} else {
ftpFail(ftpState);
}
/* Done? */
if (ftpState->pathcomps == NULL) {
debug(9, 3) ("the final component was a directory\n");
- if (!EBIT_TEST(ftpState->flags, FTP_ISDIR)) {
- debug(9, 3) ("and path did not end in /\n");
- strcat(ftpState->title_url, "/");
- EBIT_SET(ftpState->flags, FTP_ISDIR);
- EBIT_SET(ftpState->flags, FTP_USE_BASE);
- }
- ftpSendPasv(ftpState);
+ ftpListDir(ftpState);
return;
}
/* Go to next path component */
ftpSendCwd(ftpState);
} else {
debug(9, 3) ("final component is probably a file\n");
- ftpSendMdtm(ftpState);
+ ftpGetFile(ftpState);
return;
}
}
}
}
+static void
+ftpGetFile(FtpStateData * ftpState)
+{
+ assert(*ftpState->filepath != '\0');
+ EBIT_CLR(ftpState->flags, FTP_ISDIR);
+ ftpSendMdtm(ftpState);
+}
+
+static void
+ftpListDir(FtpStateData * ftpState)
+{
+ if (!EBIT_TEST(ftpState->flags, FTP_ISDIR)) {
+ debug(9, 3) ("Directory path did not end in /\n");
+ strcat(ftpState->title_url, "/");
+ EBIT_SET(ftpState->flags, FTP_ISDIR);
+ EBIT_SET(ftpState->flags, FTP_USE_BASE);
+ }
+ ftpSendPasv(ftpState);
+}
+
static void
ftpSendMdtm(FtpStateData * ftpState)
{
{
/* Only send SIZE for binary transfers. The returned size
* is useless on ASCII transfers */
- if (!EBIT_TEST(ftpState->flags, FTP_BINARY)) {
+ if (EBIT_TEST(ftpState->flags, FTP_BINARY)) {
assert(ftpState->filepath != NULL);
assert(*ftpState->filepath != '\0');
snprintf(cbuf, 1024, "SIZE %s\r\n", ftpState->filepath);
{
debug(9, 3) ("This is ftpRestOrList\n");
if (ftpState->typecode == 'D') {
+ /* XXX This should NOT be here */
ftpSendNlst(ftpState); /* sec 3.2.2 of RFC 1738 */
EBIT_SET(ftpState->flags, FTP_ISDIR);
+ EBIT_SET(ftpState->flags, FTP_USE_BASE);
} else if (EBIT_TEST(ftpState->flags, FTP_ISDIR))
ftpSendList(ftpState);
else if (ftpState->restart_offset > 0)
static void
ftpSendList(FtpStateData * ftpState)
{
- snprintf(cbuf, 1024, "LIST\r\n");
+ if (ftpState->filepath) {
+ EBIT_SET(ftpState->flags, FTP_USE_BASE);
+ snprintf(cbuf, 1024, "LIST %s\r\n", ftpState->filepath);
+ } else {
+ snprintf(cbuf, 1024, "LIST\r\n");
+ }
ftpWriteCommand(cbuf, ftpState);
ftpState->state = SENT_LIST;
}
+static void
+ftpSendNlst(FtpStateData * ftpState)
+{
+ EBIT_SET(ftpState->flags, FTP_TRIED_NLST);
+ if (ftpState->filepath) {
+ EBIT_SET(ftpState->flags, FTP_USE_BASE);
+ snprintf(cbuf, 1024, "NLST %s\r\n", ftpState->filepath);
+ } else {
+ snprintf(cbuf, 1024, "NLST\r\n");
+ }
+ ftpWriteCommand(cbuf, ftpState);
+ ftpState->state = SENT_NLST;
+}
+
static void
ftpReadList(FtpStateData * ftpState)
{
ftpAppendSuccessHeader(ftpState);
commSetSelect(ftpState->data.fd,
COMM_SELECT_READ,
- ftpReadData,
+ ftpDataRead,
ftpState,
Config.Timeout.read);
commSetDefer(ftpState->data.fd, protoCheckDeferRead, ftpState->entry);
}
}
-static void
-ftpSendNlst(FtpStateData * ftpState)
-{
- EBIT_SET(ftpState->flags, FTP_TRIED_NLST);
- if (ftpState->filepath)
- snprintf(cbuf, 1024, "NLST %s\r\n", ftpState->filepath);
- else
- snprintf(cbuf, 1024, "NLST\r\n");
- ftpWriteCommand(cbuf, ftpState);
- ftpState->state = SENT_NLST;
-}
-
static void
ftpSendRetr(FtpStateData * ftpState)
{
ftpAppendSuccessHeader(ftpState);
commSetSelect(ftpState->data.fd,
COMM_SELECT_READ,
- ftpReadData,
+ ftpDataRead,
ftpState,
Config.Timeout.read);
commSetDefer(ftpState->data.fd, protoCheckDeferRead, ftpState->entry);
rfc1738_unescape(path);
ftpState->filepath = path;
/* And off we go */
- ftpSendMdtm(ftpState);
+ ftpGetFile(ftpState);
}
static void
{
ErrorState *err;
debug(9, 3) ("ftpFail\n");
- /* Try the / hack to support "Netscape" FTP URL's
- * only if we failed on CWD or RETR, !IS_DIR */
+ /* Try the / hack to support "Netscape" FTP URL's for retreiving files */
if (!EBIT_TEST(ftpState->flags, FTP_ISDIR) &&
!EBIT_TEST(ftpState->flags, FTP_TRY_SLASH_HACK)) {
switch (ftpState->state) {