1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 * Author: Wan-Teh Chang
10 * Given an HTTP URL, httpget uses the GET method to fetch the file.
11 * The fetched file is written to stdout by default, or can be
12 * saved in an output file.
14 * This is a single-threaded program.
26 #include <stdlib.h> /* for atoi */
28 #define FCOPY_BUFFER_SIZE (16 * 1024)
29 #define INPUT_BUFFER_SIZE 1024
36 * A buffer for storing the excess input data for ReadLine.
37 * The data in the buffer starts from (including) the element pointed to
38 * by inputHead, and ends just before (not including) the element pointed
39 * to by inputTail. The buffer is empty if inputHead == inputTail.
42 static char inputBuf
[INPUT_BUFFER_SIZE
];
44 * inputBufEnd points just past the end of inputBuf
46 static char *inputBufEnd
= inputBuf
+ sizeof(inputBuf
);
47 static char *inputHead
= inputBuf
;
48 static char *inputTail
= inputBuf
;
50 static PRBool endOfStream
= PR_FALSE
;
55 * Read in a line of text, terminated by CRLF or LF, from fd into buf.
56 * The terminating CRLF or LF is included (always as '\n'). The text
57 * in buf is terminated by a null byte. The excess bytes are stored in
58 * inputBuf for use in the next ReadLine call or FetchFile call.
59 * Returns the number of bytes in buf. 0 means end of stream. Returns
63 PRInt32
ReadLine(PRFileDesc
*fd
, char *buf
, PRUint32 bufSize
)
66 char *bufEnd
= buf
+ bufSize
; /* just past the end of buf */
67 PRBool lineFound
= PR_FALSE
;
68 char *crPtr
= NULL
; /* points to the CR ('\r') character */
72 PR_ASSERT(inputBuf
<= inputHead
&& inputHead
<= inputTail
73 && inputTail
<= inputBufEnd
);
74 while (lineFound
== PR_FALSE
&& inputHead
!= inputTail
75 && dst
< bufEnd
- 1) {
76 if (*inputHead
== '\r') {
78 } else if (*inputHead
== '\n') {
80 if (crPtr
== dst
- 1) {
84 *(dst
++) = *(inputHead
++);
86 if (lineFound
== PR_TRUE
|| dst
== bufEnd
- 1 || endOfStream
== PR_TRUE
) {
92 * The input buffer should be empty now
94 PR_ASSERT(inputHead
== inputTail
);
96 nRead
= PR_Read(fd
, inputBuf
, sizeof(inputBuf
));
100 } else if (nRead
== 0) {
101 endOfStream
= PR_TRUE
;
105 inputHead
= inputBuf
;
106 inputTail
= inputBuf
+ nRead
;
110 PRInt32
DrainInputBuffer(char *buf
, PRUint32 bufSize
)
112 PRInt32 nBytes
= inputTail
- inputHead
;
121 if ((PRInt32
) bufSize
< nBytes
) {
124 memcpy(buf
, inputHead
, nBytes
);
129 PRStatus
FetchFile(PRFileDesc
*in
, PRFileDesc
*out
)
131 char buf
[FCOPY_BUFFER_SIZE
];
134 while ((nBytes
= DrainInputBuffer(buf
, sizeof(buf
))) > 0) {
135 if (PR_Write(out
, buf
, nBytes
) != nBytes
) {
136 fprintf(stderr
, "httpget: cannot write to file\n");
141 /* Input buffer is empty and end of stream */
144 while ((nBytes
= PR_Read(in
, buf
, sizeof(buf
))) > 0) {
145 if (PR_Write(out
, buf
, nBytes
) != nBytes
) {
146 fprintf(stderr
, "httpget: cannot write to file\n");
151 fprintf(stderr
, "httpget: cannot read from socket\n");
157 PRStatus
FastFetchFile(PRFileDesc
*in
, PRFileDesc
*out
, PRUint32 size
)
164 PRUint32 bytesToRead
;
169 outfMap
= PR_CreateFileMap(out
, sz64
, PR_PROT_READWRITE
);
171 addr
= PR_MemMap(outfMap
, LL_ZERO
, size
);
173 fprintf(stderr
, "cannot memory-map file: (%d, %d)\n", PR_GetError(),
176 PR_CloseFileMap(outfMap
);
179 start
= (char *) addr
;
181 while ((nBytes
= DrainInputBuffer(start
, rem
)) > 0) {
186 /* Input buffer is empty and end of stream */
189 bytesToRead
= (rem
< FCOPY_BUFFER_SIZE
) ? rem
: FCOPY_BUFFER_SIZE
;
190 while (rem
> 0 && (nBytes
= PR_Read(in
, start
, bytesToRead
)) > 0) {
193 bytesToRead
= (rem
< FCOPY_BUFFER_SIZE
) ? rem
: FCOPY_BUFFER_SIZE
;
196 fprintf(stderr
, "httpget: cannot read from socket\n");
199 rv
= PR_MemUnmap(addr
, size
);
200 PR_ASSERT(rv
== PR_SUCCESS
);
201 rv
= PR_CloseFileMap(outfMap
);
202 PR_ASSERT(rv
== PR_SUCCESS
);
206 PRStatus
ParseURL(char *url
, char *host
, PRUint32 hostSize
,
207 char *port
, PRUint32 portSize
, char *path
, PRUint32 pathSize
)
215 if (strncmp(url
, "http", 4)) {
216 fprintf(stderr
, "httpget: the protocol must be http\n");
219 if (strncmp(url
+ 4, "://", 3) || url
[7] == '\0') {
220 fprintf(stderr
, "httpget: malformed URL: %s\n", url
);
224 start
= end
= url
+ 7;
226 hostEnd
= host
+ hostSize
;
227 while (*end
&& *end
!= ':' && *end
!= '/') {
228 if (dst
== hostEnd
- 1) {
229 fprintf(stderr
, "httpget: host name too long\n");
237 PR_snprintf(port
, portSize
, "%d", 80);
238 PR_snprintf(path
, pathSize
, "%s", "/");
245 portEnd
= port
+ portSize
;
246 while (*end
&& *end
!= '/') {
247 if (dst
== portEnd
- 1) {
248 fprintf(stderr
, "httpget: port number too long\n");
255 PR_snprintf(path
, pathSize
, "%s", "/");
259 PR_snprintf(port
, portSize
, "%d", 80);
263 pathEnd
= path
+ pathSize
;
265 if (dst
== pathEnd
- 1) {
266 fprintf(stderr
, "httpget: file pathname too long\n");
275 void PrintUsage(void) {
276 fprintf(stderr
, "usage: httpget url\n"
277 " httpget -o outputfile url\n"
278 " httpget url -o outputfile\n");
281 int main(int argc
, char **argv
)
284 char buf
[PR_NETDB_BUF_SIZE
];
286 PRFileDesc
*socket
= NULL
, *file
= NULL
;
288 char host
[HOST_SIZE
];
289 char port
[PORT_SIZE
];
290 char path
[PATH_SIZE
];
291 char line
[LINE_SIZE
];
293 PRBool endOfHeader
= PR_FALSE
;
295 char *fileName
= NULL
;
298 if (argc
!= 2 && argc
!= 4) {
305 * case 1: httpget url
309 if (strcmp(argv
[1], "-o") == 0) {
311 * case 2: httpget -o outputfile url
317 * case 3: httpget url -o outputfile
320 if (strcmp(argv
[2], "-o") != 0) {
328 if (ParseURL(url
, host
, sizeof(host
), port
, sizeof(port
),
329 path
, sizeof(path
)) == PR_FAILURE
) {
333 if (PR_GetHostByName(host
, buf
, sizeof(buf
), &hostentry
)
335 fprintf(stderr
, "httpget: unknown host name: %s\n", host
);
339 addr
.inet
.family
= PR_AF_INET
;
340 addr
.inet
.port
= PR_htons((short) atoi(port
));
341 addr
.inet
.ip
= *((PRUint32
*) hostentry
.h_addr_list
[0]);
343 socket
= PR_NewTCPSocket();
344 if (socket
== NULL
) {
345 fprintf(stderr
, "httpget: cannot create new tcp socket\n");
349 if (PR_Connect(socket
, &addr
, PR_INTERVAL_NO_TIMEOUT
) == PR_FAILURE
) {
350 fprintf(stderr
, "httpget: cannot connect to http server\n");
355 if (fileName
== NULL
) {
358 file
= PR_Open(fileName
, PR_RDWR
| PR_CREATE_FILE
| PR_TRUNCATE
,
361 fprintf(stderr
, "httpget: cannot open file %s: (%d, %d)\n",
362 fileName
, PR_GetError(), PR_GetOSError());
368 cmdSize
= PR_snprintf(buf
, sizeof(buf
), "GET %s HTTP/1.0\r\n\r\n", path
);
369 PR_ASSERT(cmdSize
== (PRIntn
) strlen("GET HTTP/1.0\r\n\r\n")
370 + (PRIntn
) strlen(path
));
371 if (PR_Write(socket
, buf
, cmdSize
) != cmdSize
) {
372 fprintf(stderr
, "httpget: cannot write to http server\n");
377 if (ReadLine(socket
, line
, sizeof(line
)) <= 0) {
378 fprintf(stderr
, "httpget: cannot read line from http server\n");
383 /* HTTP response: 200 == OK */
384 if (strstr(line
, "200") == NULL
) {
385 fprintf(stderr
, "httpget: %s\n", line
);
390 while (ReadLine(socket
, line
, sizeof(line
)) > 0) {
391 if (line
[0] == '\n') {
392 endOfHeader
= PR_TRUE
;
395 if (strncmp(line
, "Content-Length", 14) == 0
396 || strncmp(line
, "Content-length", 14) == 0) {
399 while (*p
== ' ' || *p
== '\t') {
406 while (*p
== ' ' || *p
== '\t') {
410 while ('0' <= *p
&& *p
<= '9') {
411 fileSize
= 10 * fileSize
+ (*p
- '0');
416 if (endOfHeader
== PR_FALSE
) {
417 fprintf(stderr
, "httpget: cannot read line from http server\n");
422 if (fileName
== NULL
|| fileSize
== 0) {
423 FetchFile(socket
, file
);
425 FastFetchFile(socket
, file
, fileSize
);
429 if (socket
) PR_Close(socket
);
430 if (file
) PR_Close(file
);