Initial revision
[platform/upstream/curl.git] / lib / ftp.c
1 /*****************************************************************************
2  *                                  _   _ ____  _     
3  *  Project                     ___| | | |  _ \| |    
4  *                             / __| | | | |_) | |    
5  *                            | (__| |_| |  _ <| |___ 
6  *                             \___|\___/|_| \_\_____|
7  *
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/
12  *
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.
17  *
18  *  The Original Code is Curl.
19  *
20  *  The Initial Developer of the Original Code is Daniel Stenberg.
21  *
22  *  Portions created by the Initial Developer are Copyright (C) 1998.
23  *  All Rights Reserved.
24  *
25  * ------------------------------------------------------------
26  * Main author:
27  * - Daniel Stenberg <Daniel.Stenberg@haxx.nu>
28  *
29  *      http://curl.haxx.nu
30  *
31  * $Source$
32  * $Revision$
33  * $Date$
34  * $Author$
35  * $State$
36  * $Locker$
37  *
38  * ------------------------------------------------------------
39  ****************************************************************************/
40
41 #include <stdio.h>
42 #include <string.h>
43 #include <stdlib.h>
44 #include <ctype.h>
45 #include <errno.h>
46
47 #include "setup.h"
48
49 #ifdef HAVE_UNISTD_H
50 #include <unistd.h>
51 #endif
52 #ifdef HAVE_SYS_SELECT_H
53 #include <sys/select.h>
54 #endif
55
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
67
68 #if defined(WIN32) && defined(__GNUC__) || defined(__MINGW32__)
69 #include <errno.h>
70 #endif
71
72
73 #include <curl/curl.h>
74 #include "urldata.h"
75 #include "sendf.h"
76
77 #include "if2ip.h"
78 #include "hostip.h"
79 #include "progress.h"
80 #include "upload.h"
81 #include "download.h"
82
83
84 /* returns last node in linked list */
85 static struct curl_slist *slist_get_last(struct curl_slist *list)
86 {
87         struct curl_slist       *item;
88
89         /* if caller passed us a NULL, return now */
90         if (!list)
91                 return NULL;
92
93         /* loop through to find the last item */
94         item = list;
95         while (item->next) {
96                 item = item->next;
97         }
98         return item;
99 }
100
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)
107 {
108         struct curl_slist       *last;
109         struct curl_slist       *new_item;
110
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);
115         }
116         else {
117                 fprintf(stderr, "Cannot allocate memory for QUOTE list.\n");
118                 exit(-1);
119         }
120
121         if (list) {
122                 last = slist_get_last(list);
123                 last->next = new_item;
124                 return list;
125         }
126
127         /* if this is the first item, then new_item *is* the list */
128         return new_item;
129 }
130
131 /* be nice and clean up resources */
132 void curl_slist_free_all(struct curl_slist *list)
133 {
134         struct curl_slist       *next;
135         struct curl_slist       *item;
136
137         if (!list)
138                 return;
139
140         item = list;
141         do {
142                 next = item->next;
143                 
144                 if (item->data) {
145                         free(item->data);
146                 }
147                 free(item);
148                 item = next;
149         } while (next);
150 }
151
152
153 static UrgError AllowServerConnect(struct UrlData *data,
154                                    int sock)
155 {
156   fd_set rdset;
157   struct timeval dt;
158   
159   FD_ZERO(&rdset);
160
161   FD_SET(sock, &rdset);
162
163   /* we give the server 10 seconds to connect to us */
164   dt.tv_sec = 10;
165   dt.tv_usec = 0;
166
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 */
178     {
179       int s;
180       size_t size = sizeof(struct sockaddr_in);
181       struct sockaddr_in add;
182
183       getsockname(sock, (struct sockaddr *) &add, (int *)&size);
184       s=accept(sock, (struct sockaddr *) &add, (int *)&size);
185
186       if( -1 == s) {
187         /* DIE! */
188         failf(data, "Error accept()ing server connect");
189         return URG_FTP_PORT_FAILED;
190       }
191       infof(data, "Connection accepted from server\n");
192
193       data->secondarysocket = s;
194     }
195     break;
196   }
197   return URG_OK;
198 }
199
200
201 /* --- parse FTP server responses --- */
202
203 #define lastline(line) (isdigit((int)line[0]) && isdigit((int)line[1]) && \
204                         isdigit((int)line[2]) && (' ' == line[3]))
205
206 static int GetLastResponse(int sockfd, char *buf,
207                            struct UrlData *data)
208 {
209   int nread;
210   int read_rc=1;
211   char *ptr;
212   do {
213     ptr=buf;
214
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);
222       }
223       else {
224 #endif
225         read_rc = sread(sockfd, ptr, 1);
226 #ifdef USE_SSLEAY
227       }
228 #endif /* USE_SSLEAY */
229       if (*ptr == '\n')
230         break;
231     }
232     *ptr=0; /* zero terminate */
233
234     if(data->conf & CONF_VERBOSE) {
235       fputs("< ", data->err);
236       fwrite(buf, 1, nread, data->err);
237       fputs("\n", data->err);
238     }
239   } while(read_rc &&
240           (nread<4 || !lastline(buf)) );
241   return nread;
242 }
243
244 /* -- who are we? -- */
245 char *getmyhost(void)
246 {
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;
256
257   if (uname(&ugnm) < 0)
258     return "localhost";
259
260   (void) strncpy(myhost, ugnm.nodename, 255);
261   myhost[255] = '\0';
262 #endif
263   return myhost;
264 }
265
266 #if 0
267 /*
268  * URLfix()
269  *
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>.
273  */
274
275 static char *URLfix(char *string)
276 {
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;
284    
285   while(*string) {
286     in = *string;
287     switch(in) {
288     case '+':
289       ns[index++] = '\\';
290       ns[index++] = ' ';
291       string++;
292       continue;
293
294     case '%':
295       /* encoded part */
296       if(sscanf(string+1, "%02X", &hex)) {
297         ns[index++] = '\\';
298         ns[index++] = hex;
299         string+=3;
300         continue;
301       }
302       /* FALLTHROUGH */
303     default:
304       ns[index++] = in;
305       string++;
306     }
307   }
308   ns[index]=0; /* terminate it */
309   return ns;
310 }
311 #endif
312
313 static
314 UrgError _ftp(struct UrlData *data,
315               long *bytecountp,
316               char *ftpuser,
317               char *ftppasswd,
318               char *ppath)
319 {
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;
327
328   struct curl_slist *qitem; /* QUOTE item */
329
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;
335   }
336
337   /* send USER */
338   sendf(data->firstsocket, data, "USER %s\r\n", ftpuser);
339
340   /* wait for feedback */
341   nread = GetLastResponse(data->firstsocket, buf, data);
342
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;
348   }
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);
354
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;
360     }
361     else if(!strncmp(buf, "230", 3)) {
362       /* 230 User ... logged in.
363          (user successfully logged in) */
364         
365       infof(data, "We have successfully logged in\n");
366     }
367     else {
368       failf(data, "Odd return code after PASS");
369       return URG_FTP_WEIRD_PASS_REPLY;
370     }
371   }
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");
376   }
377   else {
378     failf(data, "Odd return code after USER");
379     return URG_FTP_WEIRD_USER_REPLY;
380   }
381
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);
390
391         nread = GetLastResponse(data->firstsocket, buf, data);
392
393         if (buf[0] != '2') {
394           failf(data, "QUOT string not accepted: %s",
395                 qitem->data);
396           return URG_FTP_QUOTE_ERROR;
397         }
398       }
399       qitem = qitem->next;
400     }
401   }
402
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);
411
412     nread = GetLastResponse(data->firstsocket, buf, data);
413
414     if(strncmp(buf, "213", 3)) {
415       failf(data, "Couldn't get file size: %s", buf+4);
416       return URG_FTP_COULDNT_GET_SIZE;
417     }
418     /* get the size from the ascii string: */
419     filesize = atoi(buf+4);
420
421     sprintf(buf, "Content-Length: %d\n", filesize);
422
423     if(strlen(buf) != data->fwrite(buf, 1, strlen(buf), data->out)) {
424       failf (data, "Failed writing output");
425       return URG_WRITE_ERROR;
426     }
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;
432       }
433     }
434     return URG_OK;
435   }
436
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;
443
444     char *myhost=NULL;
445       
446     if(data->ftpport) {
447       myhost = if2ip(data->ftpport);
448       if(myhost) {
449         h = GetHost(data, myhost);
450       }
451       else {
452         if(strlen(data->ftpport)>1)
453           h = GetHost(data, data->ftpport);
454         if(h)
455           myhost=data->ftpport;
456       }
457     }
458     if(!myhost) {
459       myhost = getmyhost();
460       h=GetHost(data, myhost);
461     }
462     infof(data, "We connect from %s\n", myhost);
463
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);
474
475         if(bind(portsock, (struct sockaddr *)&sa, size) >= 0) {
476           /* we succeeded to bind */
477           struct sockaddr_in add;
478           size = sizeof(add);
479
480           if(getsockname(portsock, (struct sockaddr *) &add,
481                          (int *)&size)<0) {
482             failf(data, "getsockname() failed");
483             return URG_FTP_PORT_FAILED;
484           }
485           porttouse = ntohs(add.sin_port);
486
487           if ( listen(portsock, 1) < 0 ) {
488             failf(data, "listen(2) failed on socket");
489             return URG_FTP_PORT_FAILED;
490           }
491         }
492         else {
493           failf(data, "bind(2) failed on socket");
494           return URG_FTP_PORT_FAILED;
495         }
496       }
497       else {
498         failf(data, "socket(2) failed (%s)");
499         return URG_FTP_PORT_FAILED;
500       }
501     }
502     else {
503       failf(data, "could't find my own IP address (%s)", myhost);
504       return URG_FTP_PORT_FAILED;
505     }
506     {
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);
516     }
517
518     nread = GetLastResponse(data->firstsocket, buf, data);
519
520     if(strncmp(buf, "200", 3)) {
521       failf(data, "Server does not grok PORT, try without it!");
522       return URG_FTP_PORT_FAILED;
523     }     
524   }
525   else { /* we use the PASV command */
526
527     sendf(data->firstsocket, data, "PASV\r\n");
528
529     nread = GetLastResponse(data->firstsocket, buf, data);
530
531     if(strncmp(buf, "227", 3)) {
532       failf(data, "Odd return code after PASV");
533       return URG_FTP_WEIRD_PASV_REPLY;
534     }
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;
542
543       /*
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.
547        *
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"
552        */
553       
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++;
560       }
561       if(!*str) {
562          failf(data, "Couldn't interpret this 227-reply: %s", buf);
563          return URG_FTP_WEIRD_227_FORMAT;
564       }
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;
570       }
571
572         
573       newport = (port[0]<<8) + port[1];
574       data->secondarysocket = socket(AF_INET, SOCK_STREAM, 0);
575
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);
580
581       if(data->conf & CONF_VERBOSE) {
582         struct in_addr in;
583 #if 1
584         struct hostent * answer;
585
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
602       }
603         
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;
621         }
622         return URG_FTP_CANT_RECONNECT;
623       }
624     }
625
626   }
627   /* we have the (new) data connection ready */
628
629   if(data->conf & CONF_UPLOAD) {
630
631     /* Set type to binary (unless specified ASCII) */
632     sendf(data->firstsocket, data, "TYPE %s\r\n",
633           (data->conf&CONF_FTPASCII)?"A":"I");
634
635     nread = GetLastResponse(data->firstsocket, buf, data);
636
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;
642     }
643
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. */
649
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 */
653
654       /* 3. pass file-size number of bytes in the source file */
655       /* 4. lower the infilesize counter */
656       /* => transfer as usual */
657
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 */
661
662         sendf(data->firstsocket, data, "SIZE %s\r\n", ppath);
663
664         nread = GetLastResponse(data->firstsocket, buf, data);
665
666         if(strncmp(buf, "213", 3)) {
667           failf(data, "Couldn't get file size: %s", buf+4);
668           return URG_FTP_COULDNT_GET_SIZE;
669         }
670
671         /* get the size from the ascii string: */
672         data->resume_from = atoi(buf+4);
673       }
674
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);
682
683         sendf(data->firstsocket, data, "REST %d\r\n", data->resume_from);
684
685         nread = GetLastResponse(data->firstsocket, buf, data);
686
687         if(strncmp(buf, "350", 3)) {
688           failf(data, "Couldn't use REST: %s", buf+4);
689           return URG_FTP_COULDNT_USE_REST;
690         }
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;
701
702           if(readthisamountnow > BUFSIZE)
703             readthisamountnow = BUFSIZE;
704
705           actuallyread =
706             data->fread(data->buffer, 1, readthisamountnow, data->in);
707
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;
713           }
714         }
715         while(passed != data->resume_from);
716
717         /* now, decrease the size of the read */
718         if(data->infilesize>0) {
719           data->infilesize -= data->resume_from;
720
721           if(data->infilesize <= 0) {
722             infof(data, "File already completely uploaded\n");
723             return URG_OK;
724           }
725         }
726         /* we've passed, proceed as normal */
727       }
728     }
729
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);
736
737     nread = GetLastResponse(data->firstsocket, buf, data);
738
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;
743     }
744
745     if(data->conf & CONF_FTPPORT) {
746       result = AllowServerConnect(data, portsock);
747       if( result )
748         return result;
749     }
750
751     *bytecountp=0;
752
753     /* When we know we're uploading a specified file, we can get the file
754        size prior to the actual upload. */
755
756     ProgressInit(data, data->infilesize);
757     result = Upload(data, data->secondarysocket, bytecountp);
758     if(result)
759       return result;
760       
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;
765     }
766   }
767   else {
768     /* Retrieve file or directory */
769     bool dirlist=FALSE;
770     long downloadsize=-1;
771
772     if(data->conf&CONF_RANGE && data->range) {
773       int from, to;
774       int totalsize=-1;
775       char *ptr;
776       char *ptr2;
777
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;
785       }
786       if(-1 == to) {
787         /* X - */
788         data->resume_from = from;
789       }
790       else if(from < 0) {
791         /* -Y */
792         from = 0;
793         to = -from;
794         totalsize = to-from;
795         data->maxdownload = totalsize;
796       }
797       else {
798         /* X- */
799         totalsize = to-from;
800         data->maxdownload = totalsize;
801       }
802       infof(data, "range-download from %d to %d, totally %d bytes\n",
803             from, to, totalsize);
804     }
805
806     if(!ppath[0])
807       /* make sure this becomes a valid name */
808       ppath="./";
809
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;
816
817       /* Set type to ASCII */
818       sendf(data->firstsocket, data, "TYPE A\r\n");
819         
820       nread = GetLastResponse(data->firstsocket, buf, data);
821         
822       if(strncmp(buf, "200", 3)) {
823         failf(data, "Couldn't set ascii mode");
824         return URG_FTP_COULDNT_SET_ASCII;
825       }
826
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 */
830
831       sendf(data->firstsocket, data, "%s %s\r\n",
832             data->customrequest?data->customrequest:
833             (data->conf&CONF_FTPLISTONLY?"NLST":"LIST"),
834             ppath);
835     }
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");
840
841       nread = GetLastResponse(data->firstsocket, buf, data);
842
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;
848       }
849
850       if(data->resume_from) {
851
852         /* Daniel: (August 4, 1999)
853          *
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.  */
857
858         sendf(data->firstsocket, data, "SIZE %s\r\n", ppath);
859
860         nread = GetLastResponse(data->firstsocket, buf, data);
861
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. */
868         }
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;
877           }
878           /* Now store the number of bytes we are expected to download */
879           downloadsize = foundsize-data->resume_from;
880         }
881
882         /* Set resume file transfer offset */
883         infof(data, "Instructs server to resume from offset %d\n",
884               data->resume_from);
885
886         sendf(data->firstsocket, data, "REST %d\r\n", data->resume_from);
887
888         nread = GetLastResponse(data->firstsocket, buf, data);
889
890         if(strncmp(buf, "350", 3)) {
891           failf(data, "Couldn't use REST: %s", buf+4);
892           return URG_FTP_COULDNT_USE_REST;
893         }
894       }
895
896       sendf(data->firstsocket, data, "RETR %s\r\n", ppath);
897     }
898
899     nread = GetLastResponse(data->firstsocket, buf, data);
900
901     if(!strncmp(buf, "150", 3) || !strncmp(buf, "125", 3)) {
902
903       /*
904         A;
905         150 Opening BINARY mode data connection for /etc/passwd (2241
906         bytes).  (ok, the file is being transfered)
907         
908         B:
909         150 Opening ASCII mode data connection for /bin/ls 
910
911         C:
912         150 ASCII data connection for /bin/ls (137.167.104.91,37445) (0 bytes).
913
914         D:
915         150 Opening ASCII mode data connection for /linux/fisk/kpanelrc (0.0.0.0,0) (545 bytes).
916           
917         E:
918         125 Data connection already open; Transfer starting. */
919
920       int size=-1; /* default unknown size */
921
922       if(!dirlist && (-1 == downloadsize)) {
923         /*
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
927          */
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;
941             }
942             /* one more estep backwards */
943             bytes--;
944           }
945           /* only if we have nothing but digits: */
946           if(bytes++) {
947             /* get the number! */
948             size = atoi(bytes);
949           }
950             
951         }
952 #if 0
953         if(2 != sscanf(buf, "%*[^(](%d bytes%c", &size, &paren))
954           size=-1;
955 #endif
956       }
957       else if(downloadsize > -1)
958         size = downloadsize;
959
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;
967         }
968       }
969 #endif
970
971       if(data->conf & CONF_FTPPORT) {
972         result = AllowServerConnect(data, portsock);
973         if( result )
974           return result;
975       }
976
977       infof(data, "Getting file with size: %d\n", size);
978
979       /* FTP download: */
980       result=Download(data, data->secondarysocket, size, FALSE,
981                       bytecountp);
982       if(result)
983         return result;
984
985       if((-1 != size) && (size != *bytecountp)) {
986         failf(data, "Received only partial file");
987         return URG_PARTIAL_FILE;
988       }
989       else if(0 == *bytecountp) {
990         failf(data, "No data was received!");
991         return URG_FTP_COULDNT_RETR_FILE;
992       }
993     }
994     else {
995       failf(data, "%s", buf+4);
996       return URG_FTP_COULDNT_RETR_FILE;
997     }
998         
999   }
1000   /* end of transfer */
1001   ProgressEnd(data);
1002
1003   /* shut down the socket to inform the server we're done */
1004   sclose(data->secondarysocket);
1005   data->secondarysocket = -1;
1006     
1007   /* now let's see what the server says about the transfer we
1008      just performed: */
1009   nread = GetLastResponse(data->firstsocket, buf, data);
1010
1011   /* 226 Transfer complete */
1012   if(strncmp(buf, "226", 3)) {
1013     failf(data, "%s", buf+4);
1014     return URG_FTP_WRITE_ERROR;
1015   }
1016
1017   return URG_OK;
1018 }
1019
1020 /* -- deal with the ftp server!  -- */
1021
1022 UrgError ftp(struct UrlData *data,
1023              long *bytecountp,
1024              char *ftpuser,
1025              char *ftppasswd,
1026              char *urlpath)
1027 {
1028   char *realpath;
1029   UrgError retcode;
1030
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);
1039   }
1040   else
1041     /* then we try the original path */
1042     retcode = _ftp(data, bytecountp, ftpuser, ftppasswd, urlpath);
1043
1044   return retcode;
1045 }
1046