1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
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/
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
15 * The Original Code is the Netscape Portable Runtime (NSPR).
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.
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.
36 * ***** END LICENSE BLOCK ***** */
40 * Author: Wan-Teh Chang
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.
46 * This is a single-threaded program.
58 #include <stdlib.h> /* for atoi */
60 #define FCOPY_BUFFER_SIZE (16 * 1024)
61 #define INPUT_BUFFER_SIZE 1024
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.
74 static char inputBuf
[INPUT_BUFFER_SIZE
];
76 * inputBufEnd points just past the end of inputBuf
78 static char *inputBufEnd
= inputBuf
+ sizeof(inputBuf
);
79 static char *inputHead
= inputBuf
;
80 static char *inputTail
= inputBuf
;
82 static PRBool endOfStream
= PR_FALSE
;
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
95 PRInt32
ReadLine(PRFileDesc
*fd
, char *buf
, PRUint32 bufSize
)
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 */
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') {
110 } else if (*inputHead
== '\n') {
112 if (crPtr
== dst
- 1) {
116 *(dst
++) = *(inputHead
++);
118 if (lineFound
== PR_TRUE
|| dst
== bufEnd
- 1 || endOfStream
== PR_TRUE
) {
124 * The input buffer should be empty now
126 PR_ASSERT(inputHead
== inputTail
);
128 nRead
= PR_Read(fd
, inputBuf
, sizeof(inputBuf
));
132 } else if (nRead
== 0) {
133 endOfStream
= PR_TRUE
;
137 inputHead
= inputBuf
;
138 inputTail
= inputBuf
+ nRead
;
142 PRInt32
DrainInputBuffer(char *buf
, PRUint32 bufSize
)
144 PRInt32 nBytes
= inputTail
- inputHead
;
153 if ((PRInt32
) bufSize
< nBytes
) {
156 memcpy(buf
, inputHead
, nBytes
);
161 PRStatus
FetchFile(PRFileDesc
*in
, PRFileDesc
*out
)
163 char buf
[FCOPY_BUFFER_SIZE
];
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");
173 /* Input buffer is empty and end of stream */
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");
183 fprintf(stderr
, "httpget: cannot read from socket\n");
189 PRStatus
FastFetchFile(PRFileDesc
*in
, PRFileDesc
*out
, PRUint32 size
)
196 PRUint32 bytesToRead
;
201 outfMap
= PR_CreateFileMap(out
, sz64
, PR_PROT_READWRITE
);
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(),
208 PR_CloseFileMap(outfMap
);
211 PR_ASSERT(addr
!= (void *) -1);
212 start
= (char *) addr
;
214 while ((nBytes
= DrainInputBuffer(start
, rem
)) > 0) {
219 /* Input buffer is empty and end of stream */
222 bytesToRead
= (rem
< FCOPY_BUFFER_SIZE
) ? rem
: FCOPY_BUFFER_SIZE
;
223 while (rem
> 0 && (nBytes
= PR_Read(in
, start
, bytesToRead
)) > 0) {
226 bytesToRead
= (rem
< FCOPY_BUFFER_SIZE
) ? rem
: FCOPY_BUFFER_SIZE
;
229 fprintf(stderr
, "httpget: cannot read from socket\n");
232 rv
= PR_MemUnmap(addr
, size
);
233 PR_ASSERT(rv
== PR_SUCCESS
);
234 rv
= PR_CloseFileMap(outfMap
);
235 PR_ASSERT(rv
== PR_SUCCESS
);
239 PRStatus
ParseURL(char *url
, char *host
, PRUint32 hostSize
,
240 char *port
, PRUint32 portSize
, char *path
, PRUint32 pathSize
)
248 if (strncmp(url
, "http", 4)) {
249 fprintf(stderr
, "httpget: the protocol must be http\n");
252 if (strncmp(url
+ 4, "://", 3) || url
[7] == '\0') {
253 fprintf(stderr
, "httpget: malformed URL: %s\n", url
);
257 start
= end
= url
+ 7;
259 hostEnd
= host
+ hostSize
;
260 while (*end
&& *end
!= ':' && *end
!= '/') {
261 if (dst
== hostEnd
- 1) {
262 fprintf(stderr
, "httpget: host name too long\n");
270 PR_snprintf(port
, portSize
, "%d", 80);
271 PR_snprintf(path
, pathSize
, "%s", "/");
278 portEnd
= port
+ portSize
;
279 while (*end
&& *end
!= '/') {
280 if (dst
== portEnd
- 1) {
281 fprintf(stderr
, "httpget: port number too long\n");
288 PR_snprintf(path
, pathSize
, "%s", "/");
292 PR_snprintf(port
, portSize
, "%d", 80);
296 pathEnd
= path
+ pathSize
;
298 if (dst
== pathEnd
- 1) {
299 fprintf(stderr
, "httpget: file pathname too long\n");
308 void PrintUsage(void) {
309 fprintf(stderr
, "usage: httpget url\n"
310 " httpget -o outputfile url\n"
311 " httpget url -o outputfile\n");
314 int main(int argc
, char **argv
)
317 char buf
[PR_NETDB_BUF_SIZE
];
319 PRFileDesc
*socket
= NULL
, *file
= NULL
;
321 char host
[HOST_SIZE
];
322 char port
[PORT_SIZE
];
323 char path
[PATH_SIZE
];
324 char line
[LINE_SIZE
];
326 PRBool endOfHeader
= PR_FALSE
;
328 char *fileName
= NULL
;
331 if (argc
!= 2 && argc
!= 4) {
338 * case 1: httpget url
342 if (strcmp(argv
[1], "-o") == 0) {
344 * case 2: httpget -o outputfile url
350 * case 3: httpget url -o outputfile
353 if (strcmp(argv
[2], "-o") != 0) {
361 if (ParseURL(url
, host
, sizeof(host
), port
, sizeof(port
),
362 path
, sizeof(path
)) == PR_FAILURE
) {
366 if (PR_GetHostByName(host
, buf
, sizeof(buf
), &hostentry
)
368 fprintf(stderr
, "httpget: unknown host name: %s\n", host
);
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]);
376 socket
= PR_NewTCPSocket();
377 if (socket
== NULL
) {
378 fprintf(stderr
, "httpget: cannot create new tcp socket\n");
382 if (PR_Connect(socket
, &addr
, PR_INTERVAL_NO_TIMEOUT
) == PR_FAILURE
) {
383 fprintf(stderr
, "httpget: cannot connect to http server\n");
388 if (fileName
== NULL
) {
391 file
= PR_Open(fileName
, PR_RDWR
| PR_CREATE_FILE
| PR_TRUNCATE
,
394 fprintf(stderr
, "httpget: cannot open file %s: (%d, %d)\n",
395 fileName
, PR_GetError(), PR_GetOSError());
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");
410 if (ReadLine(socket
, line
, sizeof(line
)) <= 0) {
411 fprintf(stderr
, "httpget: cannot read line from http server\n");
416 /* HTTP response: 200 == OK */
417 if (strstr(line
, "200") == NULL
) {
418 fprintf(stderr
, "httpget: %s\n", line
);
423 while (ReadLine(socket
, line
, sizeof(line
)) > 0) {
424 if (line
[0] == '\n') {
425 endOfHeader
= PR_TRUE
;
428 if (strncmp(line
, "Content-Length", 14) == 0
429 || strncmp(line
, "Content-length", 14) == 0) {
432 while (*p
== ' ' || *p
== '\t') {
439 while (*p
== ' ' || *p
== '\t') {
443 while ('0' <= *p
&& *p
<= '9') {
444 fileSize
= 10 * fileSize
+ (*p
- '0');
449 if (endOfHeader
== PR_FALSE
) {
450 fprintf(stderr
, "httpget: cannot read line from http server\n");
455 if (fileName
== NULL
|| fileSize
== 0) {
456 FetchFile(socket
, file
);
458 FastFetchFile(socket
, file
, fileSize
);
462 if (socket
) PR_Close(socket
);
463 if (file
) PR_Close(file
);