netwerk/protocol/file/nsFileChannel.cpp

changeset 0
6474c204b198
     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 +}

mercurial