bugfixes; build system changed again %-)
[syren.git] / src / syren_ftp.c
blob0e091085363dcb9f15cd3f3ef70add4c772822cb
1 /*
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
23 Syren ftp utilities
25 #ifndef _SYREN_FTP_C
26 #define _SYREN_FTP_C
28 #include "syren_ftp.h"
31 char *SyFTPWait (int *code, TSySocket *fd, const TSyPrintStr *pfn) {
32 char *s, *buf, *lbuf, *rline = NULL;
33 if (code) *code = -1;
34 int multi = 0, rep = -1;
35 lbuf = calloc(1, 8200); if (!lbuf) return NULL;
36 while (1) {
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);
40 /* extract code */
41 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;
43 /* check code */
44 if (!multi) {
45 /* first line of reply */
46 if (!rep) {
47 free(lbuf);
48 SyMessage(pfn, SY_MSG_ERROR, "invalid reply code");
49 return NULL;
51 if (s[3] == '-') multi = rep;
52 else if (s[3] != ' ') {
53 free(lbuf);
54 SyMessage(pfn, SY_MSG_ERROR, "invalid reply");
55 return NULL;
56 } else {
57 /* first line */
58 rline = SyStrDup(lbuf);
59 if (!rline) {
60 free(lbuf);
61 SyMessage(pfn, SY_MSG_ERROR, "memory error");
62 return NULL;
64 break;
67 /* multiline continues */
68 if (multi == rep && s[3] == ' ') break;
69 /*free(buf);*/
71 if (code) *code = rep;
72 return buf;
76 int SyFTPWaitFor2 (TSySocket *fd, int code0, int code1, const TSyPrintStr *pfn) {
77 int c;
78 char *rep = SyFTPWait(&c, fd, pfn);
79 if (rep) free(rep);
80 if (c < 1) return -1;
81 if (code0 < 0) {
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;
86 if (code1 > 0)
87 SyMessage(pfn, SY_MSG_ERROR, "invalid reply code (got: %i; expected: %ixx or %ixx)", c, code0, code1);
88 else
89 SyMessage(pfn, SY_MSG_ERROR, "invalid reply code (got: %i; expected: %ixx)", c, code0);
90 return -1;
94 /* perform ftp 'handshake': send USER/PASS */
95 TSyResult SyFTPStart (TSySocket *fd, const char *user, const char* pass, const TSyPrintStr *pfn) {
96 int code;
97 char *s;
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;
102 if (code/100 == 3) {
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; }
107 free(s);
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) {
118 char *s, *t;
119 TSyResult res;
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) {
134 int f, info[6];
135 char *host = NULL;
136 int code; char *rep;
137 TSyResult res;
139 if (SyTCPSendLine(pfn, 1, fd, "PASV") != SY_OK) return SY_ERROR;
140 rep = SyFTPWait(&code, fd, pfn);
141 if (!rep) return SY_ERROR;
142 if (code/100 != 2) {
143 free(rep);
144 SyMessage(pfn, SY_MSG_ERROR, "invalid reply code (got: %i; expected: 2xx)", code);
145 return SY_ERROR;
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]);
151 break;
154 free(rep);
155 if (!host) { SyMessage(pfn, SY_MSG_ERROR, "invalid PASV reply"); return SY_ERROR; }
156 if (proxy) {
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);
159 } else {
160 res = SyTCPConnect(datafd, host, info[4]*256+info[5], iface, timeout, pfn);
162 free(host);
163 return res;
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;
174 int code; char *rep;
175 TSySocket datafd;
176 int64_t sz;
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;
182 if (code/100 == 2) {
183 s = rep; while (*s && *s > ' ') s++;
184 sz = SyStr2Long(s);
185 free(rep);
186 if (sz < 0) {
187 SyMessage(pfn, SY_MSG_ERROR, "invalid SIZE reply");
188 return -1;
190 return sz;
191 } else if (code/10 != 50) {
192 free(rep);
193 SyMessage(pfn, SY_MSG_ERROR, "file not found");
194 return -1;
197 if (maxredir <= 0) {
198 SyMessage(pfn, SY_MSG_ERROR, "too many redirects");
199 return -1;
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);
208 if (!reply) {
209 SyTCPCloseSocket(&datafd);
210 SyMessage(pfn, SY_MSG_ERROR, "memory error");
211 return -1;
213 f = 1;
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';
217 if (size-f <= 10) {
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; }
225 strcat(reply, "\n");
227 /* count the number of probably legal matches: Files&Links only */
228 c = 0;
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 */
235 if (c != 1) {
236 free(reply);
237 if (c == 0) SyMessage(pfn, SY_MSG_ERROR, "file not found");
238 else SyMessage(pfn, SY_MSG_ERROR, "multiple matches for file");
239 return -1;
242 /* symlink handling */
243 s = strstr(reply, "\nl");
244 if (s) {
245 /* get the real filename */
246 fn = calloc(1, 1024); if (!fn) {
247 free(reply);
248 SyMessage(pfn, SY_MSG_ERROR, "memory error");
249 return -1;
251 sscanf(s, "%*s %*i %*s %*s %*i %*s %*i %*s %1023s", fn);
252 t = strstr(s, "->");
253 if (!t) {
254 free(fn);
255 free(reply);
256 SyMessage(pfn, SY_MSG_ERROR, "invalid LIST reply");
257 return -1;
259 /* get size of the file linked to */
260 strcpy(fn, t+3);
261 free(reply);
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);
265 free(fn);
266 return sz;
267 } else {
268 /* normal file, so read the size */
269 s = strstr(reply, "\n-");
270 if (!s) {
271 free(reply);
272 SyMessage(pfn, SY_MSG_ERROR, "invalid LIST reply");
273 return -1;
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);
277 if (f < 2) {
278 f = sscanf(s, "%*s %*i %lli %*i %*s %*i %*i %*s", &sz);
279 if (f < 2) {
280 free(reply);
281 SyMessage(pfn, SY_MSG_ERROR, "invalid LIST reply");
282 return -1;
285 sz = -1;
286 for (f = 2; f; f--) {
287 for (c = 2; c; c--) { while (*s && *s > ' ') s++; while (*s && *s <= ' ') s++; }
288 if (!s[0]) break;
289 t = s; while (isdigit(*t)) t++;
290 if (t > s && *t && *t <= ' ') {
291 *t = '\0'; sz = SyStr2Long(s);
292 break;
295 free(reply);
296 return sz;
301 #endif