LCOV - code coverage report
Current view: top level - netwerk/protocol/file - nsFileChannel.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 72 200 36.0 %
Date: 2017-07-14 16:53:18 Functions: 10 29 34.5 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.13