netwerk/protocol/file/nsFileChannel.cpp

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

mercurial