/*****************************************************************************
 *                                  _   _ ____  _     
 *  Project                     ___| | | |  _ \| |    
 *                             / __| | | | |_) | |    
 *                            | (__| |_| |  _ <| |___ 
 *                             \___|\___/|_| \_\_____|
 *
 *  The contents of this file are subject to the Mozilla Public License
 *  Version 1.0 (the "License"); you may not use this file except in
 *  compliance with the License. You may obtain a copy of the License at
 *  http://www.mozilla.org/MPL/
 *
 *  Software distributed under the License is distributed on an "AS IS"
 *  basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 *  License for the specific language governing rights and limitations
 *  under the License.
 *
 *  The Original Code is Curl.
 *
 *  The Initial Developer of the Original Code is Daniel Stenberg.
 *
 *  Portions created by the Initial Developer are Copyright (C) 1998.
 *  All Rights Reserved.
 *
 * ------------------------------------------------------------
 * Main author:
 * - Daniel Stenberg <Daniel.Stenberg@sth.frontec.se>
 *
 * 	http://www.fts.frontec.se/~dast/curl/
 *
 * $Source: /cvs/curl/curl/lib/download.c,v $
 * $Revision: 1.10 $
 * $Date: 1999/04/19 22:21:20 $
 * $Author: dast $
 * $State: Exp $
 * $Locker:  $
 *
 * ------------------------------------------------------------
 ****************************************************************************/

#include <stdio.h>
#include <string.h>

#include "../include/setup.h"

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif

#include "urldata.h"
#include "../include/curl.h"

#ifdef WIN32
#ifndef __GNUC__
#include <winsock.h>
#endif
#include <time.h> /* for the time_t typedef! */

#if defined(__GNUC__) && defined(TIME_WITH_SYS_TIME)
#include <sys/time.h>
#endif

#endif

#include "progress.h"
#include "speedcheck.h"
#include "sendf.h"

/* --- download a stream from a socket --- */

/* This newly edited version of Download() was brought to us by the friendly
   Mark Butler <butlerm@xmission.com>. Re-indented with the indent command. */

