2 Syren -- a lightweight downloader for Linux/BSD/Win/MacOSX
3 inspired by Axel Copyright 2001-2002 Wilmer van der Gaast
4 version 0.0.6 (atomic alien)
5 coded by Ketmar // Avalon Group
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License with
18 the Debian GNU/Linux distribution in file /usr/doc/copyright/GPL;
19 if not, write to the Free Software Foundation, Inc., 59 Temple Place,
20 Suite 330, Boston, MA 02111-1307 USA
28 #include "syren_ftp.h"
31 char *SyFTPWait (int *code
, TSySocket
*fd
, const TSyPrintStr
*pfn
) {
32 char *s
, *buf
, *lbuf
, *rline
= NULL
;
34 int multi
= 0, rep
= -1;
35 lbuf
= calloc(1, 8200); if (!lbuf
) return NULL
;
37 buf
= SyTCPReceiveStrEx(fd
, 8192, lbuf
);
38 if (!buf
) { free(lbuf
); SyMessage(pfn
, SY_MSG_ERROR
, "read error"); return NULL
; }
39 SyMessage(pfn
, SY_MSG_NOTICE
, " %s", buf
);
42 if (isdigit(s
[0]) && isdigit(s
[1]) & isdigit(s
[2])) rep
= (s
[0]-'0')*100+(s
[1]-'0')*10+(s
[2]-'0'); else rep
= 0;
45 /* first line of reply */
48 SyMessage(pfn
, SY_MSG_ERROR
, "invalid reply code");
51 if (s
[3] == '-') multi
= rep
;
52 else if (s
[3] != ' ') {
54 SyMessage(pfn
, SY_MSG_ERROR
, "invalid reply");
58 rline
= SyStrDup(lbuf
);
61 SyMessage(pfn
, SY_MSG_ERROR
, "memory error");
67 /* multiline continues */
68 if (multi
== rep
&& s
[3] == ' ') break;
71 if (code
) *code
= rep
;
76 int SyFTPWaitFor2 (TSySocket
*fd
, int code0
, int code1
, const TSyPrintStr
*pfn
) {
78 char *rep
= SyFTPWait(&c
, fd
, pfn
);
82 if (code1
< 0) return c
;
83 code0
= code1
; code1
= -1;
85 if ((code0
> 0 && c
/100 == code0
) || (code1
> 0 && c
/100 == code1
)) return c
;
87 SyMessage(pfn
, SY_MSG_ERROR
, "invalid reply code (got: %i; expected: %ixx or %ixx)", c
, code0
, code1
);
89 SyMessage(pfn
, SY_MSG_ERROR
, "invalid reply code (got: %i; expected: %ixx)", c
, code0
);
94 /* perform ftp 'handshake': send USER/PASS */
95 TSyResult
SyFTPStart (TSySocket
*fd
, const char *user
, const char* pass
, const TSyPrintStr
*pfn
) {
98 if (SyFTPWaitFor2(fd
, 2, -1, pfn
) < 0) return SY_ERROR
;
99 if (SyTCPSendLine(pfn
, 1, fd
, "USER %s", (user
&&*user
)?user
:"anonymous") != SY_OK
) return SY_ERROR
;
100 code
= SyFTPWaitFor2(fd
, 2, 3, pfn
);
101 if (code
< 0) return SY_ERROR
;
103 s
= SySPrintf("PASS %s\r\n", (pass
&&*pass
)?pass
:"syren@nowhere.org");
104 if (!s
) { SyMessage(pfn
, SY_MSG_ERROR
, "memory error"); return SY_ERROR
; }
105 SyMessage(pfn
, SY_MSG_NOTICE
, " PASS ********");
106 if (SyTCPSendStr(fd
, s
) != SY_OK
) { free(s
); SyMessage(pfn
, SY_MSG_ERROR
, "socket write error"); return SY_ERROR
; }
108 if (SyFTPWaitFor2(fd
, 2, -1, pfn
) < 0) return SY_ERROR
;
110 /* set binary mode */
111 if (SyTCPSendLine(pfn
, 1, fd
, "TYPE I") != SY_OK
) return SY_ERROR
;
112 return (SyFTPWaitFor2(fd
, 2, -1, pfn
)<0?SY_ERROR
:SY_OK
);
116 /* change current working directory */
117 TSyResult
SyFTPCwd (TSySocket
*fd
, const char *cwd
, const TSyPrintStr
*pfn
) {
121 if (!cwd
) return SY_OK
;
122 s
= SyStrNew(cwd
, -1); if (!s
) { SyMessage(pfn
, SY_MSG_ERROR
, "memory error"); return SY_ERROR
; }
123 t
= s
+strlen(s
)-1; if (t
> s
&& *t
== '/') *t
= '\0';
124 res
= SyTCPSendLine(pfn
, 1, fd
, "CWD %s", s
); free(s
);
125 if (res
!= SY_OK
) return SY_ERROR
;
126 return (SyFTPWaitFor2(fd
, 2, -1, pfn
)<0?SY_ERROR
:SY_OK
);
130 /* open a data connection */
131 TSyResult
SyFTPOpenDataPassv (TSySocket
*fd
, TSySocket
*datafd
, const char *iface
, int timeout
,
132 const TSyPrintStr
*pfn
, TSyProxy
*proxy
) {
139 if (SyTCPSendLine(pfn
, 1, fd
, "PASV") != SY_OK
) return SY_ERROR
;
140 rep
= SyFTPWait(&code
, fd
, pfn
);
141 if (!rep
) return SY_ERROR
;
144 SyMessage(pfn
, SY_MSG_ERROR
, "invalid reply code (got: %i; expected: 2xx)", code
);
147 for (f
= 0; rep
[f
]; f
++) {
148 if (sscanf(&(rep
[f
]), "%i,%i,%i,%i,%i,%i",
149 &info
[0], &info
[1], &info
[2], &info
[3], &info
[4], &info
[5]) == 6) {
150 host
= SySPrintf("%i.%i.%i.%i", info
[0], info
[1], info
[2], info
[3]);
155 if (!host
) { SyMessage(pfn
, SY_MSG_ERROR
, "invalid PASV reply"); return SY_ERROR
; }
157 res
= SyTCPConnect(datafd
, proxy
->url
->host
, proxy
->url
->port
, iface
, timeout
, pfn
);
158 if (res
== SY_OK
) res
= SyProxyConnect(datafd
, proxy
, host
, info
[4]*256+info
[5], pfn
);
160 res
= SyTCPConnect(datafd
, host
, info
[4]*256+info
[5], iface
, timeout
, pfn
);
167 /* get file size; <0: error */
168 /* FIXME: remove recursion! */
169 int64_t SyFTPGetSize (TSySocket
*fd
, const char *iface
, const char *filename
, int maxredir
, int timeout
,
170 const TSyPrintStr
*pfn
, TSyProxy
*proxy
) {
172 int f
, c
, size
= 256;
173 char *reply
, *rr
, *s
, *t
, *fn
;
178 /* try the SIZE command first, if possible */
179 if (SyTCPSendLine(pfn
, 1, fd
, "SIZE %s", filename
) != SY_OK
) return SY_ERROR
;
180 rep
= SyFTPWait(&code
, fd
, pfn
);
181 if (!rep
) return SY_ERROR
;
183 s
= rep
; while (*s
&& *s
> ' ') s
++;
187 SyMessage(pfn
, SY_MSG_ERROR
, "invalid SIZE reply");
191 } else if (code
/10 != 50) {
193 SyMessage(pfn
, SY_MSG_ERROR
, "file not found");
198 SyMessage(pfn
, SY_MSG_ERROR
, "too many redirects");
202 if (SyFTPOpenDataPassv(fd
, &datafd
, iface
, timeout
, pfn
, proxy
) != SY_OK
) return -1;
203 if (SyTCPSendLine(pfn
, 1, fd
, "LIST %s", filename
) != SY_OK
) { SyTCPCloseSocket(&datafd
); return -1; }
204 if (SyFTPWaitFor2(fd
, 1, -1, pfn
) < 0) { SyTCPCloseSocket(&datafd
); return -1; }
206 /* read reply from the server */
207 reply
= calloc(1, size
+4);
209 SyTCPCloseSocket(&datafd
);
210 SyMessage(pfn
, SY_MSG_ERROR
, "memory error");
214 /*while ((c = recv(datafd, reply+f, size-f-3, 0)) > 0) {*/
215 while ((c
= SyTCPReceiveEx(&datafd
, reply
+f
, size
-f
-3, SY_TCP_ALLOWPARTIAL
)) > 0) {
216 f
+= c
; reply
[f
] = '\0';
218 if (size
>= 1024*1024) { SyTCPCloseSocket(&datafd
); free(reply
); return -1; }
219 size
*= 2; rr
= realloc(reply
, size
+4);
220 if (!rr
) { SyTCPCloseSocket(&datafd
); free(reply
); return -1; }
223 SyTCPCloseSocket(&datafd
);
224 if (SyFTPWaitFor2(fd
, 1, -1, pfn
) < 0) { free(reply
); return -1; }
227 /* count the number of probably legal matches: Files&Links only */
229 for (f
= 1; reply
[f
] && reply
[f
+1]; f
++) {
230 if (reply
[f
] == '-' || reply
[f
] == 'l') c
++;
231 else while (reply
[f
] != '\n' && reply
[f
]) f
++;
234 /* no match or more than one match */
237 if (c
== 0) SyMessage(pfn
, SY_MSG_ERROR
, "file not found");
238 else SyMessage(pfn
, SY_MSG_ERROR
, "multiple matches for file");
242 /* symlink handling */
243 s
= strstr(reply
, "\nl");
245 /* get the real filename */
246 fn
= calloc(1, 1024); if (!fn
) {
248 SyMessage(pfn
, SY_MSG_ERROR
, "memory error");
251 sscanf(s
, "%*s %*i %*s %*s %*i %*s %*i %*s %1023s", fn
);
256 SyMessage(pfn
, SY_MSG_ERROR
, "invalid LIST reply");
259 /* get size of the file linked to */
262 if ((reply
= strchr(fn
, '\r')) != NULL
) *reply
= '\0';
263 if ((reply
= strchr(fn
, '\n')) != NULL
) *reply
= '\0';
264 sz
= SyFTPGetSize(fd
, iface
, fn
, maxredir
-1, timeout
, pfn
, proxy
);
268 /* normal file, so read the size */
269 s
= strstr(reply
, "\n-");
272 SyMessage(pfn
, SY_MSG_ERROR
, "invalid LIST reply");
275 /* fuck m$! msvcrt doesn't undersand %lli (and %Li too) */
276 /*f = sscanf(s, "%*s %*i %*s %*s %lli %*s %*i %*s %*s", &sz);
278 f = sscanf(s, "%*s %*i %lli %*i %*s %*i %*i %*s", &sz);
281 SyMessage(pfn, SY_MSG_ERROR, "invalid LIST reply");
286 for (f
= 2; f
; f
--) {
287 for (c
= 2; c
; c
--) { while (*s
&& *s
> ' ') s
++; while (*s
&& *s
<= ' ') s
++; }
289 t
= s
; while (isdigit(*t
)) t
++;
290 if (t
> s
&& *t
&& *t
<= ' ') {
291 *t
= '\0'; sz
= SyStr2Long(s
);