1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/netwerk/protocol/file/nsFileChannel.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,458 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim:set ts=2 sw=2 sts=2 et cin: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "nsIOService.h" 1.11 +#include "nsFileChannel.h" 1.12 +#include "nsBaseContentStream.h" 1.13 +#include "nsDirectoryIndexStream.h" 1.14 +#include "nsThreadUtils.h" 1.15 +#include "nsTransportUtils.h" 1.16 +#include "nsStreamUtils.h" 1.17 +#include "nsMimeTypes.h" 1.18 +#include "nsNetUtil.h" 1.19 +#include "nsProxyRelease.h" 1.20 +#include "nsAutoPtr.h" 1.21 + 1.22 +#include "nsIFileURL.h" 1.23 +#include "nsIMIMEService.h" 1.24 +#include <algorithm> 1.25 + 1.26 +//----------------------------------------------------------------------------- 1.27 + 1.28 +class nsFileCopyEvent : public nsRunnable { 1.29 +public: 1.30 + nsFileCopyEvent(nsIOutputStream *dest, nsIInputStream *source, int64_t len) 1.31 + : mDest(dest) 1.32 + , mSource(source) 1.33 + , mLen(len) 1.34 + , mStatus(NS_OK) 1.35 + , mInterruptStatus(NS_OK) { 1.36 + } 1.37 + 1.38 + // Read the current status of the file copy operation. 1.39 + nsresult Status() { return mStatus; } 1.40 + 1.41 + // Call this method to perform the file copy synchronously. 1.42 + void DoCopy(); 1.43 + 1.44 + // Call this method to perform the file copy on a background thread. The 1.45 + // callback is dispatched when the file copy completes. 1.46 + nsresult Dispatch(nsIRunnable *callback, 1.47 + nsITransportEventSink *sink, 1.48 + nsIEventTarget *target); 1.49 + 1.50 + // Call this method to interrupt a file copy operation that is occuring on 1.51 + // a background thread. The status parameter passed to this function must 1.52 + // be a failure code and is set as the status of this file copy operation. 1.53 + void Interrupt(nsresult status) { 1.54 + NS_ASSERTION(NS_FAILED(status), "must be a failure code"); 1.55 + mInterruptStatus = status; 1.56 + } 1.57 + 1.58 + NS_IMETHOD Run() { 1.59 + DoCopy(); 1.60 + return NS_OK; 1.61 + } 1.62 + 1.63 +private: 1.64 + nsCOMPtr<nsIEventTarget> mCallbackTarget; 1.65 + nsCOMPtr<nsIRunnable> mCallback; 1.66 + nsCOMPtr<nsITransportEventSink> mSink; 1.67 + nsCOMPtr<nsIOutputStream> mDest; 1.68 + nsCOMPtr<nsIInputStream> mSource; 1.69 + int64_t mLen; 1.70 + nsresult mStatus; // modified on i/o thread only 1.71 + nsresult mInterruptStatus; // modified on main thread only 1.72 +}; 1.73 + 1.74 +void 1.75 +nsFileCopyEvent::DoCopy() 1.76 +{ 1.77 + // We'll copy in chunks this large by default. This size affects how 1.78 + // frequently we'll check for interrupts. 1.79 + const int32_t chunk = nsIOService::gDefaultSegmentSize * nsIOService::gDefaultSegmentCount; 1.80 + 1.81 + nsresult rv = NS_OK; 1.82 + 1.83 + int64_t len = mLen, progress = 0; 1.84 + while (len) { 1.85 + // If we've been interrupted, then stop copying. 1.86 + rv = mInterruptStatus; 1.87 + if (NS_FAILED(rv)) 1.88 + break; 1.89 + 1.90 + int32_t num = std::min((int32_t) len, chunk); 1.91 + 1.92 + uint32_t result; 1.93 + rv = mSource->ReadSegments(NS_CopySegmentToStream, mDest, num, &result); 1.94 + if (NS_FAILED(rv)) 1.95 + break; 1.96 + if (result != (uint32_t) num) { 1.97 + rv = NS_ERROR_FILE_DISK_FULL; // stopped prematurely (out of disk space) 1.98 + break; 1.99 + } 1.100 + 1.101 + // Dispatch progress notification 1.102 + if (mSink) { 1.103 + progress += num; 1.104 + mSink->OnTransportStatus(nullptr, NS_NET_STATUS_WRITING, progress, 1.105 + mLen); 1.106 + } 1.107 + 1.108 + len -= num; 1.109 + } 1.110 + 1.111 + if (NS_FAILED(rv)) 1.112 + mStatus = rv; 1.113 + 1.114 + // Close the output stream before notifying our callback so that others may 1.115 + // freely "play" with the file. 1.116 + mDest->Close(); 1.117 + 1.118 + // Notify completion 1.119 + if (mCallback) { 1.120 + mCallbackTarget->Dispatch(mCallback, NS_DISPATCH_NORMAL); 1.121 + 1.122 + // Release the callback on the target thread to avoid destroying stuff on 1.123 + // the wrong thread. 1.124 + nsIRunnable *doomed = nullptr; 1.125 + mCallback.swap(doomed); 1.126 + NS_ProxyRelease(mCallbackTarget, doomed); 1.127 + } 1.128 +} 1.129 + 1.130 +nsresult 1.131 +nsFileCopyEvent::Dispatch(nsIRunnable *callback, 1.132 + nsITransportEventSink *sink, 1.133 + nsIEventTarget *target) 1.134 +{ 1.135 + // Use the supplied event target for all asynchronous operations. 1.136 + 1.137 + mCallback = callback; 1.138 + mCallbackTarget = target; 1.139 + 1.140 + // Build a coalescing proxy for progress events 1.141 + nsresult rv = net_NewTransportEventSinkProxy(getter_AddRefs(mSink), sink, 1.142 + target, true); 1.143 + if (NS_FAILED(rv)) 1.144 + return rv; 1.145 + 1.146 + // Dispatch ourselves to I/O thread pool... 1.147 + nsCOMPtr<nsIEventTarget> pool = 1.148 + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); 1.149 + if (NS_FAILED(rv)) 1.150 + return rv; 1.151 + 1.152 + return pool->Dispatch(this, NS_DISPATCH_NORMAL); 1.153 +} 1.154 + 1.155 +//----------------------------------------------------------------------------- 1.156 + 1.157 +// This is a dummy input stream that when read, performs the file copy. The 1.158 +// copy happens on a background thread via mCopyEvent. 1.159 + 1.160 +class nsFileUploadContentStream : public nsBaseContentStream { 1.161 +public: 1.162 + NS_DECL_ISUPPORTS_INHERITED 1.163 + 1.164 + nsFileUploadContentStream(bool nonBlocking, 1.165 + nsIOutputStream *dest, 1.166 + nsIInputStream *source, 1.167 + int64_t len, 1.168 + nsITransportEventSink *sink) 1.169 + : nsBaseContentStream(nonBlocking) 1.170 + , mCopyEvent(new nsFileCopyEvent(dest, source, len)) 1.171 + , mSink(sink) { 1.172 + } 1.173 + 1.174 + bool IsInitialized() { 1.175 + return mCopyEvent != nullptr; 1.176 + } 1.177 + 1.178 + NS_IMETHODIMP ReadSegments(nsWriteSegmentFun fun, void *closure, 1.179 + uint32_t count, uint32_t *result); 1.180 + NS_IMETHODIMP AsyncWait(nsIInputStreamCallback *callback, uint32_t flags, 1.181 + uint32_t count, nsIEventTarget *target); 1.182 + 1.183 +private: 1.184 + void OnCopyComplete(); 1.185 + 1.186 + nsRefPtr<nsFileCopyEvent> mCopyEvent; 1.187 + nsCOMPtr<nsITransportEventSink> mSink; 1.188 +}; 1.189 + 1.190 +NS_IMPL_ISUPPORTS_INHERITED0(nsFileUploadContentStream, 1.191 + nsBaseContentStream) 1.192 + 1.193 +NS_IMETHODIMP 1.194 +nsFileUploadContentStream::ReadSegments(nsWriteSegmentFun fun, void *closure, 1.195 + uint32_t count, uint32_t *result) 1.196 +{ 1.197 + *result = 0; // nothing is ever actually read from this stream 1.198 + 1.199 + if (IsClosed()) 1.200 + return NS_OK; 1.201 + 1.202 + if (IsNonBlocking()) { 1.203 + // Inform the caller that they will have to wait for the copy operation to 1.204 + // complete asynchronously. We'll kick of the copy operation once they 1.205 + // call AsyncWait. 1.206 + return NS_BASE_STREAM_WOULD_BLOCK; 1.207 + } 1.208 + 1.209 + // Perform copy synchronously, and then close out the stream. 1.210 + mCopyEvent->DoCopy(); 1.211 + nsresult status = mCopyEvent->Status(); 1.212 + CloseWithStatus(NS_FAILED(status) ? status : NS_BASE_STREAM_CLOSED); 1.213 + return status; 1.214 +} 1.215 + 1.216 +NS_IMETHODIMP 1.217 +nsFileUploadContentStream::AsyncWait(nsIInputStreamCallback *callback, 1.218 + uint32_t flags, uint32_t count, 1.219 + nsIEventTarget *target) 1.220 +{ 1.221 + nsresult rv = nsBaseContentStream::AsyncWait(callback, flags, count, target); 1.222 + if (NS_FAILED(rv) || IsClosed()) 1.223 + return rv; 1.224 + 1.225 + if (IsNonBlocking()) { 1.226 + nsCOMPtr<nsIRunnable> callback = 1.227 + NS_NewRunnableMethod(this, &nsFileUploadContentStream::OnCopyComplete); 1.228 + mCopyEvent->Dispatch(callback, mSink, target); 1.229 + } 1.230 + 1.231 + return NS_OK; 1.232 +} 1.233 + 1.234 +void 1.235 +nsFileUploadContentStream::OnCopyComplete() 1.236 +{ 1.237 + // This method is being called to indicate that we are done copying. 1.238 + nsresult status = mCopyEvent->Status(); 1.239 + 1.240 + CloseWithStatus(NS_FAILED(status) ? status : NS_BASE_STREAM_CLOSED); 1.241 +} 1.242 + 1.243 +//----------------------------------------------------------------------------- 1.244 + 1.245 +nsFileChannel::nsFileChannel(nsIURI *uri) 1.246 +{ 1.247 + // If we have a link file, we should resolve its target right away. 1.248 + // This is to protect against a same origin attack where the same link file 1.249 + // can point to different resources right after the first resource is loaded. 1.250 + nsCOMPtr<nsIFile> file; 1.251 + nsCOMPtr <nsIURI> targetURI; 1.252 + nsAutoCString fileTarget; 1.253 + nsCOMPtr<nsIFile> resolvedFile; 1.254 + bool symLink; 1.255 + nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(uri); 1.256 + if (fileURL && 1.257 + NS_SUCCEEDED(fileURL->GetFile(getter_AddRefs(file))) && 1.258 + NS_SUCCEEDED(file->IsSymlink(&symLink)) && 1.259 + symLink && 1.260 + NS_SUCCEEDED(file->GetNativeTarget(fileTarget)) && 1.261 + NS_SUCCEEDED(NS_NewNativeLocalFile(fileTarget, PR_TRUE, 1.262 + getter_AddRefs(resolvedFile))) && 1.263 + NS_SUCCEEDED(NS_NewFileURI(getter_AddRefs(targetURI), 1.264 + resolvedFile, nullptr))) { 1.265 + SetURI(targetURI); 1.266 + SetOriginalURI(uri); 1.267 + nsLoadFlags loadFlags = 0; 1.268 + GetLoadFlags(&loadFlags); 1.269 + SetLoadFlags(loadFlags | nsIChannel::LOAD_REPLACE); 1.270 + } else { 1.271 + SetURI(uri); 1.272 + } 1.273 +} 1.274 + 1.275 +nsresult 1.276 +nsFileChannel::MakeFileInputStream(nsIFile *file, 1.277 + nsCOMPtr<nsIInputStream> &stream, 1.278 + nsCString &contentType, 1.279 + bool async) 1.280 +{ 1.281 + // we accept that this might result in a disk hit to stat the file 1.282 + bool isDir; 1.283 + nsresult rv = file->IsDirectory(&isDir); 1.284 + if (NS_FAILED(rv)) { 1.285 + // canonicalize error message 1.286 + if (rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) 1.287 + rv = NS_ERROR_FILE_NOT_FOUND; 1.288 + 1.289 + if (async && (NS_ERROR_FILE_NOT_FOUND == rv)) { 1.290 + // We don't return "Not Found" errors here. Since we could not find 1.291 + // the file, it's not a directory anyway. 1.292 + isDir = false; 1.293 + } else { 1.294 + return rv; 1.295 + } 1.296 + } 1.297 + 1.298 + if (isDir) { 1.299 + rv = nsDirectoryIndexStream::Create(file, getter_AddRefs(stream)); 1.300 + if (NS_SUCCEEDED(rv) && !HasContentTypeHint()) 1.301 + contentType.AssignLiteral(APPLICATION_HTTP_INDEX_FORMAT); 1.302 + } else { 1.303 + rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file, -1, -1, 1.304 + async? nsIFileInputStream::DEFER_OPEN : 0); 1.305 + if (NS_SUCCEEDED(rv) && !HasContentTypeHint()) { 1.306 + // Use file extension to infer content type 1.307 + nsCOMPtr<nsIMIMEService> mime = do_GetService("@mozilla.org/mime;1", &rv); 1.308 + if (NS_SUCCEEDED(rv)) { 1.309 + mime->GetTypeFromFile(file, contentType); 1.310 + } 1.311 + } 1.312 + } 1.313 + return rv; 1.314 +} 1.315 + 1.316 +nsresult 1.317 +nsFileChannel::OpenContentStream(bool async, nsIInputStream **result, 1.318 + nsIChannel** channel) 1.319 +{ 1.320 + // NOTE: the resulting file is a clone, so it is safe to pass it to the 1.321 + // file input stream which will be read on a background thread. 1.322 + nsCOMPtr<nsIFile> file; 1.323 + nsresult rv = GetFile(getter_AddRefs(file)); 1.324 + if (NS_FAILED(rv)) 1.325 + return rv; 1.326 + 1.327 + nsCOMPtr<nsIFileProtocolHandler> fileHandler; 1.328 + rv = NS_GetFileProtocolHandler(getter_AddRefs(fileHandler)); 1.329 + if (NS_FAILED(rv)) 1.330 + return rv; 1.331 + 1.332 + nsCOMPtr<nsIURI> newURI; 1.333 + rv = fileHandler->ReadURLFile(file, getter_AddRefs(newURI)); 1.334 + if (NS_SUCCEEDED(rv)) { 1.335 + nsCOMPtr<nsIChannel> newChannel; 1.336 + rv = NS_NewChannel(getter_AddRefs(newChannel), newURI); 1.337 + if (NS_FAILED(rv)) 1.338 + return rv; 1.339 + 1.340 + *result = nullptr; 1.341 + newChannel.forget(channel); 1.342 + return NS_OK; 1.343 + } 1.344 + 1.345 + nsCOMPtr<nsIInputStream> stream; 1.346 + 1.347 + if (mUploadStream) { 1.348 + // Pass back a nsFileUploadContentStream instance that knows how to perform 1.349 + // the file copy when "read" (the resulting stream in this case does not 1.350 + // actually return any data). 1.351 + 1.352 + nsCOMPtr<nsIOutputStream> fileStream; 1.353 + rv = NS_NewLocalFileOutputStream(getter_AddRefs(fileStream), file, 1.354 + PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 1.355 + PR_IRUSR | PR_IWUSR); 1.356 + if (NS_FAILED(rv)) 1.357 + return rv; 1.358 + 1.359 + nsFileUploadContentStream *uploadStream = 1.360 + new nsFileUploadContentStream(async, fileStream, mUploadStream, 1.361 + mUploadLength, this); 1.362 + if (!uploadStream || !uploadStream->IsInitialized()) { 1.363 + delete uploadStream; 1.364 + return NS_ERROR_OUT_OF_MEMORY; 1.365 + } 1.366 + stream = uploadStream; 1.367 + 1.368 + mContentLength = 0; 1.369 + 1.370 + // Since there isn't any content to speak of we just set the content-type 1.371 + // to something other than "unknown" to avoid triggering the content-type 1.372 + // sniffer code in nsBaseChannel. 1.373 + // However, don't override explicitly set types. 1.374 + if (!HasContentTypeHint()) 1.375 + SetContentType(NS_LITERAL_CSTRING(APPLICATION_OCTET_STREAM)); 1.376 + } else { 1.377 + nsAutoCString contentType; 1.378 + rv = MakeFileInputStream(file, stream, contentType, async); 1.379 + if (NS_FAILED(rv)) 1.380 + return rv; 1.381 + 1.382 + EnableSynthesizedProgressEvents(true); 1.383 + 1.384 + // fixup content length and type 1.385 + if (mContentLength < 0) { 1.386 + int64_t size; 1.387 + rv = file->GetFileSize(&size); 1.388 + if (NS_FAILED(rv)) { 1.389 + if (async && 1.390 + (NS_ERROR_FILE_NOT_FOUND == rv || 1.391 + NS_ERROR_FILE_TARGET_DOES_NOT_EXIST == rv)) { 1.392 + size = 0; 1.393 + } else { 1.394 + return rv; 1.395 + } 1.396 + } 1.397 + mContentLength = size; 1.398 + } 1.399 + if (!contentType.IsEmpty()) 1.400 + SetContentType(contentType); 1.401 + } 1.402 + 1.403 + *result = nullptr; 1.404 + stream.swap(*result); 1.405 + return NS_OK; 1.406 +} 1.407 + 1.408 +//----------------------------------------------------------------------------- 1.409 +// nsFileChannel::nsISupports 1.410 + 1.411 +NS_IMPL_ISUPPORTS_INHERITED(nsFileChannel, 1.412 + nsBaseChannel, 1.413 + nsIUploadChannel, 1.414 + nsIFileChannel) 1.415 + 1.416 +//----------------------------------------------------------------------------- 1.417 +// nsFileChannel::nsIFileChannel 1.418 + 1.419 +NS_IMETHODIMP 1.420 +nsFileChannel::GetFile(nsIFile **file) 1.421 +{ 1.422 + nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(URI()); 1.423 + NS_ENSURE_STATE(fileURL); 1.424 + 1.425 + // This returns a cloned nsIFile 1.426 + return fileURL->GetFile(file); 1.427 +} 1.428 + 1.429 +//----------------------------------------------------------------------------- 1.430 +// nsFileChannel::nsIUploadChannel 1.431 + 1.432 +NS_IMETHODIMP 1.433 +nsFileChannel::SetUploadStream(nsIInputStream *stream, 1.434 + const nsACString &contentType, 1.435 + int64_t contentLength) 1.436 +{ 1.437 + NS_ENSURE_TRUE(!Pending(), NS_ERROR_IN_PROGRESS); 1.438 + 1.439 + if ((mUploadStream = stream)) { 1.440 + mUploadLength = contentLength; 1.441 + if (mUploadLength < 0) { 1.442 + // Make sure we know how much data we are uploading. 1.443 + uint64_t avail; 1.444 + nsresult rv = mUploadStream->Available(&avail); 1.445 + if (NS_FAILED(rv)) 1.446 + return rv; 1.447 + if (avail < INT64_MAX) 1.448 + mUploadLength = avail; 1.449 + } 1.450 + } else { 1.451 + mUploadLength = -1; 1.452 + } 1.453 + return NS_OK; 1.454 +} 1.455 + 1.456 +NS_IMETHODIMP 1.457 +nsFileChannel::GetUploadStream(nsIInputStream **result) 1.458 +{ 1.459 + NS_IF_ADDREF(*result = mUploadStream); 1.460 + return NS_OK; 1.461 +}