1 | /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
---|
2 | /* ***** BEGIN LICENSE BLOCK *****
|
---|
3 | * Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
---|
4 | *
|
---|
5 | * The contents of this file are subject to the Mozilla Public License Version
|
---|
6 | * 1.1 (the "License"); you may not use this file except in compliance with
|
---|
7 | * the License. You may obtain a copy of the License at
|
---|
8 | * http://www.mozilla.org/MPL/
|
---|
9 | *
|
---|
10 | * Software distributed under the License is distributed on an "AS IS" basis,
|
---|
11 | * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
---|
12 | * for the specific language governing rights and limitations under the
|
---|
13 | * License.
|
---|
14 | *
|
---|
15 | * The Original Code is the Netscape Portable Runtime (NSPR).
|
---|
16 | *
|
---|
17 | * The Initial Developer of the Original Code is
|
---|
18 | * Netscape Communications Corporation.
|
---|
19 | * Portions created by the Initial Developer are Copyright (C) 1998-2000
|
---|
20 | * the Initial Developer. All Rights Reserved.
|
---|
21 | *
|
---|
22 | * Contributor(s):
|
---|
23 | *
|
---|
24 | * Alternatively, the contents of this file may be used under the terms of
|
---|
25 | * either the GNU General Public License Version 2 or later (the "GPL"), or
|
---|
26 | * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
---|
27 | * in which case the provisions of the GPL or the LGPL are applicable instead
|
---|
28 | * of those above. If you wish to allow use of your version of this file only
|
---|
29 | * under the terms of either the GPL or the LGPL, and not to allow others to
|
---|
30 | * use your version of this file under the terms of the MPL, indicate your
|
---|
31 | * decision by deleting the provisions above and replace them with the notice
|
---|
32 | * and other provisions required by the GPL or the LGPL. If you do not delete
|
---|
33 | * the provisions above, a recipient may use your version of this file under
|
---|
34 | * the terms of any one of the MPL, the GPL or the LGPL.
|
---|
35 | *
|
---|
36 | * ***** END LICENSE BLOCK ***** */
|
---|
37 |
|
---|
38 |
|
---|
39 | /*
|
---|
40 | * Author: Wan-Teh Chang
|
---|
41 | *
|
---|
42 | * Given an HTTP URL, httpget uses the GET method to fetch the file.
|
---|
43 | * The fetched file is written to stdout by default, or can be
|
---|
44 | * saved in an output file.
|
---|
45 | *
|
---|
46 | * This is a single-threaded program.
|
---|
47 | */
|
---|
48 |
|
---|
49 | #include "prio.h"
|
---|
50 | #include "prnetdb.h"
|
---|
51 | #include "prlog.h"
|
---|
52 | #include "prerror.h"
|
---|
53 | #include "prprf.h"
|
---|
54 | #include "prinit.h"
|
---|
55 |
|
---|
56 | #include <stdio.h>
|
---|
57 | #include <string.h>
|
---|
58 | #include <stdlib.h> /* for atoi */
|
---|
59 |
|
---|
60 | #define FCOPY_BUFFER_SIZE (16 * 1024)
|
---|
61 | #define INPUT_BUFFER_SIZE 1024
|
---|
62 | #define LINE_SIZE 512
|
---|
63 | #define HOST_SIZE 256
|
---|
64 | #define PORT_SIZE 32
|
---|
65 | #define PATH_SIZE 512
|
---|
66 |
|
---|
67 | /*
|
---|
68 | * A buffer for storing the excess input data for ReadLine.
|
---|
69 | * The data in the buffer starts from (including) the element pointed to
|
---|
70 | * by inputHead, and ends just before (not including) the element pointed
|
---|
71 | * to by inputTail. The buffer is empty if inputHead == inputTail.
|
---|
72 | */
|
---|
73 |
|
---|
74 | static char inputBuf[INPUT_BUFFER_SIZE];
|
---|
75 | /*
|
---|
76 | * inputBufEnd points just past the end of inputBuf
|
---|
77 | */
|
---|
78 | static char *inputBufEnd = inputBuf + sizeof(inputBuf);
|
---|
79 | static char *inputHead = inputBuf;
|
---|
80 | static char *inputTail = inputBuf;
|
---|
81 |
|
---|
82 | static PRBool endOfStream = PR_FALSE;
|
---|
83 |
|
---|
84 | /*
|
---|
85 | * ReadLine --
|
---|
86 | *
|
---|
87 | * Read in a line of text, terminated by CRLF or LF, from fd into buf.
|
---|
88 | * The terminating CRLF or LF is included (always as '\n'). The text
|
---|
89 | * in buf is terminated by a null byte. The excess bytes are stored in
|
---|
90 | * inputBuf for use in the next ReadLine call or FetchFile call.
|
---|
91 | * Returns the number of bytes in buf. 0 means end of stream. Returns
|
---|
92 | * -1 if read fails.
|
---|
93 | */
|
---|
94 |
|
---|
95 | PRInt32 ReadLine(PRFileDesc *fd, char *buf, PRUint32 bufSize)
|
---|
96 | {
|
---|
97 | char *dst = buf;
|
---|
98 | char *bufEnd = buf + bufSize; /* just past the end of buf */
|
---|
99 | PRBool lineFound = PR_FALSE;
|
---|
100 | char *crPtr = NULL; /* points to the CR ('\r') character */
|
---|
101 | PRInt32 nRead;
|
---|
102 |
|
---|
103 | loop:
|
---|
104 | PR_ASSERT(inputBuf <= inputHead && inputHead <= inputTail
|
---|
105 | && inputTail <= inputBufEnd);
|
---|
106 | while (lineFound == PR_FALSE && inputHead != inputTail
|
---|
107 | && dst < bufEnd - 1) {
|
---|
108 | if (*inputHead == '\r') {
|
---|
109 | crPtr = dst;
|
---|
110 | } else if (*inputHead == '\n') {
|
---|
111 | lineFound = PR_TRUE;
|
---|
112 | if (crPtr == dst - 1) {
|
---|
113 | dst--;
|
---|
114 | }
|
---|
115 | }
|
---|
116 | *(dst++) = *(inputHead++);
|
---|
117 | }
|
---|
118 | if (lineFound == PR_TRUE || dst == bufEnd - 1 || endOfStream == PR_TRUE) {
|
---|
119 | *dst = '\0';
|
---|
120 | return dst - buf;
|
---|
121 | }
|
---|
122 |
|
---|
123 | /*
|
---|
124 | * The input buffer should be empty now
|
---|
125 | */
|
---|
126 | PR_ASSERT(inputHead == inputTail);
|
---|
127 |
|
---|
128 | nRead = PR_Read(fd, inputBuf, sizeof(inputBuf));
|
---|
129 | if (nRead == -1) {
|
---|
130 | *dst = '\0';
|
---|
131 | return -1;
|
---|
132 | } else if (nRead == 0) {
|
---|
133 | endOfStream = PR_TRUE;
|
---|
134 | *dst = '\0';
|
---|
135 | return dst - buf;
|
---|
136 | }
|
---|
137 | inputHead = inputBuf;
|
---|
138 | inputTail = inputBuf + nRead;
|
---|
139 | goto loop;
|
---|
140 | }
|
---|
141 |
|
---|
142 | PRInt32 DrainInputBuffer(char *buf, PRUint32 bufSize)
|
---|
143 | {
|
---|
144 | PRInt32 nBytes = inputTail - inputHead;
|
---|
145 |
|
---|
146 | if (nBytes == 0) {
|
---|
147 | if (endOfStream) {
|
---|
148 | return -1;
|
---|
149 | } else {
|
---|
150 | return 0;
|
---|
151 | }
|
---|
152 | }
|
---|
153 | if ((PRInt32) bufSize < nBytes) {
|
---|
154 | nBytes = bufSize;
|
---|
155 | }
|
---|
156 | memcpy(buf, inputHead, nBytes);
|
---|
157 | inputHead += nBytes;
|
---|
158 | return nBytes;
|
---|
159 | }
|
---|
160 |
|
---|
161 | PRStatus FetchFile(PRFileDesc *in, PRFileDesc *out)
|
---|
162 | {
|
---|
163 | char buf[FCOPY_BUFFER_SIZE];
|
---|
164 | PRInt32 nBytes;
|
---|
165 |
|
---|
166 | while ((nBytes = DrainInputBuffer(buf, sizeof(buf))) > 0) {
|
---|
167 | if (PR_Write(out, buf, nBytes) != nBytes) {
|
---|
168 | fprintf(stderr, "httpget: cannot write to file\n");
|
---|
169 | return PR_FAILURE;
|
---|
170 | }
|
---|
171 | }
|
---|
172 | if (nBytes < 0) {
|
---|
173 | /* Input buffer is empty and end of stream */
|
---|
174 | return PR_SUCCESS;
|
---|
175 | }
|
---|
176 | while ((nBytes = PR_Read(in, buf, sizeof(buf))) > 0) {
|
---|
177 | if (PR_Write(out, buf, nBytes) != nBytes) {
|
---|
178 | fprintf(stderr, "httpget: cannot write to file\n");
|
---|
179 | return PR_FAILURE;
|
---|
180 | }
|
---|
181 | }
|
---|
182 | if (nBytes < 0) {
|
---|
183 | fprintf(stderr, "httpget: cannot read from socket\n");
|
---|
184 | return PR_FAILURE;
|
---|
185 | }
|
---|
186 | return PR_SUCCESS;
|
---|
187 | }
|
---|
188 |
|
---|
189 | PRStatus FastFetchFile(PRFileDesc *in, PRFileDesc *out, PRUint32 size)
|
---|
190 | {
|
---|
191 | PRInt32 nBytes;
|
---|
192 | PRFileMap *outfMap;
|
---|
193 | void *addr;
|
---|
194 | char *start;
|
---|
195 | PRUint32 rem;
|
---|
196 | PRUint32 bytesToRead;
|
---|
197 | PRStatus rv;
|
---|
198 | PRInt64 sz64;
|
---|
199 |
|
---|
200 | LL_UI2L(sz64, size);
|
---|
201 | outfMap = PR_CreateFileMap(out, sz64, PR_PROT_READWRITE);
|
---|
202 | PR_ASSERT(outfMap);
|
---|
203 | addr = PR_MemMap(outfMap, LL_ZERO, size);
|
---|
204 | if (addr == (void *) -1) {
|
---|
205 | fprintf(stderr, "cannot memory-map file: (%d, %d)\n", PR_GetError(),
|
---|
206 | PR_GetOSError());
|
---|
207 |
|
---|
208 | PR_CloseFileMap(outfMap);
|
---|
209 | return PR_FAILURE;
|
---|
210 | }
|
---|
211 | PR_ASSERT(addr != (void *) -1);
|
---|
212 | start = (char *) addr;
|
---|
213 | rem = size;
|
---|
214 | while ((nBytes = DrainInputBuffer(start, rem)) > 0) {
|
---|
215 | start += nBytes;
|
---|
216 | rem -= nBytes;
|
---|
217 | }
|
---|
218 | if (nBytes < 0) {
|
---|
219 | /* Input buffer is empty and end of stream */
|
---|
220 | return PR_SUCCESS;
|
---|
221 | }
|
---|
222 | bytesToRead = (rem < FCOPY_BUFFER_SIZE) ? rem : FCOPY_BUFFER_SIZE;
|
---|
223 | while (rem > 0 && (nBytes = PR_Read(in, start, bytesToRead)) > 0) {
|
---|
224 | start += nBytes;
|
---|
225 | rem -= nBytes;
|
---|
226 | bytesToRead = (rem < FCOPY_BUFFER_SIZE) ? rem : FCOPY_BUFFER_SIZE;
|
---|
227 | }
|
---|
228 | if (nBytes < 0) {
|
---|
229 | fprintf(stderr, "httpget: cannot read from socket\n");
|
---|
230 | return PR_FAILURE;
|
---|
231 | }
|
---|
232 | rv = PR_MemUnmap(addr, size);
|
---|
233 | PR_ASSERT(rv == PR_SUCCESS);
|
---|
234 | rv = PR_CloseFileMap(outfMap);
|
---|
235 | PR_ASSERT(rv == PR_SUCCESS);
|
---|
236 | return PR_SUCCESS;
|
---|
237 | }
|
---|
238 |
|
---|
239 | PRStatus ParseURL(char *url, char *host, PRUint32 hostSize,
|
---|
240 | char *port, PRUint32 portSize, char *path, PRUint32 pathSize)
|
---|
241 | {
|
---|
242 | char *start, *end;
|
---|
243 | char *dst;
|
---|
244 | char *hostEnd;
|
---|
245 | char *portEnd;
|
---|
246 | char *pathEnd;
|
---|
247 |
|
---|
248 | if (strncmp(url, "http", 4)) {
|
---|
249 | fprintf(stderr, "httpget: the protocol must be http\n");
|
---|
250 | return PR_FAILURE;
|
---|
251 | }
|
---|
252 | if (strncmp(url + 4, "://", 3) || url[7] == '\0') {
|
---|
253 | fprintf(stderr, "httpget: malformed URL: %s\n", url);
|
---|
254 | return PR_FAILURE;
|
---|
255 | }
|
---|
256 |
|
---|
257 | start = end = url + 7;
|
---|
258 | dst = host;
|
---|
259 | hostEnd = host + hostSize;
|
---|
260 | while (*end && *end != ':' && *end != '/') {
|
---|
261 | if (dst == hostEnd - 1) {
|
---|
262 | fprintf(stderr, "httpget: host name too long\n");
|
---|
263 | return PR_FAILURE;
|
---|
264 | }
|
---|
265 | *(dst++) = *(end++);
|
---|
266 | }
|
---|
267 | *dst = '\0';
|
---|
268 |
|
---|
269 | if (*end == '\0') {
|
---|
270 | PR_snprintf(port, portSize, "%d", 80);
|
---|
271 | PR_snprintf(path, pathSize, "%s", "/");
|
---|
272 | return PR_SUCCESS;
|
---|
273 | }
|
---|
274 |
|
---|
275 | if (*end == ':') {
|
---|
276 | end++;
|
---|
277 | dst = port;
|
---|
278 | portEnd = port + portSize;
|
---|
279 | while (*end && *end != '/') {
|
---|
280 | if (dst == portEnd - 1) {
|
---|
281 | fprintf(stderr, "httpget: port number too long\n");
|
---|
282 | return PR_FAILURE;
|
---|
283 | }
|
---|
284 | *(dst++) = *(end++);
|
---|
285 | }
|
---|
286 | *dst = '\0';
|
---|
287 | if (*end == '\0') {
|
---|
288 | PR_snprintf(path, pathSize, "%s", "/");
|
---|
289 | return PR_SUCCESS;
|
---|
290 | }
|
---|
291 | } else {
|
---|
292 | PR_snprintf(port, portSize, "%d", 80);
|
---|
293 | }
|
---|
294 |
|
---|
295 | dst = path;
|
---|
296 | pathEnd = path + pathSize;
|
---|
297 | while (*end) {
|
---|
298 | if (dst == pathEnd - 1) {
|
---|
299 | fprintf(stderr, "httpget: file pathname too long\n");
|
---|
300 | return PR_FAILURE;
|
---|
301 | }
|
---|
302 | *(dst++) = *(end++);
|
---|
303 | }
|
---|
304 | *dst = '\0';
|
---|
305 | return PR_SUCCESS;
|
---|
306 | }
|
---|
307 |
|
---|
308 | void PrintUsage(void) {
|
---|
309 | fprintf(stderr, "usage: httpget url\n"
|
---|
310 | " httpget -o outputfile url\n"
|
---|
311 | " httpget url -o outputfile\n");
|
---|
312 | }
|
---|
313 |
|
---|
314 | int main(int argc, char **argv)
|
---|
315 | {
|
---|
316 | PRHostEnt hostentry;
|
---|
317 | char buf[PR_NETDB_BUF_SIZE];
|
---|
318 | PRNetAddr addr;
|
---|
319 | PRFileDesc *socket = NULL, *file = NULL;
|
---|
320 | PRIntn cmdSize;
|
---|
321 | char host[HOST_SIZE];
|
---|
322 | char port[PORT_SIZE];
|
---|
323 | char path[PATH_SIZE];
|
---|
324 | char line[LINE_SIZE];
|
---|
325 | int exitStatus = 0;
|
---|
326 | PRBool endOfHeader = PR_FALSE;
|
---|
327 | char *url;
|
---|
328 | char *fileName = NULL;
|
---|
329 | PRUint32 fileSize;
|
---|
330 |
|
---|
331 | if (argc != 2 && argc != 4) {
|
---|
332 | PrintUsage();
|
---|
333 | exit(1);
|
---|
334 | }
|
---|
335 |
|
---|
336 | if (argc == 2) {
|
---|
337 | /*
|
---|
338 | * case 1: httpget url
|
---|
339 | */
|
---|
340 | url = argv[1];
|
---|
341 | } else {
|
---|
342 | if (strcmp(argv[1], "-o") == 0) {
|
---|
343 | /*
|
---|
344 | * case 2: httpget -o outputfile url
|
---|
345 | */
|
---|
346 | fileName = argv[2];
|
---|
347 | url = argv[3];
|
---|
348 | } else {
|
---|
349 | /*
|
---|
350 | * case 3: httpget url -o outputfile
|
---|
351 | */
|
---|
352 | url = argv[1];
|
---|
353 | if (strcmp(argv[2], "-o") != 0) {
|
---|
354 | PrintUsage();
|
---|
355 | exit(1);
|
---|
356 | }
|
---|
357 | fileName = argv[3];
|
---|
358 | }
|
---|
359 | }
|
---|
360 |
|
---|
361 | if (ParseURL(url, host, sizeof(host), port, sizeof(port),
|
---|
362 | path, sizeof(path)) == PR_FAILURE) {
|
---|
363 | exit(1);
|
---|
364 | }
|
---|
365 |
|
---|
366 | if (PR_GetHostByName(host, buf, sizeof(buf), &hostentry)
|
---|
367 | == PR_FAILURE) {
|
---|
368 | fprintf(stderr, "httpget: unknown host name: %s\n", host);
|
---|
369 | exit(1);
|
---|
370 | }
|
---|
371 |
|
---|
372 | addr.inet.family = PR_AF_INET;
|
---|
373 | addr.inet.port = PR_htons((short) atoi(port));
|
---|
374 | addr.inet.ip = *((PRUint32 *) hostentry.h_addr_list[0]);
|
---|
375 |
|
---|
376 | socket = PR_NewTCPSocket();
|
---|
377 | if (socket == NULL) {
|
---|
378 | fprintf(stderr, "httpget: cannot create new tcp socket\n");
|
---|
379 | exit(1);
|
---|
380 | }
|
---|
381 |
|
---|
382 | if (PR_Connect(socket, &addr, PR_INTERVAL_NO_TIMEOUT) == PR_FAILURE) {
|
---|
383 | fprintf(stderr, "httpget: cannot connect to http server\n");
|
---|
384 | exitStatus = 1;
|
---|
385 | goto done;
|
---|
386 | }
|
---|
387 |
|
---|
388 | if (fileName == NULL) {
|
---|
389 | file = PR_STDOUT;
|
---|
390 | } else {
|
---|
391 | file = PR_Open(fileName, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE,
|
---|
392 | 00777);
|
---|
393 | if (file == NULL) {
|
---|
394 | fprintf(stderr, "httpget: cannot open file %s: (%d, %d)\n",
|
---|
395 | fileName, PR_GetError(), PR_GetOSError());
|
---|
396 | exitStatus = 1;
|
---|
397 | goto done;
|
---|
398 | }
|
---|
399 | }
|
---|
400 |
|
---|
401 | cmdSize = PR_snprintf(buf, sizeof(buf), "GET %s HTTP/1.0\r\n\r\n", path);
|
---|
402 | PR_ASSERT(cmdSize == (PRIntn) strlen("GET HTTP/1.0\r\n\r\n")
|
---|
403 | + (PRIntn) strlen(path));
|
---|
404 | if (PR_Write(socket, buf, cmdSize) != cmdSize) {
|
---|
405 | fprintf(stderr, "httpget: cannot write to http server\n");
|
---|
406 | exitStatus = 1;
|
---|
407 | goto done;
|
---|
408 | }
|
---|
409 |
|
---|
410 | if (ReadLine(socket, line, sizeof(line)) <= 0) {
|
---|
411 | fprintf(stderr, "httpget: cannot read line from http server\n");
|
---|
412 | exitStatus = 1;
|
---|
413 | goto done;
|
---|
414 | }
|
---|
415 |
|
---|
416 | /* HTTP response: 200 == OK */
|
---|
417 | if (strstr(line, "200") == NULL) {
|
---|
418 | fprintf(stderr, "httpget: %s\n", line);
|
---|
419 | exitStatus = 1;
|
---|
420 | goto done;
|
---|
421 | }
|
---|
422 |
|
---|
423 | while (ReadLine(socket, line, sizeof(line)) > 0) {
|
---|
424 | if (line[0] == '\n') {
|
---|
425 | endOfHeader = PR_TRUE;
|
---|
426 | break;
|
---|
427 | }
|
---|
428 | if (strncmp(line, "Content-Length", 14) == 0
|
---|
429 | || strncmp(line, "Content-length", 14) == 0) {
|
---|
430 | char *p = line + 14;
|
---|
431 |
|
---|
432 | while (*p == ' ' || *p == '\t') {
|
---|
433 | p++;
|
---|
434 | }
|
---|
435 | if (*p != ':') {
|
---|
436 | continue;
|
---|
437 | }
|
---|
438 | p++;
|
---|
439 | while (*p == ' ' || *p == '\t') {
|
---|
440 | p++;
|
---|
441 | }
|
---|
442 | fileSize = 0;
|
---|
443 | while ('0' <= *p && *p <= '9') {
|
---|
444 | fileSize = 10 * fileSize + (*p - '0');
|
---|
445 | p++;
|
---|
446 | }
|
---|
447 | }
|
---|
448 | }
|
---|
449 | if (endOfHeader == PR_FALSE) {
|
---|
450 | fprintf(stderr, "httpget: cannot read line from http server\n");
|
---|
451 | exitStatus = 1;
|
---|
452 | goto done;
|
---|
453 | }
|
---|
454 |
|
---|
455 | if (fileName == NULL || fileSize == 0) {
|
---|
456 | FetchFile(socket, file);
|
---|
457 | } else {
|
---|
458 | FastFetchFile(socket, file, fileSize);
|
---|
459 | }
|
---|
460 |
|
---|
461 | done:
|
---|
462 | if (socket) PR_Close(socket);
|
---|
463 | if (file) PR_Close(file);
|
---|
464 | PR_Cleanup();
|
---|
465 | return exitStatus;
|
---|
466 | }
|
---|