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
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
) {
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
) {
68 si
->interval
= 30; /* seconds */
74 void SyUpdateStats (TSyState
*state
, TSyStats
*si
, int64_t bytesDone
, int64_t bytesTotal
) {
76 double ctime
, ptime
, t
;
77 double w0
, w1
; /* weights */
93 i1
->bytesDone
= bytesDone
;
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 */
108 i1
->bytesFrom
= bytesDone
;
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;
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
);
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
));
137 if (SyTCPInitSocket(&state
->fd
) != SY_OK
|| SyTCPInitSocket(&state
->datafd
) != SY_OK
) {
141 state
->maxBufferSize
= 1024*1024; /* 1mb */
142 state
->ioTimeout
= 60;
143 state
->maxRedirects
= 6;
144 state
->allowOnly2XX
= SY_TRUE
;
145 SyClearInternal(state
, 0);
151 void SyFree (TSyState
*state
) {
153 SyClear(state
); free(state
);
157 void SyClear (TSyState
*state
) {
159 SyClearInternal(state
, 0);
164 TSyResult
SyPrepare (TSyState
*state
, const char *urlstr
, const char *httpproxyurl
, const char *ftpproxyurl
,
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
;
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");
180 if (state
->url
->proto
== SY_PROTO_UNKNOWN
) {
181 SyClearInternal(state
, 1);
182 SyMessage(state
->pfn
, SY_MSG_ERROR
, "unknown protocol");
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");
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");
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");
208 state
->status
= SY_STATUS_PREPARED
;
213 TSyResult
SyEnd (TSyState
*state
) {
219 void SyReset (TSyState
*state
) {
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];
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
;
247 SyProxyFree(proxy
); proxy
= NULL
;
248 SyClearStr(&state
->httpFName
);
249 SyTCPCloseSocket(&state
->datafd
); SyTCPCloseSocket(&state
->fd
);
250 state
->status
= SY_STATUS_CONNECTING
;
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
;
261 else if (url
->proto
== SY_PROTO_HTTP
&& state
->httpproxy
) purl
= state
->httpproxy
;
264 state
->fd
.errCode
= 0;
265 if (url
->proto
== SY_PROTO_FTP
&& (!state
->ftpproxy
|| state
->ftpUseConnect
)) {
268 if (SyTCPConnect(&state
->fd
, purl
->host
, purl
->port
, state
->iface
, state
->ioTimeout
, state
->pfn
) != SY_OK
) break;
271 state
->status
= SY_STATUS_HANDSHAKING
;
272 if (state
->ftpUseConnect
&& state
->ftpproxy
) {
273 proxy
= SyProxyNew(state
->ftpproxy
, SY_PROXY_HTTP_CONNECT
, state
->userAgent
);
275 if (SyProxyConnect(&state
->fd
, proxy
, url
->host
, url
->port
, state
->pfn
) != SY_OK
) break;
278 if (SyFTPStart(&state
->fd
, url
->user
, url
->pass
, state
->pfn
) != SY_OK
) break;
280 if (SyFTPCwd(&state
->fd
, url
->dir
, state
->pfn
) != SY_OK
) break;
282 state
->fileSize
= SyFTPGetSize(&state
->fd
, state
->iface
, url
->file
, rdcnt
, state
->ioTimeout
, state
->pfn
, proxy
);
283 if (state
->firstByte
> state
->fileSize
) break;
285 if (state
->firstByte
> 0) {
287 if (SyLong2Str(tmp
, state
->firstByte
) != SY_OK
) break;
288 if (SyTCPSendLine(state
->pfn
, 1, &state
->fd
, "REST %s", tmp
) != SY_OK
) break;
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;
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;
301 if (SyFTPOpenDataPassv(&state
->fd
, &state
->datafd
, state
->iface
, state
->ioTimeout
, state
->pfn
, proxy
) != SY_OK
) break;
303 if (SyTCPSendLine(state
->pfn
, 1, &state
->fd
, "RETR %s", url
->file
) != SY_OK
) break;
305 if (SyFTPWaitFor2(&state
->fd
, 1, -1, state
->pfn
) <= 0) break;
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");
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");
325 if (state
->userAgent
&& state
->userAgent
[0]) {
326 s
= SySPrintf("User-Agent: %s", state
->userAgent
);
328 SyMessage(state
->pfn
, SY_MSG_ERROR
, "can't build request headers (out of memory)");
331 if (SyHdrAddLine(hdrs
, s
) != SY_OK
) {
333 SyMessage(state
->pfn
, SY_MSG_ERROR
, "can't build request headers");
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");
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
) {
349 SyMessage(state
->pfn
, SY_MSG_ERROR
, "can't build request headers");
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");
360 if (state
->fnprephdrs
&& state
->fnprephdrs(state
, hdrs
) != SY_OK
) break;
363 if (SyTCPConnect(&state
->fd
, purl
->host
, purl
->port
, state
->iface
, state
->ioTimeout
, state
->pfn
) != SY_OK
) break;
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
);
371 if (SyProxyConnect(&state
->fd
, proxy
, url
->host
, url
->port
, state
->pfn
) != SY_OK
) break;
374 if (url
->proto
== SY_PROTO_HTTPS
) {
375 if (SyTCPInitSSL(&state
->fd
, url
->host
, state
->ioTimeout
, state
->pfn
) != SY_OK
) break;
379 if (SyHTTPSendQuery(&state
->fd
, hdrs
, state
->pfn
) != SY_OK
) break;
381 /* send post data, if any */
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");
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;
393 code
= hdrs
->code
/100;
394 state
->replyCode
= hdrs
->code
;
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 '/'? */
406 if (!strstr(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; }
413 t = SySPrintf("/%s", s); SyStrFree(s);
414 if (!t) { SyMessage(state->pfn, SY_MSG_ERROR, "memory error"); break; }
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'; }
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");
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
;
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
;
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! */
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;
489 if ((s
= SyHdrGetFieldValue(hdrs
, "Content-Disposition")) != NULL
) {
490 /* extract file name, if any */
493 t
= strcasestr(s
, "filename");
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
++;
504 if (*t
!= '=') { s
= t
; continue; }
505 t
++; while (*t
&& *t
<= ' ') t
++;
511 while (*s
&& *s
!= '"') s
++;
514 while (*s
&& (*s
!= ' ' && *s
!= ';')) s
++;
516 // check for idiotic servers that doesn't know about quoting
518 while (*tx
&& *tx
!= ';') ++tx
;
523 state
->httpFName
= SyStrNew(t
, -1);
524 SyStrTrim(state
->httpFName
);
525 if (!state
->httpFName
[0]) SyClearStr(&state
->httpFName
);
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");
543 } else if (state
->fd
.errCode
) {
544 s
= SyTCPGetLastErrorMsg (&state
->fd
);
546 SyMessage(state
->pfn
, SY_MSG_ERROR
, "socket error: %s", 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
);
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
;
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
;
571 if (!state
->fnopen
) {
572 /* no 'open' function, assume simple check */
573 state
->status
= SY_STATUS_COMPLETE
;
577 bufSize
= state
->initBufferSize
; if (bufSize
< 1) bufSize
= 1;
578 buf
= malloc(bufSize
);
580 SyMessage(state
->pfn
, SY_MSG_ERROR
, "memory error");
581 state
->error
= SY_TRUE
;
585 if (state
->fnopen(state
) != SY_OK
) {
587 state
->error
= SY_TRUE
;
591 total
= state
->fileSize
;
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
;
606 while (state
->chunked
|| left
< 0 || left
> 0) {
609 /*fprintf(stderr, "left: %i\n", (int)left);*/
610 if (state
->chunked
&& chunkdone
) {
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);
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);
628 /* how many bytes we should receive? */
630 if (left
>= (int64_t)0) {
631 flg
= (int64_t)tord
> left
;
632 if (flg
) tord
= (int)left
;
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);
642 int xrd
= SyTCPReceiveEx(rsock
, bufPtr
, bufLeft
, SY_TCP_ALLOWPARTIAL
);
643 //fprintf(stderr, "...xrd=%d (bufleft=%d)\n", xrd, (int)bufLeft);
644 ctime
= SyGetTimeD();
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) {
651 SyMessage(state
->pfn
, SY_MSG_ERROR
, "internal error in chunk receiver!");
653 state
->breakNow
= SY_TRUE
;
657 bufPtr
= buf
; bufLeft
= tord
; rd
= 0;
659 int xrd
= SyTCPReceiveEx(rsock
, bufPtr
, bufLeft
, SY_TCP_ALLOWPARTIAL
);
660 ctime
= SyGetTimeD();
662 if (xrd
< 0) { rd
= xrd
; break; }
663 rd
+= xrd
; bufPtr
+= xrd
; bufLeft
-= xrd
;
664 } while (!bufLeft
|| ctime
-tm
>= 4.0);
667 /* write everything %-) */
668 if (state
->fnwrite
&& state
->fnwrite(state
, buf
, rd
) != SY_OK
) break;
672 if (state
->chunked
) chunkdone
= (left
== 0);
673 //fprintf(stderr, "...left=%d; chunkdone=%d\n", (int)left, (int)chunkdone);
675 if (state
->chunked
) chunkdone
= 1;
677 state
->currentByte
= done
;
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
) {
688 if (bufLeft
> state
->maxBufferSize
) bufLeft
= state
->maxBufferSize
;
689 bufPtr
= realloc(buf
, bufLeft
);
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) {
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
;
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 */
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
);
720 SyMessage(state
->pfn
, SY_MSG_ERROR
, "socket error: %s", s
);
722 } else SyMessage(state
->pfn
, SY_MSG_ERROR
, "socket error: %i", rsock
->errCode
);