Bug 564953: Port YARR! Lands macroassembler. (r=gal)
[mozilla-central.git] / nsprpub / tools / httpget.c
blob774a4c7a7b322a0cab2170f077c85a45871ebac0
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
13 * License.
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.
22 * Contributor(s):
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.
49 #include "prio.h"
50 #include "prnetdb.h"
51 #include "prlog.h"
52 #include "prerror.h"
53 #include "prprf.h"
54 #include "prinit.h"
56 #include <stdio.h>
57 #include <string.h>
58 #include <stdlib.h> /* for atoi */
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
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;
85 * ReadLine --
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.
95 PRInt32 ReadLine(PRFileDesc *fd, char *buf, PRUint32 bufSize)
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;
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--;
116 *(dst++) = *(inputHead++);
118 if (lineFound == PR_TRUE || dst == bufEnd - 1 || endOfStream == PR_TRUE) {
119 *dst = '\0';
120 return dst - buf;
124 * The input buffer should be empty now
126 PR_ASSERT(inputHead == inputTail);
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;
137 inputHead = inputBuf;
138 inputTail = inputBuf + nRead;
139 goto loop;
142 PRInt32 DrainInputBuffer(char *buf, PRUint32 bufSize)
144 PRInt32 nBytes = inputTail - inputHead;
146 if (nBytes == 0) {
147 if (endOfStream) {
148 return -1;
149 } else {
150 return 0;
153 if ((PRInt32) bufSize < nBytes) {
154 nBytes = bufSize;
156 memcpy(buf, inputHead, nBytes);
157 inputHead += nBytes;
158 return nBytes;
161 PRStatus FetchFile(PRFileDesc *in, PRFileDesc *out)
163 char buf[FCOPY_BUFFER_SIZE];
164 PRInt32 nBytes;
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;
172 if (nBytes < 0) {
173 /* Input buffer is empty and end of stream */
174 return PR_SUCCESS;
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;
182 if (nBytes < 0) {
183 fprintf(stderr, "httpget: cannot read from socket\n");
184 return PR_FAILURE;
186 return PR_SUCCESS;
189 PRStatus FastFetchFile(PRFileDesc *in, PRFileDesc *out, PRUint32 size)
191 PRInt32 nBytes;
192 PRFileMap *outfMap;
193 void *addr;
194 char *start;
195 PRUint32 rem;
196 PRUint32 bytesToRead;
197 PRStatus rv;
198 PRInt64 sz64;
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());
208 PR_CloseFileMap(outfMap);
209 return PR_FAILURE;
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;
218 if (nBytes < 0) {
219 /* Input buffer is empty and end of stream */
220 return PR_SUCCESS;
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;
228 if (nBytes < 0) {
229 fprintf(stderr, "httpget: cannot read from socket\n");
230 return PR_FAILURE;
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;
239 PRStatus ParseURL(char *url, char *host, PRUint32 hostSize,
240 char *port, PRUint32 portSize, char *path, PRUint32 pathSize)
242 char *start, *end;
243 char *dst;
244 char *hostEnd;
245 char *portEnd;
246 char *pathEnd;
248 if (strncmp(url, "http", 4)) {
249 fprintf(stderr, "httpget: the protocol must be http\n");
250 return PR_FAILURE;
252 if (strncmp(url + 4, "://", 3) || url[7] == '\0') {
253 fprintf(stderr, "httpget: malformed URL: %s\n", url);
254 return PR_FAILURE;
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;
265 *(dst++) = *(end++);
267 *dst = '\0';
269 if (*end == '\0') {
270 PR_snprintf(port, portSize, "%d", 80);
271 PR_snprintf(path, pathSize, "%s", "/");
272 return PR_SUCCESS;
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;
284 *(dst++) = *(end++);
286 *dst = '\0';
287 if (*end == '\0') {
288 PR_snprintf(path, pathSize, "%s", "/");
289 return PR_SUCCESS;
291 } else {
292 PR_snprintf(port, portSize, "%d", 80);
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;
302 *(dst++) = *(end++);
304 *dst = '\0';
305 return PR_SUCCESS;
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)
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;
331 if (argc != 2 && argc != 4) {
332 PrintUsage();
333 exit(1);
336 if (argc == 2) {
338 * case 1: httpget url
340 url = argv[1];
341 } else {
342 if (strcmp(argv[1], "-o") == 0) {
344 * case 2: httpget -o outputfile url
346 fileName = argv[2];
347 url = argv[3];
348 } else {
350 * case 3: httpget url -o outputfile
352 url = argv[1];
353 if (strcmp(argv[2], "-o") != 0) {
354 PrintUsage();
355 exit(1);
357 fileName = argv[3];
361 if (ParseURL(url, host, sizeof(host), port, sizeof(port),
362 path, sizeof(path)) == PR_FAILURE) {
363 exit(1);
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);
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");
379 exit(1);
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;
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;
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;
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;
416 /* HTTP response: 200 == OK */
417 if (strstr(line, "200") == NULL) {
418 fprintf(stderr, "httpget: %s\n", line);
419 exitStatus = 1;
420 goto done;
423 while (ReadLine(socket, line, sizeof(line)) > 0) {
424 if (line[0] == '\n') {
425 endOfHeader = PR_TRUE;
426 break;
428 if (strncmp(line, "Content-Length", 14) == 0
429 || strncmp(line, "Content-length", 14) == 0) {
430 char *p = line + 14;
432 while (*p == ' ' || *p == '\t') {
433 p++;
435 if (*p != ':') {
436 continue;
438 p++;
439 while (*p == ' ' || *p == '\t') {
440 p++;
442 fileSize = 0;
443 while ('0' <= *p && *p <= '9') {
444 fileSize = 10 * fileSize + (*p - '0');
445 p++;
449 if (endOfHeader == PR_FALSE) {
450 fprintf(stderr, "httpget: cannot read line from http server\n");
451 exitStatus = 1;
452 goto done;
455 if (fileName == NULL || fileSize == 0) {
456 FetchFile(socket, file);
457 } else {
458 FastFetchFile(socket, file, fileSize);
461 done:
462 if (socket) PR_Close(socket);
463 if (file) PR_Close(file);
464 PR_Cleanup();
465 return exitStatus;