Bug 219556 necko support for resumable downloads via http

r=darin sr=bzbarsky
This commit is contained in:
cbiesinger%web.de 2004-04-14 17:37:39 +00:00
parent 00ef7e8765
commit 91a47b5ddb
11 changed files with 274 additions and 58 deletions

View File

@ -42,18 +42,24 @@
[scriptable, uuid(c9df38d4-1dd1-11b2-81ae-c9e767256d1b)]
interface nsIResumableEntityID : nsISupports {
/** Size of the entity, -1 if unknown */
/**
* Size of the entity.
* @throw NS_ERROR_NOT_AVAILABLE if the size is not known.
*/
attribute unsigned long size;
/** Last modified time, in seconds since epoch. Note that the timezone
* these are in may not be known
/**
* An opaque, but human-readable ASCII string specifying the date when the
* resource was last modified. May be empty.
*/
attribute PRTime lastModified;
attribute ACString lastModified;
/** Entity, may be empty. This is meant to hold the E-Tag for http */
// currently we only support ftp; add this in when its utility can be
// checked
//attribute string entity;
/**
* The entity tag is a strong identifier specified by the protocol. For
* HTTP, this attribute corresponds to the value of the ETag response
* header. This attribute may be empty. Otherwise it is an ASCII string.
*/
attribute ACString entityTag;
/** Compare to another nsIResumableEntityID */
boolean equals(in nsIResumableEntityID other);

View File

@ -620,7 +620,8 @@ NS_GetURLSpecFromFile(nsIFile *aFile,
inline nsresult
NS_NewResumableEntityID(nsIResumableEntityID **aRes,
PRUint32 size,
PRTime lastModified)
const nsACString &lastModified,
const nsACString &entityTag)
{
nsresult rv;
nsCOMPtr<nsIResumableEntityID> ent =
@ -628,6 +629,7 @@ NS_NewResumableEntityID(nsIResumableEntityID **aRes,
if (NS_SUCCEEDED(rv)) {
ent->SetSize(size);
ent->SetLastModified(lastModified);
ent->SetEntityTag(entityTag);
NS_ADDREF(*aRes = ent);
}
return rv;

View File

@ -1,4 +1,5 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim:set et ts=4 sw=4 sts=4 cin: */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
@ -17,10 +18,11 @@
* The Initial Developer of the Original Code is
* Bradley Baetz
* Portions created by the Initial Developer are Copyright (C) 2002
* Portions created by the Initial Developer are Copyright (C) 2002
* the Initial Developer. All Rights Reserved.
*
* Contributor(s): Bradley Baetz <bbaetz@student.usyd.edu.au>
* Contributor(s):
* Bradley Baetz <bbaetz@student.usyd.edu.au>
* Christian Biesinger <cbiesinger@web.de>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -37,18 +39,20 @@
* ***** END LICENSE BLOCK ***** */
#include "nsResumableEntityID.h"
#include "nsReadableUtils.h"
NS_IMPL_ISUPPORTS1(nsResumableEntityID, nsIResumableEntityID)
nsResumableEntityID::nsResumableEntityID() :
mSize(PRUint32(-1)),
mLastModified(PRTime(LL_INIT(-1,-1))) {
mSize(PR_UINT32_MAX) {
}
nsResumableEntityID::~nsResumableEntityID() {}
NS_IMETHODIMP
nsResumableEntityID::GetSize(PRUint32 *aSize) {
if (mSize == PR_UINT32_MAX)
return NS_ERROR_NOT_AVAILABLE;
*aSize = mSize;
return NS_OK;
}
@ -60,27 +64,52 @@ nsResumableEntityID::SetSize(PRUint32 aSize) {
}
NS_IMETHODIMP
nsResumableEntityID::GetLastModified(PRTime *aLastModified) {
*aLastModified = mLastModified;
nsResumableEntityID::GetLastModified(nsACString& aLastModified) {
aLastModified = mLastModified;
return NS_OK;
}
NS_IMETHODIMP
nsResumableEntityID::SetLastModified(PRTime aLastModified) {
nsResumableEntityID::SetLastModified(const nsACString& aLastModified) {
mLastModified = aLastModified;
return NS_OK;
}
NS_IMETHODIMP
nsResumableEntityID::SetEntityTag(const nsACString& aTag) {
mEntityTag = aTag;
return NS_OK;
}
NS_IMETHODIMP
nsResumableEntityID::GetEntityTag(nsACString& aTag) {
aTag = mEntityTag;
return NS_OK;
}
NS_IMETHODIMP
nsResumableEntityID::Equals(nsIResumableEntityID *other, PRBool *ret) {
PRUint32 size;
PRInt64 lastMod;
nsCAutoString lastMod;
nsCAutoString entityTag;
nsresult rv = other->GetSize(&size);
if (NS_FAILED(rv)) return rv;
if (NS_FAILED(rv))
size = PR_UINT32_MAX;
rv = other->GetLastModified(&lastMod);
if (NS_FAILED(rv)) return rv;
rv = other->GetLastModified(lastMod);
if (NS_FAILED(rv))
lastMod.Truncate();
rv = other->GetEntityTag(entityTag);
if (NS_FAILED(rv))
entityTag.Truncate();
// This assumes that the server generated the last modification time in
// exactly the same way for both of these entity IDs (same timezone, same
// format, etc).
*ret = mEntityTag.Equals(entityTag) && lastMod.Equals(mLastModified) &&
(mSize == size);
return NS_OK;
}

View File

@ -16,7 +16,6 @@
*
* The Initial Developer of the Original Code is Bradley Baetz
* Portions created by the Initial Developer are Copyright (C) 2002
* Portions created by the Initial Developer are Copyright (C) 2002
* the Initial Developer. All Rights Reserved.
*
* Contributor(s): Bradley Baetz <bbaetz@student.usyd.edu.au>
@ -36,6 +35,7 @@
* ***** END LICENSE BLOCK ***** */
#include "nsIResumableEntityID.h"
#include "nsString.h"
class nsResumableEntityID : public nsIResumableEntityID {
public:
@ -43,9 +43,10 @@ public:
NS_DECL_NSIRESUMABLEENTITYID
nsResumableEntityID();
virtual ~nsResumableEntityID();
~nsResumableEntityID();
private:
PRUint32 mSize;
PRTime mLastModified;
nsCString mLastModified;
nsCString mEntityTag;
};

View File

@ -384,7 +384,6 @@ nsFtpState::nsFtpState()
mControlConnection = nsnull;
mDRequestForwarder = nsnull;
mFileSize = PRUint32(-1);
mModTime = -1;
// make sure handler stays around
NS_ADDREF(gFtpHandler);
@ -1357,26 +1356,12 @@ nsFtpState::R_mdtm() {
if (mResponseMsg.Length() != 14) {
NS_ASSERTION(mResponseMsg.Length() == 14, "Unknown MDTM response");
} else {
const char* date = mResponseMsg.get();
PRExplodedTime exp;
exp.tm_year = (date[0]-'0')*1000 + (date[1]-'0')*100 +
(date[2]-'0')*10 + (date[3]-'0');
exp.tm_month = (date[4]-'0')*10 + (date[5]-'0');
exp.tm_mday = (date[6]-'0')*10 + (date[7]-'0');
exp.tm_hour = (date[8]-'0')*10 + (date[9]-'0');
exp.tm_min = (date[10]-'0')*10 + (date[11]-'0');
exp.tm_sec = (date[12]-'0')*10 + (date[13]-'0');
exp.tm_usec = 0;
exp.tm_wday = 0;
exp.tm_yday = 0;
exp.tm_params.tp_gmt_offset = 0;
exp.tm_params.tp_dst_offset = 0;
mModTime = PR_ImplodeTime(&exp);
mModTime = mResponseMsg;
}
}
nsresult rv = NS_NewResumableEntityID(getter_AddRefs(mEntityID),
mFileSize, mModTime);
mFileSize, mModTime, EmptyCString());
if (NS_FAILED(rv)) return FTP_ERROR;
mDRequestForwarder->SetEntityID(mEntityID);

View File

@ -194,7 +194,7 @@ private:
nsCOMPtr<nsIRequest> mDPipeRequest;
DataRequestForwarder* mDRequestForwarder;
PRUint32 mFileSize;
PRTime mModTime;
nsCString mModTime;
// ****** consumer vars
nsCOMPtr<nsIFTPChannel> mChannel; // our owning FTP channel we pass through our events

View File

@ -1,5 +1,5 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
// vim:expandtab:ts=4 sw=4:
/* vim:set expandtab ts=4 sw=4 sts=4: */
/*
* The contents of this file are subject to the Mozilla Public
* License Version 1.1 (the "License"); you may not use this file
@ -19,7 +19,8 @@
* Rights Reserved.
*
* Contributor(s):
* Darin Fisher <darin@netscape.com> (original author)
* Darin Fisher <darin@meer.net> (original author)
* Christian Biesinger <cbiesinger@web.de>
*/
#include "nsHttpChannel.h"
@ -52,6 +53,7 @@
#include "prprf.h"
#include "nsEscape.h"
#include "nsICookieService.h"
#include "nsIResumableChannel.h"
static NS_DEFINE_CID(kStreamListenerTeeCID, NS_STREAMLISTENERTEE_CID);
@ -83,6 +85,7 @@ nsHttpChannel::nsHttpChannel()
, mPostID(0)
, mRequestTime(0)
, mAuthContinuationState(nsnull)
, mStartPos(0)
, mRedirectionLimit(gHttpHandler->RedirectionLimit())
, mIsPending(PR_FALSE)
, mApplyConversion(PR_TRUE)
@ -94,6 +97,7 @@ nsHttpChannel::nsHttpChannel()
, mTransactionReplaced(PR_FALSE)
, mUploadStreamHasHeaders(PR_FALSE)
, mAuthRetryPending(PR_FALSE)
, mResuming(PR_FALSE)
{
LOG(("Creating nsHttpChannel @%x\n", this));
@ -288,6 +292,12 @@ nsHttpChannel::Connect(PRBool firstTime)
if (offline)
mLoadFlags |= LOAD_ONLY_FROM_CACHE;
// Don't allow resuming when cache must be used
if (mResuming && (mLoadFlags & LOAD_ONLY_FROM_CACHE)) {
LOG(("Resuming from cache is not supported yet"));
return NS_ERROR_DOCUMENT_NOT_CACHED;
}
// open a cache entry for this channel...
rv = OpenCacheEntry(offline, &delayed);
@ -518,6 +528,28 @@ nsHttpChannel::SetupTransaction()
mRequestHead.SetHeader(nsHttp::Pragma, NS_LITERAL_CSTRING("no-cache"), PR_TRUE);
}
if (mResuming) {
char buf[32];
PR_snprintf(buf, sizeof(buf), "bytes=%u-", mStartPos);
mRequestHead.SetHeader(nsHttp::Range, nsDependentCString(buf));
if (mEntityID) {
// Also, we want an error if this resource changed in the meantime
nsCAutoString entityTag;
rv = mEntityID->GetEntityTag(entityTag);
if (NS_SUCCEEDED(rv) && !entityTag.IsEmpty()) {
mRequestHead.SetHeader(nsHttp::If_Match, entityTag);
}
nsCAutoString lastMod;
rv = mEntityID->GetLastModified(lastMod);
if (NS_SUCCEEDED(rv) && !lastMod.IsEmpty()) {
mRequestHead.SetHeader(nsHttp::If_Unmodified_Since, lastMod);
}
}
}
if (!mEventQ) {
// grab a reference to the calling thread's event queue.
nsCOMPtr<nsIEventQueueService> eqs;
@ -662,6 +694,14 @@ nsHttpChannel::ProcessResponse()
switch (httpStatus) {
case 200:
case 203:
// Per RFC 2616, 14.35.2, "A server MAY ignore the Range header".
// So if a server does that and sends 200 instead of 206 that we
// expect, notify our caller.
if (mResuming) {
Cancel(NS_ERROR_NOT_RESUMABLE);
rv = CallOnStartRequest();
break;
}
// these can normally be cached
rv = ProcessNormal();
break;
@ -713,6 +753,14 @@ nsHttpChannel::ProcessResponse()
rv = ProcessNormal();
}
break;
case 412: // Precondition failed
case 416: // Invalid range
if (mResuming) {
Cancel(NS_ERROR_NOT_RESUMABLE);
rv = CallOnStartRequest();
break;
}
// fall through
default:
CloseCacheEntry(NS_ERROR_ABORT);
rv = ProcessNormal();
@ -763,6 +811,24 @@ nsHttpChannel::ProcessNormal()
if (NS_FAILED(rv)) return rv;
}
// Check that the server sent us what we were asking for
if (mResuming) {
// Create an entity id from the response
nsCOMPtr<nsIResumableEntityID> id;
rv = GetEntityID(getter_AddRefs(id));
if (NS_FAILED(rv)) {
// If creating an entity id is not possible -> error
Cancel(NS_ERROR_NOT_RESUMABLE);
}
// If we were passed an entity id, verify it's equal to the server's
else if (mEntityID) {
PRBool equal;
rv = mEntityID->Equals(id, &equal);
if (NS_FAILED(rv) || !equal)
Cancel(NS_ERROR_NOT_RESUMABLE);
}
}
rv = CallOnStartRequest();
if (NS_FAILED(rv)) return rv;
@ -1095,7 +1161,7 @@ nsHttpChannel::OpenCacheEntry(PRBool offline, PRBool *delayed)
}
else if (mRequestHead.PeekHeader(nsHttp::Range)) {
// we don't support caching for byte range requests initiated
// by our clients.
// by our clients or via nsIResumableChannel.
// XXX perhaps we could munge their byte range into the cache
// key to make caching sort'a work.
return NS_OK;
@ -2509,6 +2575,7 @@ NS_INTERFACE_MAP_BEGIN(nsHttpChannel)
NS_INTERFACE_MAP_ENTRY(nsICacheListener)
NS_INTERFACE_MAP_ENTRY(nsIEncodedChannel)
NS_INTERFACE_MAP_ENTRY(nsIHttpChannelInternal)
NS_INTERFACE_MAP_ENTRY(nsIResumableChannel)
NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIChannel)
NS_INTERFACE_MAP_END
@ -3646,6 +3713,51 @@ nsHttpChannel::IsFromCache(PRBool *value)
return NS_OK;
}
//-----------------------------------------------------------------------------
// nsHttpChannel::nsIResumableChannel
//-----------------------------------------------------------------------------
NS_IMETHODIMP
nsHttpChannel::AsyncOpenAt(nsIStreamListener* aListener,
nsISupports* aCtxt,
PRUint32 aStartPos,
nsIResumableEntityID* aEntityID)
{
mEntityID = aEntityID;
mStartPos = aStartPos;
mResuming = PR_TRUE;
return AsyncOpen(aListener, aCtxt);
}
NS_IMETHODIMP
nsHttpChannel::GetEntityID(nsIResumableEntityID** aEntityID)
{
// Don't return an entity ID for HTTP/1.0 servers
if (mResponseHead && (mResponseHead->Version() < NS_HTTP_VERSION_1_1)) {
*aEntityID = nsnull;
return NS_OK;
}
// Neither return one for Non-GET requests which require additional data
if (mRequestHead.Method() != nsHttp::Get) {
*aEntityID = nsnull;
return NS_OK;
}
PRUint32 size = PR_UINT32_MAX;
nsCAutoString etag, lastmod;
if (mResponseHead) {
size = mResponseHead->TotalEntitySize();
const char* cLastMod = mResponseHead->PeekHeader(nsHttp::Last_Modified);
if (cLastMod)
lastmod = cLastMod;
const char* cEtag = mResponseHead->PeekHeader(nsHttp::ETag);
if (cEtag)
etag = cEtag;
}
return NS_NewResumableEntityID(aEntityID, size, lastmod, etag);
}
//-----------------------------------------------------------------------------
// nsHttpChannel::nsICacheListener
//-----------------------------------------------------------------------------

View File

@ -1,4 +1,5 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim:set et cin ts=4 sw=4 sts=4: */
/*
* The contents of this file are subject to the Mozilla Public
* License Version 1.1 (the "License"); you may not use this file
@ -53,11 +54,13 @@
#include "nsIAsyncInputStream.h"
#include "nsIInputStreamPump.h"
#include "nsIPrompt.h"
#include "nsIResumableChannel.h"
class nsHttpResponseHead;
class nsAHttpConnection;
class nsIHttpAuthenticator;
class nsIProxyInfo;
class nsIResumableEntityID;
//-----------------------------------------------------------------------------
// nsHttpChannel
@ -71,6 +74,7 @@ class nsHttpChannel : public nsIHttpChannel
, public nsICacheListener
, public nsIEncodedChannel
, public nsITransportEventSink
, public nsIResumableChannel
{
public:
NS_DECL_ISUPPORTS
@ -85,6 +89,7 @@ public:
NS_DECL_NSIENCODEDCHANNEL
NS_DECL_NSIHTTPCHANNELINTERNAL
NS_DECL_NSITRANSPORTEVENTSINK
NS_DECL_NSIRESUMABLECHANNEL
nsHttpChannel();
virtual ~nsHttpChannel();
@ -208,6 +213,10 @@ private:
nsHttpAuthIdentity mIdent;
nsHttpAuthIdentity mProxyIdent;
// Resumable channel specific data
nsCOMPtr<nsIResumableEntityID> mEntityID;
PRUint32 mStartPos;
// redirection specific data.
PRUint8 mRedirectionLimit;
@ -222,6 +231,7 @@ private:
PRUint32 mTransactionReplaced : 1;
PRUint32 mUploadStreamHasHeaders : 1;
PRUint32 mAuthRetryPending : 1;
PRUint32 mResuming : 1;
class nsContentEncodings : public nsIUTF8StringEnumerator
{

View File

@ -1,4 +1,5 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim:set ts=4 sw=4 sts=4 et cin: */
/*
* The contents of this file are subject to the Mozilla Public
* License Version 1.1 (the "License"); you may not use this file
@ -20,6 +21,7 @@
* Contributor(s):
* Darin Fisher <darin@netscape.com> (original author)
* Andreas M. Schneider <clarence@clarence.de>
* Christian Biesinger <cbiesinger@web.de>
*/
#include <stdlib.h>
@ -503,6 +505,25 @@ nsHttpResponseHead::GetExpiresValue(PRUint32 *result)
return NS_OK;
}
PRInt32
nsHttpResponseHead::TotalEntitySize()
{
const char* contentRange = PeekHeader(nsHttp::Content_Range);
if (!contentRange)
return ContentLength();
// Total length is after a slash
const char* slash = strrchr(contentRange, '/');
if (!slash)
return -1; // No idea what the length is
slash++;
if (*slash == '*') // Server doesn't know the length
return -1;
return atoi(slash);
}
//-----------------------------------------------------------------------------
// nsHttpResponseHead <private>
//-----------------------------------------------------------------------------

View File

@ -56,6 +56,12 @@ public:
const nsAFlatCString &ContentCharset() { return mContentCharset; }
PRBool NoStore() { return mCacheControlNoStore; }
PRBool NoCache() { return (mCacheControlNoCache || mPragmaNoCache); }
/**
* Full length of the entity. For byte-range requests, this may be larger
* than ContentLength(), which will only represent the requested part of the
* entity.
*/
PRInt32 TotalEntitySize();
const char *PeekHeader(nsHttpAtom h) { return mHeaders.PeekHeader(h); }
nsresult SetHeader(nsHttpAtom h, const nsACString &v, PRBool m=PR_FALSE);

View File

@ -1,5 +1,5 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set ts=2 sw=2 et cindent: */
/* vim: set ts=4 sw=4 et cindent: */
/* ***** BEGIN LICENSE BLOCK *****
* Version: NPL 1.1/GPL 2.0/LGPL 2.1
*
@ -44,6 +44,7 @@
-Gagan Saksena 04/29/99
*/
#define FORCE_PR_LOG
#include <stdio.h>
#ifdef WIN32
#include <windows.h>
@ -66,6 +67,7 @@
#include "nsIResumableEntityID.h"
#include "nsIURL.h"
#include "nsIHttpChannel.h"
#include "nsIHttpChannelInternal.h"
#include "nsIHttpHeaderVisitor.h"
#include "nsIHttpEventSink.h"
#include "nsIInterfaceRequestor.h"
@ -79,6 +81,7 @@
#include "nsXPIDLString.h"
#include "nsNetUtil.h"
#include "prlog.h"
#include "prtime.h"
#if defined(PR_LOGGING)
//
@ -99,6 +102,10 @@ static PRBool gAskUserForInput = PR_FALSE;
static PRBool gResume = PR_FALSE;
static PRUint32 gStartAt = 0;
static const char* gLastMod = NULL;
static const char* gETag = NULL;
static PRUint32 gTotalSize = PR_UINT32_MAX;
//-----------------------------------------------------------------------------
// Set proxy preferences for testing
//-----------------------------------------------------------------------------
@ -357,6 +364,7 @@ InputTestConsumer::OnStartRequest(nsIRequest *request, nsISupports* context)
if (channel) {
nsresult status;
channel->GetStatus(&status);
LOG(("Channel Status: %08x\n", status));
if (NS_SUCCEEDED(status)) {
LOG(("Channel Info:\n"));
@ -381,6 +389,13 @@ InputTestConsumer::OnStartRequest(nsIRequest *request, nsISupports* context)
LOG(("\tChannel Owner: %x\n", owner.get()));
}
nsCOMPtr<nsIHttpChannelInternal> httpChannelInt(do_QueryInterface(request));
if (httpChannelInt) {
PRUint32 majorVer, minorVer;
nsresult rv = httpChannelInt->GetResponseVersion(&majorVer, &minorVer);
if (NS_SUCCEEDED(rv))
LOG(("HTTP Response version: %u.%u\n", majorVer, minorVer));
}
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(request));
if (httpChannel) {
HeaderVisitor *visitor = new HeaderVisitor();
@ -409,18 +424,18 @@ InputTestConsumer::OnStartRequest(nsIRequest *request, nsISupports* context)
LOG(("\tSize: %d\n", size));
else
LOG(("\tSize: Unknown\n"));
PRTime lastModified;
if (NS_SUCCEEDED(entityID->GetLastModified(&lastModified)) &&
lastModified != -1) {
PRExplodedTime exploded;
PR_ExplodeTime(lastModified, PR_LocalTimeParameters, &exploded);
char buf[100];
PR_FormatTime(buf, 100, "%c", &exploded);
LOG(("\tLast Modified: %s\n", buf));
} else
nsCAutoString lastModified;
if (NS_SUCCEEDED(entityID->GetLastModified(lastModified)) &&
!lastModified.IsEmpty())
LOG(("\tLast Modified: %s\n", lastModified.get()));
else
LOG(("\tLast Modified: Unknown\n"));
nsCAutoString etag;
if (NS_SUCCEEDED(entityID->GetEntityTag(etag)) &&
!etag.IsEmpty())
LOG(("\tETag: %s\n", etag.get()));
else
LOG(("\tEtag: Unknown\n"));
}
}
@ -647,10 +662,24 @@ nsresult StartLoadingURL(const char* aUrlString)
NS_ERROR("Channel is not resumable!");
return NS_ERROR_UNEXPECTED;
}
nsCOMPtr<nsIResumableEntityID> id;
if (gETag || gLastMod || gTotalSize != PR_UINT32_MAX) {
id = do_CreateInstance(NS_RESUMABLEENTITYID_CONTRACTID);
if (!id) {
fprintf(stderr, "Error creating entityid\n");
}
else {
if (gETag)
id->SetEntityTag(nsDependentCString(gETag));
if (gLastMod)
id->SetLastModified(nsDependentCString(gLastMod));
id->SetSize(gTotalSize);
}
}
rv = res->AsyncOpenAt(listener,
info,
gStartAt,
nsnull);
id);
} else {
rv = pChannel->AsyncOpen(listener, // IStreamListener consumer
info);
@ -732,7 +761,7 @@ main(int argc, char* argv[])
{
nsresult rv= (nsresult)-1;
if (argc < 2) {
printf("usage: %s [-verbose] [-file <name>] <url> <url> ... \n", argv[0]);
printf("usage: %s [-verbose] [-file <name>] [-resume <startoffset> [-etag <etag>] [-lastmod <lastmod (string)>] [-totalsize <size>] [-proxy <proxy>] [-console] <url> <url> ... \n", argv[0]);
return -1;
}
@ -782,6 +811,21 @@ main(int argc, char* argv[])
continue;
}
if (PL_strcasecmp(argv[i], "-etag") == 0) {
gETag = argv[++i];
continue;
}
if (PL_strcasecmp(argv[i], "-lastmod") == 0) {
gLastMod = argv[++i];
continue;
}
if (PL_strcasecmp(argv[i], "-totalsize") == 0) {
gTotalSize = atoi(argv[++i]);
continue;
}
if (PL_strcasecmp(argv[i], "-proxy") == 0) {
SetHttpProxy(argv[++i]);
continue;