michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim:set ts=2 sw=2 sts=2 et cin: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsIOService.h" michael@0: #include "nsFileChannel.h" michael@0: #include "nsBaseContentStream.h" michael@0: #include "nsDirectoryIndexStream.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsTransportUtils.h" michael@0: #include "nsStreamUtils.h" michael@0: #include "nsMimeTypes.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsProxyRelease.h" michael@0: #include "nsAutoPtr.h" michael@0: michael@0: #include "nsIFileURL.h" michael@0: #include "nsIMIMEService.h" michael@0: #include michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: class nsFileCopyEvent : public nsRunnable { michael@0: public: michael@0: nsFileCopyEvent(nsIOutputStream *dest, nsIInputStream *source, int64_t len) michael@0: : mDest(dest) michael@0: , mSource(source) michael@0: , mLen(len) michael@0: , mStatus(NS_OK) michael@0: , mInterruptStatus(NS_OK) { michael@0: } michael@0: michael@0: // Read the current status of the file copy operation. michael@0: nsresult Status() { return mStatus; } michael@0: michael@0: // Call this method to perform the file copy synchronously. michael@0: void DoCopy(); michael@0: michael@0: // Call this method to perform the file copy on a background thread. The michael@0: // callback is dispatched when the file copy completes. michael@0: nsresult Dispatch(nsIRunnable *callback, michael@0: nsITransportEventSink *sink, michael@0: nsIEventTarget *target); michael@0: michael@0: // Call this method to interrupt a file copy operation that is occuring on michael@0: // a background thread. The status parameter passed to this function must michael@0: // be a failure code and is set as the status of this file copy operation. michael@0: void Interrupt(nsresult status) { michael@0: NS_ASSERTION(NS_FAILED(status), "must be a failure code"); michael@0: mInterruptStatus = status; michael@0: } michael@0: michael@0: NS_IMETHOD Run() { michael@0: DoCopy(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: nsCOMPtr mCallbackTarget; michael@0: nsCOMPtr mCallback; michael@0: nsCOMPtr mSink; michael@0: nsCOMPtr mDest; michael@0: nsCOMPtr mSource; michael@0: int64_t mLen; michael@0: nsresult mStatus; // modified on i/o thread only michael@0: nsresult mInterruptStatus; // modified on main thread only michael@0: }; michael@0: michael@0: void michael@0: nsFileCopyEvent::DoCopy() michael@0: { michael@0: // We'll copy in chunks this large by default. This size affects how michael@0: // frequently we'll check for interrupts. michael@0: const int32_t chunk = nsIOService::gDefaultSegmentSize * nsIOService::gDefaultSegmentCount; michael@0: michael@0: nsresult rv = NS_OK; michael@0: michael@0: int64_t len = mLen, progress = 0; michael@0: while (len) { michael@0: // If we've been interrupted, then stop copying. michael@0: rv = mInterruptStatus; michael@0: if (NS_FAILED(rv)) michael@0: break; michael@0: michael@0: int32_t num = std::min((int32_t) len, chunk); michael@0: michael@0: uint32_t result; michael@0: rv = mSource->ReadSegments(NS_CopySegmentToStream, mDest, num, &result); michael@0: if (NS_FAILED(rv)) michael@0: break; michael@0: if (result != (uint32_t) num) { michael@0: rv = NS_ERROR_FILE_DISK_FULL; // stopped prematurely (out of disk space) michael@0: break; michael@0: } michael@0: michael@0: // Dispatch progress notification michael@0: if (mSink) { michael@0: progress += num; michael@0: mSink->OnTransportStatus(nullptr, NS_NET_STATUS_WRITING, progress, michael@0: mLen); michael@0: } michael@0: michael@0: len -= num; michael@0: } michael@0: michael@0: if (NS_FAILED(rv)) michael@0: mStatus = rv; michael@0: michael@0: // Close the output stream before notifying our callback so that others may michael@0: // freely "play" with the file. michael@0: mDest->Close(); michael@0: michael@0: // Notify completion michael@0: if (mCallback) { michael@0: mCallbackTarget->Dispatch(mCallback, NS_DISPATCH_NORMAL); michael@0: michael@0: // Release the callback on the target thread to avoid destroying stuff on michael@0: // the wrong thread. michael@0: nsIRunnable *doomed = nullptr; michael@0: mCallback.swap(doomed); michael@0: NS_ProxyRelease(mCallbackTarget, doomed); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsFileCopyEvent::Dispatch(nsIRunnable *callback, michael@0: nsITransportEventSink *sink, michael@0: nsIEventTarget *target) michael@0: { michael@0: // Use the supplied event target for all asynchronous operations. michael@0: michael@0: mCallback = callback; michael@0: mCallbackTarget = target; michael@0: michael@0: // Build a coalescing proxy for progress events michael@0: nsresult rv = net_NewTransportEventSinkProxy(getter_AddRefs(mSink), sink, michael@0: target, true); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // Dispatch ourselves to I/O thread pool... michael@0: nsCOMPtr pool = michael@0: do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: return pool->Dispatch(this, NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: // This is a dummy input stream that when read, performs the file copy. The michael@0: // copy happens on a background thread via mCopyEvent. michael@0: michael@0: class nsFileUploadContentStream : public nsBaseContentStream { michael@0: public: michael@0: NS_DECL_ISUPPORTS_INHERITED michael@0: michael@0: nsFileUploadContentStream(bool nonBlocking, michael@0: nsIOutputStream *dest, michael@0: nsIInputStream *source, michael@0: int64_t len, michael@0: nsITransportEventSink *sink) michael@0: : nsBaseContentStream(nonBlocking) michael@0: , mCopyEvent(new nsFileCopyEvent(dest, source, len)) michael@0: , mSink(sink) { michael@0: } michael@0: michael@0: bool IsInitialized() { michael@0: return mCopyEvent != nullptr; michael@0: } michael@0: michael@0: NS_IMETHODIMP ReadSegments(nsWriteSegmentFun fun, void *closure, michael@0: uint32_t count, uint32_t *result); michael@0: NS_IMETHODIMP AsyncWait(nsIInputStreamCallback *callback, uint32_t flags, michael@0: uint32_t count, nsIEventTarget *target); michael@0: michael@0: private: michael@0: void OnCopyComplete(); michael@0: michael@0: nsRefPtr mCopyEvent; michael@0: nsCOMPtr mSink; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS_INHERITED0(nsFileUploadContentStream, michael@0: nsBaseContentStream) michael@0: michael@0: NS_IMETHODIMP michael@0: nsFileUploadContentStream::ReadSegments(nsWriteSegmentFun fun, void *closure, michael@0: uint32_t count, uint32_t *result) michael@0: { michael@0: *result = 0; // nothing is ever actually read from this stream michael@0: michael@0: if (IsClosed()) michael@0: return NS_OK; michael@0: michael@0: if (IsNonBlocking()) { michael@0: // Inform the caller that they will have to wait for the copy operation to michael@0: // complete asynchronously. We'll kick of the copy operation once they michael@0: // call AsyncWait. michael@0: return NS_BASE_STREAM_WOULD_BLOCK; michael@0: } michael@0: michael@0: // Perform copy synchronously, and then close out the stream. michael@0: mCopyEvent->DoCopy(); michael@0: nsresult status = mCopyEvent->Status(); michael@0: CloseWithStatus(NS_FAILED(status) ? status : NS_BASE_STREAM_CLOSED); michael@0: return status; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFileUploadContentStream::AsyncWait(nsIInputStreamCallback *callback, michael@0: uint32_t flags, uint32_t count, michael@0: nsIEventTarget *target) michael@0: { michael@0: nsresult rv = nsBaseContentStream::AsyncWait(callback, flags, count, target); michael@0: if (NS_FAILED(rv) || IsClosed()) michael@0: return rv; michael@0: michael@0: if (IsNonBlocking()) { michael@0: nsCOMPtr callback = michael@0: NS_NewRunnableMethod(this, &nsFileUploadContentStream::OnCopyComplete); michael@0: mCopyEvent->Dispatch(callback, mSink, target); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsFileUploadContentStream::OnCopyComplete() michael@0: { michael@0: // This method is being called to indicate that we are done copying. michael@0: nsresult status = mCopyEvent->Status(); michael@0: michael@0: CloseWithStatus(NS_FAILED(status) ? status : NS_BASE_STREAM_CLOSED); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsFileChannel::nsFileChannel(nsIURI *uri) michael@0: { michael@0: // If we have a link file, we should resolve its target right away. michael@0: // This is to protect against a same origin attack where the same link file michael@0: // can point to different resources right after the first resource is loaded. michael@0: nsCOMPtr file; michael@0: nsCOMPtr targetURI; michael@0: nsAutoCString fileTarget; michael@0: nsCOMPtr resolvedFile; michael@0: bool symLink; michael@0: nsCOMPtr fileURL = do_QueryInterface(uri); michael@0: if (fileURL && michael@0: NS_SUCCEEDED(fileURL->GetFile(getter_AddRefs(file))) && michael@0: NS_SUCCEEDED(file->IsSymlink(&symLink)) && michael@0: symLink && michael@0: NS_SUCCEEDED(file->GetNativeTarget(fileTarget)) && michael@0: NS_SUCCEEDED(NS_NewNativeLocalFile(fileTarget, PR_TRUE, michael@0: getter_AddRefs(resolvedFile))) && michael@0: NS_SUCCEEDED(NS_NewFileURI(getter_AddRefs(targetURI), michael@0: resolvedFile, nullptr))) { michael@0: SetURI(targetURI); michael@0: SetOriginalURI(uri); michael@0: nsLoadFlags loadFlags = 0; michael@0: GetLoadFlags(&loadFlags); michael@0: SetLoadFlags(loadFlags | nsIChannel::LOAD_REPLACE); michael@0: } else { michael@0: SetURI(uri); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsFileChannel::MakeFileInputStream(nsIFile *file, michael@0: nsCOMPtr &stream, michael@0: nsCString &contentType, michael@0: bool async) michael@0: { michael@0: // we accept that this might result in a disk hit to stat the file michael@0: bool isDir; michael@0: nsresult rv = file->IsDirectory(&isDir); michael@0: if (NS_FAILED(rv)) { michael@0: // canonicalize error message michael@0: if (rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) michael@0: rv = NS_ERROR_FILE_NOT_FOUND; michael@0: michael@0: if (async && (NS_ERROR_FILE_NOT_FOUND == rv)) { michael@0: // We don't return "Not Found" errors here. Since we could not find michael@0: // the file, it's not a directory anyway. michael@0: isDir = false; michael@0: } else { michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: if (isDir) { michael@0: rv = nsDirectoryIndexStream::Create(file, getter_AddRefs(stream)); michael@0: if (NS_SUCCEEDED(rv) && !HasContentTypeHint()) michael@0: contentType.AssignLiteral(APPLICATION_HTTP_INDEX_FORMAT); michael@0: } else { michael@0: rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file, -1, -1, michael@0: async? nsIFileInputStream::DEFER_OPEN : 0); michael@0: if (NS_SUCCEEDED(rv) && !HasContentTypeHint()) { michael@0: // Use file extension to infer content type michael@0: nsCOMPtr mime = do_GetService("@mozilla.org/mime;1", &rv); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: mime->GetTypeFromFile(file, contentType); michael@0: } michael@0: } michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsFileChannel::OpenContentStream(bool async, nsIInputStream **result, michael@0: nsIChannel** channel) michael@0: { michael@0: // NOTE: the resulting file is a clone, so it is safe to pass it to the michael@0: // file input stream which will be read on a background thread. michael@0: nsCOMPtr file; michael@0: nsresult rv = GetFile(getter_AddRefs(file)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: nsCOMPtr fileHandler; michael@0: rv = NS_GetFileProtocolHandler(getter_AddRefs(fileHandler)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: nsCOMPtr newURI; michael@0: rv = fileHandler->ReadURLFile(file, getter_AddRefs(newURI)); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: nsCOMPtr newChannel; michael@0: rv = NS_NewChannel(getter_AddRefs(newChannel), newURI); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: *result = nullptr; michael@0: newChannel.forget(channel); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr stream; michael@0: michael@0: if (mUploadStream) { michael@0: // Pass back a nsFileUploadContentStream instance that knows how to perform michael@0: // the file copy when "read" (the resulting stream in this case does not michael@0: // actually return any data). michael@0: michael@0: nsCOMPtr fileStream; michael@0: rv = NS_NewLocalFileOutputStream(getter_AddRefs(fileStream), file, michael@0: PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, michael@0: PR_IRUSR | PR_IWUSR); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: nsFileUploadContentStream *uploadStream = michael@0: new nsFileUploadContentStream(async, fileStream, mUploadStream, michael@0: mUploadLength, this); michael@0: if (!uploadStream || !uploadStream->IsInitialized()) { michael@0: delete uploadStream; michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: stream = uploadStream; michael@0: michael@0: mContentLength = 0; michael@0: michael@0: // Since there isn't any content to speak of we just set the content-type michael@0: // to something other than "unknown" to avoid triggering the content-type michael@0: // sniffer code in nsBaseChannel. michael@0: // However, don't override explicitly set types. michael@0: if (!HasContentTypeHint()) michael@0: SetContentType(NS_LITERAL_CSTRING(APPLICATION_OCTET_STREAM)); michael@0: } else { michael@0: nsAutoCString contentType; michael@0: rv = MakeFileInputStream(file, stream, contentType, async); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: EnableSynthesizedProgressEvents(true); michael@0: michael@0: // fixup content length and type michael@0: if (mContentLength < 0) { michael@0: int64_t size; michael@0: rv = file->GetFileSize(&size); michael@0: if (NS_FAILED(rv)) { michael@0: if (async && michael@0: (NS_ERROR_FILE_NOT_FOUND == rv || michael@0: NS_ERROR_FILE_TARGET_DOES_NOT_EXIST == rv)) { michael@0: size = 0; michael@0: } else { michael@0: return rv; michael@0: } michael@0: } michael@0: mContentLength = size; michael@0: } michael@0: if (!contentType.IsEmpty()) michael@0: SetContentType(contentType); michael@0: } michael@0: michael@0: *result = nullptr; michael@0: stream.swap(*result); michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsFileChannel::nsISupports michael@0: michael@0: NS_IMPL_ISUPPORTS_INHERITED(nsFileChannel, michael@0: nsBaseChannel, michael@0: nsIUploadChannel, michael@0: nsIFileChannel) michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsFileChannel::nsIFileChannel michael@0: michael@0: NS_IMETHODIMP michael@0: nsFileChannel::GetFile(nsIFile **file) michael@0: { michael@0: nsCOMPtr fileURL = do_QueryInterface(URI()); michael@0: NS_ENSURE_STATE(fileURL); michael@0: michael@0: // This returns a cloned nsIFile michael@0: return fileURL->GetFile(file); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsFileChannel::nsIUploadChannel michael@0: michael@0: NS_IMETHODIMP michael@0: nsFileChannel::SetUploadStream(nsIInputStream *stream, michael@0: const nsACString &contentType, michael@0: int64_t contentLength) michael@0: { michael@0: NS_ENSURE_TRUE(!Pending(), NS_ERROR_IN_PROGRESS); michael@0: michael@0: if ((mUploadStream = stream)) { michael@0: mUploadLength = contentLength; michael@0: if (mUploadLength < 0) { michael@0: // Make sure we know how much data we are uploading. michael@0: uint64_t avail; michael@0: nsresult rv = mUploadStream->Available(&avail); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: if (avail < INT64_MAX) michael@0: mUploadLength = avail; michael@0: } michael@0: } else { michael@0: mUploadLength = -1; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFileChannel::GetUploadStream(nsIInputStream **result) michael@0: { michael@0: NS_IF_ADDREF(*result = mUploadStream); michael@0: return NS_OK; michael@0: }