syren understands chunked HTTP encoding now
[syren.git] / src / syren_dloader.c
blobab25249e6c497eb7fffc4293a1f295c031d67420
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 SyClearInternal(state, 0);
146 return state;
150 void SyFree (TSyState *state) {
151 if (!state) return;
152 SyClear(state); free(state);
156 void SyClear (TSyState *state) {
157 if (!state) return;
158 SyClearInternal(state, 0);
163 TSyResult SyPrepare (TSyState *state, const char *urlstr, const char *httpproxyurl, const char *ftpproxyurl,
164 const char *iface) {
165 if (!state) return SY_ERROR;
166 if (state->status != SY_STATUS_UNPREPARED) return SY_ERROR;
167 SyInitStats(state, &state->stats);
168 SyClearInternal(state, 1); /* keep sizes and positions */
169 state->url = SyURLNew(); if (!state->url) return SY_ERROR;
170 if (iface) {
171 state->iface = SyStrDup(iface);
172 if (!state->iface) return SY_ERROR;
173 SyStrTrim(state->iface);
175 if (SyURLParse(state->url, urlstr) != SY_OK) {
176 SyMessage(state->pfn, SY_MSG_ERROR, "invalid URL");
177 return SY_ERROR;
179 if (state->url->proto == SY_PROTO_UNKNOWN) {
180 SyClearInternal(state, 1);
181 SyMessage(state->pfn, SY_MSG_ERROR, "unknown protocol");
182 return SY_ERROR;
184 #ifndef SYOPT_ALLOW_HTTPS
185 if (state->url->proto == SY_PROTO_HTTPS) {
186 SyClearInternal(state, 1);
187 SyMessage(state->pfn, SY_MSG_ERROR, "HTTPS not supported");
188 return SY_ERROR;
190 #endif
191 if (httpproxyurl && *httpproxyurl) {
192 state->httpproxy = SyURLNew(); if (!state->httpproxy) return SY_ERROR;
193 if (SyURLParse(state->httpproxy, httpproxyurl) != SY_OK) {
194 SyClearInternal(state, 1);
195 SyMessage(state->pfn, SY_MSG_ERROR, "invalid HTTP proxy URL");
196 return SY_ERROR;
199 if (ftpproxyurl && *ftpproxyurl) {
200 state->ftpproxy = SyURLNew(); if (!state->ftpproxy) return SY_ERROR;
201 if (SyURLParse(state->ftpproxy, ftpproxyurl) != SY_OK) {
202 SyClearInternal(state, 1);
203 SyMessage(state->pfn, SY_MSG_ERROR, "invalid FTP proxy URL");
204 return SY_ERROR;
207 state->status = SY_STATUS_PREPARED;
208 return SY_OK;
212 TSyResult SyEnd (TSyState *state) {
213 SyClear(state);
214 return SY_OK;
218 void SyReset (TSyState *state) {
219 if (!state) return;
220 if (state->status == SY_STATUS_UNPREPARED) return;
221 SyTCPCloseSocket(&state->datafd); SyTCPCloseSocket(&state->fd);
222 state->status = SY_STATUS_PREPARED;
223 state->breakNow = state->interrupted = SY_FALSE;
228 #define SY_CHECK_BREAK0 if (state->breakNow != SY_FALSE) { state->interrupted = SY_TRUE; break; }
231 TSyResult SyBegin (TSyState *state) {
232 TSyResult res = SY_ERROR;
233 TSyProxy *proxy = NULL;
234 TSyHdrs *hdrs; TSyURL *url, *purl, *lurl;
235 char *s, *t, *buf, tmp[32];
236 int rdcnt, code;
238 if (!state || (state->status != SY_STATUS_PREPARED && state->status != SY_STATUS_DOWNLOADING)) return SY_ERROR;
239 rdcnt = state->maxRedirects;
240 state->breakNow = state->interrupted = state->error = SY_FALSE; state->currentByte = 0;
241 state->chunked = SY_FALSE;
242 if (state->firstByte < 0) state->firstByte = 0;
243 if (state->lastByte >= 0 && state->firstByte > state->lastByte) return SY_ERROR;
244 hdrs = SyHdrNew(); if (!hdrs) return SY_ERROR;
245 while (1) {
246 SyProxyFree(proxy); proxy = NULL;
247 SyClearStr(&state->httpFName);
248 SyTCPCloseSocket(&state->datafd); SyTCPCloseSocket(&state->fd);
249 state->status = SY_STATUS_CONNECTING;
250 SY_CHECK_BREAK0
251 url = state->url;
252 SyMessage(state->pfn, SY_MSG_MSG, "downloading: %s://%s:%i%s%s%s%s",
253 url->protostr, url->host, url->port, url->dir, url->file, url->query, url->anchor);
254 /* check for proxy */
255 if (url->proto == SY_PROTO_FTP && state->ftpproxy) purl = state->ftpproxy;
256 #ifdef SYOPT_ALLOW_HTTPS
257 else if (url->proto == SY_PROTO_HTTP && state->httpproxy) purl = state->httpproxy;
258 else if (url->proto == SY_PROTO_HTTPS && state->httpproxy) purl = state->httpproxy;
259 #else
260 else if (url->proto == SY_PROTO_HTTP && state->httpproxy) purl = state->httpproxy;
261 #endif
262 else purl = url;
263 state->fd.errCode = 0;
264 if (url->proto == SY_PROTO_FTP && (!state->ftpproxy || state->ftpUseConnect)) {
265 /* FTP */
266 /* connecting */
267 if (SyTCPConnect(&state->fd, purl->host, purl->port, state->iface, state->ioTimeout, state->pfn) != SY_OK) break;
268 SY_CHECK_BREAK0
269 /* handshaking */
270 state->status = SY_STATUS_HANDSHAKING;
271 if (state->ftpUseConnect && state->ftpproxy) {
272 proxy = SyProxyNew(state->ftpproxy, SY_PROXY_HTTP_CONNECT, state->userAgent);
273 if (!proxy) break;
274 if (SyProxyConnect(&state->fd, proxy, url->host, url->port, state->pfn) != SY_OK) break;
275 SY_CHECK_BREAK0
277 if (SyFTPStart(&state->fd, url->user, url->pass, state->pfn) != SY_OK) break;
278 SY_CHECK_BREAK0
279 if (SyFTPCwd(&state->fd, url->dir, state->pfn) != SY_OK) break;
280 SY_CHECK_BREAK0
281 state->fileSize = SyFTPGetSize(&state->fd, state->iface, url->file, rdcnt, state->ioTimeout, state->pfn, proxy);
282 if (state->firstByte > state->fileSize) break;
283 SY_CHECK_BREAK0
284 if (state->firstByte > 0) {
285 /* do REST */
286 if (SyLong2Str(tmp, state->firstByte) != SY_OK) break;
287 if (SyTCPSendLine(state->pfn, 1, &state->fd, "REST %s", tmp) != SY_OK) break;
288 SY_CHECK_BREAK0
289 /*if (SyFTPWaitFor2(&state->fd, 3, 2, state->pfn) <= 0) break;*/
290 if ((s = SyFTPWait(&code, &state->fd, state->pfn)) == NULL) break;
291 if (code <= 0) break;
292 code /= 100;
293 if (code != 3 && code != 2) {
294 SyMessage(state->pfn, SY_MSG_WARNING, "can't resume");
295 if (state->fnnoresume && state->fnnoresume(state) != SY_OK) break;
296 state->firstByte = 0;
298 SY_CHECK_BREAK0
300 if (SyFTPOpenDataPassv(&state->fd, &state->datafd, state->iface, state->ioTimeout, state->pfn, proxy) != SY_OK) break;
301 SY_CHECK_BREAK0
302 if (SyTCPSendLine(state->pfn, 1, &state->fd, "RETR %s", url->file) != SY_OK) break;
303 SY_CHECK_BREAK0
304 if (SyFTPWaitFor2(&state->fd, 1, -1, state->pfn) <= 0) break;
305 res = SY_OK; break;
306 } else {
307 /* HTTP/HTTPS */
308 int nodir;
309 int postLen = strlen(state->postData?state->postData:"");
310 #ifdef SYOPT_ALLOW_HTTPS
311 if (SyHTTPBuildQuery(hdrs, postLen?"POST":"GET", url,
312 (url->proto==SY_PROTO_FTP)?state->ftpproxy:
313 (url->proto==SY_PROTO_HTTP)?state->httpproxy:NULL) != SY_OK) {
314 SyMessage(state->pfn, SY_MSG_ERROR, "can't build request headers");
315 break;
317 #else
318 if (SyHTTPBuildQuery(hdrs, postLen?"POST":"GET", url,
319 (url->proto==SY_PROTO_FTP)?state->ftpproxy:state->httpproxy) != SY_OK) {
320 SyMessage(state->pfn, SY_MSG_ERROR, "can't build request headers");
321 break;
323 #endif
324 if (state->userAgent && state->userAgent[0]) {
325 s = SySPrintf("User-Agent: %s", state->userAgent);
326 if (!s) {
327 SyMessage(state->pfn, SY_MSG_ERROR, "can't build request headers (out of memory)");
328 break;
330 if (SyHdrAddLine(hdrs, s) != SY_OK) {
331 SyStrFree(s);
332 SyMessage(state->pfn, SY_MSG_ERROR, "can't build request headers");
333 break;
335 SyStrFree(s);
337 if (postLen) {
338 if (SyHdrAddLine(hdrs, "Content-Type: application/x-www-form-urlencoded") != SY_OK) {
339 SyMessage(state->pfn, SY_MSG_ERROR, "can't build request headers");
340 break;
342 s = SySPrintf("Content-Length: %i", postLen); /* FIXME: change when we'll be able to post files */
343 if (!s) { SyMessage(state->pfn, SY_MSG_ERROR, "memory error"); break; }
344 if (SyHdrAddLine(hdrs, s) != SY_OK) {
345 SyStrFree(s);
346 SyMessage(state->pfn, SY_MSG_ERROR, "can't build request headers");
347 break;
349 SyStrFree(s);
351 if (state->firstByte > 0) {
352 if (SyHTTPAddRange(hdrs, state->firstByte, -1) != SY_OK) {
353 SyMessage(state->pfn, SY_MSG_ERROR, "can't build request headers");
354 break;
357 if (state->fnprephdrs && state->fnprephdrs(state, hdrs) != SY_OK) break;
359 /* connecting */
360 if (SyTCPConnect(&state->fd, purl->host, purl->port, state->iface, state->ioTimeout, state->pfn) != SY_OK) break;
361 SY_CHECK_BREAK0
362 /* handshaking */
363 state->status = SY_STATUS_HANDSHAKING;
364 #ifdef SYOPT_ALLOW_HTTPS
365 if (url->proto == SY_PROTO_HTTPS && state->httpproxy) {
366 proxy = SyProxyNew(state->ftpproxy, SY_PROXY_HTTP_CONNECT, state->userAgent);
367 if (!proxy) break;
368 if (SyProxyConnect(&state->fd, proxy, url->host, url->port, state->pfn) != SY_OK) break;
369 SY_CHECK_BREAK0
371 if (url->proto == SY_PROTO_HTTPS) {
372 if (SyTCPInitSSL(&state->fd, state->ioTimeout) != SY_OK) break;
374 #endif
376 if (SyHTTPSendQuery(&state->fd, hdrs, state->pfn) != SY_OK) break;
377 SY_CHECK_BREAK0
378 /* send post data, if any */
379 if (postLen) {
380 SyMessage(state->pfn, SY_MSG_NOTICE, " sending %i bytes of POST data", postLen);
381 if (SyTCPSend(&state->fd, state->postData, postLen) != SY_OK) {
382 SyMessage(state->pfn, SY_MSG_ERROR, "can't send POST data");
383 break;
386 /* read reply headers */
387 if (SyHTTPReadHeaders(hdrs, &state->fd, state->pfn) != SY_OK) break;
388 if (state->fngothdrs && state->fngothdrs(state, hdrs) != SY_OK) break;
389 SY_CHECK_BREAK0
390 code = hdrs->code/100;
391 if (code == 3) {
392 /* redirect */
393 state->status = SY_STATUS_REDIRECTING;
394 SyTCPCloseSocket(&state->fd);
395 if (--rdcnt < 1) { SyMessage(state->pfn, SY_MSG_ERROR, "too many redirects"); break; }
396 s = SyHdrGetFieldValue(hdrs, "Location");
397 if (!s) { SyMessage(state->pfn, SY_MSG_ERROR, "redirect to nowhere"); break; }
398 if (!s[0]) { SyStrFree(s); SyMessage(state->pfn, SY_MSG_ERROR, "invalid redirect"); break; }
399 /* check for 'news.php' or 'news.php?dir=/abc' or 'news.php?n=1#/abc */
400 /*BUG: possible bug! why i'm prepending '/'? */
401 nodir = 0;
402 if (!strstr(s, "://")) {
403 if (*s != '/') {
404 /* seems to be a relative path */
405 char *x = SySPrintf("%s%s", url->dir, s); SyStrFree(s);
406 if (!x) { SyMessage(state->pfn, SY_MSG_ERROR, "memory error"); break; }
407 s = x; nodir = 0;
409 t = SySPrintf("/%s", s); SyStrFree(s);
410 if (!t) { SyMessage(state->pfn, SY_MSG_ERROR, "memory error"); break; }
411 s = t; nodir = 1;
415 /* check for spaces in url */
416 if (strncmp(s, "ftp://", 6) && strchr(s, ' ')) {
417 int cnt = 0; char *xp = s, *tp;
418 while (*xp) { if (*xp == ' ') cnt++; xp++; }
419 t = SyStrAlloc(strlen(s)+cnt*4); //actually *3
420 if (!t) { SyStrFree(s); SyMessage(state->pfn, SY_MSG_ERROR, "memory error"); break; }
421 for (xp = s, tp = t; *xp; xp++) {
422 if (*xp != ' ') *tp++ = *xp;
423 else { *tp++ = '%'; *tp++ = '2'; *tp++ = '0'; }
425 *tp = '\0';
426 SyStrFree(s); s = t;
428 lurl = SyURLNew(); if (!lurl) { SyStrFree(s); SyMessage(state->pfn, SY_MSG_ERROR, "memory error"); break; }
429 if (SyURLParse(lurl, s) != SY_OK) {
430 SyStrFree(s); SyClearURL(&lurl);
431 SyMessage(state->pfn, SY_MSG_ERROR, "invalid redirect");
432 break;
434 SyStrFree(s);
435 if (nodir) {
436 /* relative */
437 if (strcmp(lurl->dir, "/")) {
438 /* has other dir parts */
439 s = SySPrintf("%s%s", url->dir, (lurl->dir)+1);
440 if (!s) { SyClearURL(&lurl); SyMessage(state->pfn, SY_MSG_ERROR, "memory error"); break; }
441 SyClearStr(&lurl->dir); lurl->dir = s;
444 SyStrFree(url->dir); url->dir = lurl->dir;
445 if (lurl->host[0]) {
446 SyClearStr(&url->protostr); SyClearStr(&url->host);
447 SyClearStr(&url->user); SyClearStr(&url->pass);
448 url->port = lurl->port;
449 url->defaultPort = lurl->defaultPort;
450 url->proto = lurl->proto;
451 url->protostr = lurl->protostr;
452 url->host = lurl->host;
453 url->user = lurl->user;
454 url->pass = lurl->pass;
455 lurl->protostr = lurl->host = lurl->user = lurl->pass = NULL;
456 } else {
457 SyClearStr(&lurl->protostr); SyClearStr(&lurl->host);
458 SyClearStr(&lurl->user); SyClearStr(&lurl->pass);
460 SyClearStr(&url->file); url->file = lurl->file;
461 SyClearStr(&url->query); url->query = lurl->query;
462 SyClearStr(&url->anchor); url->anchor = lurl->anchor;
463 free(lurl); /* DO NOT CLEAR lurl! */
464 /* again */
465 } else {
466 /* no redirect */
467 if (code != 2) { SyMessage(state->pfn, SY_MSG_ERROR, "%s", hdrs->firstLine); break; }
468 if (state->firstByte && hdrs->code != 206) {
469 SyMessage(state->pfn, SY_MSG_WARNING, "can't resume");
470 if (state->fnnoresume && state->fnnoresume(state) != SY_OK) break;
471 state->firstByte = 0;
473 state->fileSize = SyHTTPGetSize(hdrs);
474 if (state->fileSize >= 0) {
475 state->fileSize += state->firstByte;
476 if (state->firstByte > state->fileSize) break;
478 if ((s = SyHdrGetFieldValue(hdrs, "Transfer-Encoding")) != NULL) {
479 if (strstr(s, "chunked")) {
480 state->chunked = SY_TRUE;
481 state->fileSize = -1;
483 free(s);
485 if ((s = SyHdrGetFieldValue(hdrs, "Content-Disposition")) != NULL) {
486 /* extract file name, if any */
487 buf = s;
488 while (*s) {
489 t = strcasestr(s, "filename");
490 if (!t) break;
491 if (t != buf && isalnum(*(t-1))) { s = t+1; continue; }
492 t += 8; if (*t && isalnum(*t)) { s = t; continue; }
493 while (*t && *t <= ' ') t++;
494 /* strange format: filename*=UTF-8''FurShaderExample.rar */
495 if (*t == '*' && t[1] == '=') {
496 t += 2; while (*t && *t != '\x27' && *t != ';') t++;
497 if (*t == ';') { s = t; continue; }
498 while (*t && *t == '\x27') t++;
499 } else {
500 if (*t != '=') { s = t; continue; }
501 t++; while (*t && *t <= ' ') t++;
503 if (!(*t)) break;
504 if (*t == '"') { t++; s = t; while (*s && *s != '"') s++; }
505 else { s = t; while (*s && (*s != ' ' && *s != ';')) s++; }
506 *s = '\0';
507 state->httpFName = SyStrNew(t, -1);
508 SyStrTrim(state->httpFName);
509 if (!state->httpFName[0]) SyClearStr(&state->httpFName);
510 break;
512 free(buf);
514 res = SY_OK; break;
518 SyHdrFree(hdrs);
519 SyProxyFree(proxy);
520 if (res == SY_OK) {
521 if (state->lastByte < 0 && state->fileSize >= 0) state->lastByte = state->fileSize;
522 if ((state->lastByte >= 0 && state->firstByte > state->lastByte) ||
523 (state->fileSize >= 0 && state->lastByte > state->fileSize)) {
524 SyMessage(state->pfn, SY_MSG_ERROR, "invalid file range");
525 res = SY_ERROR;
527 } else if (state->fd.errCode) {
528 s = SyTCPGetLastErrorMsg (&state->fd);
529 if (s) {
530 SyMessage(state->pfn, SY_MSG_ERROR, "socket error: %s", s);
531 SyStrFree(s);
532 } else SyMessage(state->pfn, SY_MSG_ERROR, "socket error: %i", state->fd.errCode);
534 if (res == SY_OK) state->status = SY_STATUS_CONNECTED;
535 SyInitStats(state, &state->stats);
536 return res;
540 TSyResult SyRun (TSyState *state) {
541 TSyResult res = SY_ERROR;
542 char *buf, *bufPtr; int bufSize, bufLeft;
543 int64_t total, done, left;
544 double lastprogtime, tm, ctime;
545 TSySocket *rsock;
547 if (!state || state->status != SY_STATUS_CONNECTED) return SY_ERROR;
548 state->currentByte = 0; state->error = SY_FALSE;
549 if (state->fileSize >= 0) {
550 if ((state->lastByte >= 0 && state->lastByte == state->firstByte) || state->firstByte == state->fileSize) {
551 state->status = SY_STATUS_COMPLETE;
552 return SY_OK;
555 if (!state->fnopen) {
556 /* no 'open' function, assume simple check */
557 state->status = SY_STATUS_COMPLETE;
558 return SY_OK;
560 /* prepare buffer */
561 bufSize = state->initBufferSize; if (bufSize < 1) bufSize = 1;
562 buf = malloc(bufSize);
563 if (!buf) {
564 SyMessage(state->pfn, SY_MSG_ERROR, "memory error");
565 state->error = SY_TRUE;
566 return SY_ERROR;
568 /* open file */
569 if (state->fnopen(state) != SY_OK) {
570 free(buf);
571 state->error = SY_TRUE;
572 return SY_ERROR;
574 /* init vars */
575 total = state->fileSize;
576 if (total > 0) {
577 total -= state->firstByte;
578 if (state->lastByte > 0) total -= (state->fileSize-state->lastByte);
580 left = total; done = 0;
581 SyInitStats(state, &state->stats);
582 SyUpdateStats(state, &state->stats, 0, total);
583 SyMessage(state->pfn, SY_MSG_MSG, "starting data transfer");
584 if (state->fnprog) state->fnprog(state, 0, total, SY_FALSE);
585 lastprogtime = SyGetTimeD();
586 rsock = (state->datafd.fd>=0)?&state->datafd:&state->fd;
587 /* start downloading */
588 state->status = SY_STATUS_DOWNLOADING;
589 while (state->chunked || left < 0 || left > 0) {
590 int tord, rd, flg;
591 SY_CHECK_BREAK0
592 /*fprintf(stderr, "left: %i\n", (int)left);*/
593 if (state->chunked) {
594 char cls[128];
595 if (left != -1) {
596 /* not the first chunk; should receive chunk end ("\r\n") */
597 if (SyTCPReceive(rsock, cls, 2) != 2) { res = SY_ERROR; break; }
598 if (cls[0] != '\r' || cls[1] != '\n') { res = SY_ERROR; break; }
600 /* chunked, get chunk length */
601 if (!SyTCPReceiveStrEx(rsock, 120, cls)) { res = SY_ERROR; break; }
602 /*fprintf(stderr, " [%s]\n", cls);*/
603 SyStrTrim(cls);
604 int64_t csz = SyHexStr2Long(cls);
605 if (csz < 1) { res = SY_ERROR; break; }
606 if (csz == 0) { res = SY_OK; left = 0; break; } // done
607 /*fprintf(stderr, "chunk size: %i\n", (int)csz);*/
608 left = csz;
610 /* how many bytes we should receive? */
611 tord = bufSize;
612 if (left >= (int64_t)0) {
613 flg = (int64_t)tord > left;
614 if (flg) tord = (int)left;
616 rsock->errCode = 0;
617 ctime = tm = SyGetTimeD();
618 #ifdef SYDL_USE_OLD_ALGO
619 /*rd = SyTCPReceiveEx(rsock, buf, tord, SY_TCP_DONT_ALLOWPARTIAL);
620 bufLeft = bufSize-(rd>0?rd:0);*/
621 bufPtr = buf; bufLeft = tord; rd = 0;
622 do {
623 int xrd = SyTCPReceiveEx(rsock, bufPtr, bufLeft, SY_TCP_ALLOWPARTIAL);
624 ctime = SyGetTimeD();
625 if (!xrd) break;
626 if (xrd < 0) { rd = xrd; break; }
627 rd += xrd; bufPtr += xrd; bufLeft -= xrd;
628 } while (!bufLeft || rd >= bufSize/3);
629 #else
630 bufPtr = buf; bufLeft = tord; rd = 0;
631 do {
632 int xrd = SyTCPReceiveEx(rsock, bufPtr, bufLeft, SY_TCP_ALLOWPARTIAL);
633 ctime = SyGetTimeD();
634 if (!xrd) break;
635 if (xrd < 0) { rd = xrd; break; }
636 rd += xrd; bufPtr += xrd; bufLeft -= xrd;
637 } while (!bufLeft || ctime-tm >= 4.0);
638 #endif
639 if (rd > 0) {
640 /* write everything %-) */
641 if (state->fnwrite && state->fnwrite(state, buf, rd) != SY_OK) break;
642 if (left > 0) left -= rd; done += rd;
643 state->currentByte = done;
644 /* update stats */
645 SyUpdateStats(state, &state->stats, done, total);
646 /* and draw progress */
647 if (tm-lastprogtime >= 1.0) {
648 if (state->fnprog) state->fnprog(state, done, total, SY_FALSE);
649 lastprogtime = ctime;
651 /* grow buffer if necessary */
652 if (!bufLeft && ctime-tm < 2.0 && bufSize < state->maxBufferSize) {
653 bufLeft = bufSize*2;
654 if (bufLeft > state->maxBufferSize) bufLeft = state->maxBufferSize;
655 bufPtr = realloc(buf, bufLeft);
656 if (bufPtr) {
657 /*fprintf(stderr, "\nbuffer grows from %i to %i\n", bufSize, bufLeft);*/
658 bufSize = bufLeft; buf = bufPtr;
661 /* shrink buffer if necessary */
662 /*if (bufLeft && ctime-tm > 2.0 && bufSize > 8192) {
663 bufLeft = bufSize/2;
667 if (rd <= 0 || rsock->errCode) {
668 /* read error or connection closed */
669 if (rd >= 0 && left < 0) res = SY_OK; /* size unknown? all done */
670 else if (state->breakNow == SY_TRUE) state->interrupted = SY_TRUE;
671 break;
674 /* check if downloading ok */
675 if (!left) res = SY_OK;
676 state->error = (res==SY_OK)?SY_FALSE:SY_TRUE;
677 SyUpdateStats(state, &state->stats, done, total);
678 if (state->fnprog) state->fnprog(state, done, total, SY_TRUE);
679 if (state->fnclose && state->fnclose(state) != SY_OK) res = SY_ERROR; /* don't set state->error here! */
680 /* free memory, close sockets */
681 if (buf) free(buf);
682 SyTCPCloseSocket(&state->datafd); SyTCPCloseSocket(&state->fd);
683 if (res == SY_OK) state->status = SY_STATUS_COMPLETE;
684 if (rsock->errCode && (left > 0 || res != SY_OK)) {
685 char *s = SyTCPGetLastErrorMsg(rsock);
686 if (s) {
687 SyMessage(state->pfn, SY_MSG_ERROR, "socket error: %s", s);
688 SyStrFree(s);
689 } else SyMessage(state->pfn, SY_MSG_ERROR, "socket error: %i", rsock->errCode);
691 return res;
695 #endif