1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
3
* The contents of this file are subject to the Netscape Public
4
* License Version 1.1 (the "License"); you may not use this file
5
* except in compliance with the License. You may obtain a copy of
6
* the License at http://www.mozilla.org/NPL/
8
* Software distributed under the License is distributed on an "AS
9
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
10
* implied. See the License for the specific language governing
11
* rights and limitations under the License.
13
* The Original Code is Mozilla Communicator client code,
14
* released March 31, 1998.
16
* The Initial Developer of the Original Code is Netscape Communications
17
* Corporation. Portions created by Netscape are
18
* Copyright (C) 1998 Netscape Communications Corporation. All
22
* Samir Gehani <sgehani@netscape.com>
30
#include "nsHTTPConn.h"
33
const char kHTTPProto[8] = "http://";
34
const char kFTPProto[8] = "ftp://";
35
const int kHTTPPort = 80;
36
const int kFTPPort = 21;
37
const int kRespBufSize = 1024;
38
const int kReqBufSize = 4096;
39
const int kHdrBufSize = 4096;
40
const char kCRLF[3] = "\r\n";
41
const char kHdrBodyDelim[5] = "\r\n\r\n";
42
const char kDefaultDestFile[11] = "index.html";
44
nsHTTPConn::nsHTTPConn(char *aHost, int aPort, char *aPath, int (*aEventPumpCB)(void)):
45
mEventPumpCB(aEventPumpCB),
52
mHostPathAllocd(FALSE),
60
DUMP(("mHost = %s\n", mHost));
61
DUMP(("mPort = %d\n", mPort));
62
DUMP(("mPath = %s\n", mPath));
65
nsHTTPConn::nsHTTPConn(char *aHost, int aPort, char *aPath) :
73
mHostPathAllocd(FALSE),
81
DUMP(("mHost = %s\n", mHost));
82
DUMP(("mPort = %d\n", mPort));
83
DUMP(("mPath = %s\n", mPath));
86
nsHTTPConn::nsHTTPConn(char *aURL, int (*aEventPumpCB)(void)) :
87
mEventPumpCB(aEventPumpCB),
93
mHostPathAllocd(FALSE),
98
if (ParseURL(kHTTPProto, aURL, &mHost, &mPort, &mPath) == OK)
99
mHostPathAllocd = TRUE;
106
DUMP(("mHost = %s\n", mHost));
107
DUMP(("mPort = %d\n", mPort));
108
DUMP(("mPath = %s\n", mPath));
111
nsHTTPConn::nsHTTPConn(char *aURL) :
118
mHostPathAllocd(FALSE),
123
if (ParseURL(kHTTPProto, aURL, &mHost, &mPort, &mPath) == OK)
124
mHostPathAllocd = TRUE;
131
DUMP(("mHost = %s\n", mHost));
132
DUMP(("mPort = %d\n", mPort));
133
DUMP(("mPath = %s\n", mPath));
136
nsHTTPConn::~nsHTTPConn()
150
// verify host && path
151
if (!mHost || !mPath)
152
return E_MALFORMED_URL;
155
mSocket = new nsSocket(mHost, mPort, mEventPumpCB);
160
return mSocket->Open();
164
nsHTTPConn::ResumeOrGet(HTTPGetCB aCallback, char *aDestFile)
173
/* stat local file */
174
rv = stat(aDestFile, &stbuf);
176
resPos = stbuf.st_size;
178
return Get(aCallback, aDestFile, resPos);
181
// XXX handle proxies
185
nsHTTPConn::Get(HTTPGetCB aCallback, char *aDestFile)
187
// deprecated API; wrapper for backwards compatibility
189
return ResumeOrGet(aCallback, aDestFile);
193
nsHTTPConn::Get(HTTPGetCB aCallback, char *aDestFile, int aResumePos)
198
// verify host && path
199
if (!mHost || !mPath)
200
return E_MALFORMED_URL;
205
pathToUse = mProxiedURL;
209
// no leaf: assume default file 'index.html'
210
if (*(pathToUse + strlen(pathToUse) - 1) == '/')
211
aDestFile = (char *) kDefaultDestFile;
213
aDestFile = strrchr(pathToUse, '/') + 1;
217
rv = Request(aResumePos);
221
rv = Response(aCallback, aDestFile, aResumePos);
232
rv = mSocket->Close();
241
nsHTTPConn::SetProxyInfo(char *aProxiedURL, char *aProxyUser,
244
mProxiedURL = aProxiedURL;
245
mProxyUser = aProxyUser;
246
mProxyPswd = aProxyPswd;
250
nsHTTPConn::Request(int aResumePos)
252
char req[kReqBufSize];
253
char hdr[kHdrBufSize];
256
memset(req, 0, kReqBufSize);
258
// format header buf:
261
memset(hdr, 0, kHdrBufSize);
264
char *host = NULL, *path = NULL;
268
assert(sizeof hdr > (strlen(mProxiedURL) + 15 ));
270
sprintf(hdr, "GET %s HTTP/1.0%s", mProxiedURL, kCRLF);
272
if (strncmp(mProxiedURL, kFTPProto, strlen(kFTPProto)) == 0)
274
strcpy(proto,kFTPProto);
279
strcpy(proto,kHTTPProto);
282
rv = ParseURL(proto, mProxiedURL,
283
&host, &port, &path);
285
memset(hdr, 0, kHdrBufSize);
286
sprintf(hdr, "Host: %s:%d%s", host, port, kCRLF);
296
sprintf(hdr, "GET %s HTTP/1.0%s", mPath, kCRLF);
299
memset(hdr, 0, kHdrBufSize);
300
sprintf(hdr, "Host: %s%s", mHost, kCRLF);
304
// if proxy set and proxy user/pswd set
305
if (mProxyUser && mProxyPswd)
307
char *usrPsd = (char *) malloc(strlen(mProxyUser) +
309
strlen(mProxyPswd) + 1);
312
sprintf(usrPsd, "%s:%s", mProxyUser, mProxyPswd);
314
// base 64 encode proxy header
315
char usrPsdEncoded[128]; // pray that 128 is long enough
316
memset(usrPsdEncoded, 0, 128);
318
DUMP(("Unencoded string: %s\n", usrPsd));
319
rv = Base64Encode((const unsigned char *)usrPsd, strlen(usrPsd),
321
DUMP(("Encoded string: %s\n", usrPsdEncoded));
322
DUMP(("Base64Encode returned: %d\n", rv));
328
// append proxy header to header buf
329
memset(hdr, 0, kHdrBufSize);
330
sprintf(hdr, "Proxy-authorization: Basic %s%s", usrPsdEncoded, kCRLF);
333
// XXX append host with port 21 if ftp
337
// byte range support
340
sprintf(hdr, "Range: bytes=%d-%s", aResumePos, kCRLF);
344
// headers all done so indicate
347
// send header buf over socket
348
int bufSize = strlen(req);
349
rv = mSocket->Send((unsigned char *) req, &bufSize);
350
DUMP(("\n\n%s", req));
352
if (bufSize != (int) strlen(req))
353
rv = E_REQ_INCOMPLETE;
359
nsHTTPConn::Response(HTTPGetCB aCallback, char *aDestFile, int aResumePos)
361
// NOTE: overwrites dest file if it already exists
364
char resp[kRespBufSize];
365
int bufSize, total = 0, fwriteLen, fwrote, bytesWritten = 0, expectedSize = 0;
368
int bFirstIter = TRUE;
376
destFd = fopen(aDestFile, "r+b");
380
if (fseek(destFd, aResumePos, SEEK_SET) != 0)
388
destFd = fopen(aDestFile, "w+b");
393
// iteratively recv response
396
memset(resp, 0, kRespBufSize);
397
bufSize = kRespBufSize;
399
rv = mSocket->Recv((unsigned char *) resp, &bufSize);
400
DUMP(("nsSocket::Recv returned: %d\t and recd: %d\n", rv, bufSize));
402
if(rv == nsSocket::E_EOF_FOUND || (rv != nsSocket::E_READ_MORE && rv != nsSocket::OK) ) {
408
fwritePos = strstr(resp, kHdrBodyDelim);
409
if (fwritePos == NULL)
411
// XXX no header! should we handle?
417
ParseResponseCode((const char *)resp, &mResponseCode);
419
if ( mResponseCode < 200 || mResponseCode >=300 )
421
// if we don't get a response code in the 200 range then fail
422
// TODO: handle the response codes in the 300 range
423
rv = nsHTTPConn::E_HTTP_RESPONSE;
427
ParseContentLength((const char *)resp, &expectedSize);
429
// move past hdr-body delimiter
430
fwritePos += strlen(kHdrBodyDelim);
431
fwriteLen = bufSize - (fwritePos - resp);
432
total = expectedSize + aResumePos;
443
fwrote = fwrite(fwritePos, sizeof(char), fwriteLen, destFd);
444
assert(fwrote == fwriteLen);
447
bytesWritten += fwriteLen;
449
(aCallback(bytesWritten, total) == E_USER_CANCEL))
450
rv = E_USER_CANCEL; // we want to ignore all errors returned
451
// from aCallback() except E_USER_CANCEL
456
} while ( rv == nsSocket::E_READ_MORE || rv == nsSocket::OK);
458
if ( bytesWritten == expectedSize && rv != nsHTTPConn::E_HTTP_RESPONSE)
459
rv = nsSocket::E_EOF_FOUND;
461
if (rv == nsSocket::E_EOF_FOUND)
463
DUMP(("EOF detected\n"));
473
nsHTTPConn::ParseURL(const char *aProto, char *aURL, char **aHost,
474
int *aPort, char **aPath)
476
char *pos, *nextSlash, *nextColon, *end, *hostEnd;
477
int protoLen = strlen(aProto);
479
if (!aURL || !aHost || !aPort || !aPath || !aProto)
482
if ((strncmp(aURL, aProto, protoLen) != 0) ||
484
return E_MALFORMED_URL;
486
pos = aURL + protoLen;
487
nextColon = strchr(pos, ':');
488
nextSlash = strchr(pos, '/');
490
// optional port specification
491
if (nextColon && ((nextSlash && nextColon < nextSlash) ||
496
portStrLen = nextSlash - nextColon;
498
portStrLen = strlen(nextColon);
500
char *portStr = (char *) malloc(portStrLen + 1);
503
memset(portStr, 0, portStrLen + 1);
504
strncpy(portStr, nextColon+1, portStrLen);
505
*aPort = atoi(portStr);
508
if ( (!nextColon || (nextSlash && (nextColon > nextSlash)))
509
&& *aPort <= 0) // don't override port if already set
512
// only host in URL, assume '/' for path
517
copyLen = nextColon - pos;
519
copyLen = strlen(pos);
521
*aHost = (char *) malloc(copyLen + 1); // to NULL terminate
524
memset(*aHost, 0, copyLen + 1);
525
strncpy(*aHost, pos, copyLen);
527
*aPath = (char *) malloc(2);
533
// normal parsing: both host and path exist
538
*aHost = (char *) malloc(hostEnd - pos + 1); // to NULL terminate
541
memset(*aHost, 0, hostEnd - pos + 1);
542
strncpy(*aHost, pos, hostEnd - pos);
543
*(*aHost + (hostEnd - pos)) = 0; // NULL terminate
546
end = aURL + strlen(aURL);
548
*aPath = (char *) malloc(end - pos + 1);
555
memset(*aPath, 0, end - pos + 1);
556
strncpy(*aPath, pos, end - pos);
562
nsHTTPConn::ParseResponseCode(const char *aBuf, int *aCode)
570
// make sure the beginning of the buffer is the HTTP status code
571
if (strncmp(aBuf,"HTTP/",5) == 0)
573
pos = strstr(aBuf," "); // find the space before the code
574
++pos; // move to the beginning of the code
575
strncpy((char *)codeStr,pos, 3);
577
*aCode = atoi(codeStr);
582
nsHTTPConn::ParseContentLength(const char *aBuf, int *aLength)
584
const char *clHdr; // Content-length header line start
585
const char *eol, *pos;
586
char clNameStr1[16] = "Content-length:";
587
char clNameStr2[16] = "Content-Length:";
588
char *clNameStr = clNameStr1;
590
if (!aBuf || !aLength)
591
return; // non fatal so no error codes returned
594
// XXX strcasestr() needs to be ported for Solaris (and Win32 and Mac?)
595
clHdr = strstr(aBuf, (char *)clNameStr1);
598
clHdr = strstr(aBuf, (char *)clNameStr2);
599
clNameStr = clNameStr2;
603
eol = strstr(clHdr, kCRLF); // end of line
604
pos = clHdr + strlen(clNameStr);
605
while ((pos < eol) && (*pos == ' ' || *pos == '\t'))
609
int clValStrLen = eol - pos + 1; // extra byte to NULL terminate
610
char *clValStr = (char *) malloc(clValStrLen);
612
return; // imminent doom!
614
memset(clValStr, 0, clValStrLen);
615
strncpy(clValStr, pos, eol - pos);
616
*aLength = atoi(clValStr);
622
nsHTTPConn::Base64Encode(const unsigned char *in_str, int in_len,
623
char *out_str, int out_len)
625
// NOTE: shamelessly copied from nsAbSyncPostEngine.cpp
627
static unsigned char base64[] =
629
/* 0 1 2 3 4 5 6 7 */
630
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', /* 0 */
631
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', /* 1 */
632
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', /* 2 */
633
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', /* 3 */
634
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', /* 4 */
635
'o', 'p', 'q', 'r', 's', 't', 'u', 'v', /* 5 */
636
'w', 'x', 'y', 'z', '0', '1', '2', '3', /* 6 */
637
'4', '5', '6', '7', '8', '9', '+', '/' /* 7 */
640
int curr_out_len = 0;
643
unsigned char a, b, c;
653
b = (i + 1 >= in_len) ? 0 : in_str[i + 1];
654
c = (i + 2 >= in_len) ? 0 : in_str[i + 2];
658
out_str[curr_out_len++] = (base64[(a >> 2) & 0x3F]);
659
out_str[curr_out_len++] = (base64[((a << 4) & 0x30)
660
+ ((b >> 4) & 0xf)]);
661
out_str[curr_out_len++] = (base64[((b << 2) & 0x3c)
662
+ ((c >> 6) & 0x3)]);
663
out_str[curr_out_len++] = (base64[c & 0x3F]);
665
else if (i + 1 < in_len)
667
out_str[curr_out_len++] = (base64[(a >> 2) & 0x3F]);
668
out_str[curr_out_len++] = (base64[((a << 4) & 0x30)
669
+ ((b >> 4) & 0xf)]);
670
out_str[curr_out_len++] = (base64[((b << 2) & 0x3c)
671
+ ((c >> 6) & 0x3)]);
672
out_str[curr_out_len++] = '=';
676
out_str[curr_out_len++] = (base64[(a >> 2) & 0x3F]);
677
out_str[curr_out_len++] = (base64[((a << 4) & 0x30)
678
+ ((b >> 4) & 0xf)]);
679
out_str[curr_out_len++] = '=';
680
out_str[curr_out_len++] = '=';
685
if ((curr_out_len + 4) > out_len)
691
out_str[curr_out_len] = '\0';
697
#ifdef TEST_NSHTTPCONN
700
TestHTTPCB(int aBytesRd, int aTotal)
702
DUMP(("Bytes rd: %d\tTotal: %d\n", aBytesRd, aTotal));
707
main(int argc, char **argv)
710
int rv = nsHTTPConn::OK;
711
char *proxiedURL = NULL;
712
char *proxyUser = NULL;
713
char *proxyPswd = NULL;
715
DUMP(("*** %s: A self-test for the nsHTTPConn class.\n", argv[0]));
719
printf("usage: %s <http_url> [<proxied_url> [<proxy_user> ", argv[0]);
720
printf("<proxy_pswd>]]\n");
724
conn = new nsHTTPConn(argv[1]);
728
proxiedURL = argv[2];
736
conn->SetProxyInfo(proxiedURL, proxyUser, proxyPswd);
739
DUMP(("nsHTTPConn::Open returned: %d\n", rv));
741
rv = conn->Get(TestHTTPCB, NULL); // NULL: local file name = URL leaf
742
DUMP(("nsHTTPConn::Get returned: %d\n", rv));
745
DUMP(("nsHTTPConn::Close returned: %d\n", rv));
750
#endif /* TEST_NSHTTPCONN */