#include "wordlist.h"
#include "SquidTime.h"
#include "URLScheme.h"
-#include "SquidString.h"
/**
\defgroup ServerProtocolFTPInternal Server-Side FTP Internals
bool dir_slash;
bool root_dir;
bool no_dotdot;
+ bool html_header_sent;
bool binary;
bool try_slash_hack;
bool put;
bool put_mkdir;
bool listformat_unknown;
- bool listing;
+ bool listing_started;
bool completed_forwarding;
};
int64_t restarted_offset;
char *proxy_host;
size_t list_width;
- String cwd_message;
+ wordlist *cwd_message;
char *old_request;
char *old_reply;
char *old_filepath;
char typecode;
- MemBuf listing; ///< FTP directory listing in HTML format.
// \todo: optimize ctrl and data structs member order, to minimize size
/// FTP control channel info; the channel is opened once per transaction
void failed(err_type, int xerrno);
void failedErrorMessage(err_type, int xerrno);
void unhack();
+ void listingStart();
+ void listingFinish();
void scheduleReadControlReply(int);
void handleControlReply();
void readStor();
+ char *htmlifyListEntry(const char *line);
void parseListing();
- MemBuf *htmlifyListEntry(const char *line);
void dataComplete();
void dataRead(const CommIoCbParams &io);
int checkAuth(const HttpHeader * req_hdr);
if (ctrl.message)
wordlistDestroy(&ctrl.message);
- cwd_message.clean();
+ if (cwd_message)
+ wordlistDestroy(&cwd_message);
safe_free(ctrl.last_reply);
/* failed() closes ctrl.fd and frees ftpState */
}
-#if DEAD_CODE // obsoleted by ERR_FTP_LISTING template.
void
-FtpStateData::listingFinish()
+FtpStateData::listingStart()
{
+ debugs(9,3, HERE);
+ wordlist *w;
+ char *dirup;
+ int i, j, k;
+ const char *title = title_url.buf();
+ flags.listing_started = true;
+ printfReplyBody("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n");
+ printfReplyBody("<!-- HTML listing generated by Squid %s -->\n",
+ version_string);
+ printfReplyBody("<!-- %s -->\n", mkrfc1123(squid_curtime));
+ printfReplyBody("<HTML><HEAD><TITLE>\n");
+ {
+ char *t = xstrdup(title);
+ rfc1738_unescape(t);
+ printfReplyBody("FTP Directory: %s\n", html_quote(t));
+ xfree(t);
+ }
+
+ printfReplyBody("</TITLE>\n");
+ printfReplyBody("<STYLE type=\"text/css\"><!--BODY{background-color:#ffffff;font-family:verdana,sans-serif}--></STYLE>\n");
+
+ if (flags.need_base_href)
+ printfReplyBody("<BASE HREF=\"%s\">\n",
+ html_quote(base_href.buf()));
+
+ printfReplyBody("</HEAD><BODY>\n");
+
+ if (cwd_message) {
+ printfReplyBody("<PRE>\n");
+
+ for (w = cwd_message; w; w = w->next)
+ printfReplyBody("%s\n", html_quote(w->key));
+
+ printfReplyBody("</PRE>\n");
+
+ printfReplyBody("<HR noshade size=\"1px\">\n");
+
+ wordlistDestroy(&cwd_message);
+ }
+
+ printfReplyBody("<H2>\n");
+ printfReplyBody("FTP Directory: ");
+ /* "ftp://" == 6 characters */
+ assert(title_url.size() >= 6);
+ k = 6 + strcspn(&title[6], "/");
+
+ for (i = 6, j = 0; title[i]; j = i) {
+ printfReplyBody("<A HREF=\"");
+ i += strcspn(&title[i], "/");
+
+ if (i > j) {
+ char *url = xstrdup(title);
+ url[i] = '\0';
+ printfReplyBody("%s", html_quote(url + k));
+ printfReplyBody("/");
+ printfReplyBody("\">");
+ rfc1738_unescape(url + j);
+ printfReplyBody("%s", html_quote(url + j));
+ safe_free(url);
+ printfReplyBody("</A>");
+ }
-// TODO: figure out what this means and how to show it ...
+ printfReplyBody("/");
+
+ if (title[i] == '/')
+ i++;
+
+ if (i == j) {
+ /* Error guard, or "assert" */
+ printfReplyBody("ERROR: Failed to parse URL: %s\n",
+ html_quote(title));
+ debugs(9, DBG_CRITICAL, "Failed to parse URL: " << title);
+ break;
+ }
+ }
+
+ printfReplyBody("</H2>\n");
+ printfReplyBody("<PRE>\n");
+ dirup = htmlifyListEntry("<internal-dirup>");
+ writeReplyBody(dirup, strlen(dirup));
+ flags.html_header_sent = 1;
+}
+
+void
+FtpStateData::listingFinish()
+{
+ debugs(9,3,HERE);
+ entry->buffer();
+ printfReplyBody("</PRE>\n");
if (flags.listformat_unknown && !flags.tried_nlst) {
printfReplyBody("<A HREF=\"%s/;type=d\">[As plain directory]</A>\n",
const char *path = flags.dir_slash ? filepath : ".";
printfReplyBody("<A HREF=\"%s/\">[As extended directory]</A>\n", rfc1738_escape_part(path));
}
+
+ printfReplyBody("<HR noshade size=\"1px\">\n");
+ printfReplyBody("<ADDRESS>\n");
+ printfReplyBody("Generated %s by %s (%s)\n",
+ mkrfc1123(squid_curtime),
+ getMyHostname(),
+ visible_appname_string);
+ printfReplyBody("</ADDRESS></BODY></HTML>\n");
}
-#endif /* DEAD_CODE */
/// \ingroup ServerProtocolFTPInternal
static const char *Month[] = {
return p;
}
-MemBuf *
+/// \ingroup ServerProtocolFTPInternal
+static const char *
+dots_fill(size_t len)
+{
+ static char buf[256];
+ size_t i = 0;
+
+ if (len > Config.Ftp.list_width) {
+ memset(buf, ' ', 256);
+ buf[0] = '\n';
+ buf[Config.Ftp.list_width + 4] = '\0';
+ return buf;
+ }
+
+ for (i = len; i < Config.Ftp.list_width; i++)
+ buf[i - len] = (i % 2) ? '.' : ' ';
+
+ buf[i - len] = '\0';
+
+ return buf;
+}
+
+char *
FtpStateData::htmlifyListEntry(const char *line)
{
- char icon[2048];
- char href[2048 + 40];
- char text[ 2048];
- char size[ 2048];
- char chdir[ 2048 + 40];
- char view[ 2048 + 40];
- char download[ 2048 + 40];
- char link[ 2048 + 40];
- MemBuf *html;
- char prefix[2048];
+ LOCAL_ARRAY(char, icon, 2048);
+ LOCAL_ARRAY(char, href, 2048 + 40);
+ LOCAL_ARRAY(char, text, 2048);
+ LOCAL_ARRAY(char, size, 2048);
+ LOCAL_ARRAY(char, chdir, 2048 + 40);
+ LOCAL_ARRAY(char, view, 2048 + 40);
+ LOCAL_ARRAY(char, download, 2048 + 40);
+ LOCAL_ARRAY(char, link, 2048 + 40);
+ LOCAL_ARRAY(char, html, 8192);
+ LOCAL_ARRAY(char, prefix, 2048);
+ size_t width = Config.Ftp.list_width;
ftpListParts *parts;
- *icon = *href = *text = *size = *chdir = *view = *download = *link = '\0';
+ *icon = *href = *text = *size = *chdir = *view = *download = *link = *html = '\0';
- if (strlen(line) > 1024) {
- html = new MemBuf();
- html->Printf("<tr><td colspan=\"5\">%s</td></tr>\n", line);
+ if ((int) strlen(line) > 1024) {
+ snprintf(html, 8192, "%s\n", line);
return html;
}
else
prefix[0] = '\0';
+ /* Handle builtin <dirup> */
+ if (strcmp(line, "<internal-dirup>") == 0) {
+ /* <A HREF="{href}">{icon}</A> <A HREF="{href}">{text}</A> {link} */
+ snprintf(icon, 2048, "<IMG border=\"0\" SRC=\"%s\" ALT=\"%-6s\">",
+ mimeGetIconURL("internal-dirup"),
+ "[DIRUP]");
+
+ if (!flags.no_dotdot && !flags.root_dir) {
+ /* Normal directory */
+
+ if (!flags.dir_slash)
+ strcpy(href, "../");
+ else
+ strcpy(href, "./");
+
+ strcpy(text, "Parent Directory");
+ } else if (!flags.no_dotdot && flags.root_dir) {
+ /* "Top level" directory */
+ strcpy(href, "%2e%2e/");
+ strcpy(text, "Parent Directory");
+ snprintf(link, 2048, "(<A HREF=\"%s\">%s</A>)",
+ "%2f/",
+ "Root Directory");
+ } else if (flags.no_dotdot && !flags.root_dir) {
+ char *url;
+ /* Normal directory where last component is / or .. */
+ strcpy(href, "%2e%2e/");
+ strcpy(text, "Parent Directory");
+
+ if (flags.dir_slash) {
+ url = xstrdup("./");
+ } else {
+ const char *title = title_url.buf();
+ int k = 6 + strcspn(&title[6], "/");
+ char *t;
+ url = xstrdup(title + k);
+ t = url + strlen(url) - 2;
+
+ while (t > url && *t != '/')
+ *t-- = '\0';
+ }
+
+ snprintf(link, 2048, "(<A HREF=\"%s\">%s</A>)", url, "Back");
+ safe_free(url);
+ } else { /* NO_DOTDOT && ROOT_DIR */
+ /* "UNIX Root" directory */
+ strcpy(href, "/");
+ strcpy(text, "Home Directory");
+ }
+
+ snprintf(html, 8192, "<A HREF=\"%s\">%s</A> <A HREF=\"%s\">%s</A> %s\n",
+ href, icon, href, text, link);
+ return html;
+ }
+
if ((parts = ftpListParseParts(line, flags)) == NULL) {
const char *p;
- html = new MemBuf();
- html->Printf("<tr class=\"entry\"<td colspan=\"5\">%s</td></tr>\n", line);
+ snprintf(html, 8192, "%s\n", line);
for (p = line; *p && xisspace(*p); p++);
if (*p && !xisspace(*p))
}
if (!strcmp(parts->name, ".") || !strcmp(parts->name, "..")) {
+ *html = '\0';
ftpListPartsFree(&parts);
- return NULL;
+ return html;
}
parts->size += 1023;
parts->size >>= 10;
parts->showname = xstrdup(parts->name);
+ if (!Config.Ftp.list_wrap) {
+ if (strlen(parts->showname) > width - 1) {
+ *(parts->showname + width - 1) = '>';
+ *(parts->showname + width - 0) = '\0';
+ }
+ }
+
/* {icon} {text} . . . {date}{size}{chdir}{view}{download}{link}\n */
xstrncpy(href, rfc1738_escape_part(parts->name), 2048);
switch (parts->type) {
case 'd':
- snprintf(icon, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
+ snprintf(icon, 2048, "<IMG border=\"0\" SRC=\"%s\" ALT=\"%-6s\">",
mimeGetIconURL("internal-dir"),
"[DIR]");
strcat(href, "/"); /* margin is allocated above */
break;
case 'l':
- snprintf(icon, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
+ snprintf(icon, 2048, "<IMG border=\"0\" SRC=\"%s\" ALT=\"%-6s\">",
mimeGetIconURL("internal-link"),
"[LINK]");
/* sometimes there is an 'l' flag, but no "->" link */
if (parts->link) {
char *link2 = xstrdup(html_quote(rfc1738_escape(parts->link)));
- snprintf(link, 2048, " -> <a href=\"%s%s\">%s</a>",
+ snprintf(link, 2048, " -> <A HREF=\"%s%s\">%s</A>",
*link2 != '/' ? prefix : "", link2,
html_quote(parts->link));
safe_free(link2);
break;
case '\0':
- snprintf(icon, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
+ snprintf(icon, 2048, "<IMG border=\"0\" SRC=\"%s\" ALT=\"%-6s\">",
mimeGetIconURL(parts->name),
"[UNKNOWN]");
- snprintf(chdir, 2048, "<a href=\"%s/;type=d\"><img border=\"0\" src=\"%s\" "
- "alt=\"[DIR]\"></a>",
+ snprintf(chdir, 2048, " <A HREF=\"%s/;type=d\"><IMG border=\"0\" SRC=\"%s\" "
+ "ALT=\"[DIR]\"></A>",
rfc1738_escape_part(parts->name),
mimeGetIconURL("internal-dir"));
break;
case '-':
default:
- snprintf(icon, 2048, "<img border=\"0\" src=\"%s\" alt=\"%-6s\">",
+ snprintf(icon, 2048, "<IMG border=\"0\" SRC=\"%s\" ALT=\"%-6s\">",
mimeGetIconURL(parts->name),
"[FILE]");
snprintf(size, 2048, " %6"PRId64"k", parts->size);
if (parts->type != 'd') {
if (mimeGetViewOption(parts->name)) {
- snprintf(view, 2048, "<a href=\"%s%s;type=a\"><img border=\"0\" src=\"%s\" "
- "alt=\"[VIEW]\"></a>",
+ snprintf(view, 2048, " <A HREF=\"%s%s;type=a\"><IMG border=\"0\" SRC=\"%s\" "
+ "ALT=\"[VIEW]\"></A>",
prefix, href, mimeGetIconURL("internal-view"));
}
if (mimeGetDownloadOption(parts->name)) {
- snprintf(download, 2048, "<a href=\"%s%s;type=i\"><img border=\"0\" src=\"%s\" "
- "alt=\"[DOWNLOAD]\"></a>",
+ snprintf(download, 2048, " <A HREF=\"%s%s;type=i\"><IMG border=\"0\" SRC=\"%s\" "
+ "ALT=\"[DOWNLOAD]\"></A>",
prefix, href, mimeGetIconURL("internal-download"));
}
}
- /* construct the table row from parts. */
- html = new MemBuf();
- html->Printf("<tr class=\"entry\">"
- "<td class=\"icon\"><a href=\"%s%s\">%s</a></td>"
- "<td class=\"filename\"><a href=\"%s%s\">%s</a></td>"
- "<td class=\"date\">%s</td>"
- "<td class=\"size\">%s</td>"
- "<td class=\"actions\">%s%s%s%s</td>"
- "</tr>\n",
- prefix, href, icon,
- prefix, href, html_quote(text),
- parts->date,
- size,
- chdir, view, download, link);
+ /* <A HREF="{href}">{icon}</A> <A HREF="{href}">{text}</A> . . . {date}{size}{chdir}{view}{download}{link}\n */
+ if (parts->type != '\0') {
+ snprintf(html, 8192, "<A HREF=\"%s%s\">%s</A> <A HREF=\"%s%s\">%s</A>%s "
+ "%s%8s%s%s%s%s\n",
+ prefix, href, icon, prefix, href, html_quote(text), dots_fill(strlen(text)),
+ parts->date, size, chdir, view, download, link);
+ } else {
+ /* Plain listing. {icon} {text} ... {chdir}{view}{download} */
+ snprintf(html, 8192, "<A HREF=\"%s%s\">%s</A> <A HREF=\"%s%s\">%s</A>%s "
+ "%s%s%s%s\n",
+ prefix, href, icon, prefix, href, html_quote(text), dots_fill(strlen(text)),
+ chdir, view, download, link);
+ }
ftpListPartsFree(&parts);
return html;
char *end;
char *line;
char *s;
- MemBuf *t;
+ char *t;
size_t linelen;
size_t usable;
StoreEntry *e = entry;
t = htmlifyListEntry(line);
- if( t != NULL) {
- listing.append(t->content(), t->contentSize());
- }
+ assert(t != NULL);
+
+ writeReplyBody(t, strlen(t));
}
data.readBuf->consume(usable);
maybeReadVirginBody();
} else {
- if (!flags.http_header_sent && !fwd->ftpPasvFailed() && flags.pasv_supported && !flags.listing) {
+ if (!flags.http_header_sent && !fwd->ftpPasvFailed() && flags.pasv_supported) {
fwd->dontRetry(false); /* this is a retryable error */
fwd->ftpPasvFailed(true);
}
#endif
+ if (flags.isdir && !flags.listing_started)
+ listingStart();
+
if (flags.isdir) {
- flags.listing = 1;
- listing.reset();
parseListing();
- return;
- } else if (const int csize = data.readBuf->contentSize()) {
- writeReplyBody(data.readBuf->content(), csize);
- debugs(9, 5, HERE << "consuming " << csize << " bytes of readBuf");
- data.readBuf->consume(csize);
- }
+ } else
+ if (const int csize = data.readBuf->contentSize()) {
+ writeReplyBody(data.readBuf->content(), csize);
+ debugs(9, 5, HERE << "consuming " << csize << " bytes of readBuf");
+ data.readBuf->consume(csize);
+ }
entry->flush();
/* Copy the rest of the message to cwd_message to be printed in
* error messages
*/
- cwd_message.append('\n');
- for (wordlist *w = ctrl.message; w; w = w->next) {
- cwd_message.append(' ');
- cwd_message.append(w->key);
- }
+ wordlistAddWl(&cwd_message, ctrl.message);
debugs(9, 3, HERE << "state=" << state << ", code=" << ctrl.replycode);
if (code >= 200 && code < 300) {
/* CWD OK */
ftpState->unhack();
-
/* Reset cwd_message to only include the last message */
- ftpState->cwd_message.reset("");
- for (wordlist *w = ftpState->ctrl.message; w; w = w->next) {
- ftpState->cwd_message.append(' ');
- ftpState->cwd_message.append(w->key);
- }
+
+ if (ftpState->cwd_message)
+ wordlistDestroy(&ftpState->cwd_message);
+
+ ftpState->cwd_message = ftpState->ctrl.message;
+
ftpState->ctrl.message = NULL;
/* Continue to traverse the path */
if (code == 226 || code == 250) {
/* Connection closed; retrieval done. */
- if (ftpState->flags.listing)
- ftpState->failed(ERR_FTP_LISTING, 0);
+ if (ftpState->flags.html_header_sent)
+ ftpState->listingFinish();
+
ftpSendQuit(ftpState);
} else { /* != 226 */
debugs(9, DBG_IMPORTANT, HERE << "Got code " << code << " after reading data");
err->xerrno = xerrno;
- if(error == ERR_FTP_LISTING) {
- err->ftp.listing = &listing;
- err->ftp.cwd_msg = xstrdup(html_quote(cwd_message.buf()));
- }
- else {
- err->ftp.server_msg = ctrl.message;
- ctrl.message = NULL;
- }
+ err->ftp.server_msg = ctrl.message;
+
+ ctrl.message = NULL;
if (old_request)
command = old_request;