netwerk/protocol/file/nsFileChannel.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim:set ts=2 sw=2 sts=2 et cin: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 #include "nsIOService.h"
michael@0 8 #include "nsFileChannel.h"
michael@0 9 #include "nsBaseContentStream.h"
michael@0 10 #include "nsDirectoryIndexStream.h"
michael@0 11 #include "nsThreadUtils.h"
michael@0 12 #include "nsTransportUtils.h"
michael@0 13 #include "nsStreamUtils.h"
michael@0 14 #include "nsMimeTypes.h"
michael@0 15 #include "nsNetUtil.h"
michael@0 16 #include "nsProxyRelease.h"
michael@0 17 #include "nsAutoPtr.h"
michael@0 18
michael@0 19 #include "nsIFileURL.h"
michael@0 20 #include "nsIMIMEService.h"
michael@0 21 #include <algorithm>
michael@0 22
michael@0 23 //-----------------------------------------------------------------------------
michael@0 24
michael@0 25 class nsFileCopyEvent : public nsRunnable {
michael@0 26 public:
michael@0 27 nsFileCopyEvent(nsIOutputStream *dest, nsIInputStream *source, int64_t len)
michael@0 28 : mDest(dest)
michael@0 29 , mSource(source)
michael@0 30 , mLen(len)
michael@0 31 , mStatus(NS_OK)
michael@0 32 , mInterruptStatus(NS_OK) {
michael@0 33 }
michael@0 34
michael@0 35 // Read the current status of the file copy operation.
michael@0 36 nsresult Status() { return mStatus; }
michael@0 37
michael@0 38 // Call this method to perform the file copy synchronously.
michael@0 39 void DoCopy();
michael@0 40
michael@0 41 // Call this method to perform the file copy on a background thread. The
michael@0 42 // callback is dispatched when the file copy completes.
michael@0 43 nsresult Dispatch(nsIRunnable *callback,
michael@0 44 nsITransportEventSink *sink,
michael@0 45 nsIEventTarget *target);
michael@0 46
michael@0 47 // Call this method to interrupt a file copy operation that is occuring on
michael@0 48 // a background thread. The status parameter passed to this function must
michael@0 49 // be a failure code and is set as the status of this file copy operation.
michael@0 50 void Interrupt(nsresult status) {
michael@0 51 NS_ASSERTION(NS_FAILED(status), "must be a failure code");
michael@0 52 mInterruptStatus = status;
michael@0 53 }
michael@0 54
michael@0 55 NS_IMETHOD Run() {
michael@0 56 DoCopy();
michael@0 57 return NS_OK;
michael@0 58 }
michael@0 59
michael@0 60 private:
michael@0 61 nsCOMPtr<nsIEventTarget> mCallbackTarget;
michael@0 62 nsCOMPtr<nsIRunnable> mCallback;
michael@0 63 nsCOMPtr<nsITransportEventSink> mSink;
michael@0 64 nsCOMPtr<nsIOutputStream> mDest;
michael@0 65 nsCOMPtr<nsIInputStream> mSource;
michael@0 66 int64_t mLen;
michael@0 67 nsresult mStatus; // modified on i/o thread only
michael@0 68 nsresult mInterruptStatus; // modified on main thread only
michael@0 69 };
michael@0 70
michael@0 71 void
michael@0 72 nsFileCopyEvent::DoCopy()
michael@0 73 {
michael@0 74 // We'll copy in chunks this large by default. This size affects how
michael@0 75 // frequently we'll check for interrupts.
michael@0 76 const int32_t chunk = nsIOService::gDefaultSegmentSize * nsIOService::gDefaultSegmentCount;
michael@0 77
michael@0 78 nsresult rv = NS_OK;
michael@0 79
michael@0 80 int64_t len = mLen, progress = 0;
michael@0 81 while (len) {
michael@0 82 // If we've been interrupted, then stop copying.
michael@0 83 rv = mInterruptStatus;
michael@0 84 if (NS_FAILED(rv))
michael@0 85 break;
michael@0 86
michael@0 87 int32_t num = std::min((int32_t) len, chunk);
michael@0 88
michael@0 89 uint32_t result;
michael@0 90 rv = mSource->ReadSegments(NS_CopySegmentToStream, mDest, num, &result);
michael@0 91 if (NS_FAILED(rv))
michael@0 92 break;
michael@0 93 if (result != (uint32_t) num) {
michael@0 94 rv = NS_ERROR_FILE_DISK_FULL; // stopped prematurely (out of disk space)
michael@0 95 break;
michael@0 96 }
michael@0 97
michael@0 98 // Dispatch progress notification
michael@0 99 if (mSink) {
michael@0 100 progress += num;
michael@0 101 mSink->OnTransportStatus(nullptr, NS_NET_STATUS_WRITING, progress,
michael@0 102 mLen);
michael@0 103 }
michael@0 104
michael@0 105 len -= num;
michael@0 106 }
michael@0 107
michael@0 108 if (NS_FAILED(rv))
michael@0 109 mStatus = rv;
michael@0 110
michael@0 111 // Close the output stream before notifying our callback so that others may
michael@0 112 // freely "play" with the file.
michael@0 113 mDest->Close();
michael@0 114
michael@0 115 // Notify completion
michael@0 116 if (mCallback) {
michael@0 117 mCallbackTarget->Dispatch(mCallback, NS_DISPATCH_NORMAL);
michael@0 118
michael@0 119 // Release the callback on the target thread to avoid destroying stuff on
michael@0 120 // the wrong thread.
michael@0 121 nsIRunnable *doomed = nullptr;
michael@0 122 mCallback.swap(doomed);
michael@0 123 NS_ProxyRelease(mCallbackTarget, doomed);
michael@0 124 }
michael@0 125 }
michael@0 126
michael@0 127 nsresult
michael@0 128 nsFileCopyEvent::Dispatch(nsIRunnable *callback,
michael@0 129 nsITransportEventSink *sink,
michael@0 130 nsIEventTarget *target)
michael@0 131 {
michael@0 132 // Use the supplied event target for all asynchronous operations.
michael@0 133
michael@0 134 mCallback = callback;
michael@0 135 mCallbackTarget = target;
michael@0 136
michael@0 137 // Build a coalescing proxy for progress events
michael@0 138 nsresult rv = net_NewTransportEventSinkProxy(getter_AddRefs(mSink), sink,
michael@0 139 target, true);
michael@0 140 if (NS_FAILED(rv))
michael@0 141 return rv;
michael@0 142
michael@0 143 // Dispatch ourselves to I/O thread pool...
michael@0 144 nsCOMPtr<nsIEventTarget> pool =
michael@0 145 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
michael@0 146 if (NS_FAILED(rv))
michael@0 147 return rv;
michael@0 148
michael@0 149 return pool->Dispatch(this, NS_DISPATCH_NORMAL);
michael@0 150 }
michael@0 151
michael@0 152 //-----------------------------------------------------------------------------
michael@0 153
michael@0 154 // This is a dummy input stream that when read, performs the file copy. The
michael@0 155 // copy happens on a background thread via mCopyEvent.
michael@0 156
michael@0 157 class nsFileUploadContentStream : public nsBaseContentStream {
michael@0 158 public:
michael@0 159 NS_DECL_ISUPPORTS_INHERITED
michael@0 160
michael@0 161 nsFileUploadContentStream(bool nonBlocking,
michael@0 162 nsIOutputStream *dest,
michael@0 163 nsIInputStream *source,
michael@0 164 int64_t len,
michael@0 165 nsITransportEventSink *sink)
michael@0 166 : nsBaseContentStream(nonBlocking)
michael@0 167 , mCopyEvent(new nsFileCopyEvent(dest, source, len))
michael@0 168 , mSink(sink) {
michael@0 169 }
michael@0 170
michael@0 171 bool IsInitialized() {
michael@0 172 return mCopyEvent != nullptr;
michael@0 173 }
michael@0 174
michael@0 175 NS_IMETHODIMP ReadSegments(nsWriteSegmentFun fun, void *closure,
michael@0 176 uint32_t count, uint32_t *result);
michael@0 177 NS_IMETHODIMP AsyncWait(nsIInputStreamCallback *callback, uint32_t flags,
michael@0 178 uint32_t count, nsIEventTarget *target);
michael@0 179
michael@0 180 private:
michael@0 181 void OnCopyComplete();
michael@0 182
michael@0 183 nsRefPtr<nsFileCopyEvent> mCopyEvent;
michael@0 184 nsCOMPtr<nsITransportEventSink> mSink;
michael@0 185 };
michael@0 186
michael@0 187 NS_IMPL_ISUPPORTS_INHERITED0(nsFileUploadContentStream,
michael@0 188 nsBaseContentStream)
michael@0 189
michael@0 190 NS_IMETHODIMP
michael@0 191 nsFileUploadContentStream::ReadSegments(nsWriteSegmentFun fun, void *closure,
michael@0 192 uint32_t count, uint32_t *result)
michael@0 193 {
michael@0 194 *result = 0; // nothing is ever actually read from this stream
michael@0 195
michael@0 196 if (IsClosed())
michael@0 197 return NS_OK;
michael@0 198
michael@0 199 if (IsNonBlocking()) {
michael@0 200 // Inform the caller that they will have to wait for the copy operation to
michael@0 201 // complete asynchronously. We'll kick of the copy operation once they
michael@0 202 // call AsyncWait.
michael@0 203 return NS_BASE_STREAM_WOULD_BLOCK;
michael@0 204 }
michael@0 205
michael@0 206 // Perform copy synchronously, and then close out the stream.
michael@0 207 mCopyEvent->DoCopy();
michael@0 208 nsresult status = mCopyEvent->Status();
michael@0 209 CloseWithStatus(NS_FAILED(status) ? status : NS_BASE_STREAM_CLOSED);
michael@0 210 return status;
michael@0 211 }
michael@0 212
michael@0 213 NS_IMETHODIMP
michael@0 214 nsFileUploadContentStream::AsyncWait(nsIInputStreamCallback *callback,
michael@0 215 uint32_t flags, uint32_t count,
michael@0 216 nsIEventTarget *target)
michael@0 217 {
michael@0 218 nsresult rv = nsBaseContentStream::AsyncWait(callback, flags, count, target);
michael@0 219 if (NS_FAILED(rv) || IsClosed())
michael@0 220 return rv;
michael@0 221
michael@0 222 if (IsNonBlocking()) {
michael@0 223 nsCOMPtr<nsIRunnable> callback =
michael@0 224 NS_NewRunnableMethod(this, &nsFileUploadContentStream::OnCopyComplete);
michael@0 225 mCopyEvent->Dispatch(callback, mSink, target);
michael@0 226 }
michael@0 227
michael@0 228 return NS_OK;
michael@0 229 }
michael@0 230
michael@0 231 void
michael@0 232 nsFileUploadContentStream::OnCopyComplete()
michael@0 233 {
michael@0 234 // This method is being called to indicate that we are done copying.
michael@0 235 nsresult status = mCopyEvent->Status();
michael@0 236
michael@0 237 CloseWithStatus(NS_FAILED(status) ? status : NS_BASE_STREAM_CLOSED);
michael@0 238 }
michael@0 239
michael@0 240 //-----------------------------------------------------------------------------
michael@0 241
michael@0 242 nsFileChannel::nsFileChannel(nsIURI *uri)
michael@0 243 {
michael@0 244 // If we have a link file, we should resolve its target right away.
michael@0 245 // This is to protect against a same origin attack where the same link file
michael@0 246 // can point to different resources right after the first resource is loaded.
michael@0 247 nsCOMPtr<nsIFile> file;
michael@0 248 nsCOMPtr <nsIURI> targetURI;
michael@0 249 nsAutoCString fileTarget;
michael@0 250 nsCOMPtr<nsIFile> resolvedFile;
michael@0 251 bool symLink;
michael@0 252 nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(uri);
michael@0 253 if (fileURL &&
michael@0 254 NS_SUCCEEDED(fileURL->GetFile(getter_AddRefs(file))) &&
michael@0 255 NS_SUCCEEDED(file->IsSymlink(&symLink)) &&
michael@0 256 symLink &&
michael@0 257 NS_SUCCEEDED(file->GetNativeTarget(fileTarget)) &&
michael@0 258 NS_SUCCEEDED(NS_NewNativeLocalFile(fileTarget, PR_TRUE,
michael@0 259 getter_AddRefs(resolvedFile))) &&
michael@0 260 NS_SUCCEEDED(NS_NewFileURI(getter_AddRefs(targetURI),
michael@0 261 resolvedFile, nullptr))) {
michael@0 262 SetURI(targetURI);
michael@0 263 SetOriginalURI(uri);
michael@0 264 nsLoadFlags loadFlags = 0;
michael@0 265 GetLoadFlags(&loadFlags);
michael@0 266 SetLoadFlags(loadFlags | nsIChannel::LOAD_REPLACE);
michael@0 267 } else {
michael@0 268 SetURI(uri);
michael@0 269 }
michael@0 270 }
michael@0 271
michael@0 272 nsresult
michael@0 273 nsFileChannel::MakeFileInputStream(nsIFile *file,
michael@0 274 nsCOMPtr<nsIInputStream> &stream,
michael@0 275 nsCString &contentType,
michael@0 276 bool async)
michael@0 277 {
michael@0 278 // we accept that this might result in a disk hit to stat the file
michael@0 279 bool isDir;
michael@0 280 nsresult rv = file->IsDirectory(&isDir);
michael@0 281 if (NS_FAILED(rv)) {
michael@0 282 // canonicalize error message
michael@0 283 if (rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
michael@0 284 rv = NS_ERROR_FILE_NOT_FOUND;
michael@0 285
michael@0 286 if (async && (NS_ERROR_FILE_NOT_FOUND == rv)) {
michael@0 287 // We don't return "Not Found" errors here. Since we could not find
michael@0 288 // the file, it's not a directory anyway.
michael@0 289 isDir = false;
michael@0 290 } else {
michael@0 291 return rv;
michael@0 292 }
michael@0 293 }
michael@0 294
michael@0 295 if (isDir) {
michael@0 296 rv = nsDirectoryIndexStream::Create(file, getter_AddRefs(stream));
michael@0 297 if (NS_SUCCEEDED(rv) && !HasContentTypeHint())
michael@0 298 contentType.AssignLiteral(APPLICATION_HTTP_INDEX_FORMAT);
michael@0 299 } else {
michael@0 300 rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file, -1, -1,
michael@0 301 async? nsIFileInputStream::DEFER_OPEN : 0);
michael@0 302 if (NS_SUCCEEDED(rv) && !HasContentTypeHint()) {
michael@0 303 // Use file extension to infer content type
michael@0 304 nsCOMPtr<nsIMIMEService> mime = do_GetService("@mozilla.org/mime;1", &rv);
michael@0 305 if (NS_SUCCEEDED(rv)) {
michael@0 306 mime->GetTypeFromFile(file, contentType);
michael@0 307 }
michael@0 308 }
michael@0 309 }
michael@0 310 return rv;
michael@0 311 }
michael@0 312
michael@0 313 nsresult
michael@0 314 nsFileChannel::OpenContentStream(bool async, nsIInputStream **result,
michael@0 315 nsIChannel** channel)
michael@0 316 {
michael@0 317 // NOTE: the resulting file is a clone, so it is safe to pass it to the
michael@0 318 // file input stream which will be read on a background thread.
michael@0 319 nsCOMPtr<nsIFile> file;
michael@0 320 nsresult rv = GetFile(getter_AddRefs(file));
michael@0 321 if (NS_FAILED(rv))
michael@0 322 return rv;
michael@0 323
michael@0 324 nsCOMPtr<nsIFileProtocolHandler> fileHandler;
michael@0 325 rv = NS_GetFileProtocolHandler(getter_AddRefs(fileHandler));
michael@0 326 if (NS_FAILED(rv))
michael@0 327 return rv;
michael@0 328
michael@0 329 nsCOMPtr<nsIURI> newURI;
michael@0 330 rv = fileHandler->ReadURLFile(file, getter_AddRefs(newURI));
michael@0 331 if (NS_SUCCEEDED(rv)) {
michael@0 332 nsCOMPtr<nsIChannel> newChannel;
michael@0 333 rv = NS_NewChannel(getter_AddRefs(newChannel), newURI);
michael@0 334 if (NS_FAILED(rv))
michael@0 335 return rv;
michael@0 336
michael@0 337 *result = nullptr;
michael@0 338 newChannel.forget(channel);
michael@0 339 return NS_OK;
michael@0 340 }
michael@0 341
michael@0 342 nsCOMPtr<nsIInputStream> stream;
michael@0 343
michael@0 344 if (mUploadStream) {
michael@0 345 // Pass back a nsFileUploadContentStream instance that knows how to perform
michael@0 346 // the file copy when "read" (the resulting stream in this case does not
michael@0 347 // actually return any data).
michael@0 348
michael@0 349 nsCOMPtr<nsIOutputStream> fileStream;
michael@0 350 rv = NS_NewLocalFileOutputStream(getter_AddRefs(fileStream), file,
michael@0 351 PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE,
michael@0 352 PR_IRUSR | PR_IWUSR);
michael@0 353 if (NS_FAILED(rv))
michael@0 354 return rv;
michael@0 355
michael@0 356 nsFileUploadContentStream *uploadStream =
michael@0 357 new nsFileUploadContentStream(async, fileStream, mUploadStream,
michael@0 358 mUploadLength, this);
michael@0 359 if (!uploadStream || !uploadStream->IsInitialized()) {
michael@0 360 delete uploadStream;
michael@0 361 return NS_ERROR_OUT_OF_MEMORY;
michael@0 362 }
michael@0 363 stream = uploadStream;
michael@0 364
michael@0 365 mContentLength = 0;
michael@0 366
michael@0 367 // Since there isn't any content to speak of we just set the content-type
michael@0 368 // to something other than "unknown" to avoid triggering the content-type
michael@0 369 // sniffer code in nsBaseChannel.
michael@0 370 // However, don't override explicitly set types.
michael@0 371 if (!HasContentTypeHint())
michael@0 372 SetContentType(NS_LITERAL_CSTRING(APPLICATION_OCTET_STREAM));
michael@0 373 } else {
michael@0 374 nsAutoCString contentType;
michael@0 375 rv = MakeFileInputStream(file, stream, contentType, async);
michael@0 376 if (NS_FAILED(rv))
michael@0 377 return rv;
michael@0 378
michael@0 379 EnableSynthesizedProgressEvents(true);
michael@0 380
michael@0 381 // fixup content length and type
michael@0 382 if (mContentLength < 0) {
michael@0 383 int64_t size;
michael@0 384 rv = file->GetFileSize(&size);
michael@0 385 if (NS_FAILED(rv)) {
michael@0 386 if (async &&
michael@0 387 (NS_ERROR_FILE_NOT_FOUND == rv ||
michael@0 388 NS_ERROR_FILE_TARGET_DOES_NOT_EXIST == rv)) {
michael@0 389 size = 0;
michael@0 390 } else {
michael@0 391 return rv;
michael@0 392 }
michael@0 393 }
michael@0 394 mContentLength = size;
michael@0 395 }
michael@0 396 if (!contentType.IsEmpty())
michael@0 397 SetContentType(contentType);
michael@0 398 }
michael@0 399
michael@0 400 *result = nullptr;
michael@0 401 stream.swap(*result);
michael@0 402 return NS_OK;
michael@0 403 }
michael@0 404
michael@0 405 //-----------------------------------------------------------------------------
michael@0 406 // nsFileChannel::nsISupports
michael@0 407
michael@0 408 NS_IMPL_ISUPPORTS_INHERITED(nsFileChannel,
michael@0 409 nsBaseChannel,
michael@0 410 nsIUploadChannel,
michael@0 411 nsIFileChannel)
michael@0 412
michael@0 413 //-----------------------------------------------------------------------------
michael@0 414 // nsFileChannel::nsIFileChannel
michael@0 415
michael@0 416 NS_IMETHODIMP
michael@0 417 nsFileChannel::GetFile(nsIFile **file)
michael@0 418 {
michael@0 419 nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(URI());
michael@0 420 NS_ENSURE_STATE(fileURL);
michael@0 421
michael@0 422 // This returns a cloned nsIFile
michael@0 423 return fileURL->GetFile(file);
michael@0 424 }
michael@0 425
michael@0 426 //-----------------------------------------------------------------------------
michael@0 427 // nsFileChannel::nsIUploadChannel
michael@0 428
michael@0 429 NS_IMETHODIMP
michael@0 430 nsFileChannel::SetUploadStream(nsIInputStream *stream,
michael@0 431 const nsACString &contentType,
michael@0 432 int64_t contentLength)
michael@0 433 {
michael@0 434 NS_ENSURE_TRUE(!Pending(), NS_ERROR_IN_PROGRESS);
michael@0 435
michael@0 436 if ((mUploadStream = stream)) {
michael@0 437 mUploadLength = contentLength;
michael@0 438 if (mUploadLength < 0) {
michael@0 439 // Make sure we know how much data we are uploading.
michael@0 440 uint64_t avail;
michael@0 441 nsresult rv = mUploadStream->Available(&avail);
michael@0 442 if (NS_FAILED(rv))
michael@0 443 return rv;
michael@0 444 if (avail < INT64_MAX)
michael@0 445 mUploadLength = avail;
michael@0 446 }
michael@0 447 } else {
michael@0 448 mUploadLength = -1;
michael@0 449 }
michael@0 450 return NS_OK;
michael@0 451 }
michael@0 452
michael@0 453 NS_IMETHODIMP
michael@0 454 nsFileChannel::GetUploadStream(nsIInputStream **result)
michael@0 455 {
michael@0 456 NS_IF_ADDREF(*result = mUploadStream);
michael@0 457 return NS_OK;
michael@0 458 }

mercurial