1 /* wget.c - Simple downloader to get the resource file in HTTP server
2 *
3 * Copyright 2016 Lipi C.H. Lee <lipisoft@gmail.com>
4 *
5
6 USE_WGET(NEWTOY(wget, "f:", TOYFLAG_USR|TOYFLAG_BIN))
7
8 config WGET
9 bool "wget"
10 default n
11 help
12 usage: wget -f filename URL
13 -f filename: specify the filename to be saved
14 URL: HTTP uniform resource location and only HTTP, not HTTPS
15
16 examples:
17 wget -f index.html http://www.example.com
18 wget -f sample.jpg http://www.example.com:8080/sample.jpg
19 */
20
21 #define FOR_wget
22 #include "toys.h"
23
GLOBALS(char * filename;)24 GLOBALS(
25 char *filename;
26 )
27
28 // extract hostname from url
29 static unsigned get_hn(const char *url, char *hostname) {
30 unsigned i;
31
32 for (i = 0; url[i] != '\0' && url[i] != ':' && url[i] != '/'; i++) {
33 if(i >= 1024) error_exit("too long hostname in URL");
34 hostname[i] = url[i];
35 }
36 hostname[i] = '\0';
37
38 return i;
39 }
40
41 // extract port number
get_port(const char * url,char * port,unsigned url_i)42 static unsigned get_port(const char *url, char *port, unsigned url_i) {
43 unsigned i;
44
45 for (i = 0; url[i] != '\0' && url[i] != '/'; i++, url_i++) {
46 if('0' <= url[i] && url[i] <= '9') port[i] = url[i];
47 else error_exit("wrong decimal port number");
48 }
49 if(i <= 6) port[i] = '\0';
50 else error_exit("too long port number");
51
52 return url_i;
53 }
54
55 // get http infos in URL
get_info(const char * url,char * hostname,char * port,char * path)56 static void get_info(const char *url, char* hostname, char *port, char *path) {
57 unsigned i = 7, len;
58
59 if (strncmp(url, "http://", i)) error_exit("only HTTP support");
60 len = get_hn(url+i, hostname);
61 i += len;
62
63 // get port if exists
64 if (url[i] == ':') {
65 i++;
66 i = get_port(url+i, port, i);
67 } else strcpy(port, "80");
68
69 // get uri in URL
70 if (url[i] == '\0') strcpy(path, "/");
71 else if (url[i] == '/') {
72 if (strlen(url+i) < 1024) strcpy(path, url+i);
73 else error_exit("too long path in URL");
74 } else error_exit("wrong URL");
75 }
76
77 // connect to any IPv4 or IPv6 server
conn_svr(const char * hostname,const char * port)78 static int conn_svr(const char *hostname, const char *port) {
79 struct addrinfo hints, *result, *rp;
80 int sock;
81
82 memset(&hints, 0, sizeof(struct addrinfo));
83 hints.ai_family = AF_UNSPEC;
84 hints.ai_socktype = SOCK_STREAM;
85 hints.ai_flags = 0;
86 hints.ai_protocol = 0;
87
88 if ((errno = getaddrinfo(hostname, port, &hints, &result)))
89 error_exit("getaddrinfo: %s", gai_strerror(errno));
90
91 // try all address list(IPv4 or IPv6) until success
92 for (rp = result; rp; rp = rp->ai_next) {
93 if ((sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol))
94 == -1) {
95 perror_msg("socket error");
96 continue;
97 }
98 if (connect(sock, rp->ai_addr, rp->ai_addrlen) != -1)
99 break; // succeed in connecting to any server IP
100 else perror_msg("connect error");
101 close(sock);
102 }
103 freeaddrinfo(result);
104 if(!rp) error_exit("can't connect");
105
106 return sock;
107 }
108
109 // make HTTP request header field
mk_fld(char * name,char * value)110 static void mk_fld(char *name, char *value) {
111 strcat(toybuf, name);
112 strcat(toybuf, ": ");
113 strcat(toybuf, value);
114 strcat(toybuf, "\r\n");
115 }
116
117 // get http response body starting address and its length
get_body(ssize_t len,ssize_t * body_len)118 static char *get_body(ssize_t len, ssize_t *body_len) {
119 int i;
120
121 for (i = 0; i < len-4; i++)
122 if (!strncmp(toybuf+i, "\r\n\r\n", 4)) break;
123
124 *body_len = len - i - 4;
125 return toybuf+i+4;
126 }
127
wget_main(void)128 void wget_main(void)
129 {
130 int sock;
131 FILE *fp;
132 ssize_t len, body_len;
133 char *body, *result, *rc, *r_str;
134 char ua[18] = "toybox wget/", ver[6], hostname[1024], port[6], path[1024];
135
136 // TODO extract filename to be saved from URL
137 if (!(toys.optflags & FLAG_f)) help_exit("no filename");
138 if (fopen(TT.filename, "r")) error_exit("'%s' already exists", TT.filename);
139
140 if(!toys.optargs[0]) help_exit("no URL");
141 get_info(toys.optargs[0], hostname, port, path);
142
143 sock = conn_svr(hostname, port);
144
145 // compose HTTP request
146 sprintf(toybuf, "GET %s HTTP/1.1\r\n", path);
147 mk_fld("Host", hostname);
148 strncpy(ver, TOYBOX_VERSION, 5);
149 strcat(ua, ver);
150 mk_fld("User-Agent", ua);
151 mk_fld("Connection", "close");
152 strcat(toybuf, "\r\n");
153
154 // send the HTTP request
155 len = strlen(toybuf);
156 if (write(sock, toybuf, len) != len) perror_exit("write error");
157
158 // read HTTP response
159 if ((len = read(sock, toybuf, 4096)) == -1) perror_exit("read error");
160 if (!strstr(toybuf, "\r\n\r\n")) error_exit("too long HTTP response");
161 body = get_body(len, &body_len);
162 result = strtok(toybuf, "\r");
163 strtok(result, " ");
164 rc = strtok(NULL, " ");
165 r_str = strtok(NULL, " ");
166
167 // HTTP res code check
168 // TODO handle HTTP 302 Found(Redirection)
169 if (strcmp(rc, "200")) error_exit("res: %s(%s)", rc, r_str);
170
171 if (!(fp = fopen(TT.filename, "w"))) perror_exit("fopen error");
172 if (fwrite(body, 1, body_len, fp) != body_len)
173 error_exit("fwrite error");
174 while ((len = read(sock, toybuf, 4096)) > 0)
175 if (fwrite(toybuf, 1, len, fp) != len)
176 error_exit("fwrite error");
177 if (fclose(fp) == EOF) perror_exit("fclose error");
178 }
179