Fix opt failures using gczeal. (r=Waldo)
[mozilla-central.git] / extensions / cookie / nsPermissionManager.cpp
blobf45529a839f4c1504573705c89aff32f4c60b735
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
15 * The Original Code is mozilla.org code.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 1998
20 * the Initial Developer. All Rights Reserved.
22 * Contributor(s):
23 * Michiel van Leeuwen (mvl@exedo.nl)
24 * Daniel Witte (dwitte@stanford.edu)
26 * Alternatively, the contents of this file may be used under the terms of
27 * either the GNU General Public License Version 2 or later (the "GPL"), or
28 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * ***** END LICENSE BLOCK ***** */
40 #ifdef MOZ_IPC
41 #include "mozilla/dom/ContentParent.h"
42 #include "mozilla/dom/ContentChild.h"
43 #endif
44 #include "nsPermissionManager.h"
45 #include "nsPermission.h"
46 #include "nsCRT.h"
47 #include "nsNetUtil.h"
48 #include "nsCOMArray.h"
49 #include "nsArrayEnumerator.h"
50 #include "nsTArray.h"
51 #include "nsReadableUtils.h"
52 #include "nsILineInputStream.h"
53 #include "nsIIDNService.h"
54 #include "nsAppDirectoryServiceDefs.h"
55 #include "prprf.h"
56 #include "mozIStorageService.h"
57 #include "mozIStorageStatement.h"
58 #include "mozIStorageConnection.h"
59 #include "mozStorageHelper.h"
60 #include "mozStorageCID.h"
61 #include "nsXULAppAPI.h"
63 static nsPermissionManager *gPermissionManager = nsnull;
65 #ifdef MOZ_IPC
66 using mozilla::dom::ContentParent;
67 using mozilla::dom::ContentChild;
69 static PRBool
70 IsChildProcess()
72 return XRE_GetProcessType() == GeckoProcessType_Content;
75 /**
76 * @returns The child process object, or if we are not in the child
77 * process, nsnull.
79 static ContentChild*
80 ChildProcess()
82 if (IsChildProcess()) {
83 ContentChild* cpc = ContentChild::GetSingleton();
84 if (!cpc)
85 NS_RUNTIMEABORT("Content Process is NULL!");
86 return cpc;
89 return nsnull;
93 /**
94 * @returns The parent process object, or if we are not in the parent
95 * process, nsnull.
97 static ContentParent*
98 ParentProcess()
100 if (!IsChildProcess()) {
101 ContentParent* cpc = ContentParent::GetSingleton();
102 if (!cpc)
103 NS_RUNTIMEABORT("Content Process is NULL!");
104 return cpc;
107 return nsnull;
109 #endif
111 #define ENSURE_NOT_CHILD_PROCESS_(onError) \
112 PR_BEGIN_MACRO \
113 if (IsChildProcess()) { \
114 NS_ERROR("Cannot perform action in content process!"); \
115 onError \
117 PR_END_MACRO
119 #define ENSURE_NOT_CHILD_PROCESS \
120 ENSURE_NOT_CHILD_PROCESS_({ return NS_ERROR_NOT_AVAILABLE; })
122 #define ENSURE_NOT_CHILD_PROCESS_NORET \
123 ENSURE_NOT_CHILD_PROCESS_()
125 ////////////////////////////////////////////////////////////////////////////////
127 #define PL_ARENA_CONST_ALIGN_MASK 3
128 #include "plarena.h"
130 static PLArenaPool *gHostArena = nsnull;
132 // making sHostArena 512b for nice allocation
133 // growing is quite cheap
134 #define HOST_ARENA_SIZE 512
136 // equivalent to strdup() - does no error checking,
137 // we're assuming we're only called with a valid pointer
138 static char *
139 ArenaStrDup(const char* str, PLArenaPool* aArena)
141 void* mem;
142 const PRUint32 size = strlen(str) + 1;
143 PL_ARENA_ALLOCATE(mem, aArena, size);
144 if (mem)
145 memcpy(mem, str, size);
146 return static_cast<char*>(mem);
149 nsHostEntry::nsHostEntry(const char* aHost)
151 mHost = ArenaStrDup(aHost, gHostArena);
154 // XXX this can fail on OOM
155 nsHostEntry::nsHostEntry(const nsHostEntry& toCopy)
156 : mHost(toCopy.mHost)
157 , mPermissions(toCopy.mPermissions)
161 ////////////////////////////////////////////////////////////////////////////////
162 // nsPermissionManager Implementation
164 static const char kPermissionsFileName[] = "permissions.sqlite";
165 #define HOSTS_SCHEMA_VERSION 2
167 static const char kHostpermFileName[] = "hostperm.1";
169 static const char kPermissionChangeNotification[] = PERM_CHANGE_NOTIFICATION;
171 NS_IMPL_ISUPPORTS3(nsPermissionManager, nsIPermissionManager, nsIObserver, nsISupportsWeakReference)
173 nsPermissionManager::nsPermissionManager()
174 : mLargestID(0)
175 #ifdef MOZ_IPC
176 , mUpdateChildProcess(PR_FALSE)
177 #endif
181 nsPermissionManager::~nsPermissionManager()
183 RemoveAllFromMemory();
186 // static
187 nsIPermissionManager*
188 nsPermissionManager::GetXPCOMSingleton()
190 return GetSingleton().get();
193 // static
194 already_AddRefed<nsPermissionManager>
195 nsPermissionManager::GetSingleton()
197 if (gPermissionManager) {
198 NS_ADDREF(gPermissionManager);
199 return gPermissionManager;
202 // Create a new singleton nsPermissionManager.
203 // We AddRef only once since XPCOM has rules about the ordering of module
204 // teardowns - by the time our module destructor is called, it's too late to
205 // Release our members, since GC cycles have already been completed and
206 // would result in serious leaks.
207 // See bug 209571.
208 gPermissionManager = new nsPermissionManager();
209 if (gPermissionManager) {
210 NS_ADDREF(gPermissionManager);
211 if (NS_FAILED(gPermissionManager->Init())) {
212 NS_RELEASE(gPermissionManager);
216 return gPermissionManager;
219 nsresult
220 nsPermissionManager::Init()
222 nsresult rv;
224 if (!mHostTable.Init()) {
225 return NS_ERROR_OUT_OF_MEMORY;
228 #ifdef MOZ_IPC
229 if (IsChildProcess()) {
230 // Get the permissions from the parent process
231 InfallibleTArray<IPC::Permission> perms;
232 ChildProcess()->SendReadPermissions(&perms);
234 for (int i = 0; i < perms.Length(); i++) {
235 const IPC::Permission &perm = perms[i];
236 AddInternal(perm.host, perm.type, perm.capability, 0, perm.expireType,
237 perm.expireTime, eNotify, eNoDBOperation);
240 // Stop here; we don't need the DB in the child process
241 return NS_OK;
243 #endif
245 // ignore failure here, since it's non-fatal (we can run fine without
246 // persistent storage - e.g. if there's no profile).
247 // XXX should we tell the user about this?
248 InitDB(PR_FALSE);
250 mObserverService = do_GetService("@mozilla.org/observer-service;1", &rv);
251 if (NS_SUCCEEDED(rv)) {
252 mObserverService->AddObserver(this, "profile-before-change", PR_TRUE);
253 mObserverService->AddObserver(this, "profile-do-change", PR_TRUE);
256 return NS_OK;
259 nsresult
260 nsPermissionManager::InitDB(PRBool aRemoveFile)
262 nsCOMPtr<nsIFile> permissionsFile;
263 NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(permissionsFile));
264 if (!permissionsFile)
265 return NS_ERROR_UNEXPECTED;
267 nsresult rv = permissionsFile->AppendNative(NS_LITERAL_CSTRING(kPermissionsFileName));
268 NS_ENSURE_SUCCESS(rv, rv);
270 if (aRemoveFile) {
271 PRBool exists = PR_FALSE;
272 rv = permissionsFile->Exists(&exists);
273 NS_ENSURE_SUCCESS(rv, rv);
274 if (exists) {
275 rv = permissionsFile->Remove(PR_FALSE);
276 NS_ENSURE_SUCCESS(rv, rv);
280 nsCOMPtr<mozIStorageService> storage = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
281 if (!storage)
282 return NS_ERROR_UNEXPECTED;
284 // cache a connection to the hosts database
285 rv = storage->OpenDatabase(permissionsFile, getter_AddRefs(mDBConn));
286 NS_ENSURE_SUCCESS(rv, rv);
288 PRBool ready;
289 mDBConn->GetConnectionReady(&ready);
290 if (!ready) {
291 // delete and try again
292 rv = permissionsFile->Remove(PR_FALSE);
293 NS_ENSURE_SUCCESS(rv, rv);
295 rv = storage->OpenDatabase(permissionsFile, getter_AddRefs(mDBConn));
296 NS_ENSURE_SUCCESS(rv, rv);
298 mDBConn->GetConnectionReady(&ready);
299 if (!ready)
300 return NS_ERROR_UNEXPECTED;
303 PRBool tableExists = PR_FALSE;
304 mDBConn->TableExists(NS_LITERAL_CSTRING("moz_hosts"), &tableExists);
305 if (!tableExists) {
306 rv = CreateTable();
307 NS_ENSURE_SUCCESS(rv, rv);
309 } else {
310 // table already exists; check the schema version before reading
311 PRInt32 dbSchemaVersion;
312 rv = mDBConn->GetSchemaVersion(&dbSchemaVersion);
313 NS_ENSURE_SUCCESS(rv, rv);
315 switch (dbSchemaVersion) {
316 // upgrading.
317 // every time you increment the database schema, you need to implement
318 // the upgrading code from the previous version to the new one.
319 // fall through to current version
321 case 1:
323 // previous non-expiry version of database. Upgrade it by adding the
324 // expiration columns
325 rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
326 "ALTER TABLE moz_hosts ADD expireType INTEGER"));
327 NS_ENSURE_SUCCESS(rv, rv);
329 rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
330 "ALTER TABLE moz_hosts ADD expireTime INTEGER"));
331 NS_ENSURE_SUCCESS(rv, rv);
333 rv = mDBConn->SetSchemaVersion(HOSTS_SCHEMA_VERSION);
334 NS_ENSURE_SUCCESS(rv, rv);
337 // fall through to the next upgrade
339 // current version.
340 case HOSTS_SCHEMA_VERSION:
341 break;
343 case 0:
345 NS_WARNING("couldn't get schema version!");
347 // the table may be usable; someone might've just clobbered the schema
348 // version. we can treat this case like a downgrade using the codepath
349 // below, by verifying the columns we care about are all there. for now,
350 // re-set the schema version in the db, in case the checks succeed (if
351 // they don't, we're dropping the table anyway).
352 rv = mDBConn->SetSchemaVersion(HOSTS_SCHEMA_VERSION);
353 NS_ENSURE_SUCCESS(rv, rv);
355 // fall through to downgrade check
357 // downgrading.
358 // if columns have been added to the table, we can still use the ones we
359 // understand safely. if columns have been deleted or altered, just
360 // blow away the table and start from scratch! if you change the way
361 // a column is interpreted, make sure you also change its name so this
362 // check will catch it.
363 default:
365 // check if all the expected columns exist
366 nsCOMPtr<mozIStorageStatement> stmt;
367 rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
368 "SELECT host, type, permission, expireType, expireTime FROM moz_hosts"),
369 getter_AddRefs(stmt));
370 if (NS_SUCCEEDED(rv))
371 break;
373 // our columns aren't there - drop the table!
374 rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DROP TABLE moz_hosts"));
375 NS_ENSURE_SUCCESS(rv, rv);
377 rv = CreateTable();
378 NS_ENSURE_SUCCESS(rv, rv);
380 break;
384 // make operations on the table asynchronous, for performance
385 mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("PRAGMA synchronous = OFF"));
387 // cache frequently used statements (for insertion, deletion, and updating)
388 rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
389 "INSERT INTO moz_hosts "
390 "(id, host, type, permission, expireType, expireTime) "
391 "VALUES (?1, ?2, ?3, ?4, ?5, ?6)"), getter_AddRefs(mStmtInsert));
392 NS_ENSURE_SUCCESS(rv, rv);
394 rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
395 "DELETE FROM moz_hosts "
396 "WHERE id = ?1"), getter_AddRefs(mStmtDelete));
397 NS_ENSURE_SUCCESS(rv, rv);
399 rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
400 "UPDATE moz_hosts "
401 "SET permission = ?2, expireType= ?3, expireTime = ?4 WHERE id = ?1"),
402 getter_AddRefs(mStmtUpdate));
403 NS_ENSURE_SUCCESS(rv, rv);
405 // check whether to import or just read in the db
406 if (tableExists)
407 return Read();
409 return Import();
412 // sets the schema version and creates the moz_hosts table.
413 nsresult
414 nsPermissionManager::CreateTable()
416 // set the schema version, before creating the table
417 nsresult rv = mDBConn->SetSchemaVersion(HOSTS_SCHEMA_VERSION);
418 if (NS_FAILED(rv)) return rv;
420 // create the table
421 // SQL also lives in automation.py.in. If you change this SQL change that
422 // one too.
423 return mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
424 "CREATE TABLE moz_hosts ("
425 " id INTEGER PRIMARY KEY"
426 ",host TEXT"
427 ",type TEXT"
428 ",permission INTEGER"
429 ",expireType INTEGER"
430 ",expireTime INTEGER"
431 ")"));
434 NS_IMETHODIMP
435 nsPermissionManager::Add(nsIURI *aURI,
436 const char *aType,
437 PRUint32 aPermission,
438 PRUint32 aExpireType,
439 PRInt64 aExpireTime)
441 #ifdef MOZ_IPC
442 ENSURE_NOT_CHILD_PROCESS;
443 #endif
445 NS_ENSURE_ARG_POINTER(aURI);
446 NS_ENSURE_ARG_POINTER(aType);
447 NS_ENSURE_TRUE(aExpireType == nsIPermissionManager::EXPIRE_NEVER ||
448 aExpireType == nsIPermissionManager::EXPIRE_TIME ||
449 aExpireType == nsIPermissionManager::EXPIRE_SESSION,
450 NS_ERROR_INVALID_ARG);
452 nsresult rv;
454 // Skip addition if the permission is already expired.
455 if (aExpireType == nsIPermissionManager::EXPIRE_TIME &&
456 aExpireTime <= PR_Now() / 1000)
457 return NS_OK;
459 nsCAutoString host;
460 rv = GetHost(aURI, host);
461 NS_ENSURE_SUCCESS(rv, rv);
463 return AddInternal(host, nsDependentCString(aType), aPermission, 0,
464 aExpireType, aExpireTime, eNotify, eWriteToDB);
467 nsresult
468 nsPermissionManager::AddInternal(const nsAFlatCString &aHost,
469 const nsAFlatCString &aType,
470 PRUint32 aPermission,
471 PRInt64 aID,
472 PRUint32 aExpireType,
473 PRInt64 aExpireTime,
474 NotifyOperationType aNotifyOperation,
475 DBOperationType aDBOperation)
477 #ifdef MOZ_IPC
478 if (!IsChildProcess()) {
479 // In the parent, send the update now, if the child is ready
480 if (mUpdateChildProcess) {
481 IPC::Permission permission((aHost),
482 (aType),
483 aPermission, aExpireType, aExpireTime);
484 ParentProcess()->SendAddPermission(permission);
487 #endif
489 if (!gHostArena) {
490 gHostArena = new PLArenaPool;
491 if (!gHostArena)
492 return NS_ERROR_OUT_OF_MEMORY;
493 PL_INIT_ARENA_POOL(gHostArena, "PermissionHostArena", HOST_ARENA_SIZE);
496 // look up the type index
497 PRInt32 typeIndex = GetTypeIndex(aType.get(), PR_TRUE);
498 NS_ENSURE_TRUE(typeIndex != -1, NS_ERROR_OUT_OF_MEMORY);
500 // When an entry already exists, PutEntry will return that, instead
501 // of adding a new one
502 nsHostEntry *entry = mHostTable.PutEntry(aHost.get());
503 if (!entry) return NS_ERROR_FAILURE;
504 if (!entry->GetKey()) {
505 mHostTable.RawRemoveEntry(entry);
506 return NS_ERROR_OUT_OF_MEMORY;
509 // figure out the transaction type, and get any existing permission value
510 OperationType op;
511 PRInt32 index = entry->GetPermissionIndex(typeIndex);
512 if (index == -1) {
513 if (aPermission == nsIPermissionManager::UNKNOWN_ACTION)
514 op = eOperationNone;
515 else
516 op = eOperationAdding;
518 } else {
519 nsPermissionEntry oldPermissionEntry = entry->GetPermissions()[index];
521 // remove the permission if the permission is UNKNOWN, update the
522 // permission if its value or expire type have changed OR if the time has
523 // changed and the expire type is time, otherwise, don't modify. There's
524 // no need to modify a permission that doesn't expire with time when the
525 // only thing changed is the expire time.
526 if (aPermission == oldPermissionEntry.mPermission &&
527 aExpireType == oldPermissionEntry.mExpireType &&
528 (aExpireType != nsIPermissionManager::EXPIRE_TIME ||
529 aExpireTime == oldPermissionEntry.mExpireTime))
530 op = eOperationNone;
531 else if (aPermission == nsIPermissionManager::UNKNOWN_ACTION)
532 op = eOperationRemoving;
533 else
534 op = eOperationChanging;
537 // do the work for adding, deleting, or changing a permission:
538 // update the in-memory list, write to the db, and notify consumers.
539 PRInt64 id;
540 switch (op) {
541 case eOperationNone:
543 // nothing to do
544 return NS_OK;
547 case eOperationAdding:
549 if (aDBOperation == eWriteToDB) {
550 // we'll be writing to the database - generate a known unique id
551 id = ++mLargestID;
552 } else {
553 // we're reading from the database - use the id already assigned
554 id = aID;
557 entry->GetPermissions().AppendElement(nsPermissionEntry(typeIndex, aPermission, id, aExpireType, aExpireTime));
559 if (aDBOperation == eWriteToDB && aExpireType != nsIPermissionManager::EXPIRE_SESSION)
560 UpdateDB(op, mStmtInsert, id, aHost, aType, aPermission, aExpireType, aExpireTime);
562 if (aNotifyOperation == eNotify) {
563 NotifyObserversWithPermission(aHost,
564 mTypeArray[typeIndex],
565 aPermission,
566 aExpireType,
567 aExpireTime,
568 NS_LITERAL_STRING("added").get());
571 break;
574 case eOperationRemoving:
576 nsPermissionEntry oldPermissionEntry = entry->GetPermissions()[index];
577 id = oldPermissionEntry.mID;
578 entry->GetPermissions().RemoveElementAt(index);
580 // If no more types are present, remove the entry
581 if (entry->GetPermissions().IsEmpty())
582 mHostTable.RawRemoveEntry(entry);
584 if (aDBOperation == eWriteToDB)
585 UpdateDB(op, mStmtDelete, id, EmptyCString(), EmptyCString(), 0,
586 nsIPermissionManager::EXPIRE_NEVER, 0);
588 if (aNotifyOperation == eNotify) {
589 NotifyObserversWithPermission(aHost,
590 mTypeArray[typeIndex],
591 oldPermissionEntry.mPermission,
592 oldPermissionEntry.mExpireType,
593 oldPermissionEntry.mExpireTime,
594 NS_LITERAL_STRING("deleted").get());
597 break;
600 case eOperationChanging:
602 id = entry->GetPermissions()[index].mID;
603 entry->GetPermissions()[index].mPermission = aPermission;
605 if (aDBOperation == eWriteToDB && aExpireType != nsIPermissionManager::EXPIRE_SESSION)
606 UpdateDB(op, mStmtUpdate, id, EmptyCString(), EmptyCString(), aPermission, aExpireType, aExpireTime);
608 if (aNotifyOperation == eNotify) {
609 NotifyObserversWithPermission(aHost,
610 mTypeArray[typeIndex],
611 aPermission,
612 aExpireType,
613 aExpireTime,
614 NS_LITERAL_STRING("changed").get());
617 break;
621 return NS_OK;
624 NS_IMETHODIMP
625 nsPermissionManager::Remove(const nsACString &aHost,
626 const char *aType)
628 #ifdef MOZ_IPC
629 ENSURE_NOT_CHILD_PROCESS;
630 #endif
632 NS_ENSURE_ARG_POINTER(aType);
634 // AddInternal() handles removal, just let it do the work
635 return AddInternal(PromiseFlatCString(aHost),
636 nsDependentCString(aType),
637 nsIPermissionManager::UNKNOWN_ACTION,
639 nsIPermissionManager::EXPIRE_NEVER,
641 eNotify,
642 eWriteToDB);
645 NS_IMETHODIMP
646 nsPermissionManager::RemoveAll()
648 #ifdef MOZ_IPC
649 ENSURE_NOT_CHILD_PROCESS;
650 #endif
652 nsresult rv = RemoveAllInternal();
653 NotifyObservers(nsnull, NS_LITERAL_STRING("cleared").get());
654 return rv;
657 nsresult
658 nsPermissionManager::RemoveAllInternal()
660 RemoveAllFromMemory();
662 // clear the db
663 if (mDBConn) {
664 nsresult rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_hosts"));
665 if (NS_FAILED(rv)) {
666 mStmtInsert = nsnull;
667 mStmtDelete = nsnull;
668 mStmtUpdate = nsnull;
669 mDBConn = nsnull;
670 rv = InitDB(PR_TRUE);
671 return rv;
675 return NS_OK;
678 NS_IMETHODIMP
679 nsPermissionManager::TestExactPermission(nsIURI *aURI,
680 const char *aType,
681 PRUint32 *aPermission)
683 return CommonTestPermission(aURI, aType, aPermission, PR_TRUE);
686 NS_IMETHODIMP
687 nsPermissionManager::TestPermission(nsIURI *aURI,
688 const char *aType,
689 PRUint32 *aPermission)
691 return CommonTestPermission(aURI, aType, aPermission, PR_FALSE);
694 nsresult
695 nsPermissionManager::CommonTestPermission(nsIURI *aURI,
696 const char *aType,
697 PRUint32 *aPermission,
698 PRBool aExactHostMatch)
700 NS_ENSURE_ARG_POINTER(aURI);
701 NS_ENSURE_ARG_POINTER(aType);
703 // set the default
704 *aPermission = nsIPermissionManager::UNKNOWN_ACTION;
706 nsCAutoString host;
707 nsresult rv = GetHost(aURI, host);
708 // No host doesn't mean an error. Just return the default. Unless this is
709 // a file uri. In that case use a magic host.
710 if (NS_FAILED(rv)) {
711 PRBool isFile;
712 rv = aURI->SchemeIs("file", &isFile);
713 NS_ENSURE_SUCCESS(rv, rv);
714 if (isFile) {
715 host.AssignLiteral("<file>");
717 else {
718 return NS_OK;
722 PRInt32 typeIndex = GetTypeIndex(aType, PR_FALSE);
723 // If type == -1, the type isn't known,
724 // so just return NS_OK
725 if (typeIndex == -1) return NS_OK;
727 nsHostEntry *entry = GetHostEntry(host, typeIndex, aExactHostMatch);
728 if (entry)
729 *aPermission = entry->GetPermission(typeIndex).mPermission;
731 return NS_OK;
734 // Get hostentry for given host string and permission type.
735 // walk up the domain if needed.
736 // return null if nothing found.
737 // Also accepts host on the format "<foo>". This will perform an exact match
738 // lookup as the string doesn't contain any dots.
739 nsHostEntry *
740 nsPermissionManager::GetHostEntry(const nsAFlatCString &aHost,
741 PRUint32 aType,
742 PRBool aExactHostMatch)
744 PRUint32 offset = 0;
745 nsHostEntry *entry;
746 PRInt64 now = PR_Now() / 1000;
748 do {
749 entry = mHostTable.GetEntry(aHost.get() + offset);
750 if (entry) {
751 nsPermissionEntry permEntry = entry->GetPermission(aType);
753 // if the entry is expired, remove and keep looking for others.
754 if (permEntry.mExpireType == nsIPermissionManager::EXPIRE_TIME &&
755 permEntry.mExpireTime <= now)
756 Remove(aHost, mTypeArray[aType].get());
757 else if (permEntry.mPermission != nsIPermissionManager::UNKNOWN_ACTION)
758 break;
760 // reset entry, to be able to return null on failure
761 entry = nsnull;
763 if (aExactHostMatch)
764 break; // do not try super domains
766 offset = aHost.FindChar('.', offset) + 1;
768 // walk up the domaintree (we stop as soon as we find a match,
769 // which will be the most specific domain we have an entry for).
770 } while (offset > 0);
771 return entry;
774 // helper struct for passing arguments into hash enumeration callback.
775 struct nsGetEnumeratorData
777 nsGetEnumeratorData(nsCOMArray<nsIPermission> *aArray, const nsTArray<nsCString> *aTypes)
778 : array(aArray)
779 , types(aTypes) {}
781 nsCOMArray<nsIPermission> *array;
782 const nsTArray<nsCString> *types;
785 static PLDHashOperator
786 AddPermissionsToList(nsHostEntry *entry, void *arg)
788 nsGetEnumeratorData *data = static_cast<nsGetEnumeratorData *>(arg);
790 for (PRUint32 i = 0; i < entry->GetPermissions().Length(); ++i) {
791 nsPermissionEntry &permEntry = entry->GetPermissions()[i];
793 nsPermission *perm = new nsPermission(entry->GetHost(),
794 data->types->ElementAt(permEntry.mType),
795 permEntry.mPermission,
796 permEntry.mExpireType,
797 permEntry.mExpireTime);
799 data->array->AppendObject(perm);
802 return PL_DHASH_NEXT;
805 NS_IMETHODIMP nsPermissionManager::GetEnumerator(nsISimpleEnumerator **aEnum)
807 #ifdef MOZ_IPC
808 ENSURE_NOT_CHILD_PROCESS;
809 #endif
811 // roll an nsCOMArray of all our permissions, then hand out an enumerator
812 nsCOMArray<nsIPermission> array;
813 nsGetEnumeratorData data(&array, &mTypeArray);
815 mHostTable.EnumerateEntries(AddPermissionsToList, &data);
817 return NS_NewArrayEnumerator(aEnum, array);
820 NS_IMETHODIMP nsPermissionManager::Observe(nsISupports *aSubject, const char *aTopic, const PRUnichar *someData)
822 #ifdef MOZ_IPC
823 ENSURE_NOT_CHILD_PROCESS;
824 #endif
826 if (!nsCRT::strcmp(aTopic, "profile-before-change")) {
827 // The profile is about to change,
828 // or is going away because the application is shutting down.
829 if (!nsCRT::strcmp(someData, NS_LITERAL_STRING("shutdown-cleanse").get())) {
830 // clear the permissions file
831 RemoveAllInternal();
832 } else {
833 RemoveAllFromMemory();
836 else if (!nsCRT::strcmp(aTopic, "profile-do-change")) {
837 // the profile has already changed; init the db from the new location
838 InitDB(PR_FALSE);
841 return NS_OK;
844 //*****************************************************************************
845 //*** nsPermissionManager private methods
846 //*****************************************************************************
848 nsresult
849 nsPermissionManager::RemoveAllFromMemory()
851 mLargestID = 0;
852 mTypeArray.Clear();
853 mHostTable.Clear();
854 if (gHostArena) {
855 PL_FinishArenaPool(gHostArena);
856 delete gHostArena;
858 gHostArena = nsnull;
859 return NS_OK;
862 // Returns -1 on failure
863 PRInt32
864 nsPermissionManager::GetTypeIndex(const char *aType,
865 PRBool aAdd)
867 for (PRUint32 i = 0; i < mTypeArray.Length(); ++i)
868 if (mTypeArray[i].Equals(aType))
869 return i;
871 if (!aAdd) {
872 // Not found, but that is ok - we were just looking.
873 return -1;
876 // This type was not registered before.
877 // append it to the array, without copy-constructing the string
878 nsCString *elem = mTypeArray.AppendElement();
879 if (!elem)
880 return -1;
882 elem->Assign(aType);
883 return mTypeArray.Length() - 1;
886 // wrapper function for mangling (host,type,perm,expireType,expireTime)
887 // set into an nsIPermission.
888 void
889 nsPermissionManager::NotifyObserversWithPermission(const nsACString &aHost,
890 const nsCString &aType,
891 PRUint32 aPermission,
892 PRUint32 aExpireType,
893 PRInt64 aExpireTime,
894 const PRUnichar *aData)
896 nsCOMPtr<nsIPermission> permission =
897 new nsPermission(aHost, aType, aPermission, aExpireType, aExpireTime);
898 if (permission)
899 NotifyObservers(permission, aData);
902 // notify observers that the permission list changed. there are four possible
903 // values for aData:
904 // "deleted" means a permission was deleted. aPermission is the deleted permission.
905 // "added" means a permission was added. aPermission is the added permission.
906 // "changed" means a permission was altered. aPermission is the new permission.
907 // "cleared" means the entire permission list was cleared. aPermission is null.
908 void
909 nsPermissionManager::NotifyObservers(nsIPermission *aPermission,
910 const PRUnichar *aData)
912 if (mObserverService)
913 mObserverService->NotifyObservers(aPermission,
914 kPermissionChangeNotification,
915 aData);
918 nsresult
919 nsPermissionManager::Read()
921 #ifdef MOZ_IPC
922 ENSURE_NOT_CHILD_PROCESS;
923 #endif
925 nsresult rv;
927 // delete expired permissions before we read in the db
929 // this deletion has its own scope so the write lock is released when done.
930 nsCOMPtr<mozIStorageStatement> stmtDeleteExpired;
931 rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
932 "DELETE FROM moz_hosts WHERE expireType = ?1 AND expireTime <= ?2"),
933 getter_AddRefs(stmtDeleteExpired));
934 NS_ENSURE_SUCCESS(rv, rv);
936 rv = stmtDeleteExpired->BindInt32Parameter(0, nsIPermissionManager::EXPIRE_TIME);
937 NS_ENSURE_SUCCESS(rv, rv);
939 rv = stmtDeleteExpired->BindInt64Parameter(1, PR_Now() / 1000);
940 NS_ENSURE_SUCCESS(rv, rv);
942 PRBool hasResult;
943 rv = stmtDeleteExpired->ExecuteStep(&hasResult);
944 NS_ENSURE_SUCCESS(rv, rv);
947 nsCOMPtr<mozIStorageStatement> stmt;
948 rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
949 "SELECT id, host, type, permission, expireType, expireTime "
950 "FROM moz_hosts"), getter_AddRefs(stmt));
951 NS_ENSURE_SUCCESS(rv, rv);
953 PRInt64 id;
954 nsCAutoString host, type;
955 PRUint32 permission;
956 PRUint32 expireType;
957 PRInt64 expireTime;
958 PRBool hasResult;
959 while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
960 // explicitly set our entry id counter for use in AddInternal(),
961 // and keep track of the largest id so we know where to pick up.
962 id = stmt->AsInt64(0);
963 if (id > mLargestID)
964 mLargestID = id;
966 rv = stmt->GetUTF8String(1, host);
967 NS_ENSURE_SUCCESS(rv, rv);
969 rv = stmt->GetUTF8String(2, type);
970 NS_ENSURE_SUCCESS(rv, rv);
972 permission = stmt->AsInt32(3);
973 expireType = stmt->AsInt32(4);
975 // convert into PRInt64 value (milliseconds)
976 expireTime = stmt->AsInt64(5);
978 rv = AddInternal(host, type, permission, id, expireType, expireTime,
979 eDontNotify, eNoDBOperation);
980 NS_ENSURE_SUCCESS(rv, rv);
983 return NS_OK;
986 static const char kMatchTypeHost[] = "host";
988 nsresult
989 nsPermissionManager::Import()
991 #ifdef MOZ_IPC
992 ENSURE_NOT_CHILD_PROCESS;
993 #endif
995 nsresult rv;
997 nsCOMPtr<nsIFile> permissionsFile;
998 rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(permissionsFile));
999 if (NS_FAILED(rv)) return rv;
1001 rv = permissionsFile->AppendNative(NS_LITERAL_CSTRING(kHostpermFileName));
1002 NS_ENSURE_SUCCESS(rv, rv);
1004 nsCOMPtr<nsIInputStream> fileInputStream;
1005 rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream),
1006 permissionsFile);
1007 if (NS_FAILED(rv)) return rv;
1009 nsCOMPtr<nsILineInputStream> lineInputStream = do_QueryInterface(fileInputStream, &rv);
1010 NS_ENSURE_SUCCESS(rv, rv);
1012 // start a transaction on the storage db, to optimize insertions.
1013 // transaction will automically commit on completion
1014 mozStorageTransaction transaction(mDBConn, PR_TRUE);
1016 /* format is:
1017 * matchtype \t type \t permission \t host
1018 * Only "host" is supported for matchtype
1019 * type is a string that identifies the type of permission (e.g. "cookie")
1020 * permission is an integer between 1 and 15
1023 nsCAutoString buffer;
1024 PRBool isMore = PR_TRUE;
1025 while (isMore && NS_SUCCEEDED(lineInputStream->ReadLine(buffer, &isMore))) {
1026 if (buffer.IsEmpty() || buffer.First() == '#') {
1027 continue;
1030 nsTArray<nsCString> lineArray;
1032 // Split the line at tabs
1033 ParseString(buffer, '\t', lineArray);
1035 if (lineArray[0].EqualsLiteral(kMatchTypeHost) &&
1036 lineArray.Length() == 4) {
1038 PRInt32 error;
1039 PRUint32 permission = lineArray[2].ToInteger(&error);
1040 if (error)
1041 continue;
1043 // hosts might be encoded in UTF8; switch them to ACE to be consistent
1044 if (!IsASCII(lineArray[3])) {
1045 rv = NormalizeToACE(lineArray[3]);
1046 if (NS_FAILED(rv))
1047 continue;
1050 rv = AddInternal(lineArray[3], lineArray[1], permission, 0,
1051 nsIPermissionManager::EXPIRE_NEVER, 0, eDontNotify, eWriteToDB);
1052 NS_ENSURE_SUCCESS(rv, rv);
1056 // we're done importing - delete the old file
1057 permissionsFile->Remove(PR_FALSE);
1059 return NS_OK;
1062 nsresult
1063 nsPermissionManager::NormalizeToACE(nsCString &aHost)
1065 // lazily init the IDN service
1066 if (!mIDNService) {
1067 nsresult rv;
1068 mIDNService = do_GetService(NS_IDNSERVICE_CONTRACTID, &rv);
1069 NS_ENSURE_SUCCESS(rv, rv);
1072 return mIDNService->ConvertUTF8toACE(aHost, aHost);
1075 nsresult
1076 nsPermissionManager::GetHost(nsIURI *aURI, nsACString &aResult)
1078 nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(aURI);
1079 if (!innerURI) return NS_ERROR_FAILURE;
1081 nsresult rv = innerURI->GetAsciiHost(aResult);
1083 if (NS_FAILED(rv) || aResult.IsEmpty())
1084 return NS_ERROR_UNEXPECTED;
1086 return NS_OK;
1089 void
1090 nsPermissionManager::UpdateDB(OperationType aOp,
1091 mozIStorageStatement* aStmt,
1092 PRInt64 aID,
1093 const nsACString &aHost,
1094 const nsACString &aType,
1095 PRUint32 aPermission,
1096 PRUint32 aExpireType,
1097 PRInt64 aExpireTime)
1099 #ifdef MOZ_IPC
1100 ENSURE_NOT_CHILD_PROCESS_NORET;
1101 #endif
1103 nsresult rv;
1105 // no statement is ok - just means we don't have a profile
1106 if (!aStmt)
1107 return;
1109 switch (aOp) {
1110 case eOperationAdding:
1112 rv = aStmt->BindInt64Parameter(0, aID);
1113 if (NS_FAILED(rv)) break;
1115 rv = aStmt->BindUTF8StringParameter(1, aHost);
1116 if (NS_FAILED(rv)) break;
1118 rv = aStmt->BindUTF8StringParameter(2, aType);
1119 if (NS_FAILED(rv)) break;
1121 rv = aStmt->BindInt32Parameter(3, aPermission);
1122 if (NS_FAILED(rv)) break;
1124 rv = aStmt->BindInt32Parameter(4, aExpireType);
1125 if (NS_FAILED(rv)) break;
1127 rv = aStmt->BindInt64Parameter(5, aExpireTime);
1128 break;
1131 case eOperationRemoving:
1133 rv = aStmt->BindInt64Parameter(0, aID);
1134 break;
1137 case eOperationChanging:
1139 rv = aStmt->BindInt64Parameter(0, aID);
1140 if (NS_FAILED(rv)) break;
1142 rv = aStmt->BindInt32Parameter(1, aPermission);
1143 if (NS_FAILED(rv)) break;
1145 rv = aStmt->BindInt32Parameter(2, aExpireType);
1146 if (NS_FAILED(rv)) break;
1148 rv = aStmt->BindInt64Parameter(3, aExpireTime);
1149 break;
1152 default:
1154 NS_NOTREACHED("need a valid operation in UpdateDB()!");
1155 rv = NS_ERROR_UNEXPECTED;
1156 break;
1160 if (NS_SUCCEEDED(rv)) {
1161 PRBool hasResult;
1162 rv = aStmt->ExecuteStep(&hasResult);
1163 aStmt->Reset();
1166 if (NS_FAILED(rv))
1167 NS_WARNING("db change failed!");