Initial revision
[TestXSLT.git] / libxml2 / nanoftp.c
1 /*
2  * nanoftp.c: basic FTP client support
3  *
4  *  Reference: RFC 959
5  */
6
7 #ifdef TESTING
8 #define STANDALONE
9 #define HAVE_STDLIB_H
10 #define HAVE_UNISTD_H
11 #define HAVE_SYS_SOCKET_H
12 #define HAVE_NETINET_IN_H
13 #define HAVE_NETDB_H
14 #define HAVE_SYS_TIME_H
15 #else /* TESTING */
16 #define NEED_SOCKETS
17 #endif /* TESTING */
18
19 #define IN_LIBXML
20 #include "libxml.h"
21
22 #ifdef LIBXML_FTP_ENABLED
23 #include <string.h>
24
25 #ifdef HAVE_STDLIB_H
26 #include <stdlib.h>
27 #endif
28 #ifdef HAVE_UNISTD_H
29 #include <unistd.h>
30 #endif
31 #ifdef HAVE_SYS_SOCKET_H
32 #include <sys/socket.h>
33 #endif
34 #ifdef HAVE_NETINET_IN_H
35 #include <netinet/in.h>
36 #endif
37 #ifdef HAVE_ARPA_INET_H
38 #include <arpa/inet.h>
39 #endif
40 #ifdef HAVE_NETDB_H
41 #include <netdb.h>
42 #endif
43 #ifdef HAVE_FCNTL_H
44 #include <fcntl.h> 
45 #endif
46 #ifdef HAVE_ERRNO_H
47 #include <errno.h>
48 #endif
49 #ifdef HAVE_SYS_TIME_H
50 #include <sys/time.h>
51 #endif
52 #ifdef HAVE_SYS_SELECT_H
53 #include <sys/select.h>
54 #endif
55 #ifdef HAVE_STRINGS_H
56 #include <strings.h>
57 #endif
58
59 #include <libxml/xmlmemory.h>
60 #include <libxml/parser.h>
61 #include <libxml/xmlerror.h>
62 #include <libxml/uri.h>
63 #include <libxml/nanoftp.h>
64 #include <libxml/globals.h>
65
66 /* #define DEBUG_FTP 1  */
67 #ifdef STANDALONE
68 #ifndef DEBUG_FTP
69 #define DEBUG_FTP 1
70 #endif
71 #endif
72
73 /**
74  * A couple portability macros
75  */
76 #ifndef _WINSOCKAPI_
77 #define closesocket(s) close(s)
78 #define SOCKET int
79 #endif
80 #if defined(VMS) || defined(__VMS)
81 #define SOCKLEN_T unsigned int
82 #endif
83
84 #define FTP_COMMAND_OK          200
85 #define FTP_SYNTAX_ERROR        500
86 #define FTP_GET_PASSWD          331
87 #define FTP_BUF_SIZE            512
88
89 typedef struct xmlNanoFTPCtxt {
90     char *protocol;     /* the protocol name */
91     char *hostname;     /* the host name */
92     int port;           /* the port */
93     char *path;         /* the path within the URL */
94     char *user;         /* user string */
95     char *passwd;       /* passwd string */
96     struct sockaddr_in ftpAddr; /* the socket address struct */
97     int passive;        /* currently we support only passive !!! */
98     SOCKET controlFd;   /* the file descriptor for the control socket */
99     SOCKET dataFd;      /* the file descriptor for the data socket */
100     int state;          /* WRITE / READ / CLOSED */
101     int returnValue;    /* the protocol return value */
102     /* buffer for data received from the control connection */
103     char controlBuf[FTP_BUF_SIZE + 1];
104     int controlBufIndex;
105     int controlBufUsed;
106     int controlBufAnswer;
107 } xmlNanoFTPCtxt, *xmlNanoFTPCtxtPtr;
108
109 static int initialized = 0;
110 static char *proxy = NULL;      /* the proxy name if any */
111 static int proxyPort = 0;       /* the proxy port if any */
112 static char *proxyUser = NULL;  /* user for proxy authentication */
113 static char *proxyPasswd = NULL;/* passwd for proxy authentication */
114 static int proxyType = 0;       /* uses TYPE or a@b ? */
115
116 /**
117  * xmlNanoFTPInit:
118  *
119  * Initialize the FTP protocol layer.
120  * Currently it just checks for proxy informations,
121  * and get the hostname
122  */
123
124 void
125 xmlNanoFTPInit(void) {
126     const char *env;
127 #ifdef _WINSOCKAPI_
128     WSADATA wsaData;    
129 #endif
130
131     if (initialized)
132         return;
133
134 #ifdef _WINSOCKAPI_
135     if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0)
136         return;
137 #endif
138
139     proxyPort = 21;
140     env = getenv("no_proxy");
141     if (env != NULL)
142         return;
143     env = getenv("ftp_proxy");
144     if (env != NULL) {
145         xmlNanoFTPScanProxy(env);
146     } else {
147         env = getenv("FTP_PROXY");
148         if (env != NULL) {
149             xmlNanoFTPScanProxy(env);
150         }
151     }
152     env = getenv("ftp_proxy_user");
153     if (env != NULL) {
154         proxyUser = xmlMemStrdup(env);
155     }
156     env = getenv("ftp_proxy_password");
157     if (env != NULL) {
158         proxyPasswd = xmlMemStrdup(env);
159     }
160     initialized = 1;
161 }
162
163 /**
164  * xmlNanoFTPCleanup:
165  *
166  * Cleanup the FTP protocol layer. This cleanup proxy informations.
167  */
168
169 void
170 xmlNanoFTPCleanup(void) {
171     if (proxy != NULL) {
172         xmlFree(proxy);
173         proxy = NULL;
174     }
175     if (proxyUser != NULL) {
176         xmlFree(proxyUser);
177         proxyUser = NULL;
178     }
179     if (proxyPasswd != NULL) {
180         xmlFree(proxyPasswd);
181         proxyPasswd = NULL;
182     }
183 #ifdef _WINSOCKAPI_
184     if (initialized)
185         WSACleanup();
186 #endif
187     initialized = 0;
188 }
189
190 /**
191  * xmlNanoFTPProxy:
192  * @host:  the proxy host name
193  * @port:  the proxy port
194  * @user:  the proxy user name
195  * @passwd:  the proxy password
196  * @type:  the type of proxy 1 for using SITE, 2 for USER a@b
197  *
198  * Setup the FTP proxy informations.
199  * This can also be done by using ftp_proxy ftp_proxy_user and
200  * ftp_proxy_password environment variables.
201  */
202
203 void
204 xmlNanoFTPProxy(const char *host, int port, const char *user,
205                 const char *passwd, int type) {
206     if (proxy != NULL)
207         xmlFree(proxy);
208     if (proxyUser != NULL)
209         xmlFree(proxyUser);
210     if (proxyPasswd != NULL)
211         xmlFree(proxyPasswd);
212     if (host)
213         proxy = xmlMemStrdup(host);
214     if (user)
215         proxyUser = xmlMemStrdup(user);
216     if (passwd)
217         proxyPasswd = xmlMemStrdup(passwd);
218     proxyPort = port;
219     proxyType = type;
220 }
221
222 /**
223  * xmlNanoFTPScanURL:
224  * @ctx:  an FTP context
225  * @URL:  The URL used to initialize the context
226  *
227  * (Re)Initialize an FTP context by parsing the URL and finding
228  * the protocol host port and path it indicates.
229  */
230
231 static void
232 xmlNanoFTPScanURL(void *ctx, const char *URL) {
233     xmlNanoFTPCtxtPtr ctxt = (xmlNanoFTPCtxtPtr) ctx;
234     const char *cur = URL;
235     char buf[4096];
236     int indx = 0;
237     int port = 0;
238
239     if (ctxt->protocol != NULL) { 
240         xmlFree(ctxt->protocol);
241         ctxt->protocol = NULL;
242     }
243     if (ctxt->hostname != NULL) { 
244         xmlFree(ctxt->hostname);
245         ctxt->hostname = NULL;
246     }
247     if (ctxt->path != NULL) { 
248         xmlFree(ctxt->path);
249         ctxt->path = NULL;
250     }
251     if (URL == NULL) return;
252     buf[indx] = 0;
253     while (*cur != 0) {
254         if ((cur[0] == ':') && (cur[1] == '/') && (cur[2] == '/')) {
255             buf[indx] = 0;
256             ctxt->protocol = xmlMemStrdup(buf);
257             indx = 0;
258             cur += 3;
259             break;
260         }
261         buf[indx++] = *cur++;
262     }
263     if (*cur == 0) return;
264
265     buf[indx] = 0;
266     /* allow user@ and user:pass@ forms */
267     {
268         const char *p = strchr(cur, '@');
269         if(p) {
270             while(1) {
271                 if(cur[0] == ':' || cur[0] == '@') break;
272                 buf[indx++] = *cur++;
273             }
274             buf[indx] = 0;
275             ctxt->user = xmlMemStrdup(buf);
276             indx = 0;
277             if(cur[0] == ':') {
278                 cur++;
279                 while(1) {
280                     if(cur[0] == '@') break;
281                     buf[indx++] = *cur++;
282                 }
283                 buf[indx] = 0;
284                 ctxt->passwd = xmlMemStrdup(buf);
285                 indx = 0;
286             }
287             cur = p+1;
288         }
289     }
290
291     while (1) {
292         if (cur[0] == ':') {
293             buf[indx] = 0;
294             ctxt->hostname = xmlMemStrdup(buf);
295             indx = 0;
296             cur += 1;
297             while ((*cur >= '0') && (*cur <= '9')) {
298                 port *= 10;
299                 port += *cur - '0';
300                 cur++;
301             }
302             if (port != 0) ctxt->port = port;
303             while ((cur[0] != '/') && (*cur != 0)) 
304                 cur++;
305             break;
306         }
307         if ((*cur == '/') || (*cur == 0)) {
308             buf[indx] = 0;
309             ctxt->hostname = xmlMemStrdup(buf);
310             indx = 0;
311             break;
312         }
313         buf[indx++] = *cur++;
314     }
315     if (*cur == 0) 
316         ctxt->path = xmlMemStrdup("/");
317     else {
318         indx = 0;
319         buf[indx] = 0;
320         while (*cur != 0)
321             buf[indx++] = *cur++;
322         buf[indx] = 0;
323         ctxt->path = xmlMemStrdup(buf);
324     }   
325 }
326
327 /**
328  * xmlNanoFTPUpdateURL:
329  * @ctx:  an FTP context
330  * @URL:  The URL used to update the context
331  *
332  * Update an FTP context by parsing the URL and finding
333  * new path it indicates. If there is an error in the 
334  * protocol, hostname, port or other information, the
335  * error is raised. It indicates a new connection has to
336  * be established.
337  *
338  * Returns 0 if Ok, -1 in case of error (other host).
339  */
340
341 int
342 xmlNanoFTPUpdateURL(void *ctx, const char *URL) {
343     xmlNanoFTPCtxtPtr ctxt = (xmlNanoFTPCtxtPtr) ctx;
344     const char *cur = URL;
345     char buf[4096];
346     int indx = 0;
347     int port = 0;
348
349     if (URL == NULL)
350         return(-1);
351     if (ctxt == NULL)
352         return(-1);
353     if (ctxt->protocol == NULL)
354         return(-1);
355     if (ctxt->hostname == NULL)
356         return(-1);
357     buf[indx] = 0;
358     while (*cur != 0) {
359         if ((cur[0] == ':') && (cur[1] == '/') && (cur[2] == '/')) {
360             buf[indx] = 0;
361             if (strcmp(ctxt->protocol, buf))
362                 return(-1);
363             indx = 0;
364             cur += 3;
365             break;
366         }
367         buf[indx++] = *cur++;
368     }
369     if (*cur == 0)
370         return(-1);
371
372     buf[indx] = 0;
373     while (1) {
374         if (cur[0] == ':') {
375             buf[indx] = 0;
376             if (strcmp(ctxt->hostname, buf))
377                 return(-1);
378             indx = 0;
379             cur += 1;
380             while ((*cur >= '0') && (*cur <= '9')) {
381                 port *= 10;
382                 port += *cur - '0';
383                 cur++;
384             }
385             if (port != ctxt->port)
386                 return(-1);
387             while ((cur[0] != '/') && (*cur != 0)) 
388                 cur++;
389             break;
390         }
391         if ((*cur == '/') || (*cur == 0)) {
392             buf[indx] = 0;
393             if (strcmp(ctxt->hostname, buf))
394                 return(-1);
395             indx = 0;
396             break;
397         }
398         buf[indx++] = *cur++;
399     }
400     if (ctxt->path != NULL) {
401         xmlFree(ctxt->path);
402         ctxt->path = NULL;
403     }
404
405     if (*cur == 0) 
406         ctxt->path = xmlMemStrdup("/");
407     else {
408         indx = 0;
409         buf[indx] = 0;
410         while (*cur != 0)
411             buf[indx++] = *cur++;
412         buf[indx] = 0;
413         ctxt->path = xmlMemStrdup(buf);
414     }   
415     return(0);
416 }
417
418 /**
419  * xmlNanoFTPScanProxy:
420  * @URL:  The proxy URL used to initialize the proxy context
421  *
422  * (Re)Initialize the FTP Proxy context by parsing the URL and finding
423  * the protocol host port it indicates.
424  * Should be like ftp://myproxy/ or ftp://myproxy:3128/
425  * A NULL URL cleans up proxy informations.
426  */
427
428 void
429 xmlNanoFTPScanProxy(const char *URL) {
430     const char *cur = URL;
431     char buf[4096];
432     int indx = 0;
433     int port = 0;
434
435     if (proxy != NULL) { 
436         xmlFree(proxy);
437         proxy = NULL;
438     }
439     if (proxyPort != 0) { 
440         proxyPort = 0;
441     }
442 #ifdef DEBUG_FTP
443     if (URL == NULL)
444         xmlGenericError(xmlGenericErrorContext, "Removing FTP proxy info\n");
445     else
446         xmlGenericError(xmlGenericErrorContext, "Using FTP proxy %s\n", URL);
447 #endif
448     if (URL == NULL) return;
449     buf[indx] = 0;
450     while (*cur != 0) {
451         if ((cur[0] == ':') && (cur[1] == '/') && (cur[2] == '/')) {
452             buf[indx] = 0;
453             indx = 0;
454             cur += 3;
455             break;
456         }
457         buf[indx++] = *cur++;
458     }
459     if (*cur == 0) return;
460
461     buf[indx] = 0;
462     while (1) {
463         if (cur[0] == ':') {
464             buf[indx] = 0;
465             proxy = xmlMemStrdup(buf);
466             indx = 0;
467             cur += 1;
468             while ((*cur >= '0') && (*cur <= '9')) {
469                 port *= 10;
470                 port += *cur - '0';
471                 cur++;
472             }
473             if (port != 0) proxyPort = port;
474             while ((cur[0] != '/') && (*cur != 0)) 
475                 cur++;
476             break;
477         }
478         if ((*cur == '/') || (*cur == 0)) {
479             buf[indx] = 0;
480             proxy = xmlMemStrdup(buf);
481             indx = 0;
482             break;
483         }
484         buf[indx++] = *cur++;
485     }
486 }
487
488 /**
489  * xmlNanoFTPNewCtxt:
490  * @URL:  The URL used to initialize the context
491  *
492  * Allocate and initialize a new FTP context.
493  *
494  * Returns an FTP context or NULL in case of error.
495  */
496
497 void*
498 xmlNanoFTPNewCtxt(const char *URL) {
499     xmlNanoFTPCtxtPtr ret;
500     char *unescaped;
501
502     ret = (xmlNanoFTPCtxtPtr) xmlMalloc(sizeof(xmlNanoFTPCtxt));
503     if (ret == NULL) return(NULL);
504
505     memset(ret, 0, sizeof(xmlNanoFTPCtxt));
506     ret->port = 21;
507     ret->passive = 1;
508     ret->returnValue = 0;
509     ret->controlBufIndex = 0;
510     ret->controlBufUsed = 0;
511     ret->controlFd = -1;
512
513     unescaped = xmlURIUnescapeString(URL, 0, NULL);
514     if (unescaped != NULL)
515         xmlNanoFTPScanURL(ret, unescaped);
516     else if (URL != NULL)
517         xmlNanoFTPScanURL(ret, URL);
518     xmlFree(unescaped);
519
520     return(ret);
521 }
522
523 /**
524  * xmlNanoFTPFreeCtxt:
525  * @ctx:  an FTP context
526  *
527  * Frees the context after closing the connection.
528  */
529
530 void
531 xmlNanoFTPFreeCtxt(void * ctx) {
532     xmlNanoFTPCtxtPtr ctxt = (xmlNanoFTPCtxtPtr) ctx;
533     if (ctxt == NULL) return;
534     if (ctxt->hostname != NULL) xmlFree(ctxt->hostname);
535     if (ctxt->protocol != NULL) xmlFree(ctxt->protocol);
536     if (ctxt->path != NULL) xmlFree(ctxt->path);
537     ctxt->passive = 1;
538     if (ctxt->controlFd >= 0) closesocket(ctxt->controlFd);
539     ctxt->controlFd = -1;
540     ctxt->controlBufIndex = -1;
541     ctxt->controlBufUsed = -1;
542     xmlFree(ctxt);
543 }
544
545 /**
546  * xmlNanoFTPParseResponse:
547  * @buf:  the buffer containing the response
548  * @len:  the buffer length
549  * 
550  * Parsing of the server answer, we just extract the code.
551  *
552  * returns 0 for errors
553  *     +XXX for last line of response
554  *     -XXX for response to be continued
555  */
556 static int
557 xmlNanoFTPParseResponse(char *buf, int len) {
558     int val = 0;
559
560     if (len < 3) return(-1);
561     if ((*buf >= '0') && (*buf <= '9')) 
562         val = val * 10 + (*buf - '0');
563     else
564         return(0);
565     buf++;
566     if ((*buf >= '0') && (*buf <= '9')) 
567         val = val * 10 + (*buf - '0');
568     else
569         return(0);
570     buf++;
571     if ((*buf >= '0') && (*buf <= '9')) 
572         val = val * 10 + (*buf - '0');
573     else
574         return(0);
575     buf++;
576     if (*buf == '-') 
577         return(-val);
578     return(val);
579 }
580
581 /**
582  * xmlNanoFTPGetMore:
583  * @ctx:  an FTP context
584  *
585  * Read more information from the FTP control connection
586  * Returns the number of bytes read, < 0 indicates an error
587  */
588 static int
589 xmlNanoFTPGetMore(void *ctx) {
590     xmlNanoFTPCtxtPtr ctxt = (xmlNanoFTPCtxtPtr) ctx;
591     int len;
592     int size;
593
594     if ((ctxt->controlBufIndex < 0) || (ctxt->controlBufIndex > FTP_BUF_SIZE)) {
595 #ifdef DEBUG_FTP
596         xmlGenericError(xmlGenericErrorContext,
597                 "xmlNanoFTPGetMore : controlBufIndex = %d\n",
598                 ctxt->controlBufIndex);
599 #endif
600         return(-1);
601     }
602
603     if ((ctxt->controlBufUsed < 0) || (ctxt->controlBufUsed > FTP_BUF_SIZE)) {
604 #ifdef DEBUG_FTP
605         xmlGenericError(xmlGenericErrorContext,
606                 "xmlNanoFTPGetMore : controlBufUsed = %d\n",
607                 ctxt->controlBufUsed);
608 #endif
609         return(-1);
610     }
611     if (ctxt->controlBufIndex > ctxt->controlBufUsed) {
612 #ifdef DEBUG_FTP
613         xmlGenericError(xmlGenericErrorContext,
614                 "xmlNanoFTPGetMore : controlBufIndex > controlBufUsed %d > %d\n",
615                ctxt->controlBufIndex, ctxt->controlBufUsed);
616 #endif
617         return(-1);
618     }
619
620     /*
621      * First pack the control buffer
622      */
623     if (ctxt->controlBufIndex > 0) {
624         memmove(&ctxt->controlBuf[0], &ctxt->controlBuf[ctxt->controlBufIndex],
625                 ctxt->controlBufUsed - ctxt->controlBufIndex);
626         ctxt->controlBufUsed -= ctxt->controlBufIndex;
627         ctxt->controlBufIndex = 0;
628     }
629     size = FTP_BUF_SIZE - ctxt->controlBufUsed;
630     if (size == 0) {
631 #ifdef DEBUG_FTP
632         xmlGenericError(xmlGenericErrorContext,
633                 "xmlNanoFTPGetMore : buffer full %d \n", ctxt->controlBufUsed);
634 #endif
635         return(0);
636     }
637
638     /*
639      * Read the amount left on the control connection
640      */
641     if ((len = recv(ctxt->controlFd, &ctxt->controlBuf[ctxt->controlBufIndex],
642                     size, 0)) < 0) {
643         closesocket(ctxt->controlFd); ctxt->controlFd = -1;
644         ctxt->controlFd = -1;
645         return(-1);
646     }
647 #ifdef DEBUG_FTP
648     xmlGenericError(xmlGenericErrorContext,
649             "xmlNanoFTPGetMore : read %d [%d - %d]\n", len,
650            ctxt->controlBufUsed, ctxt->controlBufUsed + len);
651 #endif
652     ctxt->controlBufUsed += len;
653     ctxt->controlBuf[ctxt->controlBufUsed] = 0;
654
655     return(len);
656 }
657
658 /**
659  * xmlNanoFTPReadResponse:
660  * @ctx:  an FTP context
661  *
662  * Read the response from the FTP server after a command.
663  * Returns the code number
664  */
665 static int
666 xmlNanoFTPReadResponse(void *ctx) {
667     xmlNanoFTPCtxtPtr ctxt = (xmlNanoFTPCtxtPtr) ctx;
668     char *ptr, *end;
669     int len;
670     int res = -1, cur = -1;
671
672 get_more:
673     /*
674      * Assumes everything up to controlBuf[controlBufIndex] has been read
675      * and analyzed.
676      */
677     len = xmlNanoFTPGetMore(ctx);
678     if (len < 0) {
679         return(-1);
680     }
681     if ((ctxt->controlBufUsed == 0) && (len == 0)) {
682         return(-1);
683     }
684     ptr = &ctxt->controlBuf[ctxt->controlBufIndex];
685     end = &ctxt->controlBuf[ctxt->controlBufUsed];
686
687 #ifdef DEBUG_FTP
688     xmlGenericError(xmlGenericErrorContext,
689             "\n<<<\n%s\n--\n", ptr);
690 #endif
691     while (ptr < end) {
692         cur = xmlNanoFTPParseResponse(ptr, end - ptr);
693         if (cur > 0) {
694             /*
695              * Successfully scanned the control code, scratch
696              * till the end of the line, but keep the index to be
697              * able to analyze the result if needed.
698              */
699             res = cur;
700             ptr += 3;
701             ctxt->controlBufAnswer = ptr - ctxt->controlBuf;
702             while ((ptr < end) && (*ptr != '\n')) ptr++;
703             if (*ptr == '\n') ptr++;
704             if (*ptr == '\r') ptr++;
705             break;
706         }
707         while ((ptr < end) && (*ptr != '\n')) ptr++;
708         if (ptr >= end) {
709             ctxt->controlBufIndex = ctxt->controlBufUsed;
710             goto get_more;
711         }
712         if (*ptr != '\r') ptr++;
713     }
714
715     if (res < 0) goto get_more;
716     ctxt->controlBufIndex = ptr - ctxt->controlBuf;
717 #ifdef DEBUG_FTP
718     ptr = &ctxt->controlBuf[ctxt->controlBufIndex];
719     xmlGenericError(xmlGenericErrorContext, "\n---\n%s\n--\n", ptr);
720 #endif
721
722 #ifdef DEBUG_FTP
723     xmlGenericError(xmlGenericErrorContext, "Got %d\n", res);
724 #endif
725     return(res / 100);
726 }
727
728 /**
729  * xmlNanoFTPGetResponse:
730  * @ctx:  an FTP context
731  *
732  * Get the response from the FTP server after a command.
733  * Returns the code number
734  */
735
736 int
737 xmlNanoFTPGetResponse(void *ctx) {
738     int res;
739
740     res = xmlNanoFTPReadResponse(ctx);
741
742     return(res);
743 }
744
745 /**
746  * xmlNanoFTPCheckResponse:
747  * @ctx:  an FTP context
748  *
749  * Check if there is a response from the FTP server after a command.
750  * Returns the code number, or 0
751  */
752
753 int
754 xmlNanoFTPCheckResponse(void *ctx) {
755     xmlNanoFTPCtxtPtr ctxt = (xmlNanoFTPCtxtPtr) ctx;
756     fd_set rfd;
757     struct timeval tv;
758
759     tv.tv_sec = 0;
760     tv.tv_usec = 0;
761     FD_ZERO(&rfd);
762     FD_SET(ctxt->controlFd, &rfd);
763     switch(select(ctxt->controlFd + 1, &rfd, NULL, NULL, &tv)) {
764         case 0:
765             return(0);
766         case -1:
767 #ifdef DEBUG_FTP
768             perror("select");
769 #endif
770             return(-1);
771                         
772     }
773
774     return(xmlNanoFTPReadResponse(ctx));
775 }
776
777 /**
778  * Send the user authentication
779  */
780
781 static int
782 xmlNanoFTPSendUser(void *ctx) {
783     xmlNanoFTPCtxtPtr ctxt = (xmlNanoFTPCtxtPtr) ctx;
784     char buf[200];
785     int len;
786     int res;
787
788     if (ctxt->user == NULL)
789         snprintf(buf, sizeof(buf), "USER anonymous\r\n");
790     else
791         snprintf(buf, sizeof(buf), "USER %s\r\n", ctxt->user);
792     buf[sizeof(buf) - 1] = 0;
793     len = strlen(buf);
794 #ifdef DEBUG_FTP
795     xmlGenericError(xmlGenericErrorContext, "%s", buf);
796 #endif
797     res = send(ctxt->controlFd, buf, len, 0);
798     if (res < 0) return(res);
799     return(0);
800 }
801
802 /**
803  * Send the password authentication
804  */
805
806 static int
807 xmlNanoFTPSendPasswd(void *ctx) {
808     xmlNanoFTPCtxtPtr ctxt = (xmlNanoFTPCtxtPtr) ctx;
809     char buf[200];
810     int len;
811     int res;
812
813     if (ctxt->passwd == NULL)
814         snprintf(buf, sizeof(buf), "PASS anonymous@\r\n");
815     else
816         snprintf(buf, sizeof(buf), "PASS %s\r\n", ctxt->passwd);
817     buf[sizeof(buf) - 1] = 0;
818     len = strlen(buf);
819 #ifdef DEBUG_FTP
820     xmlGenericError(xmlGenericErrorContext, "%s", buf);
821 #endif
822     res = send(ctxt->controlFd, buf, len, 0);
823     if (res < 0) return(res);
824     return(0);
825 }
826
827 /**
828  * xmlNanoFTPQuit:
829  * @ctx:  an FTP context
830  *
831  * Send a QUIT command to the server
832  *
833  * Returns -1 in case of error, 0 otherwise
834  */
835
836
837 int
838 xmlNanoFTPQuit(void *ctx) {
839     xmlNanoFTPCtxtPtr ctxt = (xmlNanoFTPCtxtPtr) ctx;
840     char buf[200];
841     int len;
842     int res;
843
844     snprintf(buf, sizeof(buf), "QUIT\r\n");
845     len = strlen(buf);
846 #ifdef DEBUG_FTP
847     xmlGenericError(xmlGenericErrorContext, "%s", buf); /* Just to be consistent, even though we know it can't have a % in it */
848 #endif
849     res = send(ctxt->controlFd, buf, len, 0);
850     return(0);
851 }
852
853 /**
854  * xmlNanoFTPConnect:
855  * @ctx:  an FTP context
856  *
857  * Tries to open a control connection
858  *
859  * Returns -1 in case of error, 0 otherwise
860  */
861
862 int
863 xmlNanoFTPConnect(void *ctx) {
864     xmlNanoFTPCtxtPtr ctxt = (xmlNanoFTPCtxtPtr) ctx;
865     struct hostent *hp;
866     int port;
867     int res;
868
869     if (ctxt == NULL)
870         return(-1);
871     if (ctxt->hostname == NULL)
872         return(-1);
873
874     /*
875      * do the blocking DNS query.
876      */
877     if (proxy)
878         hp = gethostbyname(proxy);
879     else
880         hp = gethostbyname(ctxt->hostname);
881     if (hp == NULL)
882         return(-1);
883
884     /*
885      * Prepare the socket
886      */
887     memset(&ctxt->ftpAddr, 0, sizeof(ctxt->ftpAddr));
888     ctxt->ftpAddr.sin_family = AF_INET;
889     memcpy(&ctxt->ftpAddr.sin_addr, hp->h_addr_list[0], hp->h_length);
890     if (proxy) {
891         port = proxyPort;
892     } else {
893         port = ctxt->port;
894     }
895     if (port == 0)
896         port = 21;
897     ctxt->ftpAddr.sin_port = htons(port);
898     ctxt->controlFd = socket(AF_INET, SOCK_STREAM, 0);
899     if (ctxt->controlFd < 0)
900         return(-1);
901
902     /*
903      * Do the connect.
904      */
905     if (connect(ctxt->controlFd, (struct sockaddr *) &ctxt->ftpAddr,
906                 sizeof(struct sockaddr_in)) < 0) {
907         closesocket(ctxt->controlFd); ctxt->controlFd = -1;
908         ctxt->controlFd = -1;
909         return(-1);
910     }
911
912     /*
913      * Wait for the HELLO from the server.
914      */
915     res = xmlNanoFTPGetResponse(ctxt);
916     if (res != 2) {
917         closesocket(ctxt->controlFd); ctxt->controlFd = -1;
918         ctxt->controlFd = -1;
919         return(-1);
920     }
921
922     /*
923      * State diagram for the login operation on the FTP server
924      *
925      * Reference: RFC 959
926      *
927      *                       1
928      * +---+   USER    +---+------------->+---+
929      * | B |---------->| W | 2       ---->| E |
930      * +---+           +---+------  |  -->+---+
931      *                  | |       | | |
932      *                3 | | 4,5   | | |
933      *    --------------   -----  | | |
934      *   |                      | | | |
935      *   |                      | | | |
936      *   |                 ---------  |
937      *   |               1|     | |   |
938      *   V                |     | |   |
939      * +---+   PASS    +---+ 2  |  ------>+---+
940      * |   |---------->| W |------------->| S |
941      * +---+           +---+   ---------->+---+
942      *                  | |   | |     |
943      *                3 | |4,5| |     |
944      *    --------------   --------   |
945      *   |                    | |  |  |
946      *   |                    | |  |  |
947      *   |                 -----------
948      *   |             1,3|   | |  |
949      *   V                |  2| |  |
950      * +---+   ACCT    +---+--  |   ----->+---+
951      * |   |---------->| W | 4,5 -------->| F |
952      * +---+           +---+------------->+---+
953      *
954      * Of course in case of using a proxy this get really nasty and is not
955      * standardized at all :-(
956      */
957     if (proxy) {
958         int len;
959         char buf[400];
960
961         if (proxyUser != NULL) {
962             /*
963              * We need proxy auth
964              */
965             snprintf(buf, sizeof(buf), "USER %s\r\n", proxyUser);
966             buf[sizeof(buf) - 1] = 0;
967             len = strlen(buf);
968 #ifdef DEBUG_FTP
969             xmlGenericError(xmlGenericErrorContext, "%s", buf);
970 #endif
971             res = send(ctxt->controlFd, buf, len, 0);
972             if (res < 0) {
973                 closesocket(ctxt->controlFd);
974                 ctxt->controlFd = -1;
975                 return(res);
976             }
977             res = xmlNanoFTPGetResponse(ctxt);
978             switch (res) {
979                 case 2:
980                     if (proxyPasswd == NULL)
981                         break;
982                 case 3:
983                     if (proxyPasswd != NULL)
984                         snprintf(buf, sizeof(buf), "PASS %s\r\n", proxyPasswd);
985                     else
986                         snprintf(buf, sizeof(buf), "PASS anonymous@\r\n");
987                     buf[sizeof(buf) - 1] = 0;
988                     len = strlen(buf);
989 #ifdef DEBUG_FTP
990                     xmlGenericError(xmlGenericErrorContext, "%s", buf);
991 #endif
992                     res = send(ctxt->controlFd, buf, len, 0);
993                     if (res < 0) {
994                         closesocket(ctxt->controlFd);
995                         ctxt->controlFd = -1;
996                         return(res);
997                     }
998                     res = xmlNanoFTPGetResponse(ctxt);
999                     if (res > 3) {
1000                         closesocket(ctxt->controlFd);
1001                         ctxt->controlFd = -1;
1002                         return(-1);
1003                     }
1004                     break;
1005                 case 1:
1006                     break;
1007                 case 4:
1008                 case 5:
1009                 case -1:
1010                 default:
1011                     closesocket(ctxt->controlFd);
1012                     ctxt->controlFd = -1;
1013                     return(-1);
1014             }
1015         }
1016
1017         /*
1018          * We assume we don't need more authentication to the proxy
1019          * and that it succeeded :-\
1020          */
1021         switch (proxyType) {
1022             case 0:
1023                 /* we will try in sequence */
1024             case 1:
1025                 /* Using SITE command */
1026                 snprintf(buf, sizeof(buf), "SITE %s\r\n", ctxt->hostname);
1027                 buf[sizeof(buf) - 1] = 0;
1028                 len = strlen(buf);
1029 #ifdef DEBUG_FTP
1030                 xmlGenericError(xmlGenericErrorContext, "%s", buf);
1031 #endif
1032                 res = send(ctxt->controlFd, buf, len, 0);
1033                 if (res < 0) {
1034                     closesocket(ctxt->controlFd); ctxt->controlFd = -1;
1035                     ctxt->controlFd = -1;
1036                     return(res);
1037                 }
1038                 res = xmlNanoFTPGetResponse(ctxt);
1039                 if (res == 2) {
1040                     /* we assume it worked :-\ 1 is error for SITE command */
1041                     proxyType = 1;
1042                     break;
1043                 }    
1044                 if (proxyType == 1) {
1045                     closesocket(ctxt->controlFd); ctxt->controlFd = -1;
1046                     ctxt->controlFd = -1;
1047                     return(-1);
1048                 }
1049             case 2:
1050                 /* USER user@host command */
1051                 if (ctxt->user == NULL)
1052                     snprintf(buf, sizeof(buf), "USER anonymous@%s\r\n",
1053                                    ctxt->hostname);
1054                 else
1055                     snprintf(buf, sizeof(buf), "USER %s@%s\r\n",
1056                                    ctxt->user, ctxt->hostname);
1057                 buf[sizeof(buf) - 1] = 0;
1058                 len = strlen(buf);
1059 #ifdef DEBUG_FTP
1060                 xmlGenericError(xmlGenericErrorContext, "%s", buf);
1061 #endif
1062                 res = send(ctxt->controlFd, buf, len, 0);
1063                 if (res < 0) {
1064                     closesocket(ctxt->controlFd); ctxt->controlFd = -1;
1065                     ctxt->controlFd = -1;
1066                     return(res);
1067                 }
1068                 res = xmlNanoFTPGetResponse(ctxt);
1069                 if ((res == 1) || (res == 2)) {
1070                     /* we assume it worked :-\ */
1071                     proxyType = 2;
1072                     return(0);
1073                 }    
1074                 if (ctxt->passwd == NULL)
1075                     snprintf(buf, sizeof(buf), "PASS anonymous@\r\n");
1076                 else
1077                     snprintf(buf, sizeof(buf), "PASS %s\r\n", ctxt->passwd);
1078                 buf[sizeof(buf) - 1] = 0;
1079                 len = strlen(buf);
1080 #ifdef DEBUG_FTP
1081                 xmlGenericError(xmlGenericErrorContext, "%s", buf);
1082 #endif
1083                 res = send(ctxt->controlFd, buf, len, 0);
1084                 if (res < 0) {
1085                     closesocket(ctxt->controlFd); ctxt->controlFd = -1;
1086                     ctxt->controlFd = -1;
1087                     return(res);
1088                 }
1089                 res = xmlNanoFTPGetResponse(ctxt);
1090                 if ((res == 1) || (res == 2)) {
1091                     /* we assume it worked :-\ */
1092                     proxyType = 2;
1093                     return(0);
1094                 }
1095                 if (proxyType == 2) {
1096                     closesocket(ctxt->controlFd); ctxt->controlFd = -1;
1097                     ctxt->controlFd = -1;
1098                     return(-1);
1099                 }
1100             case 3:
1101                 /*
1102                  * If you need support for other Proxy authentication scheme
1103                  * send the code or at least the sequence in use.
1104                  */
1105             default:
1106                 closesocket(ctxt->controlFd); ctxt->controlFd = -1;
1107                 ctxt->controlFd = -1;
1108                 return(-1);
1109         }
1110     }
1111     /*
1112      * Non-proxy handling.
1113      */
1114     res = xmlNanoFTPSendUser(ctxt);
1115     if (res < 0) {
1116         closesocket(ctxt->controlFd); ctxt->controlFd = -1;
1117         ctxt->controlFd = -1;
1118         return(-1);
1119     }
1120     res = xmlNanoFTPGetResponse(ctxt);
1121     switch (res) {
1122         case 2:
1123             return(0);
1124         case 3:
1125             break;
1126         case 1:
1127         case 4:
1128         case 5:
1129         case -1:
1130         default:
1131             closesocket(ctxt->controlFd); ctxt->controlFd = -1;
1132             ctxt->controlFd = -1;
1133             return(-1);
1134     }
1135     res = xmlNanoFTPSendPasswd(ctxt);
1136     if (res < 0) {
1137         closesocket(ctxt->controlFd); ctxt->controlFd = -1;
1138         ctxt->controlFd = -1;
1139         return(-1);
1140     }
1141     res = xmlNanoFTPGetResponse(ctxt);
1142     switch (res) {
1143         case 2:
1144             break;
1145         case 3:
1146             xmlGenericError(xmlGenericErrorContext,
1147                     "FTP server asking for ACCNT on anonymous\n");
1148         case 1:
1149         case 4:
1150         case 5:
1151         case -1:
1152         default:
1153             closesocket(ctxt->controlFd); ctxt->controlFd = -1;
1154             ctxt->controlFd = -1;
1155             return(-1);
1156     }
1157
1158     return(0);
1159 }
1160
1161 /**
1162  * xmlNanoFTPConnectTo:
1163  * @server:  an FTP server name
1164  * @port:  the port (use 21 if 0)
1165  *
1166  * Tries to open a control connection to the given server/port
1167  *
1168  * Returns an fTP context or NULL if it failed
1169  */
1170
1171 void*
1172 xmlNanoFTPConnectTo(const char *server, int port) {
1173     xmlNanoFTPCtxtPtr ctxt;
1174     int res;
1175
1176     xmlNanoFTPInit();
1177     if (server == NULL) 
1178         return(NULL);
1179     ctxt = (xmlNanoFTPCtxtPtr) xmlNanoFTPNewCtxt(NULL);
1180     ctxt->hostname = xmlMemStrdup(server);
1181     if (port != 0)
1182         ctxt->port = port;
1183     res = xmlNanoFTPConnect(ctxt);
1184     if (res < 0) {
1185         xmlNanoFTPFreeCtxt(ctxt);
1186         return(NULL);
1187     }
1188     return(ctxt);
1189 }
1190
1191 /**
1192  * xmlNanoFTPCwd:
1193  * @ctx:  an FTP context
1194  * @directory:  a directory on the server
1195  *
1196  * Tries to change the remote directory
1197  *
1198  * Returns -1 incase of error, 1 if CWD worked, 0 if it failed
1199  */
1200
1201 int
1202 xmlNanoFTPCwd(void *ctx, char *directory) {
1203     xmlNanoFTPCtxtPtr ctxt = (xmlNanoFTPCtxtPtr) ctx;
1204     char buf[400];
1205     int len;
1206     int res;
1207
1208     /*
1209      * Expected response code for CWD:
1210      *
1211      * CWD
1212      *     250
1213      *     500, 501, 502, 421, 530, 550
1214      */
1215     snprintf(buf, sizeof(buf), "CWD %s\r\n", directory);
1216     buf[sizeof(buf) - 1] = 0;
1217     len = strlen(buf);
1218 #ifdef DEBUG_FTP
1219     xmlGenericError(xmlGenericErrorContext, "%s", buf);
1220 #endif
1221     res = send(ctxt->controlFd, buf, len, 0);
1222     if (res < 0) return(res);
1223     res = xmlNanoFTPGetResponse(ctxt);
1224     if (res == 4) {
1225         return(-1);
1226     }
1227     if (res == 2) return(1);
1228     if (res == 5) {
1229         return(0);
1230     }
1231     return(0);
1232 }
1233
1234 /**
1235  * xmlNanoFTPGetConnection:
1236  * @ctx:  an FTP context
1237  *
1238  * Try to open a data connection to the server. Currently only
1239  * passive mode is supported.
1240  *
1241  * Returns -1 incase of error, 0 otherwise
1242  */
1243
1244 int
1245 xmlNanoFTPGetConnection(void *ctx) {
1246     xmlNanoFTPCtxtPtr ctxt = (xmlNanoFTPCtxtPtr) ctx;
1247     char buf[200], *cur;
1248     int len, i;
1249     int res;
1250     unsigned char ad[6], *adp, *portp;
1251     unsigned int temp[6];
1252     struct sockaddr_in dataAddr;
1253     SOCKLEN_T dataAddrLen;
1254
1255     ctxt->dataFd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
1256     if (ctxt->dataFd < 0) {
1257         xmlGenericError(xmlGenericErrorContext,
1258                 "xmlNanoFTPGetConnection: failed to create socket\n");
1259         return(-1);
1260     }
1261     dataAddrLen = sizeof(dataAddr);
1262     memset(&dataAddr, 0, dataAddrLen);
1263     dataAddr.sin_family = AF_INET;
1264
1265     if (ctxt->passive) {
1266         snprintf(buf, sizeof(buf), "PASV\r\n");
1267         len = strlen(buf);
1268 #ifdef DEBUG_FTP
1269         xmlGenericError(xmlGenericErrorContext, "%s", buf);
1270 #endif
1271         res = send(ctxt->controlFd, buf, len, 0);
1272         if (res < 0) {
1273             closesocket(ctxt->dataFd); ctxt->dataFd = -1;
1274             return(res);
1275         }
1276         res = xmlNanoFTPReadResponse(ctx);
1277         if (res != 2) {
1278             if (res == 5) {
1279                 closesocket(ctxt->dataFd); ctxt->dataFd = -1;
1280                 return(-1);
1281             } else {
1282                 /*
1283                  * retry with an active connection
1284                  */
1285                 closesocket(ctxt->dataFd); ctxt->dataFd = -1;
1286                 ctxt->passive = 0;
1287             }
1288         }
1289         cur = &ctxt->controlBuf[ctxt->controlBufAnswer]; 
1290         while (((*cur < '0') || (*cur > '9')) && *cur != '\0') cur++;
1291         if (sscanf(cur, "%u,%u,%u,%u,%u,%u", &temp[0], &temp[1], &temp[2],
1292                     &temp[3], &temp[4], &temp[5]) != 6) {
1293             xmlGenericError(xmlGenericErrorContext,
1294                     "Invalid answer to PASV\n");
1295             if (ctxt->dataFd != -1) {
1296                 closesocket(ctxt->dataFd); ctxt->dataFd = -1;
1297             }
1298             return(-1);
1299         }
1300         for (i=0; i<6; i++) ad[i] = (unsigned char) (temp[i] & 0xff);
1301         memcpy(&dataAddr.sin_addr, &ad[0], 4);
1302         memcpy(&dataAddr.sin_port, &ad[4], 2);
1303         if (connect(ctxt->dataFd, (struct sockaddr *) &dataAddr, dataAddrLen) < 0) {
1304             xmlGenericError(xmlGenericErrorContext,
1305                     "Failed to create a data connection\n");
1306             closesocket(ctxt->dataFd); ctxt->dataFd = -1;
1307             return (-1);
1308         }
1309     } else {
1310         getsockname(ctxt->dataFd, (struct sockaddr *) &dataAddr, &dataAddrLen);
1311         dataAddr.sin_port = 0;
1312         if (bind(ctxt->dataFd, (struct sockaddr *) &dataAddr, dataAddrLen) < 0) {
1313             xmlGenericError(xmlGenericErrorContext,
1314                     "Failed to bind a port\n");
1315             closesocket(ctxt->dataFd); ctxt->dataFd = -1;
1316             return (-1);
1317         }
1318         getsockname(ctxt->dataFd, (struct sockaddr *) &dataAddr, &dataAddrLen);
1319
1320         if (listen(ctxt->dataFd, 1) < 0) {
1321             xmlGenericError(xmlGenericErrorContext,
1322                     "Could not listen on port %d\n",
1323                     ntohs(dataAddr.sin_port));
1324             closesocket(ctxt->dataFd); ctxt->dataFd = -1;
1325             return (-1);
1326         }
1327         adp = (unsigned char *) &dataAddr.sin_addr;
1328         portp = (unsigned char *) &dataAddr.sin_port;
1329         snprintf(buf, sizeof(buf), "PORT %d,%d,%d,%d,%d,%d\r\n",
1330                adp[0] & 0xff, adp[1] & 0xff, adp[2] & 0xff, adp[3] & 0xff,
1331                portp[0] & 0xff, portp[1] & 0xff);
1332         buf[sizeof(buf) - 1] = 0;
1333         len = strlen(buf);
1334 #ifdef DEBUG_FTP
1335         xmlGenericError(xmlGenericErrorContext, "%s", buf);
1336 #endif
1337
1338         res = send(ctxt->controlFd, buf, len, 0);
1339         if (res < 0) {
1340             closesocket(ctxt->dataFd); ctxt->dataFd = -1;
1341             return(res);
1342         }
1343         res = xmlNanoFTPGetResponse(ctxt);
1344         if (res != 2) {
1345             closesocket(ctxt->dataFd); ctxt->dataFd = -1;
1346             return(-1);
1347         }
1348     }
1349     return(ctxt->dataFd);
1350     
1351 }
1352
1353 /**
1354  * xmlNanoFTPCloseConnection:
1355  * @ctx:  an FTP context
1356  *
1357  * Close the data connection from the server
1358  *
1359  * Returns -1 incase of error, 0 otherwise
1360  */
1361
1362 int
1363 xmlNanoFTPCloseConnection(void *ctx) {
1364     xmlNanoFTPCtxtPtr ctxt = (xmlNanoFTPCtxtPtr) ctx;
1365     int res;
1366     fd_set rfd, efd;
1367     struct timeval tv;
1368
1369     closesocket(ctxt->dataFd); ctxt->dataFd = -1;
1370     tv.tv_sec = 15;
1371     tv.tv_usec = 0;
1372     FD_ZERO(&rfd);
1373     FD_SET(ctxt->controlFd, &rfd);
1374     FD_ZERO(&efd);
1375     FD_SET(ctxt->controlFd, &efd);
1376     res = select(ctxt->controlFd + 1, &rfd, NULL, &efd, &tv);
1377     if (res < 0) {
1378 #ifdef DEBUG_FTP
1379         perror("select");
1380 #endif
1381         closesocket(ctxt->controlFd); ctxt->controlFd = -1;
1382         return(-1);
1383     }
1384     if (res == 0) {
1385 #ifdef DEBUG_FTP
1386         xmlGenericError(xmlGenericErrorContext,
1387                 "xmlNanoFTPCloseConnection: timeout\n");
1388 #endif
1389         closesocket(ctxt->controlFd); ctxt->controlFd = -1;
1390     } else {
1391         res = xmlNanoFTPGetResponse(ctxt);
1392         if (res != 2) {
1393             closesocket(ctxt->controlFd); ctxt->controlFd = -1;
1394             return(-1);
1395         }
1396     }
1397     return(0);
1398 }
1399
1400 /**
1401  * xmlNanoFTPParseList:
1402  * @list:  some data listing received from the server
1403  * @callback:  the user callback
1404  * @userData:  the user callback data
1405  *
1406  * Parse at most one entry from the listing. 
1407  *
1408  * Returns -1 incase of error, the length of data parsed otherwise
1409  */
1410
1411 static int
1412 xmlNanoFTPParseList(const char *list, ftpListCallback callback, void *userData) {
1413     const char *cur = list;
1414     char filename[151];
1415     char attrib[11];
1416     char owner[11];
1417     char group[11];
1418     char month[4];
1419     int year = 0;
1420     int minute = 0;
1421     int hour = 0;
1422     int day = 0;
1423     unsigned long size = 0;
1424     int links = 0;
1425     int i;
1426
1427     if (!strncmp(cur, "total", 5)) {
1428         cur += 5;
1429         while (*cur == ' ') cur++;
1430         while ((*cur >= '0') && (*cur <= '9'))
1431             links = (links * 10) + (*cur++ - '0');
1432         while ((*cur == ' ') || (*cur == '\n')  || (*cur == '\r'))
1433             cur++;
1434         return(cur - list);
1435     } else if (*list == '+') {
1436         return(0);
1437     } else {
1438         while ((*cur == ' ') || (*cur == '\n')  || (*cur == '\r'))
1439             cur++;
1440         if (*cur == 0) return(0);
1441         i = 0;
1442         while (*cur != ' ') {
1443             if (i < 10) 
1444                 attrib[i++] = *cur;
1445             cur++;
1446             if (*cur == 0) return(0);
1447         }
1448         attrib[10] = 0;
1449         while (*cur == ' ') cur++;
1450         if (*cur == 0) return(0);
1451         while ((*cur >= '0') && (*cur <= '9'))
1452             links = (links * 10) + (*cur++ - '0');
1453         while (*cur == ' ') cur++;
1454         if (*cur == 0) return(0);
1455         i = 0;
1456         while (*cur != ' ') {
1457             if (i < 10) 
1458                 owner[i++] = *cur;
1459             cur++;
1460             if (*cur == 0) return(0);
1461         }
1462         owner[i] = 0;
1463         while (*cur == ' ') cur++;
1464         if (*cur == 0) return(0);
1465         i = 0;
1466         while (*cur != ' ') {
1467             if (i < 10) 
1468                 group[i++] = *cur;
1469             cur++;
1470             if (*cur == 0) return(0);
1471         }
1472         group[i] = 0;
1473         while (*cur == ' ') cur++;
1474         if (*cur == 0) return(0);
1475         while ((*cur >= '0') && (*cur <= '9'))
1476             size = (size * 10) + (*cur++ - '0');
1477         while (*cur == ' ') cur++;
1478         if (*cur == 0) return(0);
1479         i = 0;
1480         while (*cur != ' ') {
1481             if (i < 3)
1482                 month[i++] = *cur;
1483             cur++;
1484             if (*cur == 0) return(0);
1485         }
1486         month[i] = 0;
1487         while (*cur == ' ') cur++;
1488         if (*cur == 0) return(0);
1489         while ((*cur >= '0') && (*cur <= '9'))
1490             day = (day * 10) + (*cur++ - '0');
1491         while (*cur == ' ') cur++;
1492         if (*cur == 0) return(0);
1493         if ((cur[1] == 0) || (cur[2] == 0)) return(0);
1494         if ((cur[1] == ':') || (cur[2] == ':')) {
1495             while ((*cur >= '0') && (*cur <= '9'))
1496                 hour = (hour * 10) + (*cur++ - '0');
1497             if (*cur == ':') cur++;
1498             while ((*cur >= '0') && (*cur <= '9'))
1499                 minute = (minute * 10) + (*cur++ - '0');
1500         } else {
1501             while ((*cur >= '0') && (*cur <= '9'))
1502                 year = (year * 10) + (*cur++ - '0');
1503         }
1504         while (*cur == ' ') cur++;
1505         if (*cur == 0) return(0);
1506         i = 0;
1507         while ((*cur != '\n')  && (*cur != '\r')) {
1508             if (i < 150)
1509                 filename[i++] = *cur;
1510             cur++;
1511             if (*cur == 0) return(0);
1512         }
1513         filename[i] = 0;
1514         if ((*cur != '\n') && (*cur != '\r'))
1515             return(0);
1516         while ((*cur == '\n')  || (*cur == '\r'))
1517             cur++;
1518     }
1519     if (callback != NULL) {
1520         callback(userData, filename, attrib, owner, group, size, links,
1521                  year, month, day, hour, minute);
1522     }
1523     return(cur - list);
1524 }
1525
1526 /**
1527  * xmlNanoFTPList:
1528  * @ctx:  an FTP context
1529  * @callback:  the user callback
1530  * @userData:  the user callback data
1531  * @filename:  optional files to list
1532  *
1533  * Do a listing on the server. All files info are passed back
1534  * in the callbacks.
1535  *
1536  * Returns -1 incase of error, 0 otherwise
1537  */
1538
1539 int
1540 xmlNanoFTPList(void *ctx, ftpListCallback callback, void *userData,
1541                char *filename) {
1542     xmlNanoFTPCtxtPtr ctxt = (xmlNanoFTPCtxtPtr) ctx;
1543     char buf[4096 + 1];
1544     int len, res;
1545     int indx = 0, base;
1546     fd_set rfd, efd;
1547     struct timeval tv;
1548
1549     if (filename == NULL) {
1550         if (xmlNanoFTPCwd(ctxt, ctxt->path) < 1)
1551             return(-1);
1552         ctxt->dataFd = xmlNanoFTPGetConnection(ctxt);
1553         if (ctxt->dataFd == -1)
1554             return(-1);
1555         snprintf(buf, sizeof(buf), "LIST -L\r\n");
1556     } else {
1557         if (filename[0] != '/') {
1558             if (xmlNanoFTPCwd(ctxt, ctxt->path) < 1)
1559                 return(-1);
1560         }
1561         ctxt->dataFd = xmlNanoFTPGetConnection(ctxt);
1562         if (ctxt->dataFd == -1)
1563             return(-1);
1564         snprintf(buf, sizeof(buf), "LIST -L %s\r\n", filename);
1565     }
1566     buf[sizeof(buf) - 1] = 0;
1567     len = strlen(buf);
1568 #ifdef DEBUG_FTP
1569     xmlGenericError(xmlGenericErrorContext, "%s", buf);
1570 #endif
1571     res = send(ctxt->controlFd, buf, len, 0);
1572     if (res < 0) {
1573         closesocket(ctxt->dataFd); ctxt->dataFd = -1;
1574         return(res);
1575     }
1576     res = xmlNanoFTPReadResponse(ctxt);
1577     if (res != 1) {
1578         closesocket(ctxt->dataFd); ctxt->dataFd = -1;
1579         return(-res);
1580     }
1581
1582     do {
1583         tv.tv_sec = 1;
1584         tv.tv_usec = 0;
1585         FD_ZERO(&rfd);
1586         FD_SET(ctxt->dataFd, &rfd);
1587         FD_ZERO(&efd);
1588         FD_SET(ctxt->dataFd, &efd);
1589         res = select(ctxt->dataFd + 1, &rfd, NULL, &efd, &tv);
1590         if (res < 0) {
1591 #ifdef DEBUG_FTP
1592             perror("select");
1593 #endif
1594             closesocket(ctxt->dataFd); ctxt->dataFd = -1;
1595             return(-1);
1596         }
1597         if (res == 0) {
1598             res = xmlNanoFTPCheckResponse(ctxt);
1599             if (res < 0) {
1600                 closesocket(ctxt->dataFd); ctxt->dataFd = -1;
1601                 ctxt->dataFd = -1;
1602                 return(-1);
1603             }
1604             if (res == 2) {
1605                 closesocket(ctxt->dataFd); ctxt->dataFd = -1;
1606                 return(0);
1607             }
1608
1609             continue;
1610         }
1611
1612         if ((len = recv(ctxt->dataFd, &buf[indx], sizeof(buf) - (indx + 1), 0)) < 0) {
1613 #ifdef DEBUG_FTP
1614             perror("recv");
1615 #endif
1616             closesocket(ctxt->dataFd); ctxt->dataFd = -1;
1617             ctxt->dataFd = -1;
1618             return(-1);
1619         }
1620 #ifdef DEBUG_FTP
1621         write(1, &buf[indx], len);
1622 #endif
1623         indx += len;
1624         buf[indx] = 0;
1625         base = 0;
1626         do {
1627             res = xmlNanoFTPParseList(&buf[base], callback, userData);
1628             base += res;
1629         } while (res > 0);
1630
1631         memmove(&buf[0], &buf[base], indx - base);
1632         indx -= base;
1633     } while (len != 0);
1634     xmlNanoFTPCloseConnection(ctxt);
1635     return(0);
1636 }
1637
1638 /**
1639  * xmlNanoFTPGetSocket:
1640  * @ctx:  an FTP context
1641  * @filename:  the file to retrieve (or NULL if path is in context).
1642  *
1643  * Initiate fetch of the given file from the server.
1644  *
1645  * Returns the socket for the data connection, or <0 in case of error
1646  */
1647
1648
1649 int
1650 xmlNanoFTPGetSocket(void *ctx, const char *filename) {
1651     xmlNanoFTPCtxtPtr ctxt = (xmlNanoFTPCtxtPtr) ctx;
1652     char buf[300];
1653     int res, len;
1654     if ((filename == NULL) && (ctxt->path == NULL))
1655         return(-1);
1656     ctxt->dataFd = xmlNanoFTPGetConnection(ctxt);
1657     if (ctxt->dataFd == -1)
1658         return(-1);
1659
1660     snprintf(buf, sizeof(buf), "TYPE I\r\n");
1661     len = strlen(buf);
1662 #ifdef DEBUG_FTP
1663     xmlGenericError(xmlGenericErrorContext, "%s", buf);
1664 #endif
1665     res = send(ctxt->controlFd, buf, len, 0);
1666     if (res < 0) {
1667         closesocket(ctxt->dataFd); ctxt->dataFd = -1;
1668         return(res);
1669     }
1670     res = xmlNanoFTPReadResponse(ctxt);
1671     if (res != 2) {
1672         closesocket(ctxt->dataFd); ctxt->dataFd = -1;
1673         return(-res);
1674     }
1675     if (filename == NULL)
1676         snprintf(buf, sizeof(buf), "RETR %s\r\n", ctxt->path);
1677     else
1678         snprintf(buf, sizeof(buf), "RETR %s\r\n", filename);
1679     buf[sizeof(buf) - 1] = 0;
1680     len = strlen(buf);
1681 #ifdef DEBUG_FTP
1682     xmlGenericError(xmlGenericErrorContext, "%s", buf);
1683 #endif
1684     res = send(ctxt->controlFd, buf, len, 0);
1685     if (res < 0) {
1686         closesocket(ctxt->dataFd); ctxt->dataFd = -1;
1687         return(res);
1688     }
1689     res = xmlNanoFTPReadResponse(ctxt);
1690     if (res != 1) {
1691         closesocket(ctxt->dataFd); ctxt->dataFd = -1;
1692         return(-res);
1693     }
1694     return(ctxt->dataFd);
1695 }
1696
1697 /**
1698  * xmlNanoFTPGet:
1699  * @ctx:  an FTP context
1700  * @callback:  the user callback
1701  * @userData:  the user callback data
1702  * @filename:  the file to retrieve
1703  *
1704  * Fetch the given file from the server. All data are passed back
1705  * in the callbacks. The last callback has a size of 0 block.
1706  *
1707  * Returns -1 incase of error, 0 otherwise
1708  */
1709
1710 int
1711 xmlNanoFTPGet(void *ctx, ftpDataCallback callback, void *userData,
1712               const char *filename) {
1713     xmlNanoFTPCtxtPtr ctxt = (xmlNanoFTPCtxtPtr) ctx;
1714     char buf[4096];
1715     int len = 0, res;
1716     fd_set rfd;
1717     struct timeval tv;
1718
1719     if ((filename == NULL) && (ctxt->path == NULL))
1720         return(-1);
1721     if (callback == NULL)
1722         return(-1);
1723     if (xmlNanoFTPGetSocket(ctxt, filename) < 0)
1724         return(-1);
1725
1726     do {
1727         tv.tv_sec = 1;
1728         tv.tv_usec = 0;
1729         FD_ZERO(&rfd);
1730         FD_SET(ctxt->dataFd, &rfd);
1731         res = select(ctxt->dataFd + 1, &rfd, NULL, NULL, &tv);
1732         if (res < 0) {
1733 #ifdef DEBUG_FTP
1734             perror("select");
1735 #endif
1736             closesocket(ctxt->dataFd); ctxt->dataFd = -1;
1737             return(-1);
1738         }
1739         if (res == 0) {
1740             res = xmlNanoFTPCheckResponse(ctxt);
1741             if (res < 0) {
1742                 closesocket(ctxt->dataFd); ctxt->dataFd = -1;
1743                 ctxt->dataFd = -1;
1744                 return(-1);
1745             }
1746             if (res == 2) {
1747                 closesocket(ctxt->dataFd); ctxt->dataFd = -1;
1748                 return(0);
1749             }
1750
1751             continue;
1752         }
1753         if ((len = recv(ctxt->dataFd, buf, sizeof(buf), 0)) < 0) {
1754             callback(userData, buf, len);
1755             closesocket(ctxt->dataFd); ctxt->dataFd = -1;
1756             return(-1);
1757         }
1758         callback(userData, buf, len);
1759     } while (len != 0);
1760
1761     return(xmlNanoFTPCloseConnection(ctxt));
1762 }
1763
1764 /**
1765  * xmlNanoFTPRead:
1766  * @ctx:  the FTP context
1767  * @dest:  a buffer
1768  * @len:  the buffer length
1769  *
1770  * This function tries to read @len bytes from the existing FTP connection
1771  * and saves them in @dest. This is a blocking call.
1772  *
1773  * Returns the number of byte read. 0 is an indication of an end of connection.
1774  *         -1 indicates a parameter error.
1775  */
1776 int
1777 xmlNanoFTPRead(void *ctx, void *dest, int len) {
1778     xmlNanoFTPCtxtPtr ctxt = (xmlNanoFTPCtxtPtr) ctx;
1779
1780     if (ctx == NULL) return(-1);
1781     if (ctxt->dataFd < 0) return(0);
1782     if (dest == NULL) return(-1);
1783     if (len <= 0) return(0);
1784
1785     len = recv(ctxt->dataFd, dest, len, 0);
1786 #ifdef DEBUG_FTP
1787     xmlGenericError(xmlGenericErrorContext, "Recvd %d bytes\n", len);
1788 #endif
1789     if (len <= 0) {
1790         xmlNanoFTPCloseConnection(ctxt);
1791     }
1792     return(len);
1793 }
1794
1795 /**
1796  * xmlNanoFTPOpen:
1797  * @URL: the URL to the resource
1798  *
1799  * Start to fetch the given ftp:// resource
1800  *
1801  * Returns an FTP context, or NULL 
1802  */
1803
1804 void*
1805 xmlNanoFTPOpen(const char *URL) {
1806     xmlNanoFTPCtxtPtr ctxt;
1807     int sock;
1808
1809     xmlNanoFTPInit();
1810     if (URL == NULL) return(NULL);
1811     if (strncmp("ftp://", URL, 6)) return(NULL);
1812
1813     ctxt = (xmlNanoFTPCtxtPtr) xmlNanoFTPNewCtxt(URL);
1814     if (ctxt == NULL) return(NULL);
1815     if (xmlNanoFTPConnect(ctxt) < 0) {
1816         xmlNanoFTPFreeCtxt(ctxt);
1817         return(NULL);
1818     }
1819     sock = xmlNanoFTPGetSocket(ctxt, ctxt->path);
1820     if (sock < 0) {
1821         xmlNanoFTPFreeCtxt(ctxt);
1822         return(NULL);
1823     }
1824     return(ctxt);
1825 }
1826
1827 /**
1828  * xmlNanoFTPClose:
1829  * @ctx: an FTP context
1830  *
1831  * Close the connection and both control and transport
1832  *
1833  * Returns -1 incase of error, 0 otherwise
1834  */
1835
1836 int
1837 xmlNanoFTPClose(void *ctx) {
1838     xmlNanoFTPCtxtPtr ctxt = (xmlNanoFTPCtxtPtr) ctx;
1839
1840     if (ctxt == NULL)
1841         return(-1);
1842
1843     if (ctxt->dataFd >= 0) {
1844         closesocket(ctxt->dataFd);
1845         ctxt->dataFd = -1;
1846     }
1847     if (ctxt->controlFd >= 0) {
1848         xmlNanoFTPQuit(ctxt);
1849         closesocket(ctxt->controlFd);
1850         ctxt->controlFd = -1;
1851     }
1852     xmlNanoFTPFreeCtxt(ctxt);
1853     return(0);
1854 }
1855
1856 #ifdef STANDALONE
1857 /************************************************************************
1858  *                                                                      *
1859  *                      Basic test in Standalone mode                   *
1860  *                                                                      *
1861  ************************************************************************/
1862 static
1863 void ftpList(void *userData, const char *filename, const char* attrib,
1864              const char *owner, const char *group, unsigned long size, int links,
1865              int year, const char *month, int day, int hour, int minute) {
1866     xmlGenericError(xmlGenericErrorContext,
1867             "%s %s %s %ld %s\n", attrib, owner, group, size, filename);
1868 }
1869 static
1870 void ftpData(void *userData, const char *data, int len) {
1871     if (userData == NULL) return;
1872     if (len <= 0) {
1873         fclose(userData);
1874         return;
1875     }   
1876     fwrite(data, len, 1, userData);
1877 }
1878
1879 int main(int argc, char **argv) {
1880     void *ctxt;
1881     FILE *output;
1882     char *tstfile = NULL;
1883
1884     xmlNanoFTPInit();
1885     if (argc > 1) {
1886         ctxt = xmlNanoFTPNewCtxt(argv[1]);
1887         if (xmlNanoFTPConnect(ctxt) < 0) {
1888             xmlGenericError(xmlGenericErrorContext,
1889                     "Couldn't connect to %s\n", argv[1]);
1890             exit(1);
1891         }
1892         if (argc > 2)
1893             tstfile = argv[2];
1894     } else
1895         ctxt = xmlNanoFTPConnectTo("localhost", 0);
1896     if (ctxt == NULL) {
1897         xmlGenericError(xmlGenericErrorContext,
1898                 "Couldn't connect to localhost\n");
1899         exit(1);
1900     }
1901     xmlNanoFTPList(ctxt, ftpList, NULL, tstfile);
1902     output = fopen("/tmp/tstdata", "w");
1903     if (output != NULL) {
1904         if (xmlNanoFTPGet(ctxt, ftpData, (void *) output, tstfile) < 0)
1905             xmlGenericError(xmlGenericErrorContext,
1906                     "Failed to get file\n");
1907         
1908     }
1909     xmlNanoFTPClose(ctxt);
1910     xmlMemoryDump();
1911     exit(0);
1912 }
1913 #endif /* STANDALONE */
1914 #else /* !LIBXML_FTP_ENABLED */
1915 #ifdef STANDALONE
1916 #include <stdio.h>
1917 int main(int argc, char **argv) {
1918     xmlGenericError(xmlGenericErrorContext,
1919             "%s : FTP support not compiled in\n", argv[0]);
1920     return(0);
1921 }
1922 #endif /* STANDALONE */
1923 #endif /* LIBXML_FTP_ENABLED */