Coverage Report

Created: 2018-09-25 14:53

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