Tue, 06 Jan 2015 21:39:09 +0100
Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 /*
7 Notes to self:
9 - at some point, strings will be accessible from JS, so we won't have to wrap
10 flavors in an nsISupportsCString. Until then, we're kinda stuck with
11 this crappy API of nsISupportsArrays.
13 */
16 #include "nsTransferable.h"
17 #include "mozilla/Preferences.h"
18 #include "nsString.h"
19 #include "nsReadableUtils.h"
20 #include "nsTArray.h"
21 #include "nsIFormatConverter.h"
22 #include "nsIComponentManager.h"
23 #include "nsCOMPtr.h"
24 #include "nsXPCOM.h"
25 #include "nsISupportsPrimitives.h"
26 #include "nsMemory.h"
27 #include "nsPrimitiveHelpers.h"
28 #include "nsXPIDLString.h"
29 #include "nsDirectoryServiceDefs.h"
30 #include "nsDirectoryService.h"
31 #include "nsCRT.h"
32 #include "nsNetUtil.h"
33 #include "nsIOutputStream.h"
34 #include "nsIInputStream.h"
35 #include "nsIFile.h"
36 #include "nsILoadContext.h"
37 #include "nsAutoPtr.h"
39 NS_IMPL_ISUPPORTS(nsTransferable, nsITransferable)
41 uint32_t GetDataForFlavor (const nsTArray<DataStruct>& aArray,
42 const char* aDataFlavor)
43 {
44 for (uint32_t i = 0 ; i < aArray.Length () ; ++i) {
45 if (aArray[i].GetFlavor().Equals (aDataFlavor))
46 return i;
47 }
49 return aArray.NoIndex;
50 }
52 //-------------------------------------------------------------------------
53 DataStruct::~DataStruct()
54 {
55 if (mCacheFileName) free(mCacheFileName);
56 }
58 //-------------------------------------------------------------------------
59 void
60 DataStruct::SetData ( nsISupports* aData, uint32_t aDataLen )
61 {
62 // First, prepare for conditional caching according to isolation mode
63 int32_t isolationState = mozilla::Preferences::GetInt("privacy.thirdparty.isolate");
65 // Now, check to see if we consider the data to be "too large"
66 if (aDataLen > kLargeDatasetSize && isolationState == 0) {
67 // if so, cache it to disk instead of memory
68 if ( NS_SUCCEEDED(WriteCache(aData, aDataLen)) )
69 return;
70 else
71 NS_WARNING("Oh no, couldn't write data to the cache file");
72 }
74 mData = aData;
75 mDataLen = aDataLen;
76 }
79 //-------------------------------------------------------------------------
80 void
81 DataStruct::GetData ( nsISupports** aData, uint32_t *aDataLen )
82 {
83 // check here to see if the data is cached on disk
84 if ( !mData && mCacheFileName ) {
85 // if so, read it in and pass it back
86 // ReadCache creates memory and copies the data into it.
87 if ( NS_SUCCEEDED(ReadCache(aData, aDataLen)) )
88 return;
89 else {
90 // oh shit, something went horribly wrong here.
91 NS_WARNING("Oh no, couldn't read data in from the cache file");
92 *aData = nullptr;
93 *aDataLen = 0;
94 return;
95 }
96 }
98 *aData = mData;
99 if ( mData )
100 NS_ADDREF(*aData);
101 *aDataLen = mDataLen;
102 }
105 //-------------------------------------------------------------------------
106 already_AddRefed<nsIFile>
107 DataStruct::GetFileSpec(const char* aFileName)
108 {
109 nsCOMPtr<nsIFile> cacheFile;
110 NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(cacheFile));
112 if (!cacheFile)
113 return nullptr;
115 // if the param aFileName contains a name we should use that
116 // because the file probably already exists
117 // otherwise create a unique name
118 if (!aFileName) {
119 cacheFile->AppendNative(NS_LITERAL_CSTRING("clipboardcache"));
120 cacheFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
121 } else {
122 cacheFile->AppendNative(nsDependentCString(aFileName));
123 }
125 return cacheFile.forget();
126 }
129 //-------------------------------------------------------------------------
130 nsresult
131 DataStruct::WriteCache(nsISupports* aData, uint32_t aDataLen)
132 {
133 // Get a new path and file to the temp directory
134 nsCOMPtr<nsIFile> cacheFile = GetFileSpec(mCacheFileName);
135 if (cacheFile) {
136 // remember the file name
137 if (!mCacheFileName) {
138 nsXPIDLCString fName;
139 cacheFile->GetNativeLeafName(fName);
140 mCacheFileName = strdup(fName);
141 }
143 // write out the contents of the clipboard
144 // to the file
145 //uint32_t bytes;
146 nsCOMPtr<nsIOutputStream> outStr;
148 NS_NewLocalFileOutputStream(getter_AddRefs(outStr),
149 cacheFile);
151 if (!outStr) return NS_ERROR_FAILURE;
153 void* buff = nullptr;
154 nsPrimitiveHelpers::CreateDataFromPrimitive ( mFlavor.get(), aData, &buff, aDataLen );
155 if ( buff ) {
156 uint32_t ignored;
157 outStr->Write(reinterpret_cast<char*>(buff), aDataLen, &ignored);
158 nsMemory::Free(buff);
159 return NS_OK;
160 }
161 }
162 return NS_ERROR_FAILURE;
163 }
166 //-------------------------------------------------------------------------
167 nsresult
168 DataStruct::ReadCache(nsISupports** aData, uint32_t* aDataLen)
169 {
170 // if we don't have a cache filename we are out of luck
171 if (!mCacheFileName)
172 return NS_ERROR_FAILURE;
174 // get the path and file name
175 nsCOMPtr<nsIFile> cacheFile = GetFileSpec(mCacheFileName);
176 bool exists;
177 if ( cacheFile && NS_SUCCEEDED(cacheFile->Exists(&exists)) && exists ) {
178 // get the size of the file
179 int64_t fileSize;
180 int64_t max32 = 0xFFFFFFFF;
181 cacheFile->GetFileSize(&fileSize);
182 if (fileSize > max32)
183 return NS_ERROR_OUT_OF_MEMORY;
185 uint32_t size = uint32_t(fileSize);
186 // create new memory for the large clipboard data
187 nsAutoArrayPtr<char> data(new char[size]);
188 if ( !data )
189 return NS_ERROR_OUT_OF_MEMORY;
191 // now read it all in
192 nsCOMPtr<nsIInputStream> inStr;
193 NS_NewLocalFileInputStream( getter_AddRefs(inStr),
194 cacheFile);
196 if (!cacheFile) return NS_ERROR_FAILURE;
198 nsresult rv = inStr->Read(data, fileSize, aDataLen);
200 // make sure we got all the data ok
201 if (NS_SUCCEEDED(rv) && *aDataLen == size) {
202 nsPrimitiveHelpers::CreatePrimitiveForData ( mFlavor.get(), data, fileSize, aData );
203 return *aData ? NS_OK : NS_ERROR_FAILURE;
204 }
206 // zero the return params
207 *aData = nullptr;
208 *aDataLen = 0;
209 }
211 return NS_ERROR_FAILURE;
212 }
215 //-------------------------------------------------------------------------
216 //
217 // Transferable constructor
218 //
219 //-------------------------------------------------------------------------
220 nsTransferable::nsTransferable()
221 : mPrivateData(false)
222 #ifdef DEBUG
223 , mInitialized(false)
224 #endif
225 {
226 }
228 //-------------------------------------------------------------------------
229 //
230 // Transferable destructor
231 //
232 //-------------------------------------------------------------------------
233 nsTransferable::~nsTransferable()
234 {
235 }
238 NS_IMETHODIMP
239 nsTransferable::Init(nsILoadContext* aContext)
240 {
241 MOZ_ASSERT(!mInitialized);
243 if (aContext) {
244 mPrivateData = aContext->UsePrivateBrowsing();
245 }
246 #ifdef DEBUG
247 mInitialized = true;
248 #endif
249 return NS_OK;
250 }
252 //
253 // GetTransferDataFlavors
254 //
255 // Returns a copy of the internal list of flavors. This does NOT take into
256 // account any converter that may be registered. This list consists of
257 // nsISupportsCString objects so that the flavor list can be accessed from JS.
258 //
259 nsresult
260 nsTransferable::GetTransferDataFlavors(nsISupportsArray ** aDataFlavorList)
261 {
262 MOZ_ASSERT(mInitialized);
264 nsresult rv = NS_NewISupportsArray ( aDataFlavorList );
265 if (NS_FAILED(rv)) return rv;
267 for ( uint32_t i=0; i<mDataArray.Length(); ++i ) {
268 DataStruct& data = mDataArray.ElementAt(i);
269 nsCOMPtr<nsISupportsCString> flavorWrapper = do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID);
270 if ( flavorWrapper ) {
271 flavorWrapper->SetData ( data.GetFlavor() );
272 nsCOMPtr<nsISupports> genericWrapper ( do_QueryInterface(flavorWrapper) );
273 (*aDataFlavorList)->AppendElement( genericWrapper );
274 }
275 }
277 return NS_OK;
278 }
281 //
282 // GetTransferData
283 //
284 // Returns the data of the requested flavor, obtained from either having the data on hand or
285 // using a converter to get it. The data is wrapped in a nsISupports primitive so that it is
286 // accessible from JS.
287 //
288 NS_IMETHODIMP
289 nsTransferable::GetTransferData(const char *aFlavor, nsISupports **aData, uint32_t *aDataLen)
290 {
291 MOZ_ASSERT(mInitialized);
293 NS_ENSURE_ARG_POINTER(aFlavor && aData && aDataLen);
295 nsresult rv = NS_OK;
296 nsCOMPtr<nsISupports> savedData;
298 // first look and see if the data is present in one of the intrinsic flavors
299 uint32_t i;
300 for (i = 0; i < mDataArray.Length(); ++i ) {
301 DataStruct& data = mDataArray.ElementAt(i);
302 if ( data.GetFlavor().Equals(aFlavor) ) {
303 nsCOMPtr<nsISupports> dataBytes;
304 uint32_t len;
305 data.GetData(getter_AddRefs(dataBytes), &len);
306 if (len == kFlavorHasDataProvider && dataBytes) {
307 // do we have a data provider?
308 nsCOMPtr<nsIFlavorDataProvider> dataProvider = do_QueryInterface(dataBytes);
309 if (dataProvider) {
310 rv = dataProvider->GetFlavorData(this, aFlavor,
311 getter_AddRefs(dataBytes), &len);
312 if (NS_FAILED(rv))
313 break; // the provider failed. fall into the converter code below.
314 }
315 }
316 if (dataBytes && len > 0) { // XXXmats why is zero length not ok?
317 *aDataLen = len;
318 dataBytes.forget(aData);
319 return NS_OK;
320 }
321 savedData = dataBytes; // return this if format converter fails
322 break;
323 }
324 }
326 bool found = false;
328 // if not, try using a format converter to get the requested flavor
329 if ( mFormatConv ) {
330 for (i = 0; i < mDataArray.Length(); ++i) {
331 DataStruct& data = mDataArray.ElementAt(i);
332 bool canConvert = false;
333 mFormatConv->CanConvert(data.GetFlavor().get(), aFlavor, &canConvert);
334 if ( canConvert ) {
335 nsCOMPtr<nsISupports> dataBytes;
336 uint32_t len;
337 data.GetData(getter_AddRefs(dataBytes), &len);
338 if (len == kFlavorHasDataProvider && dataBytes) {
339 // do we have a data provider?
340 nsCOMPtr<nsIFlavorDataProvider> dataProvider = do_QueryInterface(dataBytes);
341 if (dataProvider) {
342 rv = dataProvider->GetFlavorData(this, aFlavor,
343 getter_AddRefs(dataBytes), &len);
344 if (NS_FAILED(rv))
345 break; // give up
346 }
347 }
348 mFormatConv->Convert(data.GetFlavor().get(), dataBytes, len, aFlavor, aData, aDataLen);
349 found = true;
350 break;
351 }
352 }
353 }
355 // for backward compatibility
356 if (!found) {
357 savedData.forget(aData);
358 *aDataLen = 0;
359 }
361 return found ? NS_OK : NS_ERROR_FAILURE;
362 }
365 //
366 // GetAnyTransferData
367 //
368 // Returns the data of the first flavor found. Caller is responsible for deleting the
369 // flavor string.
370 //
371 NS_IMETHODIMP
372 nsTransferable::GetAnyTransferData(char **aFlavor, nsISupports **aData, uint32_t *aDataLen)
373 {
374 MOZ_ASSERT(mInitialized);
376 NS_ENSURE_ARG_POINTER(aFlavor && aData && aDataLen);
378 for ( uint32_t i=0; i < mDataArray.Length(); ++i ) {
379 DataStruct& data = mDataArray.ElementAt(i);
380 if (data.IsDataAvailable()) {
381 *aFlavor = ToNewCString(data.GetFlavor());
382 data.GetData(aData, aDataLen);
383 return NS_OK;
384 }
385 }
387 return NS_ERROR_FAILURE;
388 }
391 //
392 // SetTransferData
393 //
394 //
395 //
396 NS_IMETHODIMP
397 nsTransferable::SetTransferData(const char *aFlavor, nsISupports *aData, uint32_t aDataLen)
398 {
399 MOZ_ASSERT(mInitialized);
401 NS_ENSURE_ARG(aFlavor);
403 // first check our intrinsic flavors to see if one has been registered.
404 uint32_t i = 0;
405 for (i = 0; i < mDataArray.Length(); ++i) {
406 DataStruct& data = mDataArray.ElementAt(i);
407 if ( data.GetFlavor().Equals(aFlavor) ) {
408 data.SetData ( aData, aDataLen );
409 return NS_OK;
410 }
411 }
413 // if not, try using a format converter to find a flavor to put the data in
414 if ( mFormatConv ) {
415 for (i = 0; i < mDataArray.Length(); ++i) {
416 DataStruct& data = mDataArray.ElementAt(i);
417 bool canConvert = false;
418 mFormatConv->CanConvert(aFlavor, data.GetFlavor().get(), &canConvert);
420 if ( canConvert ) {
421 nsCOMPtr<nsISupports> ConvertedData;
422 uint32_t ConvertedLen;
423 mFormatConv->Convert(aFlavor, aData, aDataLen, data.GetFlavor().get(), getter_AddRefs(ConvertedData), &ConvertedLen);
424 data.SetData(ConvertedData, ConvertedLen);
425 return NS_OK;
426 }
427 }
428 }
430 // Can't set data neither directly nor through converter. Just add this flavor and try again
431 nsresult result = NS_ERROR_FAILURE;
432 if ( NS_SUCCEEDED(AddDataFlavor(aFlavor)) )
433 result = SetTransferData (aFlavor, aData, aDataLen);
435 return result;
436 }
439 //
440 // AddDataFlavor
441 //
442 // Adds a data flavor to our list with no data. Error if it already exists.
443 //
444 NS_IMETHODIMP
445 nsTransferable::AddDataFlavor(const char *aDataFlavor)
446 {
447 MOZ_ASSERT(mInitialized);
449 if (GetDataForFlavor (mDataArray, aDataFlavor) != mDataArray.NoIndex)
450 return NS_ERROR_FAILURE;
452 // Create a new "slot" for the data
453 mDataArray.AppendElement(DataStruct ( aDataFlavor ));
455 return NS_OK;
456 }
459 //
460 // RemoveDataFlavor
461 //
462 // Removes a data flavor (and causes the data to be destroyed). Error if
463 // the requested flavor is not present.
464 //
465 NS_IMETHODIMP
466 nsTransferable::RemoveDataFlavor(const char *aDataFlavor)
467 {
468 MOZ_ASSERT(mInitialized);
470 uint32_t idx;
471 if ((idx = GetDataForFlavor(mDataArray, aDataFlavor)) != mDataArray.NoIndex) {
472 mDataArray.RemoveElementAt (idx);
473 return NS_OK;
474 }
475 return NS_ERROR_FAILURE;
476 }
479 /**
480 *
481 *
482 */
483 NS_IMETHODIMP
484 nsTransferable::IsLargeDataSet(bool *_retval)
485 {
486 MOZ_ASSERT(mInitialized);
488 NS_ENSURE_ARG_POINTER(_retval);
489 *_retval = false;
490 return NS_OK;
491 }
494 /**
495 *
496 *
497 */
498 NS_IMETHODIMP nsTransferable::SetConverter(nsIFormatConverter * aConverter)
499 {
500 MOZ_ASSERT(mInitialized);
502 mFormatConv = aConverter;
503 return NS_OK;
504 }
507 /**
508 *
509 *
510 */
511 NS_IMETHODIMP nsTransferable::GetConverter(nsIFormatConverter * *aConverter)
512 {
513 MOZ_ASSERT(mInitialized);
515 NS_ENSURE_ARG_POINTER(aConverter);
516 *aConverter = mFormatConv;
517 NS_IF_ADDREF(*aConverter);
518 return NS_OK;
519 }
522 //
523 // FlavorsTransferableCanImport
524 //
525 // Computes a list of flavors that the transferable can accept into it, either through
526 // intrinsic knowledge or input data converters.
527 //
528 NS_IMETHODIMP
529 nsTransferable::FlavorsTransferableCanImport(nsISupportsArray **_retval)
530 {
531 MOZ_ASSERT(mInitialized);
533 NS_ENSURE_ARG_POINTER(_retval);
535 // Get the flavor list, and on to the end of it, append the list of flavors we
536 // can also get to through a converter. This is so that we can just walk the list
537 // in one go, looking for the desired flavor.
538 GetTransferDataFlavors(_retval); // addrefs
539 nsCOMPtr<nsIFormatConverter> converter;
540 GetConverter(getter_AddRefs(converter));
541 if ( converter ) {
542 nsCOMPtr<nsISupportsArray> convertedList;
543 converter->GetInputDataFlavors(getter_AddRefs(convertedList));
545 if ( convertedList ) {
546 uint32_t importListLen;
547 convertedList->Count(&importListLen);
549 for ( uint32_t i=0; i < importListLen; ++i ) {
550 nsCOMPtr<nsISupports> genericFlavor;
551 convertedList->GetElementAt ( i, getter_AddRefs(genericFlavor) );
553 nsCOMPtr<nsISupportsCString> flavorWrapper ( do_QueryInterface (genericFlavor) );
554 nsAutoCString flavorStr;
555 flavorWrapper->GetData( flavorStr );
557 if (GetDataForFlavor (mDataArray, flavorStr.get())
558 == mDataArray.NoIndex) // Don't append if already in intrinsic list
559 (*_retval)->AppendElement (genericFlavor);
560 } // foreach flavor that can be converted to
561 }
562 } // if a converter exists
564 return NS_OK;
565 } // FlavorsTransferableCanImport
568 //
569 // FlavorsTransferableCanExport
570 //
571 // Computes a list of flavors that the transferable can export, either through
572 // intrinsic knowledge or output data converters.
573 //
574 NS_IMETHODIMP
575 nsTransferable::FlavorsTransferableCanExport(nsISupportsArray **_retval)
576 {
577 MOZ_ASSERT(mInitialized);
579 NS_ENSURE_ARG_POINTER(_retval);
581 // Get the flavor list, and on to the end of it, append the list of flavors we
582 // can also get to through a converter. This is so that we can just walk the list
583 // in one go, looking for the desired flavor.
584 GetTransferDataFlavors(_retval); // addrefs
585 nsCOMPtr<nsIFormatConverter> converter;
586 GetConverter(getter_AddRefs(converter));
587 if ( converter ) {
588 nsCOMPtr<nsISupportsArray> convertedList;
589 converter->GetOutputDataFlavors(getter_AddRefs(convertedList));
591 if ( convertedList ) {
592 uint32_t importListLen;
593 convertedList->Count(&importListLen);
595 for ( uint32_t i=0; i < importListLen; ++i ) {
596 nsCOMPtr<nsISupports> genericFlavor;
597 convertedList->GetElementAt ( i, getter_AddRefs(genericFlavor) );
599 nsCOMPtr<nsISupportsCString> flavorWrapper ( do_QueryInterface (genericFlavor) );
600 nsAutoCString flavorStr;
601 flavorWrapper->GetData( flavorStr );
603 if (GetDataForFlavor (mDataArray, flavorStr.get())
604 == mDataArray.NoIndex) // Don't append if already in intrinsic list
605 (*_retval)->AppendElement (genericFlavor);
606 } // foreach flavor that can be converted to
607 }
608 } // if a converter exists
610 return NS_OK;
611 } // FlavorsTransferableCanExport
613 NS_IMETHODIMP
614 nsTransferable::GetIsPrivateData(bool *aIsPrivateData)
615 {
616 MOZ_ASSERT(mInitialized);
618 NS_ENSURE_ARG_POINTER(aIsPrivateData);
620 *aIsPrivateData = mPrivateData;
622 return NS_OK;
623 }
625 NS_IMETHODIMP
626 nsTransferable::SetIsPrivateData(bool aIsPrivateData)
627 {
628 MOZ_ASSERT(mInitialized);
630 mPrivateData = aIsPrivateData;
632 return NS_OK;
633 }