Initial revision
[libcurl.git] / lib / ftp.c
blobcd64b0808d5017fb666f378b92799988c0910de0
1 /*****************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * The contents of this file are subject to the Mozilla Public License
9 * Version 1.0 (the "License"); you may not use this file except in
10 * compliance with the License. You may obtain a copy of the License at
11 * http://www.mozilla.org/MPL/
13 * Software distributed under the License is distributed on an "AS IS"
14 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
15 * License for the specific language governing rights and limitations
16 * under the License.
18 * The Original Code is Curl.
20 * The Initial Developer of the Original Code is Daniel Stenberg.
22 * Portions created by the Initial Developer are Copyright (C) 1998.
23 * All Rights Reserved.
25 * ------------------------------------------------------------
26 * Main author:
27 * - Daniel Stenberg <Daniel.Stenberg@haxx.nu>
29 * http://curl.haxx.nu
31 * $Source: /cvsroot/curl/curl/lib/ftp.c,v $
32 * $Revision: 1.1 $
33 * $Date: 1999-12-29 14:21:24 $
34 * $Author: bagder $
35 * $State: Exp $
36 * $Locker: $
38 * ------------------------------------------------------------
39 ****************************************************************************/
41 #include <stdio.h>
42 #include <string.h>
43 #include <stdlib.h>
44 #include <ctype.h>
45 #include <errno.h>
47 #include "setup.h"
49 #ifdef HAVE_UNISTD_H
50 #include <unistd.h>
51 #endif
52 #ifdef HAVE_SYS_SELECT_H
53 #include <sys/select.h>
54 #endif
56 #if defined(WIN32) && !defined(__GNUC__) || defined(__MINGW32__)
57 #include <winsock.h>
58 #else /* some kind of unix */
59 #include <sys/socket.h>
60 #include <netinet/in.h>
61 #ifdef HAVE_ARPA_INET_H
62 #include <arpa/inet.h>
63 #endif
64 #include <sys/utsname.h>
65 #include <netdb.h>
66 #endif
68 #if defined(WIN32) && defined(__GNUC__) || defined(__MINGW32__)
69 #include <errno.h>
70 #endif
73 #include <curl/curl.h>
74 #include "urldata.h"
75 #include "sendf.h"
77 #include "if2ip.h"
78 #include "hostip.h"
79 #include "progress.h"
80 #include "upload.h"
81 #include "download.h"
84 /* returns last node in linked list */
85 static struct curl_slist *slist_get_last(struct curl_slist *list)
87 struct curl_slist *item;
89 /* if caller passed us a NULL, return now */
90 if (!list)
91 return NULL;
93 /* loop through to find the last item */
94 item = list;
95 while (item->next) {
96 item = item->next;
98 return item;
101 /* append a struct to the linked list. It always retunrs the address of the
102 * first record, so that you can sure this function as an initialization
103 * function as well as an append function. If you find this bothersome,
104 * then simply create a separate _init function and call it appropriately from
105 * within the proram. */
106 struct curl_slist *curl_slist_append(struct curl_slist *list, char *data)
108 struct curl_slist *last;
109 struct curl_slist *new_item;
111 new_item = (struct curl_slist *) malloc(sizeof(struct curl_slist));
112 if (new_item) {
113 new_item->next = NULL;
114 new_item->data = strdup(data);
116 else {
117 fprintf(stderr, "Cannot allocate memory for QUOTE list.\n");
118 exit(-1);
121 if (list) {
122 last = slist_get_last(list);
123 last->next = new_item;
124 return list;
127 /* if this is the first item, then new_item *is* the list */
128 return new_item;
131 /* be nice and clean up resources */
132 void curl_slist_free_all(struct curl_slist *list)
134 struct curl_slist *next;
135 struct curl_slist *item;
137 if (!list)
138 return;
140 item = list;
141 do {
142 next = item->next;
144 if (item->data) {
145 free(item->data);
147 free(item);
148 item = next;
149 } while (next);
153 static UrgError AllowServerConnect(struct UrlData *data,
154 int sock)
156 fd_set rdset;
157 struct timeval dt;
159 FD_ZERO(&rdset);
161 FD_SET(sock, &rdset);
163 /* we give the server 10 seconds to connect to us */
164 dt.tv_sec = 10;
165 dt.tv_usec = 0;
167 switch ( select(sock+1, &rdset, NULL, NULL, &dt)) {
168 case -1: /* error */
169 /* let's die here */
170 failf(data, "Error while waiting for server connect");
171 return URG_FTP_PORT_FAILED;
172 case 0: /* timeout */
173 /* let's die here */
174 failf(data, "Timeout while waiting for server connect");
175 return URG_FTP_PORT_FAILED;
176 default:
177 /* we have received data here */
179 int s;
180 size_t size = sizeof(struct sockaddr_in);
181 struct sockaddr_in add;
183 getsockname(sock, (struct sockaddr *) &add, (int *)&size);
184 s=accept(sock, (struct sockaddr *) &add, (int *)&size);
186 if( -1 == s) {
187 /* DIE! */
188 failf(data, "Error accept()ing server connect");
189 return URG_FTP_PORT_FAILED;
191 infof(data, "Connection accepted from server\n");
193 data->secondarysocket = s;
195 break;
197 return URG_OK;
201 /* --- parse FTP server responses --- */
203 #define lastline(line) (isdigit((int)line[0]) && isdigit((int)line[1]) && \
204 isdigit((int)line[2]) && (' ' == line[3]))
206 static int GetLastResponse(int sockfd, char *buf,
207 struct UrlData *data)
209 int nread;
210 int read_rc=1;
211 char *ptr;
212 do {
213 ptr=buf;
215 /* get us a full line, terminated with a newline */
216 for(nread=0;
217 (nread<BUFSIZE) && read_rc;
218 nread++, ptr++) {
219 #ifdef USE_SSLEAY
220 if (data->use_ssl) {
221 read_rc = SSL_read(data->ssl, ptr, 1);
223 else {
224 #endif
225 read_rc = sread(sockfd, ptr, 1);
226 #ifdef USE_SSLEAY
228 #endif /* USE_SSLEAY */
229 if (*ptr == '\n')
230 break;
232 *ptr=0; /* zero terminate */
234 if(data->conf & CONF_VERBOSE) {
235 fputs("< ", data->err);
236 fwrite(buf, 1, nread, data->err);
237 fputs("\n", data->err);
239 } while(read_rc &&
240 (nread<4 || !lastline(buf)) );
241 return nread;
244 /* -- who are we? -- */
245 char *getmyhost(void)
247 static char myhost[256];
248 #if !defined(WIN32) && !defined(HAVE_UNAME) && !defined(HAVE_GETHOSTNAME)
249 /* We have no means of finding the local host name! */
250 strcpy(myhost, "localhost");
251 #endif
252 #if defined(WIN32) || !defined(HAVE_UNAME)
253 gethostname(myhost, 256);
254 #else
255 struct utsname ugnm;
257 if (uname(&ugnm) < 0)
258 return "localhost";
260 (void) strncpy(myhost, ugnm.nodename, 255);
261 myhost[255] = '\0';
262 #endif
263 return myhost;
266 #if 0
268 * URLfix()
270 * This function returns a string converted FROM the input URL format to a
271 * format that is more likely usable for the remote server. That is, all
272 * special characters (found as %XX-codes) will be eascaped with \<letter>.
275 static char *URLfix(char *string)
277 /* The length of the new string can't be longer than twice the original
278 string, if all letters are '+'... */
279 int alloc = strlen(string)*2;
280 char *ns = malloc(alloc);
281 unsigned char in;
282 int index=0;
283 int hex;
285 while(*string) {
286 in = *string;
287 switch(in) {
288 case '+':
289 ns[index++] = '\\';
290 ns[index++] = ' ';
291 string++;
292 continue;
294 case '%':
295 /* encoded part */
296 if(sscanf(string+1, "%02X", &hex)) {
297 ns[index++] = '\\';
298 ns[index++] = hex;
299 string+=3;
300 continue;
302 /* FALLTHROUGH */
303 default:
304 ns[index++] = in;
305 string++;
308 ns[index]=0; /* terminate it */
309 return ns;
311 #endif
313 static
314 UrgError _ftp(struct UrlData *data,
315 long *bytecountp,
316 char *ftpuser,
317 char *ftppasswd,
318 char *ppath)
320 /* this is FTP and no proxy */
321 size_t nread;
322 UrgError result;
323 char *buf = data->buffer; /* this is our buffer */
324 /* for the ftp PORT mode */
325 int portsock=-1;
326 struct sockaddr_in serv_addr;
328 struct curl_slist *qitem; /* QUOTE item */
330 /* The first thing we do is wait for the "220*" line: */
331 nread = GetLastResponse(data->firstsocket, buf, data);
332 if(strncmp(buf, "220", 3)) {
333 failf(data, "This doesn't seem like a nice ftp-server response");
334 return URG_FTP_WEIRD_SERVER_REPLY;
337 /* send USER */
338 sendf(data->firstsocket, data, "USER %s\r\n", ftpuser);
340 /* wait for feedback */
341 nread = GetLastResponse(data->firstsocket, buf, data);
343 if(!strncmp(buf, "530", 3)) {
344 /* 530 User ... access denied
345 (the server denies to log the specified user) */
346 failf(data, "Access denied: %s", &buf[4]);
347 return URG_FTP_ACCESS_DENIED;
349 else if(!strncmp(buf, "331", 3)) {
350 /* 331 Password required for ...
351 (the server requires to send the user's password too) */
352 sendf(data->firstsocket, data, "PASS %s\r\n", ftppasswd);
353 nread = GetLastResponse(data->firstsocket, buf, data);
355 if(!strncmp(buf, "530", 3)) {
356 /* 530 Login incorrect.
357 (the username and/or the password are incorrect) */
358 failf(data, "the username and/or the password are incorrect");
359 return URG_FTP_USER_PASSWORD_INCORRECT;
361 else if(!strncmp(buf, "230", 3)) {
362 /* 230 User ... logged in.
363 (user successfully logged in) */
365 infof(data, "We have successfully logged in\n");
367 else {
368 failf(data, "Odd return code after PASS");
369 return URG_FTP_WEIRD_PASS_REPLY;
372 else if(! strncmp(buf, "230", 3)) {
373 /* 230 User ... logged in.
374 (the user logged in without password) */
375 infof(data, "We have successfully logged in\n");
377 else {
378 failf(data, "Odd return code after USER");
379 return URG_FTP_WEIRD_USER_REPLY;
382 /* Send any QUOTE strings? */
383 if(data->quote) {
384 qitem = data->quote;
385 /* Send all QUOTE strings in same order as on command-line */
386 while (qitem) {
387 /* Send string */
388 if (qitem->data) {
389 sendf(data->firstsocket, data, "%s\r\n", qitem->data);
391 nread = GetLastResponse(data->firstsocket, buf, data);
393 if (buf[0] != '2') {
394 failf(data, "QUOT string not accepted: %s",
395 qitem->data);
396 return URG_FTP_QUOTE_ERROR;
399 qitem = qitem->next;
403 /* If we have selected NOBODY, it means that we only want file information.
404 Which in FTP can't be much more than the file size! */
405 if(data->conf & CONF_NOBODY) {
406 /* The SIZE command is _not_ RFC 959 specified, and therefor many servers
407 may not support it! It is however the only way we have to get a file's
408 size! */
409 int filesize;
410 sendf(data->firstsocket, data, "SIZE %s\r\n", ppath);
412 nread = GetLastResponse(data->firstsocket, buf, data);
414 if(strncmp(buf, "213", 3)) {
415 failf(data, "Couldn't get file size: %s", buf+4);
416 return URG_FTP_COULDNT_GET_SIZE;
418 /* get the size from the ascii string: */
419 filesize = atoi(buf+4);
421 sprintf(buf, "Content-Length: %d\n", filesize);
423 if(strlen(buf) != data->fwrite(buf, 1, strlen(buf), data->out)) {
424 failf (data, "Failed writing output");
425 return URG_WRITE_ERROR;
427 if(data->writeheader) {
428 /* the header is requested to be written to this file */
429 if(strlen(buf) != fwrite (buf, 1, strlen(buf), data->writeheader)) {
430 failf (data, "Failed writing output");
431 return URG_WRITE_ERROR;
434 return URG_OK;
437 /* We have chosen to use the PORT command */
438 if(data->conf & CONF_FTPPORT) {
439 struct sockaddr_in sa;
440 struct hostent *h=NULL;
441 size_t size;
442 unsigned short porttouse;
444 char *myhost=NULL;
446 if(data->ftpport) {
447 myhost = if2ip(data->ftpport);
448 if(myhost) {
449 h = GetHost(data, myhost);
451 else {
452 if(strlen(data->ftpport)>1)
453 h = GetHost(data, data->ftpport);
454 if(h)
455 myhost=data->ftpport;
458 if(!myhost) {
459 myhost = getmyhost();
460 h=GetHost(data, myhost);
462 infof(data, "We connect from %s\n", myhost);
464 if ( h ) {
465 if( (portsock = socket(AF_INET, SOCK_STREAM, 0)) >= 0 ) {
466 memset((char *)&sa, 0, sizeof(sa));
467 memcpy((char *)&sa.sin_addr,
468 h->h_addr,
469 h->h_length);
470 sa.sin_family = AF_INET;
471 sa.sin_addr.s_addr = INADDR_ANY;
472 sa.sin_port = 0;
473 size = sizeof(sa);
475 if(bind(portsock, (struct sockaddr *)&sa, size) >= 0) {
476 /* we succeeded to bind */
477 struct sockaddr_in add;
478 size = sizeof(add);
480 if(getsockname(portsock, (struct sockaddr *) &add,
481 (int *)&size)<0) {
482 failf(data, "getsockname() failed");
483 return URG_FTP_PORT_FAILED;
485 porttouse = ntohs(add.sin_port);
487 if ( listen(portsock, 1) < 0 ) {
488 failf(data, "listen(2) failed on socket");
489 return URG_FTP_PORT_FAILED;
492 else {
493 failf(data, "bind(2) failed on socket");
494 return URG_FTP_PORT_FAILED;
497 else {
498 failf(data, "socket(2) failed (%s)");
499 return URG_FTP_PORT_FAILED;
502 else {
503 failf(data, "could't find my own IP address (%s)", myhost);
504 return URG_FTP_PORT_FAILED;
507 struct in_addr in;
508 unsigned short ip[5];
509 (void) memcpy(&in.s_addr, *h->h_addr_list, sizeof (in.s_addr));
510 sscanf( inet_ntoa(in), "%hu.%hu.%hu.%hu",
511 &ip[0], &ip[1], &ip[2], &ip[3]);
512 sendf(data->firstsocket, data, "PORT %d,%d,%d,%d,%d,%d\n",
513 ip[0], ip[1], ip[2], ip[3],
514 porttouse >> 8,
515 porttouse & 255);
518 nread = GetLastResponse(data->firstsocket, buf, data);
520 if(strncmp(buf, "200", 3)) {
521 failf(data, "Server does not grok PORT, try without it!");
522 return URG_FTP_PORT_FAILED;
525 else { /* we use the PASV command */
527 sendf(data->firstsocket, data, "PASV\r\n");
529 nread = GetLastResponse(data->firstsocket, buf, data);
531 if(strncmp(buf, "227", 3)) {
532 failf(data, "Odd return code after PASV");
533 return URG_FTP_WEIRD_PASV_REPLY;
535 else {
536 int ip[4];
537 int port[2];
538 unsigned short newport;
539 char newhost[32];
540 struct hostent *he;
541 char *str=buf;
544 * New 227-parser June 3rd 1999.
545 * It now scans for a sequence of six comma-separated numbers and
546 * will take them as IP+port indicators.
548 * Found reply-strings include:
549 * "227 Entering Passive Mode (127,0,0,1,4,51)"
550 * "227 Data transfer will passively listen to 127,0,0,1,4,51"
551 * "227 Entering passive mode. 127,0,0,1,4,51"
554 while(*str) {
555 if (6 == sscanf(str, "%d,%d,%d,%d,%d,%d",
556 &ip[0], &ip[1], &ip[2], &ip[3],
557 &port[0], &port[1]))
558 break;
559 str++;
561 if(!*str) {
562 failf(data, "Couldn't interpret this 227-reply: %s", buf);
563 return URG_FTP_WEIRD_227_FORMAT;
565 sprintf(newhost, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
566 he = GetHost(data, newhost);
567 if(!he) {
568 failf(data, "Can't resolve new host %s", newhost);
569 return URG_FTP_CANT_GET_HOST;
573 newport = (port[0]<<8) + port[1];
574 data->secondarysocket = socket(AF_INET, SOCK_STREAM, 0);
576 memset((char *) &serv_addr, '\0', sizeof(serv_addr));
577 memcpy((char *)&(serv_addr.sin_addr), he->h_addr, he->h_length);
578 serv_addr.sin_family = he->h_addrtype;
579 serv_addr.sin_port = htons(newport);
581 if(data->conf & CONF_VERBOSE) {
582 struct in_addr in;
583 #if 1
584 struct hostent * answer;
586 unsigned long address;
587 #if defined(HAVE_INET_ADDR) || defined(WIN32)
588 address = inet_addr(newhost);
589 answer = gethostbyaddr((char *) &address, sizeof(address),
590 AF_INET);
591 #else
592 answer = NULL;
593 #endif
594 (void) memcpy(&in.s_addr, *he->h_addr_list, sizeof (in.s_addr));
595 infof(data, "Connecting to %s (%s) port %u\n",
596 answer?answer->h_name:newhost, inet_ntoa(in), newport);
597 #else
598 (void) memcpy(&in.s_addr, *he->h_addr_list, sizeof (in.s_addr));
599 infof(data, "Connecting to %s (%s) port %u\n",
600 he->h_name, inet_ntoa(in), newport);
601 #endif
604 if (connect(data->secondarysocket, (struct sockaddr *) &serv_addr,
605 sizeof(serv_addr)) < 0) {
606 switch(errno) {
607 #ifdef ECONNREFUSED
608 /* this should be made nicer */
609 case ECONNREFUSED:
610 failf(data, "Connection refused by ftp server");
611 break;
612 #endif
613 #ifdef EINTR
614 case EINTR:
615 failf(data, "Connection timeouted to ftp server");
616 break;
617 #endif
618 default:
619 failf(data, "Can't connect to ftp server");
620 break;
622 return URG_FTP_CANT_RECONNECT;
627 /* we have the (new) data connection ready */
629 if(data->conf & CONF_UPLOAD) {
631 /* Set type to binary (unless specified ASCII) */
632 sendf(data->firstsocket, data, "TYPE %s\r\n",
633 (data->conf&CONF_FTPASCII)?"A":"I");
635 nread = GetLastResponse(data->firstsocket, buf, data);
637 if(strncmp(buf, "200", 3)) {
638 failf(data, "Couldn't set %s mode",
639 (data->conf&CONF_FTPASCII)?"ASCII":"binary");
640 return (data->conf&CONF_FTPASCII)? URG_FTP_COULDNT_SET_ASCII:
641 URG_FTP_COULDNT_SET_BINARY;
644 if(data->resume_from) {
645 /* we're about to continue the uploading of a file */
646 /* 1. get already existing file's size. We use the SIZE
647 command for this which may not exist in the server!
648 The SIZE command is not in RFC959. */
650 /* 2. This used to set REST. But since we can do append, we
651 don't another ftp command. We just skip the source file
652 offset and then we APPEND the rest on the file instead */
654 /* 3. pass file-size number of bytes in the source file */
655 /* 4. lower the infilesize counter */
656 /* => transfer as usual */
658 if(data->resume_from < 0 ) {
659 /* we could've got a specified offset from the command line,
660 but now we know we didn't */
662 sendf(data->firstsocket, data, "SIZE %s\r\n", ppath);
664 nread = GetLastResponse(data->firstsocket, buf, data);
666 if(strncmp(buf, "213", 3)) {
667 failf(data, "Couldn't get file size: %s", buf+4);
668 return URG_FTP_COULDNT_GET_SIZE;
671 /* get the size from the ascii string: */
672 data->resume_from = atoi(buf+4);
675 if(data->resume_from) {
676 /* do we still game? */
677 int passed=0;
678 #if 0
679 /* Set resume file transfer offset */
680 infof(data, "Instructs server to resume from offset %d\n",
681 data->resume_from);
683 sendf(data->firstsocket, data, "REST %d\r\n", data->resume_from);
685 nread = GetLastResponse(data->firstsocket, buf, data);
687 if(strncmp(buf, "350", 3)) {
688 failf(data, "Couldn't use REST: %s", buf+4);
689 return URG_FTP_COULDNT_USE_REST;
691 #else
692 /* enable append instead */
693 data->conf |= CONF_FTPAPPEND;
694 #endif
695 /* Now, let's read off the proper amount of bytes from the
696 input. If we knew it was a proper file we could've just
697 fseek()ed but we only have a stream here */
698 do {
699 int readthisamountnow = (data->resume_from - passed);
700 int actuallyread;
702 if(readthisamountnow > BUFSIZE)
703 readthisamountnow = BUFSIZE;
705 actuallyread =
706 data->fread(data->buffer, 1, readthisamountnow, data->in);
708 passed += actuallyread;
709 if(actuallyread != readthisamountnow) {
710 failf(data, "Could only read %d bytes from the input\n",
711 passed);
712 return URG_FTP_COULDNT_USE_REST;
715 while(passed != data->resume_from);
717 /* now, decrease the size of the read */
718 if(data->infilesize>0) {
719 data->infilesize -= data->resume_from;
721 if(data->infilesize <= 0) {
722 infof(data, "File already completely uploaded\n");
723 return URG_OK;
726 /* we've passed, proceed as normal */
730 /* Send everything on data->in to the socket */
731 if(data->conf & CONF_FTPAPPEND)
732 /* we append onto the file instead of rewriting it */
733 sendf(data->firstsocket, data, "APPE %s\r\n", ppath);
734 else
735 sendf(data->firstsocket, data, "STOR %s\r\n", ppath);
737 nread = GetLastResponse(data->firstsocket, buf, data);
739 if(atoi(buf)>=400) {
740 failf(data, "Failed FTP upload:%s", buf+3);
741 /* oops, we never close the sockets! */
742 return URG_FTP_COULDNT_STOR_FILE;
745 if(data->conf & CONF_FTPPORT) {
746 result = AllowServerConnect(data, portsock);
747 if( result )
748 return result;
751 *bytecountp=0;
753 /* When we know we're uploading a specified file, we can get the file
754 size prior to the actual upload. */
756 ProgressInit(data, data->infilesize);
757 result = Upload(data, data->secondarysocket, bytecountp);
758 if(result)
759 return result;
761 if((-1 != data->infilesize) && (data->infilesize != *bytecountp)) {
762 failf(data, "Wrote only partial file (%d out of %d bytes)",
763 *bytecountp, data->infilesize);
764 return URG_PARTIAL_FILE;
767 else {
768 /* Retrieve file or directory */
769 bool dirlist=FALSE;
770 long downloadsize=-1;
772 if(data->conf&CONF_RANGE && data->range) {
773 int from, to;
774 int totalsize=-1;
775 char *ptr;
776 char *ptr2;
778 from=strtol(data->range, &ptr, 0);
779 while(ptr && *ptr && (isspace((int)*ptr) || (*ptr=='-')))
780 ptr++;
781 to=strtol(ptr, &ptr2, 0);
782 if(ptr == ptr2) {
783 /* we didn't get any digit */
784 to=-1;
786 if(-1 == to) {
787 /* X - */
788 data->resume_from = from;
790 else if(from < 0) {
791 /* -Y */
792 from = 0;
793 to = -from;
794 totalsize = to-from;
795 data->maxdownload = totalsize;
797 else {
798 /* X- */
799 totalsize = to-from;
800 data->maxdownload = totalsize;
802 infof(data, "range-download from %d to %d, totally %d bytes\n",
803 from, to, totalsize);
806 if(!ppath[0])
807 /* make sure this becomes a valid name */
808 ppath="./";
810 if((data->conf & CONF_FTPLISTONLY) ||
811 ('/' == ppath[strlen(ppath)-1] )) {
812 /* The specified path ends with a slash, and therefore we think this
813 is a directory that is requested, use LIST. But before that we
814 need to set ASCII transfer mode. */
815 dirlist = TRUE;
817 /* Set type to ASCII */
818 sendf(data->firstsocket, data, "TYPE A\r\n");
820 nread = GetLastResponse(data->firstsocket, buf, data);
822 if(strncmp(buf, "200", 3)) {
823 failf(data, "Couldn't set ascii mode");
824 return URG_FTP_COULDNT_SET_ASCII;
827 /* if this output is to be machine-parsed, the NLST command will be
828 better used since the LIST command output is not specified or
829 standard in any way */
831 sendf(data->firstsocket, data, "%s %s\r\n",
832 data->customrequest?data->customrequest:
833 (data->conf&CONF_FTPLISTONLY?"NLST":"LIST"),
834 ppath);
836 else {
837 /* Set type to binary (unless specified ASCII) */
838 sendf(data->firstsocket, data, "TYPE %s\r\n",
839 (data->conf&CONF_FTPASCII)?"A":"I");
841 nread = GetLastResponse(data->firstsocket, buf, data);
843 if(strncmp(buf, "200", 3)) {
844 failf(data, "Couldn't set %s mode",
845 (data->conf&CONF_FTPASCII)?"ASCII":"binary");
846 return (data->conf&CONF_FTPASCII)? URG_FTP_COULDNT_SET_ASCII:
847 URG_FTP_COULDNT_SET_BINARY;
850 if(data->resume_from) {
852 /* Daniel: (August 4, 1999)
854 * We start with trying to use the SIZE command to figure out the size
855 * of the file we're gonna get. If we can get the size, this is by far
856 * the best way to know if we're trying to resume beyond the EOF. */
858 sendf(data->firstsocket, data, "SIZE %s\r\n", ppath);
860 nread = GetLastResponse(data->firstsocket, buf, data);
862 if(strncmp(buf, "213", 3)) {
863 infof(data, "server doesn't support SIZE: %s", buf+4);
864 /* We couldn't get the size and therefore we can't know if there
865 really is a part of the file left to get, although the server
866 will just close the connection when we start the connection so it
867 won't cause us any harm, just not make us exit as nicely. */
869 else {
870 int foundsize=atoi(buf+4);
871 /* We got a file size report, so we check that there actually is a
872 part of the file left to get, or else we go home. */
873 if(foundsize <= data->resume_from) {
874 failf(data, "Offset (%d) was beyond file size (%d)",
875 data->resume_from, foundsize);
876 return URG_FTP_BAD_DOWNLOAD_RESUME;
878 /* Now store the number of bytes we are expected to download */
879 downloadsize = foundsize-data->resume_from;
882 /* Set resume file transfer offset */
883 infof(data, "Instructs server to resume from offset %d\n",
884 data->resume_from);
886 sendf(data->firstsocket, data, "REST %d\r\n", data->resume_from);
888 nread = GetLastResponse(data->firstsocket, buf, data);
890 if(strncmp(buf, "350", 3)) {
891 failf(data, "Couldn't use REST: %s", buf+4);
892 return URG_FTP_COULDNT_USE_REST;
896 sendf(data->firstsocket, data, "RETR %s\r\n", ppath);
899 nread = GetLastResponse(data->firstsocket, buf, data);
901 if(!strncmp(buf, "150", 3) || !strncmp(buf, "125", 3)) {
905 150 Opening BINARY mode data connection for /etc/passwd (2241
906 bytes). (ok, the file is being transfered)
909 150 Opening ASCII mode data connection for /bin/ls
912 150 ASCII data connection for /bin/ls (137.167.104.91,37445) (0 bytes).
915 150 Opening ASCII mode data connection for /linux/fisk/kpanelrc (0.0.0.0,0) (545 bytes).
918 125 Data connection already open; Transfer starting. */
920 int size=-1; /* default unknown size */
922 if(!dirlist && (-1 == downloadsize)) {
924 * It seems directory listings either don't show the size or very
925 * often uses size 0 anyway.
926 * Example D above makes this parsing a little tricky
928 char *bytes;
929 bytes=strstr(buf, " bytes");
930 if(bytes--) {
931 int index=bytes-buf;
932 /* this is a hint there is size information in there! ;-) */
933 while(--index) {
934 /* scan for the parenthesis and break there */
935 if('(' == *bytes)
936 break;
937 /* if only skip digits, or else we're in deep trouble */
938 if(!isdigit((int)*bytes)) {
939 bytes=NULL;
940 break;
942 /* one more estep backwards */
943 bytes--;
945 /* only if we have nothing but digits: */
946 if(bytes++) {
947 /* get the number! */
948 size = atoi(bytes);
952 #if 0
953 if(2 != sscanf(buf, "%*[^(](%d bytes%c", &size, &paren))
954 size=-1;
955 #endif
957 else if(downloadsize > -1)
958 size = downloadsize;
960 #if 0
961 if((size > -1) && (data->resume_from>0)) {
962 size -= data->resume_from;
963 if(size <= 0) {
964 failf(data, "Offset (%d) was beyond file size (%d)",
965 data->resume_from, data->resume_from+size);
966 return URG_PARTIAL_FILE;
969 #endif
971 if(data->conf & CONF_FTPPORT) {
972 result = AllowServerConnect(data, portsock);
973 if( result )
974 return result;
977 infof(data, "Getting file with size: %d\n", size);
979 /* FTP download: */
980 result=Download(data, data->secondarysocket, size, FALSE,
981 bytecountp);
982 if(result)
983 return result;
985 if((-1 != size) && (size != *bytecountp)) {
986 failf(data, "Received only partial file");
987 return URG_PARTIAL_FILE;
989 else if(0 == *bytecountp) {
990 failf(data, "No data was received!");
991 return URG_FTP_COULDNT_RETR_FILE;
994 else {
995 failf(data, "%s", buf+4);
996 return URG_FTP_COULDNT_RETR_FILE;
1000 /* end of transfer */
1001 ProgressEnd(data);
1003 /* shut down the socket to inform the server we're done */
1004 sclose(data->secondarysocket);
1005 data->secondarysocket = -1;
1007 /* now let's see what the server says about the transfer we
1008 just performed: */
1009 nread = GetLastResponse(data->firstsocket, buf, data);
1011 /* 226 Transfer complete */
1012 if(strncmp(buf, "226", 3)) {
1013 failf(data, "%s", buf+4);
1014 return URG_FTP_WRITE_ERROR;
1017 return URG_OK;
1020 /* -- deal with the ftp server! -- */
1022 UrgError ftp(struct UrlData *data,
1023 long *bytecountp,
1024 char *ftpuser,
1025 char *ftppasswd,
1026 char *urlpath)
1028 char *realpath;
1029 UrgError retcode;
1031 #if 0
1032 realpath = URLfix(urlpath);
1033 #else
1034 realpath = curl_unescape(urlpath);
1035 #endif
1036 if(realpath) {
1037 retcode = _ftp(data, bytecountp, ftpuser, ftppasswd, realpath);
1038 free(realpath);
1040 else
1041 /* then we try the original path */
1042 retcode = _ftp(data, bytecountp, ftpuser, ftppasswd, urlpath);
1044 return retcode;