00001
00002
00003
00004
00005
00006
00007
00008
00009 #include <ctype.h>
00010 #include <time.h>
00011 #include "wvhttppool.h"
00012 #include "wvbufstream.h"
00013 #include "wvtcp.h"
00014 #include "wvsslstream.h"
00015 #include "strutils.h"
00016
00017 WvFtpStream::WvFtpStream(const WvIPPortAddr &_remaddr, WvStringParm _username,
00018 WvStringParm _password)
00019 : WvUrlStream(_remaddr, _username, WvString("FTP %s", _remaddr))
00020 {
00021 data = NULL;
00022 logged_in = false;
00023 password = _password;
00024 uses_continue_select = true;
00025 last_request_time = time(0);
00026 alarm(60000);
00027 }
00028
00029
00030 WvFtpStream::~WvFtpStream()
00031 {
00032 close();
00033 }
00034
00035
00036 void WvFtpStream::doneurl()
00037 {
00038 log("Done URL: %s\n", curl->url);
00039
00040 curl->done();
00041 curl = NULL;
00042 if (data)
00043 {
00044 delete data;
00045 data = NULL;
00046 }
00047 urls.unlink_first();
00048 last_request_time = time(0);
00049 alarm(60000);
00050 request_next();
00051 }
00052
00053
00054 void WvFtpStream::request_next()
00055 {
00056
00057
00058 if (request_count >= max_requests || waiting_urls.isempty())
00059 return;
00060
00061 if (!urls.isempty())
00062 return;
00063
00064
00065 WvUrlRequest *url = waiting_urls.first();
00066
00067 waiting_urls.unlink_first();
00068
00069 request_count++;
00070 log("Request #%s: %s\n", request_count, url->url);
00071 urls.append(url, false, "request_url");
00072 alarm(0);
00073 }
00074
00075
00076 void WvFtpStream::close()
00077 {
00078 if (isok())
00079 log("Closing.\n");
00080 WvStreamClone::close();
00081
00082 if (geterr())
00083 {
00084
00085
00086 if (!curl && !urls.isempty())
00087 curl = urls.first();
00088 if (!curl && !waiting_urls.isempty())
00089 curl = waiting_urls.first();
00090 if (curl)
00091 log("URL '%s' is FAILED\n", curl->url);
00092 if (curl)
00093 curl->done();
00094 }
00095
00096 if (curl)
00097 curl->done();
00098 }
00099
00100
00101 char *WvFtpStream::get_important_line(int timeout)
00102 {
00103 char *line;
00104 do
00105 {
00106 line = getline(timeout);
00107 if (!line)
00108 return NULL;
00109 }
00110 while (line[3] == '-');
00111 return line;
00112 }
00113
00114
00115 bool WvFtpStream::pre_select(SelectInfo &si)
00116 {
00117 SelectRequest oldwant = si.wants;
00118
00119 if (WvUrlStream::pre_select(si))
00120 return true;
00121
00122 if (data && data->pre_select(si))
00123 return true;
00124
00125 if (curl && curl->putstream && curl->putstream->pre_select(si))
00126 return true;
00127
00128 si.wants = oldwant;
00129
00130 return false;
00131 }
00132
00133
00134 bool WvFtpStream::post_select(SelectInfo &si)
00135 {
00136 SelectRequest oldwant = si.wants;
00137
00138 if (WvUrlStream::post_select(si))
00139 return true;
00140
00141 if (data && data->post_select(si))
00142 return true;
00143
00144 if (curl && curl->putstream && curl->putstream->post_select(si))
00145 return true;
00146
00147 si.wants = oldwant;
00148
00149 return false;
00150 }
00151
00152
00153 void WvFtpStream::execute()
00154 {
00155 char buf[1024], *line;
00156 WvStreamClone::execute();
00157
00158 if (alarm_was_ticking && ((last_request_time + 60) <= time(0)))
00159 {
00160 log(WvLog::Debug4, "urls count: %s\n", urls.count());
00161 if (urls.isempty())
00162 close();
00163
00164 return;
00165 }
00166
00167 if (!logged_in)
00168 {
00169 line = get_important_line(60000);
00170 if (!line)
00171 return;
00172 if (strncmp(line, "220", 3))
00173 {
00174 log("Server rejected connection: %s\n", line);
00175 seterr("server rejected connection");
00176 return;
00177 }
00178
00179 log(WvLog::Debug5, "Got greeting: %s\n", line);
00180 write(WvString("USER %s\r\n",
00181 !target.username ? "anonymous" :
00182 target.username.cstr()));
00183 line = get_important_line(60000);
00184 if (!line)
00185 return;
00186
00187 if (!strncmp(line, "230", 3))
00188 {
00189 log(WvLog::Debug5, "Server doesn't need password.\n");
00190 logged_in = true;
00191 }
00192 else if (!strncmp(line, "33", 2))
00193 {
00194 write(WvString("PASS %s\r\n", !password ? DEFAULT_ANON_PW :
00195 password));
00196 line = get_important_line(60000);
00197 if (!line)
00198 return;
00199
00200 if (line[0] == '2')
00201 {
00202 log(WvLog::Debug5, "Authenticated.\n");
00203 logged_in = true;
00204 }
00205 else
00206 {
00207 log("Strange response to PASS command: %s\n", line);
00208 seterr("strange response to PASS command");
00209 return;
00210 }
00211 }
00212 else
00213 {
00214 log("Strange response to USER command: %s\n", line);
00215 seterr("strange response to USER command");
00216 return;
00217 }
00218
00219 write("TYPE I\r\n");
00220 line = get_important_line(60000);
00221 if (!line)
00222 return;
00223
00224 if (strncmp(line, "200", 3))
00225 {
00226 log("Strange response to TYPE I command: %s\n", line);
00227 seterr("strange response to TYPE I command");
00228 return;
00229 }
00230 }
00231
00232 if (!curl && !urls.isempty())
00233 {
00234 curl = urls.first();
00235
00236 write(WvString("CWD %s\r\n", curl->url.getfile()));
00237 line = get_important_line(60000);
00238 if (!line)
00239 return;
00240
00241 if (!strncmp(line, "250", 3))
00242 {
00243 log(WvLog::Debug5, "This is a directory.\n");
00244 curl->is_dir = true;
00245 }
00246
00247 write("PASV\r\n");
00248 line = get_important_line(60000);
00249 if (!line)
00250 return;
00251
00252 WvIPPortAddr *dataip = parse_pasv_response(line);
00253
00254 if (!dataip)
00255 return;
00256
00257 log(WvLog::Debug4, "Data port is %s.\n", *dataip);
00258
00259 data = new WvTCPConn(*dataip);
00260 if (!data)
00261 {
00262 log("Can't open data connection.\n");
00263 seterr("can't open data connection");
00264 return;
00265 }
00266
00267 if (curl->is_dir)
00268 {
00269 if (!curl->putstream)
00270 {
00271 write(WvString("LIST %s\r\n", curl->url.getfile()));
00272 if (curl->outstream)
00273 {
00274 WvString url_no_pw("ftp://%s%s%s%s", curl->url.getuser(),
00275 !!curl->url.getuser() ? "@" : "",
00276 curl->url.gethost(),
00277 curl->url.getfile());
00278 curl->outstream->write(
00279 WvString(
00280 "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML "
00281 "4.01//EN\">\n"
00282 "<html>\n<head>\n<title>%s</title>\n"
00283 "<meta http-equiv=\"Content-Type\" "
00284 "content=\"text/html; "
00285 "charset=ISO-8859-1\">\n"
00286 "<base href=\"%s\"/>\n</head>\n"
00287 "<style type=\"text/css\">\n"
00288 "img { border: 0; padding: 0 2px; vertical-align: "
00289 "text-bottom; }\n"
00290 "td { font-family: monospace; padding: 2px 3px; "
00291 "text-align: right; vertical-align: bottom; }\n"
00292 "td:first-child { text-align: left; padding: "
00293 "2px 10px 2px 3px; }\n"
00294 "table { border: 0; }\n"
00295 "a.symlink { font-style: italic; }\n"
00296 "</style>\n<body>\n"
00297 "<h1>Index of %s</h1>\n"
00298 "<hr/><table>\n", url_no_pw, curl->url, url_no_pw
00299 ));
00300 }
00301 }
00302 else
00303 {
00304 log("Target is a directory.\n");
00305 seterr("target is a directory");
00306 doneurl();
00307 return;
00308 }
00309 }
00310 else if (!curl->putstream)
00311 write(WvString("RETR %s\r\n", curl->url.getfile()));
00312 else
00313 {
00314 if (curl->create_dirs)
00315 {
00316 write(WvString("CWD %s\r\n", getdirname(curl->url.getfile())));
00317 line = get_important_line(60000);
00318 if (!line)
00319 return;
00320
00321 if (strncmp(line, "250", 3))
00322 {
00323 log("Path doesn't exist; creating directories...\n");
00324
00325 WvString current_dir("");
00326 WvStringList dirs;
00327 dirs.split(getdirname(curl->url.getfile()), "/");
00328 WvStringList::Iter i(dirs);
00329 for (i.rewind(); i.next(); )
00330 {
00331 current_dir.append(WvString("/%s", i()));
00332 write(WvString("MKD %s\r\n", current_dir));
00333 line = get_important_line(60000);
00334 if (!line)
00335 return;
00336 }
00337 }
00338 }
00339 write(WvString("STOR %s\r\n", curl->url.getfile()));
00340 }
00341
00342 log(WvLog::Debug5, "Waiting for response to STOR/RETR/LIST\n");
00343 line = get_important_line(60000);
00344 log("Response: %s\n", line);
00345 if (!line)
00346 doneurl();
00347 else if (strncmp(line, "150", 3))
00348 {
00349 log("Strange response to %s command: %s\n",
00350 curl->putstream ? "STOR" : "RETR", line);
00351 seterr(WvString("strange response to %s command",
00352 curl->putstream ? "STOR" : "RETR"));
00353 doneurl();
00354 }
00355
00356 }
00357
00358 if (curl)
00359 {
00360 if (curl->is_dir)
00361 {
00362 line = data->getline(0);
00363 if (line && curl->outstream)
00364 {
00365 WvString output_line(parse_for_links(line));
00366 if (!!output_line)
00367 curl->outstream->write(output_line);
00368 else
00369 curl->outstream->write("Unknown format of LIST "
00370 "response\n");
00371 }
00372 }
00373 else
00374 {
00375 if (curl->putstream)
00376 {
00377 int len = curl->putstream->read(buf, sizeof(buf));
00378 log(WvLog::Debug5, "Read %s bytes.\n", len);
00379
00380 if (len)
00381 data->write(buf, len);
00382 }
00383 else
00384 {
00385 int len = data->read(buf, sizeof(buf));
00386 log(WvLog::Debug5, "Read %s bytes.\n", len);
00387
00388 if (len && curl->outstream)
00389 curl->outstream->write(buf, len);
00390 }
00391 }
00392
00393 if (!data->isok() || (curl->putstream && !curl->putstream->isok()))
00394 {
00395 if (curl->putstream && data->isok())
00396 data->close();
00397 line = get_important_line(60000);
00398 if (!line)
00399 {
00400 doneurl();
00401 return;
00402 }
00403
00404 if (strncmp(line, "226", 3))
00405 log("Unexpected message: %s\n", line);
00406
00407 if (curl->is_dir)
00408 {
00409 if (curl->outstream)
00410 curl->outstream->write("</table><hr/></body>\n"
00411 "</html>\n");
00412 write("CWD /\r\n");
00413 log(WvLog::Debug5, "Waiting for response to CWD /\n");
00414 line = get_important_line(60000);
00415 if (!line)
00416 return;
00417
00418 if (strncmp(line, "250", 3))
00419 log("Strange resonse to \"CWD /\": %s\n", line);
00420
00421 }
00422 doneurl();
00423 }
00424 }
00425 }
00426
00427
00428 WvString WvFtpStream::parse_for_links(char *line)
00429 {
00430 WvString output_line("");
00431 trim_string(line);
00432
00433 if (curl->is_dir && curl->outstream)
00434 {
00435 struct ftpparse fp;
00436 int res = ftpparse(&fp, line, strlen(line));
00437 if (res)
00438 {
00439 char *linkname = (char *) alloca(fp.namelen+1);
00440 int i;
00441 for (i = 0; i < fp.namelen; i++)
00442 {
00443 if (fp.name[i] >= 32)
00444 linkname[i] = fp.name[i];
00445 else
00446 {
00447 linkname[i] = '?';
00448 }
00449 }
00450 linkname[i] = 0;
00451
00452 WvString linkurl(curl->url);
00453 if (linkurl.cstr()[linkurl.len()-1] != '/')
00454 linkurl.append("/");
00455 linkurl.append(linkname);
00456 WvUrlLink *link = new WvUrlLink(linkname, linkurl);
00457 curl->outstream->links.append(link, true);
00458
00459 output_line.append("<tr>\n");
00460
00461 output_line.append(WvString(" <td>%s%s</td>\n", linkname,
00462 fp.flagtrycwd ? "/" : ""));
00463
00464 if (fp.flagtryretr)
00465 {
00466 if (!fp.sizetype)
00467 output_line.append(" <td>? bytes</td>\n");
00468 else
00469 output_line.append(WvString(" <td>%s bytes</td>\n",
00470 fp.size));
00471 if (fp.mtimetype > 0)
00472 output_line.append(WvString(" <td>%s</td>\n", (fp.mtime)));
00473 else
00474 output_line.append(" <td>?</td>\n");
00475 }
00476 else
00477 output_line.append(" <td></td>\n");
00478
00479 output_line.append("</tr>\n");
00480 }
00481 }
00482 return output_line;
00483 }
00484
00485
00486 WvIPPortAddr *WvFtpStream::parse_pasv_response(char *line)
00487 {
00488 if (strncmp(line, "227 ", 4))
00489 {
00490 log("Strange response to PASV command: %s\n", line);
00491 seterr("strange response to PASV command");
00492 return NULL;
00493 }
00494
00495 char *p = &line[3];
00496 while (!isdigit(*p))
00497 {
00498 if (*p == '\0' || *p == '\r' || *p == '\n')
00499 {
00500 log("Couldn't parse PASV response: %s\n", line);
00501 seterr("couldn't parse response to PASV command");
00502 return NULL;
00503 }
00504 p++;
00505 }
00506 char *ipstart = p;
00507
00508 for (int i = 0; i < 4; i++)
00509 {
00510 p = strchr(p, ',');
00511 if (!p)
00512 {
00513 log("Couldn't parse PASV IP: %s\n", line);
00514 seterr("couldn't parse PASV IP");
00515 return NULL;
00516 }
00517 *p = '.';
00518 }
00519 *p = '\0';
00520 WvString pasvip(ipstart);
00521 p++;
00522 int pasvport;
00523 pasvport = atoi(p)*256;
00524 p = strchr(p, ',');
00525 if (!p)
00526 {
00527 log("Couldn't parse PASV IP port: %s\n", line);
00528 seterr("couldn't parse PASV IP port");
00529 return NULL;
00530 }
00531 pasvport += atoi(++p);
00532
00533 WvIPPortAddr *res = new WvIPPortAddr(pasvip.cstr(), pasvport);
00534
00535 return res;
00536 }