/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:set ts=2 sw=2 sts=2 et cindent: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
class nsIncrementalDownload final : public nsIIncrementalDownload, public nsIThreadRetargetableStreamListener, public nsIObserver, public nsIInterfaceRequestor, public nsIChannelEventSink, public nsSupportsWeakReference, public nsIAsyncVerifyRedirectCallback { public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSISTREAMLISTENER
NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
NS_DECL_NSIREQUEST
NS_DECL_NSIINCREMENTALDOWNLOAD
NS_DECL_NSIREQUESTOBSERVER
NS_DECL_NSIOBSERVER
NS_DECL_NSIINTERFACEREQUESTOR
NS_DECL_NSICHANNELEVENTSINK
NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
// nsITimerCallback is implemented on a subclass so that the name attribute // doesn't conflict with the name attribute of the nsIRequest interface. class TimerCallback final : public nsITimerCallback, public nsINamed { public:
NS_DECL_ISUPPORTS
NS_DECL_NSITIMERCALLBACK
NS_DECL_NSINAMED
nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(channel, &rv); if (NS_FAILED(rv)) return rv;
NS_ASSERTION(mCurrentSize != int64_t(-1), "we should know the current file size by now");
rv = ClearRequestHeader(http); if (NS_FAILED(rv)) return rv;
if (!mExtraHeaders.IsEmpty()) {
rv = AddExtraHeaders(http, mExtraHeaders); if (NS_FAILED(rv)) return rv;
}
// Don't bother making a range request if we are just going to fetch the // entire document. if (mInterval || mCurrentSize != int64_t(0)) {
nsAutoCString range;
MakeRangeSpec(mCurrentSize, mTotalSize, mChunkSize, mInterval == 0, range);
rv = http->SetRequestHeader("Range"_ns, range, false); if (NS_FAILED(rv)) return rv;
if (!mPartialValidator.IsEmpty()) {
rv = http->SetRequestHeader("If-Range"_ns, mPartialValidator, false); if (NS_FAILED(rv)) {
LOG(
("nsIncrementalDownload::ProcessTimeout\n" " failed to set request header: If-Range\n"));
}
}
if (mCacheBust) {
rv = http->SetRequestHeader("Cache-Control"_ns, "no-cache"_ns, false); if (NS_FAILED(rv)) {
LOG(
("nsIncrementalDownload::ProcessTimeout\n" " failed to set request header: If-Range\n"));
}
rv = http->SetRequestHeader("Pragma"_ns, "no-cache"_ns, false); if (NS_FAILED(rv)) {
LOG(
("nsIncrementalDownload::ProcessTimeout\n" " failed to set request header: If-Range\n"));
}
}
}
rv = channel->AsyncOpen(this); if (NS_FAILED(rv)) return rv;
// Wait to assign mChannel when we know we are going to succeed. This is // important because we don't want to introduce a reference cycle between // mChannel and this until we know for a fact that AsyncOpen has succeeded, // thus ensuring that our stream listener methods will be invoked.
mChannel = channel; return NS_OK;
}
// Reads the current file size and validates it.
nsresult nsIncrementalDownload::ReadCurrentSize() {
int64_t size;
nsresult rv = mDest->GetFileSize((int64_t*)&size); if (rv == NS_ERROR_FILE_NOT_FOUND) {
mCurrentSize = 0; return NS_OK;
} if (NS_FAILED(rv)) return rv;
NS_IMETHODIMP
nsIncrementalDownload::GetDestination(nsIFile** result) { if (!mDest) {
*result = nullptr; return NS_OK;
} // Return a clone of mDest so that callers may modify the resulting nsIFile // without corrupting our internal object. This also works around the fact // that some nsIFile impls may cache the result of stat'ing the filesystem. return mDest->Clone(result);
}
// Observe system shutdown so we can be sure to release any reference held // between ourselves and the timer. We have the observer service hold a weak // reference to us, so that we don't have to worry about calling // RemoveObserver. XXX(darin): The timer code should do this for us.
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); if (obs) obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
nsresult rv = ReadCurrentSize(); if (NS_FAILED(rv)) return rv;
rv = StartTimer(0); if (NS_FAILED(rv)) return rv;
mObserver = observer;
mProgressSink = do_QueryInterface(observer); // ok if null
nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequest, &rv); if (NS_FAILED(rv)) return rv;
// Ensure that we are receiving a 206 response.
uint32_t code;
rv = http->GetResponseStatus(&code); if (NS_FAILED(rv)) return rv; if (code != 206) { // We may already have the entire file downloaded, in which case // our request for a range beyond the end of the file would have // been met with an error response code. if (code == 416 && mTotalSize == int64_t(-1)) {
mTotalSize = mCurrentSize; // Return an error code here to suppress OnDataAvailable. return NS_ERROR_DOWNLOAD_COMPLETE;
} // The server may have decided to give us all of the data in one chunk. If // we requested a partial range, then we don't want to download all of the // data at once. So, we'll just try again, but if this keeps happening then // we'll eventually give up. if (code == 200) { if (mInterval) {
mChannel = nullptr; if (++mNonPartialCount > MAX_RETRY_COUNT) {
NS_WARNING("unable to fetch a byte range; giving up"); return NS_ERROR_FAILURE;
} // Increase delay with each failure.
StartTimer(mInterval * mNonPartialCount); return NS_ERROR_DOWNLOAD_NOT_PARTIAL;
} // Since we have been asked to download the rest of the file, we can deal // with a 200 response. This may result in downloading the beginning of // the file again, but that can't really be helped.
} else {
NS_WARNING("server response was unexpected"); return NS_ERROR_UNEXPECTED;
}
} else { // We got a partial response, so clear this counter in case the next chunk // results in a 200 response.
mNonPartialCount = 0;
// confirm that the content-range response header is consistent with // expectations on each 206. If it is not then drop this response and // retry with no-cache set. if (!mCacheBust) {
nsAutoCString buf;
int64_t startByte = 0; bool confirmedOK = false;
rv = http->GetResponseHeader("Content-Range"_ns, buf); if (NS_FAILED(rv)) { return rv; // it isn't a useful 206 without a CONTENT-RANGE of some
} // sort
// Content-Range: bytes 0-299999/25604694
int32_t p = buf.Find("bytes ");
// first look for the starting point of the content-range // to make sure it is what we expect if (p != -1) { char* endptr = nullptr; constchar* s = buf.get() + p + 6; while (*s && *s == ' ') s++;
startByte = strtol(s, &endptr, 10);
if (*s && endptr && (endptr != s) && (mCurrentSize == startByte)) { // ok the starting point is confirmed. We still need to check the // total size of the range for consistency if this isn't // the first chunk if (mTotalSize == int64_t(-1)) { // first chunk
confirmedOK = true;
} else {
int32_t slash = buf.FindChar('/');
int64_t rangeSize = 0; if (slash != kNotFound &&
(PR_sscanf(buf.get() + slash + 1, "%lld",
(int64_t*)&rangeSize) == 1) &&
rangeSize == mTotalSize) {
confirmedOK = true;
}
}
}
}
if (!confirmedOK) {
NS_WARNING("unexpected content-range");
mCacheBust = true;
mChannel = nullptr; if (++mNonPartialCount > MAX_RETRY_COUNT) {
NS_WARNING("unable to fetch a byte range; giving up"); return NS_ERROR_FAILURE;
} // Increase delay with each failure.
StartTimer(mInterval * mNonPartialCount); return NS_ERROR_DOWNLOAD_NOT_PARTIAL;
}
}
}
// Do special processing after the first response. if (mTotalSize == int64_t(-1)) { // Update knowledge of mFinalURI
rv = http->GetURI(getter_AddRefs(mFinalURI)); if (NS_FAILED(rv)) return rv;
Unused << http->GetResponseHeader("Etag"_ns, mPartialValidator); if (StringBeginsWith(mPartialValidator, "W/"_ns)) {
mPartialValidator.Truncate(); // don't use weak validators
} if (mPartialValidator.IsEmpty()) {
rv = http->GetResponseHeader("Last-Modified"_ns, mPartialValidator); if (NS_FAILED(rv)) {
LOG(
("nsIncrementalDownload::OnStartRequest\n" " empty validator\n"));
}
}
if (code == 206) { // OK, read the Content-Range header to determine the total size of this // download file.
nsAutoCString buf;
rv = http->GetResponseHeader("Content-Range"_ns, buf); if (NS_FAILED(rv)) return rv;
int32_t slash = buf.FindChar('/'); if (slash == kNotFound) {
NS_WARNING("server returned invalid Content-Range header!"); return NS_ERROR_UNEXPECTED;
} if (PR_sscanf(buf.get() + slash + 1, "%lld", (int64_t*)&mTotalSize) !=
1) { return NS_ERROR_UNEXPECTED;
}
} else {
rv = http->GetContentLength(&mTotalSize); if (NS_FAILED(rv)) return rv; // We need to know the total size of the thing we're trying to download. if (mTotalSize == int64_t(-1)) {
NS_WARNING("server returned no content-length header!"); return NS_ERROR_UNEXPECTED;
} // Need to truncate (or create, if it doesn't exist) the file since we // are downloading the whole thing.
WriteToFile(mDest, nullptr, 0, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE);
mCurrentSize = 0;
}
// Notify observer that we are starting...
rv = CallOnStartRequest(); if (NS_FAILED(rv)) return rv;
}
// Adjust mChunkSize accordingly if mCurrentSize is close to mTotalSize.
int64_t diff = mTotalSize - mCurrentSize; if (diff <= int64_t(0)) {
NS_WARNING("about to set a bogus chunk size; giving up"); return NS_ERROR_UNEXPECTED;
}
if (diff < int64_t(mChunkSize)) mChunkSize = uint32_t(diff);
mChunk = mozilla::MakeUniqueFallible<char[]>(mChunkSize); if (!mChunk) rv = NS_ERROR_OUT_OF_MEMORY;
if (nsIOService::UseSocketProcess() || NS_FAILED(rv)) { return rv;
}
if (nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(aRequest)) {
nsCOMPtr<nsIEventTarget> sts =
do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
RefPtr queue =
TaskQueue::Create(sts.forget(), "nsIncrementalDownload Delivery Queue");
LOG(
("nsIncrementalDownload::OnStartRequest\n" " Retarget to stream transport service\n"));
rr->RetargetDeliveryTo(queue);
}
NS_IMETHODIMP
nsIncrementalDownload::OnStopRequest(nsIRequest* request, nsresult status) { // Not a real error; just a trick to kill off the channel without our // listener having to care. if (status == NS_ERROR_DOWNLOAD_NOT_PARTIAL) return NS_OK;
// Not a real error; just a trick used to suppress OnDataAvailable calls. if (status == NS_ERROR_DOWNLOAD_COMPLETE) status = NS_OK;
if (NS_SUCCEEDED(mStatus)) mStatus = status;
if (mChunk) { if (NS_SUCCEEDED(mStatus)) mStatus = FlushChunk();
// Since the app is shutting down, we need to go ahead and notify our // observer here. Otherwise, we would notify them after XPCOM has been // shutdown or not at all.
CallOnStopRequest();
} return NS_OK;
}
// We don't support encodings -- they make the Content-Length not equal // to the actual size of the data. return channel->SetRequestHeader("Accept-Encoding"_ns, ""_ns, false);
}
// nsIChannelEventSink
NS_IMETHODIMP
nsIncrementalDownload::AsyncOnChannelRedirect(
nsIChannel* oldChannel, nsIChannel* newChannel, uint32_t flags,
nsIAsyncVerifyRedirectCallback* cb) { // In response to a redirect, we need to propagate the Range header. See bug // 311595. Any failure code returned from this function aborts the redirect.
nsresult rv = ClearRequestHeader(newHttpChannel); if (NS_FAILED(rv)) return rv;
if (!mExtraHeaders.IsEmpty()) {
rv = AddExtraHeaders(http, mExtraHeaders); if (NS_FAILED(rv)) return rv;
}
// If we didn't have a Range header, then we must be doing a full download.
nsAutoCString rangeVal;
Unused << http->GetRequestHeader(rangeHdr, rangeVal); if (!rangeVal.IsEmpty()) {
rv = newHttpChannel->SetRequestHeader(rangeHdr, rangeVal, false);
NS_ENSURE_SUCCESS(rv, rv);
}
// A redirection changes the validator
mPartialValidator.Truncate();
if (mCacheBust) {
rv = newHttpChannel->SetRequestHeader("Cache-Control"_ns, "no-cache"_ns, false); if (NS_FAILED(rv)) {
LOG(
("nsIncrementalDownload::AsyncOnChannelRedirect\n" " failed to set request header: Cache-Control\n"));
}
rv = newHttpChannel->SetRequestHeader("Pragma"_ns, "no-cache"_ns, false); if (NS_FAILED(rv)) {
LOG(
("nsIncrementalDownload::AsyncOnChannelRedirect\n" " failed to set request header: Pragma\n"));
}
}
// Give the observer a chance to see this redirect notification.
nsCOMPtr<nsIChannelEventSink> sink = do_GetInterface(mObserver); if (sink) {
rv = sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, this); if (NS_FAILED(rv)) {
mRedirectCallback = nullptr;
mNewRedirectChannel = nullptr;
} return rv;
}
(void)OnRedirectVerifyCallback(NS_OK); return NS_OK;
}
NS_IMETHODIMP
nsIncrementalDownload::OnRedirectVerifyCallback(nsresult result) {
NS_ASSERTION(mRedirectCallback, "mRedirectCallback not set in callback");
NS_ASSERTION(mNewRedirectChannel, "mNewRedirectChannel not set in callback");
// Update mChannel, so we can Cancel the new channel. if (NS_SUCCEEDED(result)) mChannel = mNewRedirectChannel;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.