fixed bug in chunked reader
[syren.git] / src / sylib / syren_dloader.c
blobadf0da2269ac8e5deed9490ab3d49496de0e1c03
1 /*
2 Syren -- a lightweight downloader for Linux/BSD/MacOSX
3 inspired by Axel Copyright 2001-2002 Wilmer van der Gaast
4 version 0.0.6 (atomic alien)
5 coded by Ketmar // Vampire Avalon
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 3 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 download driver
25 #ifndef _SYREN_DLOADER_C
26 #define _SYREN_DLOADER_C
29 #include "syren_dloader.h"
32 /* old algo seems to be faster on fast networks */
33 #define SYDL_USE_OLD_ALGO
36 static void SyClearURL (TSyURL **url) {
37 if (!url || !(*url)) return;
38 SyURLClear(*url); free(*url); *url = NULL;
42 static void SyClearStr (char **s) {
43 if (!s || !(*s)) return;
44 SyStrFree(*s); *s = NULL;
48 static void SyClearInternal (TSyState *state, int keepSizesAndPData) {
49 if (!state) return;
50 SyTCPCloseSocket(&state->datafd); SyTCPCloseSocket(&state->fd);
51 SyClearURL(&state->url); SyClearURL(&state->httpproxy); SyClearURL(&state->ftpproxy);
52 SyClearStr(&state->iface); SyClearStr(&state->httpFName);
53 SyClearStr(&state->userAgent);
54 state->status = SY_STATUS_UNPREPARED;
55 state->breakNow = state->interrupted = SY_FALSE;
56 if (!keepSizesAndPData) {
57 SyClearStr(&state->postData);
58 state->fileSize = state->lastByte = -1;
59 state->firstByte = state->currentByte = 0;
65 void SyInitStats (TSyState *state, TSyStats *si) {
66 si->i[0].active = 0;
67 si->i[1].active = 0;
68 si->interval = 30; /* seconds */
69 si->bps = 0.0;
70 si->eta = 0.0;
74 void SyUpdateStats (TSyState *state, TSyStats *si, int64_t bytesDone, int64_t bytesTotal) {
75 TSyStatInfo *i0, *i1;
76 double ctime, ptime, t;
77 double w0, w1; /* weights */
78 double bps0, bps1;
80 i0 = &(si->i[0]);
81 i1 = &(si->i[1]);
83 ctime = SyGetTimeD();
85 if (!i1->active) {
86 /* nothing started */
87 i0->active = 0;
88 i0->bytesFrom = 0;
89 i0->bytesDone = 0;
90 i0->time = 0.0;
91 i1->active = 1;
92 i1->bytesFrom = 0;
93 i1->bytesDone = bytesDone;
94 i1->time = ctime;
95 si->sttime = ctime;
96 si->bps = 0.0;
97 si->eta = 0.0;
98 return;
101 /* update "bytes done" */
102 i1->bytesDone = bytesDone-(i1->bytesFrom);
104 ptime = ctime-(i1->time);
105 if (ptime >= si->interval) {
106 /* interval passed, move state */
107 *i0 = *i1;
108 i1->bytesFrom = bytesDone;
109 i1->bytesDone = 0;
110 i1->time = ctime;
111 i0->time = ptime; /* i0->time: time, taken by this stat */
114 /* calculate weights */
115 t = ctime-(i1->time);
116 w1 = i0->active?t/si->interval:1.0;
117 if (w1 > 1.0) w1 = 1.0;
118 if (t < 0.5) w1 = 0.0;
119 w0 = 1.0-w1;
121 /* calculate bps */
122 bps0 = i0->active?(double)(i0->bytesDone)/i0->time:0.0;
123 bps1 = t>=0.5?(double)(i1->bytesDone)/t:0.0;
124 si->bps = (bps0*w0)+(bps1*w1);
126 /* calculate eta */
127 if (bytesTotal > 0 && si->bps >= 1) {
128 si->eta = (double)(bytesTotal-bytesDone)/(si->bps);
129 } else si->eta = 0.0;
134 TSyState *SyNew (void) {
135 TSyState *state = calloc(1, sizeof(TSyState));
136 if (state) {
137 if (SyTCPInitSocket(&state->fd) != SY_OK || SyTCPInitSocket(&state->datafd) != SY_OK) {
138 free(state);
139 return NULL;
141 state->maxBufferSize = 1024*1024; /* 1mb */
142 state->ioTimeout = 60;
143 state->maxRedirects = 6;
144 state->allowOnly2XX = SY_TRUE;
145 SyClearInternal(state, 0);
147 return state;
151 void SyFree (TSyState *state) {
152 if (!state) return;
153 SyClear(state); free(state);
157 void SyClear (TSyState *state) {
158 if (!state) return;
159 SyClearInternal(state, 0);
164 TSyResult SyPrepare (TSyState *state, const char *urlstr, const char *httpproxyurl, const char *ftpproxyurl,
165 const char *iface) {
166 if (!state) return SY_ERROR;
167 if (state->status != SY_STATUS_UNPREPARED) return SY_ERROR;
168 SyInitStats(state, &state->stats);
169 SyClearInternal(state, 1); /* keep sizes and positions */
170 state->url = SyURLNew(); if (!state->url) return SY_ERROR;
171 if (iface) {
172 state->iface = SyStrDup(iface);
173 if (!state->iface) return SY_ERROR;
174 SyStrTrim(state->iface);
176 if (SyURLParse(state->url, urlstr) != SY_OK) {
177 SyMessage(state->pfn, SY_MSG_ERROR, "invalid URL");
178 return SY_ERROR;
180 if (state->url->proto == SY_PROTO_UNKNOWN) {
181 SyClearInternal(state, 1);
182 SyMessage(state->pfn, SY_MSG_ERROR, "unknown protocol");
183 return SY_ERROR;
185 #ifndef SYOPT_ALLOW_HTTPS
186 if (state->url->proto == SY_PROTO_HTTPS) {
187 SyClearInternal(state, 1);
188 SyMessage(state->pfn, SY_MSG_ERROR, "HTTPS not supported");
189 return SY_ERROR;
191 #endif
192 if (httpproxyurl && *httpproxyurl) {
193 state->httpproxy = SyURLNew(); if (!state->httpproxy) return SY_ERROR;
194 if (SyURLParse(state->httpproxy, httpproxyurl) != SY_OK) {
195 SyClearInternal(state, 1);
196 SyMessage(state->pfn, SY_MSG_ERROR, "invalid HTTP proxy URL");
197 return SY_ERROR;
200 if (ftpproxyurl && *ftpproxyurl) {
201 state->ftpproxy = SyURLNew(); if (!state->ftpproxy) return SY_ERROR;
202 if (SyURLParse(state->ftpproxy, ftpproxyurl) != SY_OK) {
203 SyClearInternal(state, 1);
204 SyMessage(state->pfn, SY_MSG_ERROR, "invalid FTP proxy URL");
205 return SY_ERROR;
208 state->status = SY_STATUS_PREPARED;
209 return SY_OK;
213 TSyResult SyEnd (TSyState *state) {
214 SyClear(state);
215 return SY_OK;
219 void SyReset (TSyState *state) {
220 if (!state) return;
221 if (state->status == SY_STATUS_UNPREPARED) return;
222 SyTCPCloseSocket(&state->datafd); SyTCPCloseSocket(&state->fd);
223 state->status = SY_STATUS_PREPARED;
224 state->breakNow = state->interrupted = SY_FALSE;
229 #define SY_CHECK_BREAK0 if (state->breakNow != SY_FALSE) { state->interrupted = SY_TRUE; break; }
232 TSyResult SyBegin (TSyState *state) {
233 TSyResult res = SY_ERROR;
234 TSyProxy *proxy = NULL;
235 TSyHdrs *hdrs; TSyURL *url, *purl, *lurl;
236 char *s, *t, *buf, tmp[32];
237 int rdcnt, code;
239 if (!state || (state->status != SY_STATUS_PREPARED && state->status != SY_STATUS_DOWNLOADING)) return SY_ERROR;
240 rdcnt = state->maxRedirects;
241 state->breakNow = state->interrupted = state->error = SY_FALSE; state->currentByte = 0;
242 state->chunked = SY_FALSE;
243 if (state->firstByte < 0) state->firstByte = 0;
244 if (state->lastByte >= 0 && state->firstByte > state->lastByte) return SY_ERROR;
245 hdrs = SyHdrNew(); if (!hdrs) return SY_ERROR;
246 while (1) {
247 SyProxyFree(proxy); proxy = NULL;
248 SyClearStr(&state->httpFName);
249 SyTCPCloseSocket(&state->datafd); SyTCPCloseSocket(&state->fd);
250 state->status = SY_STATUS_CONNECTING;
251 SY_CHECK_BREAK0
252 url = state->url;
253 SyMessage(state->pfn, SY_MSG_MSG, "downloading: %s://%s:%i%s%s%s%s",
254 url->protostr, url->host, url->port, url->dir, url->file, url->query, url->anchor);
255 /* check for proxy */
256 if (url->proto == SY_PROTO_FTP && state->ftpproxy) purl = state->ftpproxy;
257 #ifdef SYOPT_ALLOW_HTTPS
258 else if (url->proto == SY_PROTO_HTTP && state->httpproxy) purl = state->httpproxy;
259 else if (url->proto == SY_PROTO_HTTPS && state->httpproxy) purl = state->httpproxy;
260 #else
261 else if (url->proto == SY_PROTO_HTTP && state->httpproxy) purl = state->httpproxy;
262 #endif
263 else purl = url;
264 state->fd.errCode = 0;
265 if (url->proto == SY_PROTO_FTP && (!state->ftpproxy || state->ftpUseConnect)) {
266 /* FTP */
267 /* connecting */
268 if (SyTCPConnect(&state->fd, purl->host, purl->port, state->iface, state->ioTimeout, state->pfn) != SY_OK) break;
269 SY_CHECK_BREAK0
270 /* handshaking */
271 state->status = SY_STATUS_HANDSHAKING;
272 if (state->ftpUseConnect && state->ftpproxy) {
273 proxy = SyProxyNew(state->ftpproxy, SY_PROXY_HTTP_CONNECT, state->userAgent);
274 if (!proxy) break;
275 if (SyProxyConnect(&state->fd, proxy, url->host, url->port, state->pfn) != SY_OK) break;
276 SY_CHECK_BREAK0
278 if (SyFTPStart(&state->fd, url->user, url->pass, state->pfn) != SY_OK) break;
279 SY_CHECK_BREAK0
280 if (SyFTPCwd(&state->fd, url->dir, state->pfn) != SY_OK) break;
281 SY_CHECK_BREAK0
282 state->fileSize = SyFTPGetSize(&state->fd, state->iface, url->file, rdcnt, state->ioTimeout, state->pfn, proxy);
283 if (state->firstByte > state->fileSize) break;
284 SY_CHECK_BREAK0
285 if (state->firstByte > 0) {
286 /* do REST */
287 if (SyLong2Str(tmp, state->firstByte) != SY_OK) break;
288 if (SyTCPSendLine(state->pfn, 1, &state->fd, "REST %s", tmp) != SY_OK) break;
289 SY_CHECK_BREAK0
290 /*if (SyFTPWaitFor2(&state->fd, 3, 2, state->pfn) <= 0) break;*/
291 if ((s = SyFTPWait(&code, &state->fd, state->pfn)) == NULL) break;
292 if (code <= 0) break;
293 code /= 100;
294 if (code != 3 && code != 2) {
295 SyMessage(state->pfn, SY_MSG_WARNING, "can't resume");
296 if (state->fnnoresume && state->fnnoresume(state) != SY_OK) break;
297 state->firstByte = 0;
299 SY_CHECK_BREAK0
301 if (SyFTPOpenDataPassv(&state->fd, &state->datafd, state->iface, state->ioTimeout, state->pfn, proxy) != SY_OK) break;
302 SY_CHECK_BREAK0
303 if (SyTCPSendLine(state->pfn, 1, &state->fd, "RETR %s", url->file) != SY_OK) break;
304 SY_CHECK_BREAK0
305 if (SyFTPWaitFor2(&state->fd, 1, -1, state->pfn) <= 0) break;
306 res = SY_OK; break;
307 } else {
308 /* HTTP/HTTPS */
309 int nodir;
310 int postLen = strlen(state->postData?state->postData:"");
311 #ifdef SYOPT_ALLOW_HTTPS
312 if (SyHTTPBuildQuery(hdrs, postLen?"POST":"GET", url,
313 (url->proto==SY_PROTO_FTP)?state->ftpproxy:
314 (url->proto==SY_PROTO_HTTP)?state->httpproxy:NULL) != SY_OK) {
315 SyMessage(state->pfn, SY_MSG_ERROR, "can't build request headers");
316 break;
318 #else
319 if (SyHTTPBuildQuery(hdrs, postLen?"POST":"GET", url,
320 (url->proto==SY_PROTO_FTP)?state->ftpproxy:state->httpproxy) != SY_OK) {
321 SyMessage(state->pfn, SY_MSG_ERROR, "can't build request headers");
322 break;
324 #endif
325 if (state->userAgent && state->userAgent[0]) {
326 s = SySPrintf("User-Agent: %s", state->userAgent);
327 if (!s) {
328 SyMessage(state->pfn, SY_MSG_ERROR, "can't build request headers (out of memory)");
329 break;
331 if (SyHdrAddLine(hdrs, s) != SY_OK) {
332 SyStrFree(s);
333 SyMessage(state->pfn, SY_MSG_ERROR, "can't build request headers");
334 break;
336 SyStrFree(s);
338 if (postLen) {
339 if (!SyHdrHasField(hdrs, "Content-Type")) {
340 if (SyHdrAddLine(hdrs, "Content-Type: application/x-www-form-urlencoded") != SY_OK) {
341 SyMessage(state->pfn, SY_MSG_ERROR, "can't build request headers");
342 break;
345 s = SySPrintf("Content-Length: %i", postLen); /* FIXME: change when we'll be able to post files */
346 if (!s) { SyMessage(state->pfn, SY_MSG_ERROR, "memory error"); break; }
347 if (SyHdrAddLine(hdrs, s) != SY_OK) {
348 SyStrFree(s);
349 SyMessage(state->pfn, SY_MSG_ERROR, "can't build request headers");
350 break;
352 SyStrFree(s);
354 if (state->firstByte > 0) {
355 if (SyHTTPAddRange(hdrs, state->firstByte, -1) != SY_OK) {
356 SyMessage(state->pfn, SY_MSG_ERROR, "can't build request headers");
357 break;
360 if (state->fnprephdrs && state->fnprephdrs(state, hdrs) != SY_OK) break;
362 /* connecting */
363 if (SyTCPConnect(&state->fd, purl->host, purl->port, state->iface, state->ioTimeout, state->pfn) != SY_OK) break;
364 SY_CHECK_BREAK0
365 /* handshaking */
366 state->status = SY_STATUS_HANDSHAKING;
367 #ifdef SYOPT_ALLOW_HTTPS
368 if (url->proto == SY_PROTO_HTTPS && state->httpproxy) {
369 proxy = SyProxyNew(state->ftpproxy, SY_PROXY_HTTP_CONNECT, state->userAgent);
370 if (!proxy) break;
371 if (SyProxyConnect(&state->fd, proxy, url->host, url->port, state->pfn) != SY_OK) break;
372 SY_CHECK_BREAK0
374 if (url->proto == SY_PROTO_HTTPS) {
375 if (SyTCPInitSSL(&state->fd, url->host, state->ioTimeout, state->pfn) != SY_OK) break;
377 #endif
379 if (SyHTTPSendQuery(&state->fd, hdrs, state->pfn) != SY_OK) break;
380 SY_CHECK_BREAK0
381 /* send post data, if any */
382 if (postLen) {
383 SyMessage(state->pfn, SY_MSG_NOTICE, " sending %i bytes of POST data", postLen);
384 if (SyTCPSend(&state->fd, state->postData, postLen) != SY_OK) {
385 SyMessage(state->pfn, SY_MSG_ERROR, "can't send POST data");
386 break;
389 /* read reply headers */
390 if (SyHTTPReadHeaders(hdrs, &state->fd, state->pfn) != SY_OK) break;
391 if (state->fngothdrs && state->fngothdrs(state, hdrs) != SY_OK) break;
392 SY_CHECK_BREAK0
393 code = hdrs->code/100;
394 state->replyCode = hdrs->code;
395 if (code == 3) {
396 /* redirect */
397 state->status = SY_STATUS_REDIRECTING;
398 SyTCPCloseSocket(&state->fd);
399 if (--rdcnt < 1) { SyMessage(state->pfn, SY_MSG_ERROR, "too many redirects"); break; }
400 s = SyHdrGetFieldValue(hdrs, "Location");
401 if (!s) { SyMessage(state->pfn, SY_MSG_ERROR, "redirect to nowhere"); break; }
402 if (!s[0]) { SyStrFree(s); SyMessage(state->pfn, SY_MSG_ERROR, "invalid redirect"); break; }
403 /* check for 'news.php' or 'news.php?dir=/abc' or 'news.php?n=1#/abc */
404 /*BUG: possible bug! why i'm prepending '/'? */
405 nodir = 0;
406 if (!strstr(s, "://")) {
407 if (*s != '/') {
408 /* seems to be a relative path */
409 char *x = SySPrintf("%s%s", url->dir, s); SyStrFree(s);
410 if (!x) { SyMessage(state->pfn, SY_MSG_ERROR, "memory error"); break; }
411 s = x; nodir = 0;
413 t = SySPrintf("/%s", s); SyStrFree(s);
414 if (!t) { SyMessage(state->pfn, SY_MSG_ERROR, "memory error"); break; }
415 s = t; nodir = 1;
419 /* check for spaces in url */
420 if (strncmp(s, "ftp://", 6) && strchr(s, ' ')) {
421 int cnt = 0; char *xp = s, *tp;
422 while (*xp) { if (*xp == ' ') cnt++; xp++; }
423 t = SyStrAlloc(strlen(s)+cnt*4); //actually *3
424 if (!t) { SyStrFree(s); SyMessage(state->pfn, SY_MSG_ERROR, "memory error"); break; }
425 for (xp = s, tp = t; *xp; xp++) {
426 if (*xp != ' ') *tp++ = *xp;
427 else { *tp++ = '%'; *tp++ = '2'; *tp++ = '0'; }
429 *tp = '\0';
430 SyStrFree(s); s = t;
432 lurl = SyURLNew(); if (!lurl) { SyStrFree(s); SyMessage(state->pfn, SY_MSG_ERROR, "memory error"); break; }
433 if (SyURLParse(lurl, s) != SY_OK) {
434 SyStrFree(s); SyClearURL(&lurl);
435 SyMessage(state->pfn, SY_MSG_ERROR, "invalid redirect");
436 break;
438 SyStrFree(s);
439 if (nodir) {
440 /* relative */
441 if (strcmp(lurl->dir, "/")) {
442 /* has other dir parts */
443 s = SySPrintf("%s%s", url->dir, (lurl->dir)+1);
444 if (!s) { SyClearURL(&lurl); SyMessage(state->pfn, SY_MSG_ERROR, "memory error"); break; }
445 SyClearStr(&lurl->dir); lurl->dir = s;
448 SyStrFree(url->dir); url->dir = lurl->dir;
449 if (lurl->host[0]) {
450 SyClearStr(&url->protostr); SyClearStr(&url->host);
451 SyClearStr(&url->user); SyClearStr(&url->pass);
452 url->port = lurl->port;
453 url->defaultPort = lurl->defaultPort;
454 url->proto = lurl->proto;
455 url->protostr = lurl->protostr;
456 url->host = lurl->host;
457 url->user = lurl->user;
458 url->pass = lurl->pass;
459 lurl->protostr = lurl->host = lurl->user = lurl->pass = NULL;
460 } else {
461 SyClearStr(&lurl->protostr); SyClearStr(&lurl->host);
462 SyClearStr(&lurl->user); SyClearStr(&lurl->pass);
464 SyClearStr(&url->file); url->file = lurl->file;
465 SyClearStr(&url->query); url->query = lurl->query;
466 SyClearStr(&url->anchor); url->anchor = lurl->anchor;
467 free(lurl); /* DO NOT CLEAR lurl! */
468 /* again */
469 } else {
470 /* no redirect */
471 if (code != 2 && state->allowOnly2XX != SY_FALSE) { SyMessage(state->pfn, SY_MSG_ERROR, "%s", hdrs->firstLine); break; }
472 if (state->firstByte && hdrs->code != 206) {
473 SyMessage(state->pfn, SY_MSG_WARNING, "can't resume");
474 if (state->fnnoresume && state->fnnoresume(state) != SY_OK) break;
475 state->firstByte = 0;
477 state->fileSize = SyHTTPGetSize(hdrs);
478 if (state->fileSize >= 0) {
479 state->fileSize += state->firstByte;
480 if (state->firstByte > state->fileSize) break;
482 if ((s = SyHdrGetFieldValue(hdrs, "Transfer-Encoding")) != NULL) {
483 if (strstr(s, "chunked")) {
484 state->chunked = SY_TRUE;
485 state->fileSize = -1;
487 free(s);
489 if ((s = SyHdrGetFieldValue(hdrs, "Content-Disposition")) != NULL) {
490 /* extract file name, if any */
491 buf = s;
492 while (*s) {
493 t = strcasestr(s, "filename");
494 if (!t) break;
495 if (t != buf && isalnum(*(t-1))) { s = t+1; continue; }
496 t += 8; if (*t && isalnum(*t)) { s = t; continue; }
497 while (*t && *t <= ' ') t++;
498 /* strange format: filename*=UTF-8''FurShaderExample.rar */
499 if (*t == '*' && t[1] == '=') {
500 t += 2; while (*t && *t != '\x27' && *t != ';') t++;
501 if (*t == ';') { s = t; continue; }
502 while (*t && *t == '\x27') t++;
503 } else {
504 if (*t != '=') { s = t; continue; }
505 t++; while (*t && *t <= ' ') t++;
507 if (!(*t)) break;
508 if (*t == '"') {
509 t++;
510 s = t;
511 while (*s && *s != '"') s++;
512 } else {
513 s = t;
514 while (*s && (*s != ' ' && *s != ';')) s++;
515 if (*s == ' ') {
516 // check for idiotic servers that doesn't know about quoting
517 char *tx = s;
518 while (*tx && *tx != ';') ++tx;
519 if (!tx[0]) s = tx;
522 *s = '\0';
523 state->httpFName = SyStrNew(t, -1);
524 SyStrTrim(state->httpFName);
525 if (!state->httpFName[0]) SyClearStr(&state->httpFName);
526 break;
528 free(buf);
530 res = SY_OK; break;
534 SyHdrFree(hdrs);
535 SyProxyFree(proxy);
536 if (res == SY_OK) {
537 if (state->lastByte < 0 && state->fileSize >= 0) state->lastByte = state->fileSize;
538 if ((state->lastByte >= 0 && state->firstByte > state->lastByte) ||
539 (state->fileSize >= 0 && state->lastByte > state->fileSize)) {
540 SyMessage(state->pfn, SY_MSG_ERROR, "invalid file range");
541 res = SY_ERROR;
543 } else if (state->fd.errCode) {
544 s = SyTCPGetLastErrorMsg (&state->fd);
545 if (s) {
546 SyMessage(state->pfn, SY_MSG_ERROR, "socket error: %s", s);
547 SyStrFree(s);
548 } else SyMessage(state->pfn, SY_MSG_ERROR, "socket error: %i", state->fd.errCode);
550 if (res == SY_OK) state->status = SY_STATUS_CONNECTED;
551 SyInitStats(state, &state->stats);
552 return res;
556 TSyResult SyRun (TSyState *state) {
557 TSyResult res = SY_ERROR;
558 char *buf, *bufPtr; int bufSize, bufLeft;
559 int64_t total, done, left;
560 double lastprogtime, tm, ctime;
561 TSySocket *rsock;
563 if (!state || state->status != SY_STATUS_CONNECTED) return SY_ERROR;
564 state->currentByte = 0; state->error = SY_FALSE;
565 if (state->fileSize >= 0) {
566 if ((state->lastByte >= 0 && state->lastByte == state->firstByte) || state->firstByte == state->fileSize) {
567 state->status = SY_STATUS_COMPLETE;
568 return SY_OK;
571 if (!state->fnopen) {
572 /* no 'open' function, assume simple check */
573 state->status = SY_STATUS_COMPLETE;
574 return SY_OK;
576 /* prepare buffer */
577 bufSize = state->initBufferSize; if (bufSize < 1) bufSize = 1;
578 buf = malloc(bufSize);
579 if (!buf) {
580 SyMessage(state->pfn, SY_MSG_ERROR, "memory error");
581 state->error = SY_TRUE;
582 return SY_ERROR;
584 /* open file */
585 if (state->fnopen(state) != SY_OK) {
586 free(buf);
587 state->error = SY_TRUE;
588 return SY_ERROR;
590 /* init vars */
591 total = state->fileSize;
592 if (total > 0) {
593 total -= state->firstByte;
594 if (state->lastByte > 0) total -= (state->fileSize-state->lastByte);
596 left = total; done = 0;
597 SyInitStats(state, &state->stats);
598 SyUpdateStats(state, &state->stats, 0, total);
599 SyMessage(state->pfn, SY_MSG_MSG, "starting data transfer");
600 if (state->fnprog) state->fnprog(state, 0, total, SY_FALSE);
601 lastprogtime = SyGetTimeD();
602 rsock = (state->datafd.fd>=0)?&state->datafd:&state->fd;
603 /* start downloading */
604 state->status = SY_STATUS_DOWNLOADING;
605 int chunkdone = 1;
606 while (state->chunked || left < 0 || left > 0) {
607 int tord, rd, flg;
608 SY_CHECK_BREAK0
609 /*fprintf(stderr, "left: %i\n", (int)left);*/
610 if (state->chunked && chunkdone) {
611 char cls[128];
612 if (left != -1) {
613 /* not the first chunk; should receive chunk end ("\r\n") */
614 if (SyTCPReceive(rsock, cls, 2) != 2) { res = SY_ERROR; break; }
615 if (cls[0] != '\r' || cls[1] != '\n') { res = SY_ERROR; break; }
617 /* chunked, get chunk length */
618 if (!SyTCPReceiveStrEx(rsock, 120, cls)) { res = SY_ERROR; break; }
619 //fprintf(stderr, " [%s]\n", cls);
620 SyStrTrim(cls);
621 int64_t csz = SyHexStr2Long(cls);
622 if (csz < 1) { res = SY_ERROR; break; }
623 if (csz == 0) { res = SY_OK; left = 0; break; } // done
624 //fprintf(stderr, "chunk size: %d\n", (int)csz);
625 left = csz;
626 chunkdone = 0;
628 /* how many bytes we should receive? */
629 tord = bufSize;
630 if (left >= (int64_t)0) {
631 flg = (int64_t)tord > left;
632 if (flg) tord = (int)left;
634 rsock->errCode = 0;
635 ctime = tm = SyGetTimeD();
636 #ifdef SYDL_USE_OLD_ALGO
637 /*rd = SyTCPReceiveEx(rsock, buf, tord, SY_TCP_DONT_ALLOWPARTIAL);
638 bufLeft = bufSize-(rd>0?rd:0);*/
639 bufPtr = buf; bufLeft = tord; rd = 0;
640 //fprintf(stderr, "TOREAD: %d (left=%d)\n", (int)bufLeft, (int)left);
641 do {
642 int xrd = SyTCPReceiveEx(rsock, bufPtr, bufLeft, SY_TCP_ALLOWPARTIAL);
643 //fprintf(stderr, "...xrd=%d (bufleft=%d)\n", xrd, (int)bufLeft);
644 ctime = SyGetTimeD();
645 if (!xrd) break;
646 if (xrd < 0) { rd = xrd; break; }
647 rd += xrd; bufPtr += xrd; bufLeft -= xrd;
648 } while (!bufLeft || rd >= bufSize/3);
649 if (state->chunked && rd >= 0) {
650 if (rd > left) {
651 SyMessage(state->pfn, SY_MSG_ERROR, "internal error in chunk receiver!");
652 rd = -1;
653 state->breakNow = SY_TRUE;
656 #else
657 bufPtr = buf; bufLeft = tord; rd = 0;
658 do {
659 int xrd = SyTCPReceiveEx(rsock, bufPtr, bufLeft, SY_TCP_ALLOWPARTIAL);
660 ctime = SyGetTimeD();
661 if (!xrd) break;
662 if (xrd < 0) { rd = xrd; break; }
663 rd += xrd; bufPtr += xrd; bufLeft -= xrd;
664 } while (!bufLeft || ctime-tm >= 4.0);
665 #endif
666 if (rd > 0) {
667 /* write everything %-) */
668 if (state->fnwrite && state->fnwrite(state, buf, rd) != SY_OK) break;
669 if (left > 0) {
670 left -= rd;
671 done += rd;
672 if (state->chunked) chunkdone = (left == 0);
673 //fprintf(stderr, "...left=%d; chunkdone=%d\n", (int)left, (int)chunkdone);
674 } else {
675 if (state->chunked) chunkdone = 1;
677 state->currentByte = done;
678 /* update stats */
679 SyUpdateStats(state, &state->stats, done, total);
680 /* and draw progress */
681 if (tm-lastprogtime >= 1.0) {
682 if (state->fnprog) state->fnprog(state, done, total, SY_FALSE);
683 lastprogtime = ctime;
685 /* grow buffer if necessary */
686 if (!bufLeft && ctime-tm < 2.0 && bufSize < state->maxBufferSize) {
687 bufLeft = bufSize*2;
688 if (bufLeft > state->maxBufferSize) bufLeft = state->maxBufferSize;
689 bufPtr = realloc(buf, bufLeft);
690 if (bufPtr) {
691 /*fprintf(stderr, "\nbuffer grows from %i to %i\n", bufSize, bufLeft);*/
692 bufSize = bufLeft; buf = bufPtr;
695 /* shrink buffer if necessary */
696 /*if (bufLeft && ctime-tm > 2.0 && bufSize > 8192) {
697 bufLeft = bufSize/2;
700 if (rd <= 0 || rsock->errCode) {
701 /* read error or connection closed */
702 if (rd >= 0 && left < 0) res = SY_OK; /* size unknown? all done */
703 else if (state->breakNow == SY_TRUE) state->interrupted = SY_TRUE;
704 break;
707 /* check if downloading ok */
708 if (!left) res = SY_OK;
709 state->error = (res==SY_OK)?SY_FALSE:SY_TRUE;
710 SyUpdateStats(state, &state->stats, done, total);
711 if (state->fnprog) state->fnprog(state, done, total, SY_TRUE);
712 if (state->fnclose && state->fnclose(state) != SY_OK) res = SY_ERROR; /* don't set state->error here! */
713 /* free memory, close sockets */
714 if (buf) free(buf);
715 SyTCPCloseSocket(&state->datafd); SyTCPCloseSocket(&state->fd);
716 if (res == SY_OK) state->status = SY_STATUS_COMPLETE;
717 if (rsock->errCode && (left > 0 || res != SY_OK)) {
718 char *s = SyTCPGetLastErrorMsg(rsock);
719 if (s) {
720 SyMessage(state->pfn, SY_MSG_ERROR, "socket error: %s", s);
721 SyStrFree(s);
722 } else SyMessage(state->pfn, SY_MSG_ERROR, "socket error: %i", rsock->errCode);
724 return res;
728 #endif