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 : }
|