Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/*****************************************************************************
* _ _ ____ _
* 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@haxx.nu>
*
* http://curl.haxx.nu
*
* $Source$
* $Revision$
* $Date$
* $Author$
* $State$
* $Locker$
*
* ------------------------------------------------------------
****************************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "setup.h"
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#include "urldata.h"
#include <curl/curl.h>
#ifdef __BEOS__
#include <net/socket.h>
#endif
#ifdef WIN32
#if !defined( __GNUC__) || defined(__MINGW32__)
#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"
#ifdef USE_ZLIB
#include <zlib.h>
#endif
#define MAX(x,y) ((x)>(y)?(x):(y))
Daniel Stenberg
committed
/* --- download and upload a stream from/to a socket --- */
Daniel Stenberg
committed
/* Parts of this function was brought to us by the friendly Mark Butler
<butlerm@xmission.com>. */
Daniel Stenberg
committed
Transfer (struct UrlData *data,
/* READ stuff */
int sockfd, /* socket to read from or -1 */
int size, /* -1 if unknown at this point */
bool getheader, /* TRUE if header parsing is wanted */
Daniel Stenberg
committed
long *bytecountp, /* return number of bytes read or NULL */
/* WRITE stuff */
int writesockfd, /* socket to write to, it may very well be
the same we read from. -1 disables */
long *writebytecountp /* return number of bytes written or NULL */
)
{
char *buf = data->buffer;
size_t nread;
Daniel Stenberg
committed
int bytecount = 0; /* number of bytes read */
int writebytecount = 0; /* number of bytes written */
long contentlength=0; /* size of incoming data */
struct timeval start = tvnow();
struct timeval now = start;
Daniel Stenberg
committed
bool header = TRUE; /* incoming data has HTTP header */
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;
time_t timeofdoc=0;
long bodywrites=0;
char newurl[URL_MAX_LENGTH]; /* buffer for Location: URL */
Daniel Stenberg
committed
/* the highest fd we use + 1 */
int maxfd = (sockfd>writesockfd?sockfd:writesockfd)+1;
hbufp = data->headerbuff;
myalarm (0); /* switch off the alarm-style timeout */
now = tvnow();
start = now;
#define KEEP_READ 1
#define KEEP_WRITE 2
if(size > 0)
pgrsSetDownloadSize(data, size);
Daniel Stenberg
committed
fd_set writefd;
fd_set rkeepfd;
fd_set wkeepfd;
/* 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 */
Daniel Stenberg
committed
if(sockfd != -1) {
FD_SET (sockfd, &readfd); /* read socket */
Daniel Stenberg
committed
}
FD_ZERO (&writefd); /* clear it */
if(writesockfd != -1) {
FD_SET (writesockfd, &writefd); /* write socket */
Daniel Stenberg
committed
}
/* get these in backup variables to be able to restore them on each lap in
the select() loop */
rkeepfd = readfd;
wkeepfd = writefd;
Daniel Stenberg
committed
readfd = rkeepfd; /* set those every lap in the loop */
writefd = wkeepfd;
interval.tv_sec = 1;
Daniel Stenberg
committed
switch (select (maxfd, &readfd, &writefd, NULL, &interval)) {
case -1: /* select() error, stop reading */
#ifdef EINTR
/* The EINTR is not serious, and it seems you might get this more
ofen when using the lib in a multi-threaded environment! */
if(errno == EINTR)
;
else
#endif
keepon = 0; /* no more read or write */
Daniel Stenberg
committed
default:
if((keepon & KEEP_READ) && FD_ISSET(sockfd, &readfd)) {
Daniel Stenberg
committed
/* read! */
Daniel Stenberg
committed
if (data->use_ssl) {
nread = SSL_read (data->ssl, buf, BUFSIZE - 1);
}
else {
Daniel Stenberg
committed
nread = sread (sockfd, buf, BUFSIZE - 1);
Daniel Stenberg
committed
}
Daniel Stenberg
committed
/* NULL terminate, allowing string ops to be used */
if (0 < (signed int) nread)
buf[nread] = 0;
Daniel Stenberg
committed
/* if we receive 0 or less here, the server closed the connection and
we bail out from this! */
else if (0 >= (signed int) nread) {
Daniel Stenberg
committed
break;
}
Daniel Stenberg
committed
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. */
Daniel Stenberg
committed
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
/* 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 */
int str_length = (int)strlen(str);
if (hbuflen + (int)str_length >= data->headersize) {
char *newbuff;
long newsize=MAX((hbuflen+str_length)*3/2,
data->headersize*2);
hbufp_index = hbufp - data->headerbuff;
newbuff = (char *)realloc(data->headerbuff, newsize);
if(!newbuff) {
failf (data, "Failed to alloc memory for big header!");
return URG_READ_ERROR;
}
data->headersize=newsize;
data->headerbuff = newbuff;
hbufp = data->headerbuff + hbufp_index;
}
strcpy (hbufp, str);
hbufp += strlen (str);
hbuflen += strlen (str);
break; /* read more and try again */
}
Daniel Stenberg
committed
str = end_ptr + 1; /* move just past new line */
Daniel Stenberg
committed
if (hbuflen + (str - str_start) >= data->headersize) {
Daniel Stenberg
committed
long newsize=MAX((hbuflen+(str-str_start))*3/2,
data->headersize*2);
hbufp_index = hbufp - data->headerbuff;
newbuff = (char *)realloc(data->headerbuff, newsize);
if(!newbuff) {
failf (data, "Failed to alloc memory for big header!");
return URG_READ_ERROR;
}
Daniel Stenberg
committed
data->headersize= newsize;
data->headerbuff = newbuff;
hbufp = data->headerbuff + hbufp_index;
}
Daniel Stenberg
committed
/* 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 */
pgrsSetDownloadSize(data, size);
Daniel Stenberg
committed
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((p - data->headerbuff) !=
data->fwrite (data->headerbuff, 1,
p - data->headerbuff, data->out)) {
failf (data, "Failed writing output");
return URG_WRITE_ERROR;
}
}
if(data->writeheader) {
/* obviously, the header is requested to be written to
this file: */
if((p - data->headerbuff) !=
data->fwrite (data->headerbuff, 1, p - data->headerbuff,
data->writeheader)) {
Daniel Stenberg
committed
failf (data, "Failed writing output");
return URG_WRITE_ERROR;
}
}
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 (
( ((data->conf & CONF_FOLLOWLOCATION) && (code >= 400))
||
!(data->conf & CONF_FOLLOWLOCATION) && (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;
}
Daniel Stenberg
committed
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
}
else {
header = FALSE; /* this is not a header line */
break;
}
}
/* check for Content-Length: header lines to get size */
if (strnequal("Content-Length", p, 14) &&
sscanf (p+14, ": %ld", &contentlength))
size = contentlength;
else if (strnequal("Content-Range", p, 13) &&
sscanf (p+13, ": 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, TRUE, &p[12]);
}
else if(strnequal("Last-Modified:", p,
strlen("Last-Modified:")) &&
data->timecondition) {
time_t secs=time(NULL);
timeofdoc = get_date(p+strlen("Last-Modified:"), &secs);
}
else if ((code >= 300 && code < 400) &&
(data->conf & CONF_FOLLOWLOCATION) &&
strnequal("Location", p, 8) &&
sscanf (p+8, ": %" URL_MAX_LENGTH_TXT "s", newurl)) {
/* this is the URL that the server advices us to get
instead */
data->newurl = strdup (newurl);
}
if (data->conf & CONF_HEADER) {
if(hbuflen != data->fwrite (p, 1, hbuflen, data->out)) {
failf (data, "Failed writing output");
return URG_WRITE_ERROR;
}
Daniel Stenberg
committed
}
Daniel Stenberg
committed
/* the header is requested to be written to this file */
if(hbuflen != data->fwrite (p, 1, hbuflen,
data->writeheader)) {
failf (data, "Failed writing output");
return URG_WRITE_ERROR;
}
}
Daniel Stenberg
committed
/* reset hbufp pointer && hbuflen */
hbufp = data->headerbuff;
hbuflen = 0;
Daniel Stenberg
committed
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);
Daniel Stenberg
committed
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
} /* 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(0 == bodywrites) {
/* These checks are only made the first time we are about to
write a chunk of the body */
if(data->conf&CONF_HTTP) {
/* HTTP-only checks */
if (data->resume_from && !content_range ) {
/* 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;
}
else if(data->timecondition && !data->range) {
/* A time condition has been set AND no ranges have been
requested. This seems to be what chapter 13.3.4 of
RFC 2616 defines to be the correct action for a
HTTP/1.1 client */
if((timeofdoc > 0) && (data->timevalue > 0)) {
switch(data->timecondition) {
case TIMECOND_IFMODSINCE:
default:
if(timeofdoc < data->timevalue) {
infof(data,
"The requested document is not new enough");
return URG_OK;
}
break;
case TIMECOND_IFUNMODSINCE:
if(timeofdoc > data->timevalue) {
infof(data,
"The requested document is not old enough");
return URG_OK;
}
break;
} /* switch */
} /* two valid time strings */
} /* we have a time condition */
} /* this is HTTP */
} /* this is the first time we write a body part */
bodywrites++;
if(data->maxdownload &&
(bytecount + nread > data->maxdownload)) {
nread = data->maxdownload - bytecount;
if(nread < 0 ) /* this should be unusual */
nread = 0;
keepon &= ~KEEP_READ; /* we're done reading */
Daniel Stenberg
committed
}
bytecount += nread;
pgrsSetDownloadCounter(data, (double)bytecount);
Daniel Stenberg
committed
if (nread != data->fwrite (str, 1, nread, data->out)) {
failf (data, "Failed writing output");
return URG_WRITE_ERROR;
Daniel Stenberg
committed
} /* if (! header and data to read ) */
} /* if( read from socket ) */
if((keepon & KEEP_WRITE) && FD_ISSET(writesockfd, &writefd)) {
Daniel Stenberg
committed
/* write */
char scratch[BUFSIZE * 2];
int i, si;
int bytes_written;
if(data->crlf)
buf = data->buffer; /* put it back on the buffer */
nread = data->fread(buf, 1, BUFSIZE, data->in);
writebytecount += nread;
pgrsSetUploadCounter(data, (double)writebytecount);
Daniel Stenberg
committed
if (nread<=0) {
/* done */
keepon &= ~KEEP_WRITE; /* we're done writing */
Daniel Stenberg
committed
break;
}
/* convert LF to CRLF if so asked */
if (data->crlf) {
for(i = 0, si = 0; i < (int)nread; i++, si++) {
if (buf[i] == 0x0a) {
scratch[si++] = 0x0d;
scratch[si] = 0x0a;
Daniel Stenberg
committed
else {
scratch[si] = buf[i];
Daniel Stenberg
committed
}
nread = si;
buf = scratch; /* point to the new buffer */
Daniel Stenberg
committed
/* write to socket */
#ifdef USE_SSLEAY
if (data->use_ssl) {
bytes_written = SSL_write(data->ssl, buf, nread);
}
else {
#endif
bytes_written = swrite(writesockfd, buf, nread);
#ifdef USE_SSLEAY
}
#endif /* USE_SSLEAY */
if(nread != bytes_written) {
failf(data, "Failed uploading data");
return URG_WRITE_ERROR;
}
Daniel Stenberg
committed
}
Daniel Stenberg
committed
break;
Daniel Stenberg
committed
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
}
}
Daniel Stenberg
committed
if(!(data->conf&CONF_NOBODY) && contentlength &&
(bytecount != contentlength)) {
failf(data, "transfer closed with %d bytes remaining to read",
contentlength-bytecount);
Daniel Stenberg
committed
if(bytecountp)
*bytecountp = bytecount; /* read count */
if(writebytecountp)
*writebytecountp = writebytecount; /* write count */