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 // 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 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 #include "syren_common.h"
27 #include "syren_str.h"
28 #include "syren_msg.h"
29 #include "syren_cfg.h"
30 #include "syren_hdrs.h"
31 #include "syren_tcp.h"
32 #include "syren_http.h"
33 #include "syren_proxy.h"
34 #include "syren_ftp.h"
35 #include "syren_dloader.h"
37 #include "syren_script.h"
42 /* uncomment this to use Linux TTY ioctls */
49 /* uncomment this to use ANSI/VT100 TTY codes */
52 /* uncomment this to use "DEC private" TTY codes (ESC [ ? n)*/
58 cfgNoOutput
= SY_FALSE
,
60 cfgNoIndicator
= SY_FALSE
,
61 cfgUseProxyHTTP
= SY_TRUE
,
62 cfgUseProxyFTP
= SY_TRUE
,
63 cfgDecodeFTPURL
= SY_TRUE
,
64 cfgFTPUseConnect
= SY_FALSE
,
65 cfgProgressWriteCR
= SY_FALSE
;
68 cfgMaxBufferSize
= 1024*1024,
70 cfgReconnectDelay
= 10,
71 cfgReconnectAttempts
= 666,
76 *cfgDefaultName
= NULL
,
80 *cfgRefererStr
= NULL
,
84 *oOutFileName
= NULL
, /* -o/-O */
85 *oStateFileName
= NULL
, /* -s */
90 *oAddonHeaders
= NULL
;
94 oCanRemoveState
= SY_TRUE
,
95 oIgnoreStatePos
= SY_FALSE
,
96 oNoStateFile
= SY_FALSE
,
97 oForceRetry
= SY_FALSE
,
100 replyOnly
= SY_FALSE
,
101 oPreallocSpace
= SY_TRUE
;
102 int uEncodeDecode
= 0;
103 TSyBool doRXVT
= SY_FALSE
;
106 TSyKVList
*oURLList
= NULL
;
108 double lastSSave
= 0;
110 TSyState
*state
= NULL
; /* dl state */
111 #ifdef SYREN_USE_SCRIPT
112 TSyBool scAbort
= SY_FALSE
;
116 static int GetTTYWidth (void) {
118 if (!isatty(fileno(stderr
))) return -1;
119 if (ioctl(fileno(stderr
), TIOCGWINSZ
, &ws
)) return -1;
125 static void PrintProgress (TSyState
*state
, int64_t bytesDone
, int64_t bytesTotal
, TSyBool done
) {
126 char tmp0
[128], tmp1
[128], tmp2
[128], tmp3
[128], tmp4
[128], fmt
[128], str
[1024];
127 int len
; int64_t fb
= state
->firstByte
;
128 TSyStats
*stats
= &state
->stats
;
130 if (cfgNoIndicator
) return;
131 fprintf(stderr
, "\r");
133 fputs("\x1b[?7l", stderr
);
135 if (bytesTotal
> 0) {
136 len
= SyLong2StrComma(tmp1
, (bytesTotal
+fb
));
137 SyLong2StrComma(tmp0
, (bytesDone
+fb
));
138 sprintf(fmt
, "[%%%is/%%s] ", len
);
139 sprintf(tmp2
, fmt
, tmp0
, tmp1
); /*!*/
140 sprintf(tmp3
, "[%3i%%] ", (int)((double)100*(bytesDone
+fb
)/(bytesTotal
+fb
))); /*!*/
142 SySize2Str(tmp0
, (int64_t)stats
->bps
);
143 sprintf(tmp4
, "[SPD:%s] ", tmp0
); /*!*/
144 SyTime2Str(tmp0
, (int)(SyGetTimeD()-stats
->sttime
));
145 SyTime2Str(tmp1
, (int)stats
->eta
);
146 sprintf(fmt
, "TIME:%s ETA:%s", tmp0
, tmp1
); /*!*/
147 fprintf(stderr
, "%s%s%s%s", tmp2
, tmp3
, tmp4
, fmt
);
148 sprintf(str
, "%s%s%s/%s", tmp2
, tmp3
, tmp0
, tmp1
);
150 SyLong2StrComma(tmp0
, bytesDone
+fb
);
151 SyTime2Str(tmp1
, (int)(SyGetTimeD()-stats
->sttime
));
152 SySize2Str(fmt
, (int64_t)stats
->bps
);
153 sprintf(str
, "[%s] [SPD:%s] TIME:%s", tmp0
, fmt
, tmp1
);
154 fprintf(stderr
, "%s", str
);
156 if (isatty(fileno(stdout
)) && doRXVT
== SY_TRUE
) {
157 fprintf(stdout
, "\x1b]2;%s\7", str
);
161 fprintf(stderr
, "\x1b[K");
164 fputs("\x1b[?7h", stderr
);
166 if (done
== SY_TRUE
) fputs("\n", stderr
);
167 else if (cfgProgressWriteCR
== SY_TRUE
) fprintf(stderr
, "\n");
172 static void SyPrintStr (void *udata
, TSyMsgType msgtype
, const char *msg
) {
176 static const char *ltr
= "NMWE?";
177 if (cfgNoOutput
) return;
178 if (cfgQuiet
&& msgtype
< SY_MSG_MSG
) return;
182 fputs("\x1b[?7l", stderr
);
186 if (wdt
< 0) wdt
= strlen(msg
)+16;
189 if (msgtype
< SY_MSG_NOTICE
|| msgtype
> SY_MSG_ERROR
) msgtype
= SY_MSG_ERROR
+1;
190 fputc(ltr
[msgtype
], stderr
);
195 if (wdt
>= strlen(msg
)) fputs(msg
, stderr
);
196 else while (wdt
--) fputc(*(msg
++), stderr
);
203 fputs("\x1b[K", stderr
);
207 fputs("\x1b[?7h", stderr
);
212 if (isatty(fileno(stdout
)) && doRXVT
== SY_TRUE
&& msgtype
> SY_MSG_NOTICE
) {
213 fprintf(stdout
, "\x1b]2;%s\7", msg
);
219 static void SyDefaultCfg (TSyCfg
*cfg
) {
220 SyCfgAddIntKey(cfg
, "reconnect_delay", "number of seconds to wait before reconnecting to server", &cfgReconnectDelay
);
221 SyCfgAddIntKey(cfg
, "reconnect_attempts", "number of reconnection attempts (0: do not reconnect)", &cfgReconnectAttempts
);
222 SyCfgAddIntKey(cfg
, "io_timeout", "i/o timeout in seconds", &cfgTimeout
);
223 SyCfgAddIntKey(cfg
, "save_state_interval", "save state every x seconds (0: disable)", &cfgSaveInterval
);
224 cfgDefaultName
= SyStrNew("index.html", -1);
225 SyCfgAddStrKey(cfg
, "default_file_name", "default file name for http", &cfgDefaultName
);
226 SyCfgAddIntKey(cfg
, "max_redirects", "maximum number of redirects/symlinks", &cfgMaxRedirects
);
227 SyCfgAddIntKey(cfg
, "init_buffer_size", "initial buffer size", &cfgBufferSize
);
228 SyCfgAddIntKey(cfg
, "max_buffer_size", "maximum amount of bytes read buffer can grow to", &cfgMaxBufferSize
);
229 SyCfgAddBoolKey(cfg
, "quiet", "tan: do not show notices", &cfgQuiet
);
230 SyCfgAddBoolKey(cfg
, "no_output", "tan: disable any output", &cfgNoOutput
);
231 SyCfgAddBoolKey(cfg
, "no_indicator", "tan: hide download indicator", &cfgNoIndicator
);
233 cfgIFace
= SyStrNew("", -1);
234 SyCfgAddStrKey(cfg
, "interface", "net interface to use for connection (name or IP)", &cfgIFace
);
236 cfgProxyHTTP
= SyStrNew(getenv("http_proxy"), -1);
237 SyCfgAddStrKey(cfg
, "http_proxy", "proxy for http connections", &cfgProxyHTTP
);
238 cfgProxyFTP
= SyStrNew(getenv("ftp_proxy"), -1);
239 SyCfgAddStrKey(cfg
, "ftp_proxy", "proxy for ftp connections", &cfgProxyFTP
);
240 SyCfgAddBoolKey(cfg
, "use_http_proxy", "use http proxy (if specified)?", &cfgUseProxyHTTP
);
241 SyCfgAddBoolKey(cfg
, "use_ftp_proxy", "use ftp proxy (if specified)?", &cfgUseProxyFTP
);
242 SyCfgAddBoolKey(cfg
, "ftp_proxy_use_connect", "use CONNECT method for proxied ftp connections", &cfgFTPUseConnect
);
244 SyCfgAddIntKey(cfg
, "referer_type", "set referer type (0:none; 1:orig URL; 2:follow URL; 3:user)", &cfgRefererType
);
245 cfgRefererStr
= SyStrNew(NULL
, -1);
246 SyCfgAddStrKey(cfg
, "referer_str", "referer for mode 3", &cfgRefererStr
);
247 SyCfgAddBoolKey(cfg
, "decode_ftp_url", "decode URLs for FTP", &cfgDecodeFTPURL
);
249 cfgUAStr
= SyStrNew(SYREN_DEFAULT_USER_AGENT
, -1);
250 SyCfgAddStrKey(cfg
, "ua_str", "user agent (set to empty string if no UA should be sent)", &cfgUAStr
);
255 TSyResult
SySaveInt (int fd
, int64_t v
, int size
) {
258 if (fd
< 0 || size
< 1 || size
> 8) return SY_ERROR
;
260 b
= (v
>>(size
*8))&0xff;
261 if (SyWriteFile(fd
, &b
, 1) != SY_OK
) return SY_ERROR
;
268 TSyResult
SyLoadInt (int fd
, int64_t *v
, int size
) {
269 uint8_t b
; int64_t res
= 0;
272 if (!fd
|| size
< 1 || size
> 8) return SY_ERROR
;
274 if (SyReadFile(fd
, &b
, 1) != SY_OK
) return SY_ERROR
;
283 TSyResult
SySaveStr (int fd
, const char *v
) {
286 if (!fd
) return SY_ERROR
;
287 if (v
) len
= strlen(v
);
288 if (len
> 65535) return SY_ERROR
;
289 if (SySaveInt(fd
, len
, 2) != SY_OK
) return SY_ERROR
;
290 if (len
) { if (SyWriteFile(fd
, v
, len
) != SY_OK
) return SY_ERROR
; }
296 char *SyLoadStr (int fd
) {
300 if (!fd
) return NULL
;
301 if (SyLoadInt(fd
, &len
, 2) != SY_OK
) return NULL
;
302 if (!len
) return SyStrNewEmpty();
303 v
= calloc(1, len
+8); if (!v
) return NULL
;
304 if (SyReadFile(fd
, v
, len
) != SY_OK
) { free(v
); return NULL
; }
310 static const char *SF_STR
= "SYREN STATE FILE v6\r\n\x1a";
311 TSyResult
SySaveState (const char *destfile
, TSyState
*state
) {
312 TSyResult res
= SY_ERROR
;
317 if (!state
) return SY_ERROR
;
319 s
= SySPrintf("%s://%s:%s@%s:%i%s%s%s%s",
320 url
->protostr
, url
->user
, url
->pass
, url
->host
, url
->port
,
321 url
->dir
, url
->file
, url
->query
, url
->anchor
);
322 if (!s
) return SY_ERROR
;
323 fd
= SyOpenFile(destfile
, SY_FMODE_WRITE
, SY_TRUE
);
326 /*if (SySaveStr(fd, "SYREN STATE FILE v5") != SY_OK) break;*/
327 if (SyWriteFile(fd
, SF_STR
, strlen(SF_STR
)) != SY_OK
) break;
328 if (SySaveInt(fd
, state
->fileSize
, 8) != SY_OK
) break;
329 if (SySaveInt(fd
, state
->currentByte
+state
->firstByte
, 8) != SY_OK
) break;
330 if (SySaveInt(fd
, state
->lastByte
, 8) != SY_OK
) break;
331 if (SySaveStr(fd
, s
) != SY_OK
) break;
332 t
= strrchr(oOutFileName
, '/'); if (!t
) t
= oOutFileName
; else t
++;
333 if (SySaveStr(fd
, t
) != SY_OK
) break;
334 if (SySaveInt(fd
, cfgRefererType
, 1) != SY_OK
) break;
335 if (SySaveStr(fd
, cfgRefererStr
) != SY_OK
) break;
336 if (SySaveStr(fd
, oPostData
) != SY_OK
) break;
341 if (res
!= SY_OK
) SyDeleteFile(destfile
);
348 char *SyLoadStateV5 (int fd
, TSyState
*state
) {
349 char *destfile
= NULL
, *s
= NULL
;
353 s
= SyLoadStr(fd
); if (!s
) break;
354 if (strcmp(s
, "SYREN STATE FILE v5")) break;
355 if (SyLoadInt(fd
, &state
->fileSize
, 8) != SY_OK
) break;
356 if (SyLoadInt(fd
, &state
->firstByte
, 8) != SY_OK
) break;
357 if (oIgnoreStatePos
!= SY_FALSE
) state
->firstByte
= 0;
358 if (state
->firstByte
< 0 || (state
->fileSize
>= 0 && state
->firstByte
> state
->fileSize
)) break;
359 if (SyLoadInt(fd
, &state
->lastByte
, 8) != SY_OK
) break;
360 if (state
->lastByte
< -1 || (state
->lastByte
>= 0 && state
->firstByte
> state
->lastByte
)) break;
361 free(s
); s
= SyLoadStr(fd
); if (!s
) break;
362 if (oURL
) free(oURL
); oURL
= s
;
363 s
= SyLoadStr(fd
); if (!s
) break;
364 destfile
= s
; s
= NULL
;
365 if (SyLoadInt(fd
, &i
, 1) != SY_OK
) break;
366 cfgRefererType
= (int)i
;
367 s
= SyLoadStr(fd
); if (!s
) break;
368 if (cfgRefererStr
) free(cfgRefererStr
);
370 s
= SyLoadStr(fd
); if (!s
) { free(destfile
); destfile
= NULL
; break; }
371 if (state
->postData
) free(state
->postData
);
372 state
->postData
= s
; s
= NULL
;
381 char *SyLoadStateV6 (int fd
, TSyState
*state
) {
382 char *destfile
= NULL
, *s
= NULL
, buf
[128];
384 int l
= strlen(SF_STR
);
387 memset(buf
, 0, sizeof(buf
));
388 if (SyReadFile(fd
, buf
, l
) != SY_OK
) break;
389 if (strcmp(buf
, SF_STR
)) break;
390 if (SyLoadInt(fd
, &state
->fileSize
, 8) != SY_OK
) break;
391 if (SyLoadInt(fd
, &state
->firstByte
, 8) != SY_OK
) break;
392 if (oIgnoreStatePos
!= SY_FALSE
) state
->firstByte
= 0;
393 if (state
->firstByte
< 0 || (state
->fileSize
>= 0 && state
->firstByte
> state
->fileSize
)) break;
394 if (SyLoadInt(fd
, &state
->lastByte
, 8) != SY_OK
) break;
395 if (state
->lastByte
< -1 || (state
->lastByte
>= 0 && state
->firstByte
> state
->lastByte
)) break;
396 free(s
); s
= SyLoadStr(fd
); if (!s
) break;
397 if (oURL
) free(oURL
); oURL
= s
;
398 s
= SyLoadStr(fd
); if (!s
) break;
399 destfile
= s
; s
= NULL
;
400 if (SyLoadInt(fd
, &i
, 1) != SY_OK
) break;
401 cfgRefererType
= (int)i
;
402 s
= SyLoadStr(fd
); if (!s
) break;
403 if (cfgRefererStr
) free(cfgRefererStr
);
405 s
= SyLoadStr(fd
); if (!s
) { free(destfile
); destfile
= NULL
; break; }
406 if (state
->postData
) free(state
->postData
);
407 state
->postData
= s
; s
= NULL
;
416 /* return destfile */
417 char *SyLoadState (const char *statefile
, TSyState
*state
) {
418 char *destfile
= NULL
;
422 fd
= SyOpenFile(statefile
, SY_FMODE_READ
, SY_FALSE
);
423 if (fd
< 0) return NULL
;
424 if (SyReadFile(fd
, &b
, 1) == SY_OK
) {
425 if (SySeekFile(fd
, 0) == SY_OK
) {
426 /*fprintf(stderr, "state v%i\n", b?6:5);*/
427 if (!b
) destfile
= SyLoadStateV5(fd
, state
);
428 else destfile
= SyLoadStateV6(fd
, state
);
437 void DumpState (TSyState
*state
) {
438 char t0
[32], t1
[32], t2
[32];
439 SyLong2StrComma(t0
, state
->fileSize
);
440 SyLong2StrComma(t1
, state
->firstByte
);
441 SyLong2StrComma(t2
, state
->lastByte
);
442 printf("state dump\n==========\nURL: %s\nfile name: %s\n"
443 "file size: %s\nfirst byte: %s\nlast byte: %s\n",
444 oURL
, oOutFileName
, t0
, t1
, t2
449 static void ShowHelp (void) {
450 fprintf(stderr
, "%s",
451 "usage: syren [options] url [+url] [+url] [@urllistfile]\n"
454 " -h all show 'long' configuration options\n"
455 " -h <longopt> show 'long' configuration option description\n"
456 " -o filename output to specified file\n" \
457 " -O filename output to specified file (same as '-o')\n" \
458 " -r filename resume download (do not use saved state, use file)\n" \
459 " -s filename restore state\n"
460 " -S filename redownload file using state (ignore starting position, recreate)\n"
461 " -D filename dump state file and exit\n"
462 " -k keep state file\n"
463 " -P <str|@fn> make POST request\n"
464 " -c <str|@fn> send cookies\n"
465 " -C filename dump cookies\n"
466 " -H <str|@fn> additional headers\n"
467 " -i download server reply and stop\n"
468 " -I name_or_ip use specified net interface (\"-\" any)\n"
469 " -X do not use proxies\n"
470 " -A don't preallocate disk space\n"
471 " -T turn off terminal window title changing\n"
472 " -Z write CR after progress string\n"
473 " -w generate .syrenrc file with the current config\n"
474 " -N don't write state file\n"
475 " -F force retry on any download error\n"
479 " -Q be VERY quiet\n"
485 static char *GetStrOpt (TSyPrintStr
*prs
, int argc
, char *argv
[], int *f
, const char *optName
,
486 char **value
, TSyBool allowEmpty
, TSyBool exclusive
) {
488 if (*f
>= argc
|| !argv
[*f
]) { SyMessage(prs
, SY_MSG_ERROR
, "no value for '%s'", optName
); return NULL
; }
489 if (exclusive
== SY_TRUE
&& *value
) { SyMessage(prs
, SY_MSG_ERROR
, "duplicate '%s'", optName
); return NULL
; }
490 res
= SyStrNew(argv
[*f
], -1); (*f
)++;
491 if (!res
) { SyMessage(prs
, SY_MSG_ERROR
, "memory error"); return NULL
; }
492 if (allowEmpty
!= SY_TRUE
&& !res
[0]) {
494 SyMessage(prs
, SY_MSG_ERROR
, "empty value for '%s'", optName
);
497 if (*value
) free(*value
);
504 static TSyResult
GetStrOptWithAt (TSyPrintStr
*prs
, int argc
, char *argv
[], int *f
, const char *optName
,
505 char **value
, TSyBool allowEmpty
, TSyBool exclusive
) {
506 char *res
= GetStrOpt(prs
, argc
, argv
, f
, optName
, value
, allowEmpty
, exclusive
);
507 if (!res
) return SY_ERROR
;
509 res
= SyLoadWholeFile(res
+1);
510 if (!res
) { SyMessage(prs
, SY_MSG_ERROR
, "can't load data from '%s'", (*value
)+1); return SY_ERROR
; }
511 free(*value
); *value
= res
;
518 static void SyAddURLFromFile (const char *fname
) {
521 fl
= SyLoadWholeFile(fname
);
524 e
= s
; while (*e
&& *e
!= '\n') e
++;
528 SyKVListDelete(oURLList
, s
);
529 SyKVListSet(oURLList
, s
, "", NULL
);
537 static TSyResult
ParseCmdLine (TSyPrintStr
*prs
, TSyCfg
*cfg
, int argc
, char *argv
[]) {
538 int f
, nomoreopt
= 0;
539 char *s
, *t
, *sopt
, soptN
[2];
542 f
= 1; while (f
< argc
) {
544 if (!s
) break; if (!(*s
)) continue;
545 if (!nomoreopt
&& *s
== '-') {
547 /* long option (config) */
548 if (!s
[3]) nomoreopt
= 1;
551 if (SyCfgSet(cfg
, s
) != SY_OK
) { SyMessage(prs
, SY_MSG_ERROR
, "invalid option: %s", (s
-2)); return SY_ERROR
; }
556 soptN
[0] = *sopt
; soptN
[1] = '\0';
559 printf("%s", SYREN_VDHEADER_STRING
);
561 case 'e': uEncodeDecode
= 1; break;
562 case 'E': uEncodeDecode
= -1; break;
563 case 'w': writeCfg
= SY_TRUE
; break;
564 case 'X': cfgUseProxyHTTP
= cfgUseProxyFTP
= SY_FALSE
; break;
565 case 'T': doRXVT
= SY_FALSE
; break;
566 case 'q': cfgQuiet
= SY_TRUE
; break;
567 case 'Q': cfgQuiet
= cfgNoOutput
= SY_TRUE
; break;
568 case 'i': replyOnly
= SY_TRUE
; break;
569 case 'N': oNoStateFile
= SY_TRUE
; break;
570 case 'F': oForceRetry
= SY_TRUE
; break;
571 case 'k': oCanRemoveState
= SY_FALSE
; break;
572 case 'Z': cfgProgressWriteCR
= SY_TRUE
; break;
573 case 'A': oPreallocSpace
= SY_FALSE
; break;
576 SyMessage(prs
, SY_MSG_ERROR
, "no argument for -I");
580 cfgIFace
= SyStrDup(argv
[f
++]);
581 /*SyStrTrim(cfgIFace);*/
582 /*if (!cfgIFace[0] || !strcmp(cfgIFace, "-")) { SyStrFree(cfgIFace); cfgIFace = NULL; }*/
584 case 'o': case 'O': case 'r':
585 if (*sopt
== 'r') oResume
= SY_TRUE
;
586 s
= GetStrOpt(prs
, argc
, argv
, &f
, soptN
, &oOutFileName
, SY_FALSE
, SY_TRUE
);
587 if (!s
) return SY_ERROR
;
588 oCanRename
= SY_FALSE
;
590 case 's': case 'S': case 'D':
591 if (*sopt
== 'S') oIgnoreStatePos
= SY_TRUE
;
592 else if (*sopt
== 'D') dumpState
= SY_TRUE
;
593 s
= GetStrOpt(prs
, argc
, argv
, &f
, soptN
, &oStateFileName
, SY_FALSE
, SY_TRUE
);
594 if (!s
) return SY_ERROR
;
595 oCanRename
= SY_FALSE
;
598 if (GetStrOptWithAt(prs
, argc
, argv
, &f
, soptN
, &oPostData
, SY_FALSE
, SY_TRUE
) != SY_OK
) return SY_ERROR
;
601 if (GetStrOptWithAt(prs
, argc
, argv
, &f
, soptN
, &oSendCookies
, SY_FALSE
, SY_TRUE
) != SY_OK
) return SY_ERROR
;
604 if (!GetStrOpt(prs
, argc
, argv
, &f
, soptN
, &oCookieDump
, SY_FALSE
, SY_TRUE
)) return SY_ERROR
;
607 if (GetStrOptWithAt(prs
, argc
, argv
, &f
, soptN
, &oAddonHeaders
, SY_FALSE
, SY_TRUE
)) return SY_ERROR
;
610 fprintf(stderr
, "%s\n", SYREN_VERSION_DATETIME_STRING
);
611 key
= cfg
->opts
->first
;
612 if (f
< argc
&& !strcmp(argv
[f
], "all")) {
613 fprintf(stderr
, "long options:\n");
614 while (key
) { fprintf(stderr
, " --%s\n", key
->key
); key
= key
->next
; }
616 if (f
< argc
&& argv
[f
]) {
618 if (!strcasecmp(key
->key
, argv
[f
])) {
619 fprintf(stderr
, "--%s %s\n %s\n", key
->key
,
620 key
->uidata
==SY_CI_STRING
?"string":(key
->uidata
==SY_CI_INT
?"integer":(key
->uidata
==SY_CI_BOOL
?"boolean":"?")),
627 if (!key
) ShowHelp();
631 SyMessage(prs
, SY_MSG_ERROR
, "unknown option: %s", soptN
);
639 if (!t
) { SyMessage(prs
, SY_MSG_ERROR
, "memory error"); return SY_ERROR
; }
641 if (*t
== '@') SyAddURLFromFile(t
+1);
643 if (t
[0] == '+') { strcpy(t
, &(t
[1])); SyStrTrim(t
); }
645 SyKVListDelete(oURLList
, t
);
646 SyKVListSet(oURLList
, t
, "", NULL
);
652 if (oResume
&& oStateFileName
) { SyMessage(prs
, SY_MSG_ERROR
, "'-r'/'-s' conflict"); return SY_ERROR
; }
658 #ifdef SYREN_USE_SCRIPT
659 TSyHdrs
*scHdrs
= NULL
;
662 TSyResult
SyDrvAddHeaders (TSyState
*state
, TSyHdrs
*hdrs
) {
663 char *s
= NULL
, *t
, *cs
;
664 TSyURL
*url
= state
->url
;
667 if (cfgRefererType
) {
668 switch (cfgRefererType
) {
669 case 1: s
= SyStrNew(oURL
, -1); break;
671 if (url
->proto
== SY_PROTO_FTP
&& url
->port
!= 80) sprintf(port
, ":%i", url
->port
); else port
[0] = '\0';
672 s
= SySPrintf("%s://%s%s%s%s%s%s", url
->protostr
, url
->host
, port
,
673 url
->dir
, url
->file
, url
->query
, url
->anchor
);
675 default: s
= SyStrNew(cfgRefererStr
, -1); break;
677 if (!s
) { SyMessage(state
->pfn
, SY_MSG_ERROR
, "memory error"); return SY_ERROR
; }
678 t
= SySPrintf("Referer: %s", s
); free(s
);
679 if (!t
) { SyMessage(state
->pfn
, SY_MSG_ERROR
, "memory error"); return SY_ERROR
; }
680 if (SyHdrAddLine(hdrs
, t
) != SY_OK
) { free(t
); SyMessage(state
->pfn
, SY_MSG_ERROR
, "hdr error"); return SY_ERROR
; }
683 if (oAddonHeaders
&& *oAddonHeaders
) {
686 t
= s
; while (*t
&& *t
!= '\n') t
++;
687 cs
= SyStrNew(s
, t
-s
); if (!cs
) return SY_ERROR
;
690 if (SyHdrAddLine(hdrs
, cs
) != SY_OK
) { free(cs
); SyMessage(state
->pfn
, SY_MSG_ERROR
, "hdr error"); return SY_ERROR
; }
692 free(cs
); if (!s
[0]) break;
693 s
= t
; if (*s
) s
++; /* don't skip latest 0 */
696 if (oSendCookies
&& *oSendCookies
) {
699 t
= s
; while (*t
&& *t
!= '\n') t
++;
700 cs
= SyStrNew(s
, t
-s
); if (!cs
) return SY_ERROR
;
703 if (SyHdrSetCookie(hdrs
, cs
) != SY_OK
) { free(cs
); return SY_ERROR
; }
705 free(cs
); if (!s
[0]) break;
706 s
= t
; if (*s
) s
++; /* don't skip latest 0 */
709 #ifdef SYREN_USE_SCRIPT
711 SyScriptCallback("Event.AddHeaders", state
->pfn
);
713 if (scAbort
== SY_TRUE
) state
->breakNow
= SY_TRUE
;
720 TSyResult
SyDrvGotHeaders (TSyState
*state
, TSyHdrs
*hdrs
) {
721 TSyResult res
= SY_OK
;
722 TSyKVList
*cc
; TSyKVListItem
*item
;
724 static char *crlf
= "\r\n";
726 #ifdef SYREN_USE_SCRIPT
728 SyScriptCallback("Event.GotHeaders", state
->pfn
);
730 if (scAbort
== SY_TRUE
) state
->breakNow
= SY_TRUE
;
732 if (oCookieDump
&& *oCookieDump
) {
733 cc
= SyHdrFindCookieList(hdrs
);
734 if (cc
&& cc
->count
) {
735 if (strcmp(oCookieDump
, "-")) {
736 fd
= SyOpenFile(oCookieDump
, SY_FMODE_WRITE
, SY_TRUE
);
737 if (fd
< 0) { SyMessage(state
->pfn
, SY_MSG_ERROR
, "can't dump cookies to '%s'", oCookieDump
); return SY_ERROR
; }
741 if (SyWriteFile(fd
, item
->value
, strlen(item
->value
)) != SY_OK
) { res
= SY_ERROR
; break; }
742 if (SyWriteFile(fd
, crlf
, strlen(crlf
)) != SY_OK
) { res
= SY_ERROR
; break; }
745 if (fd
) SyCloseFile(fd
);
753 TSyResult
SyXPrealloc (int fd
, int64_t fileSize
) {
757 if (fileSize
< 1) return SY_ERROR
;
760 if (bufSz
> fileSize
) bufSz
= fileSize
;
761 buf
= calloc(1, bufSz
);
762 if (!buf
) return SY_ERROR
;
764 while (fileSize
> 0) {
765 wr
= fileSize
>bufSz
?bufSz
:fileSize
;
766 if (SyWriteFile(fd
, buf
, wr
) != SY_OK
) return SY_ERROR
;
770 return SySeekFile(fd
, 0);
774 TSyResult
SyDrvOpenFile (TSyState
*state
) {
775 int fd
, sto
= 0; int64_t len
= -1;
778 if (replyOnly
!= SY_FALSE
) return SY_OK
;
781 if (!oOutFileName
|| !oOutFileName
[0]) {
782 if (oOutFileName
) free(oOutFileName
);
783 if (state
->httpFName
&& *state
->httpFName
) oOutFileName
= SyStrDup(state
->httpFName
);
784 else oOutFileName
= SyStrDup(state
->url
->file
);
785 #ifdef SYREN_USE_SCRIPT
786 SyScriptCallback("Event.CheckFileName", state
->pfn
);
788 /*if (scAbort == SY_TRUE) goto done;*/
789 if (!oOutFileName
|| !oOutFileName
[0]) {
790 if (oOutFileName
) free(oOutFileName
);
791 oOutFileName
= SyStrNew((cfgDefaultName
&&*cfgDefaultName
)?cfgDefaultName
:"index.html", -1);
793 if (!oOutFileName
) { SyMessage(state
->pfn
, SY_MSG_ERROR
, "memory error"); return SY_ERROR
; }
795 /* check for duplicates */
796 fd
= SyOpenFile(oOutFileName
, SY_FMODE_READ
, SY_FALSE
);
798 int f
= -1; s
= NULL
;
800 SyCloseFile(fd
); if (s
) free(s
);
801 s
= SySPrintf("%s.%i", oOutFileName
, f
);
802 if (!s
) { SyMessage(state
->pfn
, SY_MSG_ERROR
, "memory error"); return SY_ERROR
; }
803 fd
= SyOpenFile(s
, SY_FMODE_READ
, SY_FALSE
);
805 free(oOutFileName
); oOutFileName
= s
;
808 /* open/create file */
809 if (!oOutFileName
|| !oOutFileName
[0]) { SyMessage(state
->pfn
, SY_MSG_ERROR
, "invalid file name"); return SY_ERROR
; }
810 if (state
->firstByte
> 0) {
811 if (!strcmp(oOutFileName
, "-")) { SyMessage(state
->pfn
, SY_MSG_ERROR
, "can't resume stdout"); return SY_ERROR
; }
812 fd
= SyOpenFile(oOutFileName
, SY_FMODE_WRITE
, SY_FALSE
);
814 len
= SyFileSize(fd
);
815 if (len
< 0) { SyCloseFile(fd
); SyMessage(state
->pfn
, SY_MSG_ERROR
, "can't resume: file error"); return SY_ERROR
; }
816 if (len
< state
->firstByte
) { SyCloseFile(fd
); SyMessage(state
->pfn
, SY_MSG_ERROR
, "can't resume: corrupted file"); return SY_ERROR
; }
817 if (SySeekFile(fd
, state
->firstByte
) != SY_OK
) {
819 SyMessage(state
->pfn
, SY_MSG_ERROR
, "can't resume: file error");
822 SyLong2StrComma(tmp
, state
->firstByte
);
823 SyMessage(state
->pfn
, SY_MSG_MSG
, "resuming %s at %s", oOutFileName
, tmp
);
826 if (!strcmp(oOutFileName
, "-")) { fd
= 0; sto
= 1; oNoStateFile
= SY_TRUE
; }
828 SyDeleteFile(oOutFileName
);
829 fd
= SyOpenFile(oOutFileName
, SY_FMODE_WRITE
, SY_TRUE
);
830 /* preallocating w/o state file means no resumes, so disable it */
831 if (fd
> 0 && oPreallocSpace
!= SY_FALSE
&& state
->fileSize
> 0 && oNoStateFile
!= SY_TRUE
) {
832 SyLong2StrComma(tmp
, state
->fileSize
);
833 SyMessage(state
->pfn
, SY_MSG_MSG
, "preallocating %s bytes", tmp
);
834 if (SyXPrealloc(fd
, state
->fileSize
) != SY_OK
) {
835 SyMessage(state
->pfn
, SY_MSG_ERROR
, "preallocating failed");
837 SyDeleteFile(oOutFileName
);
842 if (fd
< 0) { SyMessage(state
->pfn
, SY_MSG_ERROR
, "can't open file %s", oOutFileName
); return SY_ERROR
; }
843 if (state
->firstByte
<= 0 || sto
) SyMessage(state
->pfn
, SY_MSG_MSG
, "writing to %s", sto
?"stdout":oOutFileName
);
844 state
->udatai
= sto
?-2:fd
;
845 if (!oStateFileName
|| !oStateFileName
[0]) {
846 if (oStateFileName
) free(oStateFileName
);
847 oStateFileName
= SySPrintf("%s.syren", oOutFileName
);
848 if (!oStateFileName
) SyMessage(state
->pfn
, SY_MSG_WARNING
, "can't create state file name");
849 else if (oNoStateFile
!= SY_TRUE
) SySaveState(oStateFileName
, state
);
856 TSyResult
SyDrvCloseFile (TSyState
*state
) {
857 if (replyOnly
!= SY_FALSE
) return SY_OK
;
858 if (state
->udatai
>= 0) {
859 SyMessage(state
->pfn
, SY_MSG_MSG
, "closing %s%s", oOutFileName
,
860 (state
->error
==SY_FALSE
?"":" (incomplete file)"));
861 SyCloseFile(state
->udatai
);
863 if (oStateFileName
&& *oStateFileName
&& oNoStateFile
!= SY_TRUE
) SySaveState(oStateFileName
, state
);
870 TSyResult
SyDrvWriteFile (TSyState
*state
, void *buf
, int bufSize
) {
873 if (replyOnly
!= SY_FALSE
) return SY_OK
;
874 fd
= state
->udatai
; if (fd
== -2) fd
= 1; /* stdout */
876 if (SyWriteFile(fd
, buf
, bufSize
) != SY_OK
) return SY_ERROR
;
878 if (oStateFileName
&& *oStateFileName
&& oNoStateFile
!= SY_TRUE
&& state
->udatai
>= 0) {
879 if (cfgSaveInterval
>= 1.0 && SyGetTimeD()-lastSSave
>= cfgSaveInterval
) {
880 SySaveState(oStateFileName
, state
);
881 lastSSave
= SyGetTimeD();
882 fsync(state
->udatai
);
890 TSyResult
SyDrvNoResume (TSyState
*state
) {
891 if (replyOnly
!= SY_FALSE
) {
892 SyMessage(state
->pfn
, SY_MSG_WARNING
, "can't resume");
895 if (state
->udatai
>= 0) {
896 if (state
->udatai
) SyCloseFile(state
->udatai
);
897 SyDeleteFile(oOutFileName
);
898 state
->udatai
= SyOpenFile(oOutFileName
, SY_FMODE_WRITE
, SY_TRUE
);
899 if (state
->udatai
< 0) {
900 SyMessage(state
->pfn
, SY_MSG_WARNING
, "can't create file %s", oOutFileName
);
904 SyMessage(state
->pfn
, SY_MSG_WARNING
, "can't resume file %s, file truncated", oOutFileName
);
910 TSyResult
CheckResume (TSyState
*state
) {
913 SyMessage(state
->pfn
, SY_MSG_NOTICE
, "checking file for resuming");
914 if (!strcmp(oOutFileName
, "-")) { SyMessage(state
->pfn
, SY_MSG_ERROR
, "can't resume stdout"); return SY_ERROR
; }
915 fd
= SyOpenFile(oOutFileName
, SY_FMODE_WRITE
, SY_FALSE
);
916 if (fd
< 0) { SyMessage(state
->pfn
, SY_MSG_ERROR
, "resume: can't open file %s", oOutFileName
); return SY_ERROR
; }
917 len
= SyFileSize(fd
);
919 if (len
< 0) { SyMessage(state
->pfn
, SY_MSG_ERROR
, "can't determine file size for %s", oOutFileName
); return SY_ERROR
; }
920 state
->firstByte
= len
;
921 SyMessage(state
->pfn
, SY_MSG_NOTICE
, "resume check passed");
927 TSyState
*curState
= NULL
;
929 void BreakSignal (int signo
) {
931 if (curState
) curState
->breakNow
= SY_TRUE
;
935 void SaveStateSignal (int signo
) {
936 /*fprintf(stderr, "\nsave state\n");*/
941 int main (int argc
, char *argv
[]) {
950 prs
.print
= &SyPrintStr
; prs
.udata
= NULL
;
953 if (t
&& (!strcmp(t
, "rxvt") || !strcmp(t
, "mrxvt") || !strcmp(t
, "xterm") || !strcmp(t
, "eterm"))) doRXVT
= SY_TRUE
;
956 if (!state
) { SyMessage(&prs
, SY_MSG_ERROR
, "memory error"); }
957 prs
.udata
= (void *)state
;
960 if (!cfg
) { SyMessage(&prs
, SY_MSG_ERROR
, "memory error"); }
962 SyCfgLoad(cfg
, "/etc/syren/.syrenrc", &prs
);
965 s
= SySPrintf("%s/.syren/.syrenrc", t
);
966 SyCfgLoad(cfg
, s
, &prs
);
968 s
= SySPrintf("%s/.syrenrc", t
);
969 SyCfgLoad(cfg
, s
, &prs
);
972 SyCfgLoad(cfg
, ".syrenrc", &prs
);
974 oURLList
= SyKVListNew();
975 if (!oURLList
) goto done
;
976 oURLList
->casesens
= 1;
978 if (ParseCmdLine(&prs
, cfg
, argc
, argv
) != SY_OK
) goto done
;
980 SyMessage(&prs
, SY_MSG_MSG
, "%s", SYREN_VERSION_DATETIME_STRING
);
982 #ifdef SYREN_USE_SCRIPT
984 SyScriptLoadInit(argv
[0], &prs
);
987 if (!oStateFileName
) {
988 if (!oURLList
->first
) { SyMessage(&prs
, SY_MSG_ERROR
, "URL?"); goto done
; }
991 if (cfgMaxBufferSize
< 1) { SyMessage(&prs
, SY_MSG_ERROR
, "buffer size too small"); goto done
; }
992 if (cfgTimeout
< 1) { SyMessage(&prs
, SY_MSG_ERROR
, "timeout too small"); goto done
; }
994 if (writeCfg
== SY_TRUE
) {
995 SyMessage(&prs
, SY_MSG_MSG
, "generating sample config");
996 SyCfgSave(cfg
, ".syrenrc", &prs
);
1001 if (SySocketInit() != SY_OK
) goto done
;
1005 state
->ftpUseConnect
= cfgFTPUseConnect
;
1008 state
->fnprog
= PrintProgress
;
1009 state
->fnprephdrs
= SyDrvAddHeaders
;
1010 state
->fngothdrs
= SyDrvGotHeaders
;
1012 state
->fnopen
= SyDrvOpenFile
;
1013 state
->fnclose
= SyDrvCloseFile
;
1014 state
->fnwrite
= SyDrvWriteFile
;
1015 state
->fnnoresume
= SyDrvNoResume
;
1017 state
->postData
= oPostData
; oPostData
= NULL
;
1023 if (!oStateFileName
) {
1024 if (!oURLList
->first
) break;
1025 oURL
= SyStrDup(oURLList
->first
->key
);
1026 SyKVListDelete(oURLList
, oURLList
->first
->key
);
1028 SyMessage(&prs
, SY_MSG_ERROR
, "memory error");
1031 /*if (!oURL) { SyMessage(&prs, SY_MSG_ERROR, "URL?"); goto done; }*/
1033 SyKVListClear(oURLList
);
1034 SyMessage(&prs
, SY_MSG_MSG
, "restoring state from file %s", oStateFileName
);
1035 if (oStateFileName
&& *oStateFileName
) {
1036 s
= SyLoadState(oStateFileName
, state
);
1039 SyMessage(&prs
, SY_MSG_ERROR
, "can't restore state");
1042 if (oOutFileName
) free(oOutFileName
);
1045 if (dumpState
== SY_TRUE
) { DumpState(state
); goto done
; }
1048 if (replyOnly
== SY_FALSE
&& oResume
== SY_TRUE
&& CheckResume(state
) != SY_OK
) goto done
;
1050 #ifdef SYREN_USE_SCRIPT
1052 if (!(state->url = SyURLNew())) {
1053 SyMessage(&prs, SY_MSG_ERROR, "memory error!");
1056 if (SyURLParse(state->url, oURL) != SY_OK) {
1057 SyMessage(state->pfn, SY_MSG_ERROR, "invalid URL");
1060 SyScriptCallback("Event.BeforePrepare", &prs);
1061 s = SyURL2StrEx(state->url, SY_TRUE, SY_FALSE);
1063 SyMessage(&prs, SY_MSG_ERROR, "memory error!");
1066 SyStrFree(oURL); oURL = s; s = NULL;
1067 SyURLFree(state->url); state->url = NULL;
1068 fprintf(stderr, "URL: <%s>\n", oURL);
1071 if (SyPrepare(state
, oURL
,
1072 cfgUseProxyHTTP
?cfgProxyHTTP
:NULL
,
1073 cfgUseProxyFTP
?cfgProxyFTP
:NULL
, cfgIFace
) != SY_OK
) { SyMessage(&prs
, SY_MSG_ERROR
, "SyPrepare() failed"); goto done
; }
1074 #ifdef SYREN_USE_SCRIPT
1075 SyScriptCallback("Event.AfterPrepare", &prs
);
1076 if (scAbort
== SY_TRUE
) goto done
;
1079 if (uEncodeDecode
< 0 ||
1080 (cfgDecodeFTPURL
== SY_TRUE
&& state
->url
->proto
== SY_PROTO_FTP
)) {
1081 s
= SyURLDecode(state
->url
->dir
);
1082 if (!s
) { SyMessage(&prs
, SY_MSG_ERROR
, "SyURLDecode() failed"); goto done
; }
1083 SyStrFree(state
->url
->dir
); state
->url
->dir
= s
;
1084 s
= SyURLDecode(state
->url
->file
);
1085 if (!s
) { SyMessage(&prs
, SY_MSG_ERROR
, "SyURLDecode() failed"); goto done
; }
1086 SyStrFree(state
->url
->file
); state
->url
->file
= s
;
1087 } else if (uEncodeDecode
> 0) {
1088 s
= SyURLEncode(state
->url
->dir
);
1089 if (!s
) { SyMessage(&prs
, SY_MSG_ERROR
, "SyURLEncode() failed"); goto done
; }
1090 SyStrFree(state
->url
->dir
); state
->url
->dir
= s
;
1091 s
= SyURLEncode(state
->url
->file
);
1092 if (!s
) { SyMessage(&prs
, SY_MSG_ERROR
, "SyURLEncode() failed"); goto done
; }
1093 SyStrFree(state
->url
->file
); state
->url
->file
= s
;
1096 signal(SIGHUP
, SaveStateSignal
);
1097 signal(SIGUSR1
, SaveStateSignal
);
1099 signal(SIGINT
, BreakSignal
);
1100 signal(SIGQUIT
, BreakSignal
);
1101 signal(SIGTERM
, BreakSignal
);
1103 retCnt
= cfgReconnectAttempts
;
1105 state
->initBufferSize
= cfgBufferSize
;
1106 state
->maxBufferSize
= cfgMaxBufferSize
;
1107 state
->ioTimeout
= cfgTimeout
;
1108 state
->maxRedirects
= cfgMaxRedirects
;
1109 if (cfgUAStr
&& *cfgUAStr
) {
1110 SyStrFree(state
->userAgent
);
1111 state
->userAgent
= SyStrNew(cfgUAStr
, -1);
1115 res
= SyBegin(state
);
1116 if (replyOnly
!= SY_FALSE
) break;
1117 if (res
== SY_OK
) res
= SyRun(state
);
1119 if (res
== SY_OK
) break;
1120 if (state
->interrupted
== SY_TRUE
) {
1122 if (oStateFileName
&& *oStateFileName
&& state
->status
== SY_STATUS_DOWNLOADING
&& oNoStateFile
!= SY_TRUE
) {
1123 SyMessage(&prs
, SY_MSG_WARNING
, "user break, saving state");
1124 SySaveState(oStateFileName
, state
);
1125 } else SyMessage(&prs
, SY_MSG_WARNING
, "user break");
1128 if (retCnt
< 1) break;
1129 if (state
->status
!= SY_STATUS_DOWNLOADING
&& oForceRetry
!= SY_TRUE
) break;
1130 SyMessage(&prs
, SY_MSG_ERROR
, "download failed");
1131 if (cfgReconnectDelay
> 0) {
1132 SyMessage(&prs
, SY_MSG_MSG
, "waiting %i seconds", cfgReconnectDelay
);
1133 SySleep(cfgReconnectDelay
);
1135 SyMessage(&prs
, SY_MSG_MSG
, "restarting");
1136 state
->firstByte
+= state
->currentByte
; /* start from here */
1137 oCanRename
= SY_FALSE
;
1140 #ifdef SYREN_USE_SCRIPT
1141 if (res
== SY_OK
) SyScriptCallback("Event.DownloadComplete", &prs
);
1142 else SyScriptCallback("Event.DownloadFailed", &prs
);
1143 if (scAbort
== SY_TRUE
) goto done
;
1147 SyMessage(&prs
, SY_MSG_MSG
, "download complete");
1148 if (oCanRemoveState
&& oStateFileName
&& *oStateFileName
) {
1149 if (SyDeleteFile(oStateFileName
) == SY_OK
) SyMessage(&prs
, SY_MSG_NOTICE
, "state file removed");
1151 } else SyMessage(&prs
, SY_MSG_ERROR
, "download failed");
1153 mainres
= (res
==SY_OK
)?0:1;
1156 SyStrFree(oURL
); oURL
= NULL
;
1157 SyStrFree(oOutFileName
); oOutFileName
= NULL
;
1158 SyStrFree(oStateFileName
); oStateFileName
= NULL
;
1159 oCanRename
= SY_TRUE
;
1160 } while (!userBreak
);
1162 SyFree(state
); SyCfgFree(cfg
);
1163 if (oURLList
) SyKVListFree(oURLList
);
1164 if (cfgDefaultName
) SyStrFree(cfgDefaultName
);
1165 if (cfgIFace
) SyStrFree(cfgIFace
);
1166 if (cfgProxyHTTP
) SyStrFree(cfgProxyHTTP
);
1167 if (cfgProxyFTP
) SyStrFree(cfgProxyFTP
);
1168 if (cfgRefererStr
) SyStrFree(cfgRefererStr
);
1169 if (cfgUAStr
) SyStrFree(cfgUAStr
);
1171 SyStrFree(oOutFileName
);
1172 SyStrFree(oStateFileName
);
1173 if (oPostData
) SyStrFree(oPostData
);
1174 if (oSendCookies
) SyStrFree(oSendCookies
);
1175 if (oCookieDump
) SyStrFree(oCookieDump
);
1176 if (oAddonHeaders
) SyStrFree(oAddonHeaders
);
1178 #ifdef SYREN_USE_SCRIPT