home *** CD-ROM | disk | FTP | other *** search
/ Tools / WinSN5.0Ver.iso / NETSCAP.50 / WIN1998.ZIP / ns / nsprpub / tools / httpget.c next >
Encoding:
C/C++ Source or Header  |  1998-04-08  |  11.3 KB  |  447 lines

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