UrgError 
Download (struct UrlData *data,
	  int sockfd,		/* socket to read from */
	  int size,		/* -1 if unknown at this point */
	  bool getheader,	/* TRUE if header parsing is wanted */
	  long *bytecountp	/* return number of bytes read */
)
{
  char *buf = data->buffer;
  size_t nread;
  int bytecount = 0;
  long contentlength=0;
  struct timeval start = tvnow();
  struct timeval now = start;
  bool header = TRUE;
  int headerline = 0;		/* counts header lines to better track the first one */

  char *hbufp;			/* points at *end* of header line */
  int hbuflen = 0;
  char *str;			/* within buf */
  char *str_start;		/* within buf */
  char *end_ptr;		/* within buf */
  char *p;			/* within headerbuff */
  bool content_range = FALSE;	/* set TRUE if Content-Range: was found */
  int offset = 0;		/* possible resume offset read from the
                                   Content-Range: header */
  int code = 0;			/* error code from the 'HTTP/1.? XXX' line */

  /* for the low speed checks: */
  UrgError urg;

  char newurl[256];		/* buffer for Location: URL */

  hbufp = data->headerbuff;

  myalarm (0);			/* switch off the alarm-style timeout */

  now = tvnow();
  start = now;

  if (!getheader) {
    header = FALSE;
    ProgressInit (data, size);
  }
  {
    fd_set readfd;
    fd_set keepfd;
    struct timeval interval;
    bool keepon = TRUE;

    /* timeout every X second
       - makes a better progressmeter (i.e even when no data is read, the
       meter can be updated and reflect reality)
       - allows removal of the alarm() crap
       - variable timeout is easier
     */

    FD_ZERO (&readfd);		/* clear it */
    FD_SET (sockfd, &readfd);

    keepfd = readfd;

    while (keepon) {
      readfd = keepfd;		/* set this every lap in the loop */
      interval.tv_sec = 2;
      interval.tv_usec = 0;

      switch (select (sockfd + 1, &readfd, NULL, NULL, &interval)) {
      case -1:			/* error, stop reading */
	keepon = FALSE;
	continue;
      case 0:			/* timeout */
	break;
      default:			/* read! */
#ifndef USE_SSLEAY
	nread = sread (sockfd, buf, BUFSIZE - 1);
#else
	if (data->use_ssl) {
	  nread = SSL_read (data->ssl, buf, BUFSIZE - 1);
	}
	else {
	  nread = sread (sockfd, buf, BUFSIZE - 1);
	}
#endif /* USE_SSLEAY */

#if 0
	fprintf (stderr, "Got %d\n", nread);
#endif
	/* NULL terminate, allowing string ops to be used */
	if (0 <= (signed int) nread)
	  buf[nread] = 0;

	/* if we receive 0 here, the server closed the connection and we
	   bail out from this! */
	if (nread == 0) {
	  keepon = FALSE;
	  break;
	}

	str = buf;		/* Default buffer to use when we write the
                                   buffer, it may be changed in the flow below
                                   before the actual storing is done. */

	/* Since this is a two-state thing, we check if we are parsing
	   headers at the moment or not. */

	if (header) {
	  /* we are in parse-the-header-mode */

	  /* header line within buffer loop */
	  do {
            int hbufp_index;

	    str_start = str;	/* str_start is start of line within buf */

	    end_ptr = strchr (str_start, '\n');

	    if (!end_ptr) {
	      /* no more complete header lines within buffer */
	      /* copy what is remaining into headerbuff */

	      if (hbuflen + strlen (str) >= data->headersize) {
                char *newbuff;
                hbufp_index = hbufp - data->headerbuff;
                newbuff = (char *)realloc(data->headerbuff, data->headersize*2);
                if(!newbuff) {
                  failf (data, "Failed to alloc memory for big header!");
                  return URG_READ_ERROR;
                }
                data->headersize*=2;
                data->headerbuff = newbuff;
                hbufp = data->headerbuff + hbufp_index;
	      }
	      strcpy (hbufp, str);
	      hbufp += strlen (str);
	      hbuflen += strlen (str);
	      break;		/* read more and try again */
	    }

	    str = end_ptr + 1;	/* move just past new line */

	    if (hbuflen + (str - str_start) >= data->headersize) {
              char *newbuff;
              hbufp_index = hbufp - data->headerbuff;
              newbuff = (char *)realloc(data->headerbuff, data->headersize*2);
              if(!newbuff) {
                failf (data, "Failed to alloc memory for big header!");
                return URG_READ_ERROR;
              }
              data->headersize*=2;
              data->headerbuff = newbuff;
              hbufp = data->headerbuff + hbufp_index;
	    }

	    /* copy to end of line */
	    strncpy (hbufp, str_start, str - str_start);
	    hbufp += str - str_start;
	    hbuflen += str - str_start;
	    *hbufp = 0;

	    p = data->headerbuff;

	    /* we now have a full line that p points to */
	    if (('\n' == *p) || ('\r' == *p)) {
	      /* Zero-length line means end of header! */
	      if (-1 != size)	/* if known */
		size += bytecount;	/* we append the already read size */


	      if ('\r' == *p)
		p++;		/* pass the \r byte */
	      if ('\n' == *p)
		p++;		/* pass the \n byte */

	      ProgressInit (data, size);	/* init progress meter */
	      header = FALSE;	/* no more header to parse! */

	      /* now, only output this if the header AND body are requested: */
	      if ((data->conf & (CONF_HEADER | CONF_NOBODY)) == CONF_HEADER) {
#if 0
		fprintf (stderr, "Wrote %d\n", p - data->headerbuff);
#endif
		data->fwrite (data->headerbuff, 1, p - data->headerbuff, data->out);
	      }
              if(data->writeheader) {
                /* obviously, the header is requested to be written to
                   this file: */
                data->fwrite (data->headerbuff, 1, p - data->headerbuff,
                              data->writeheader);
              }
	      break;		/* exit header line loop */
	    }

	    if (!headerline++) {
	      /* This is the first header, it MUST be the error code line
	         or else we consiser this to be the body right away! */
	      if (sscanf (p, " HTTP/1.%*c %3d", &code)) {
		/* 404 -> URL not found! */
		if ((code >= 300) && (data->conf & CONF_FAILONERROR)) {
		  /* If we have been told to fail hard on HTTP-errors,
		     here is the check for that: */
		  /* serious error, go home! */
		  failf (data, "The requested file was not found");
		  return URG_HTTP_NOT_FOUND;
		}
	      }
	      else {
		header = FALSE;	/* this is not a header line */
		break;
	      }
	    }
	    /* check for Content-Length: header lines to get size */
	    if (sscanf (p, "Content-%*cength: %d", &contentlength))
              size = contentlength;
	    else if (sscanf (p, "Content-Range: bytes %d-", &offset)) {
	      if (data->resume_from == offset) {
		/* we asked for a resume and we got it */
		content_range = TRUE;
	      }
	    }
            else if(data->cookies &&
                    strnequal("Set-Cookie: ", p, 11)) {
              cookie_add(data->cookies, &p[12]);
            }
	    else if ((code >= 300 && code < 400) &&
		     (data->conf & CONF_FOLLOWLOCATION) &&
		     sscanf (p, "Location: %255s", newurl)) {
	      /* this is the URL that the server advices us to get
	         instead */
	      data->newurl = strdup (newurl);
	    }

	    if (data->conf & CONF_HEADER) {
	      data->fwrite (p, 1, hbuflen, data->out);
	    }
            if(data->writeheader) {
              /* the header is requested to be written to this file */
              data->fwrite (p, 1, hbuflen, data->writeheader);
            }

	    /* reset hbufp pointer && hbuflen */
	    hbufp = data->headerbuff;
	    hbuflen = 0;
	  }
	  while (*str);		/* header line within buffer */

	  /* We might have reached the end of the header part here, but
	     there might be a non-header part left in the end of the read
	     buffer. */

	  if (!header) {
	    /* the next token and forward is not part of
	       the header! */

	    /* we subtract the remaining header size from the buffer */
	    nread -= (str - buf);
	  }

	}			/* end if header mode */

	/* This is not an 'else if' since it may be a rest from the header
	   parsing, where the beginning of the buffer is headers and the end
	   is non-headers. */
	if (str && !header && (nread > 0)) {

	  if (data->resume_from &&
              !content_range &&
              (data->conf&CONF_HTTP)) {
	    /* we wanted to resume a download, although the server doesn't
	       seem to support this */
	    failf (data, "HTTP server doesn't seem to support byte ranges. Cannot resume.");
	    return URG_HTTP_RANGE_ERROR;
	  }
	  else if (data->newurl) {
	    /* abort after the headers if "follow Location" is set */
	    infof (data, "Follow to new URL: %s\n", data->newurl);
	    return URG_OK;
	  }

	  bytecount += nread;
	  data->fwrite (str, 1, nread, data->out);
#ifdef CHECK_THIS_OUT
	  if (nread != data->fwrite (str, 1, nread, data->out)) {
	    failf (data, "Failed writing output");
	    return URG_WRITE_ERROR;
	  }
#endif
	}
	break;
      }
      now = tvnow();
      if (!header) {
	ProgressShow (data, bytecount, start, now, FALSE);
      }
      urg = speedcheck (data, now);
      if (urg)
	return urg;

      if (data->timeout && (tvdiff (now, start) > data->timeout)) {
	failf (data, "Operation timed out with %d out of %d bytes received",
	       bytecount, size);
	return URG_OPERATION_TIMEOUTED;
      }
#ifdef MULTIDOC
      if(contentlength && bytecount >= contentlength) {
        /* we're done with this download, now stop it */
        break;
      }
#endif
    }
  }

  ProgressShow (data, bytecount, start, now, TRUE);

  *bytecountp = bytecount;
  return URG_OK;
}


