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
4 : *
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/
9 : *
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.
14 : *
15 : * The Original Code is mozilla.org code.
16 : *
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.
21 : *
22 : * Contributor(s):
23 : * Michiel van Leeuwen (mvl@exedo.nl)
24 : * Daniel Witte (dwitte@stanford.edu)
25 : *
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.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : #include "mozilla/dom/ContentParent.h"
41 : #include "mozilla/dom/ContentChild.h"
42 : #include "mozilla/unused.h"
43 : #include "nsPermissionManager.h"
44 : #include "nsPermission.h"
45 : #include "nsCRT.h"
46 : #include "nsNetUtil.h"
47 : #include "nsCOMArray.h"
48 : #include "nsArrayEnumerator.h"
49 : #include "nsTArray.h"
50 : #include "nsReadableUtils.h"
51 : #include "nsILineInputStream.h"
52 : #include "nsIIDNService.h"
53 : #include "nsAppDirectoryServiceDefs.h"
54 : #include "prprf.h"
55 : #include "mozIStorageService.h"
56 : #include "mozIStorageStatement.h"
57 : #include "mozIStorageConnection.h"
58 : #include "mozStorageHelper.h"
59 : #include "mozStorageCID.h"
60 : #include "nsXULAppAPI.h"
61 :
62 : static nsPermissionManager *gPermissionManager = nsnull;
63 :
64 : using mozilla::dom::ContentParent;
65 : using mozilla::dom::ContentChild;
66 : using mozilla::unused; // ha!
67 :
68 : static bool
69 2518 : IsChildProcess()
70 : {
71 2518 : return XRE_GetProcessType() == GeckoProcessType_Content;
72 : }
73 :
74 : /**
75 : * @returns The child process object, or if we are not in the child
76 : * process, nsnull.
77 : */
78 : static ContentChild*
79 0 : ChildProcess()
80 : {
81 0 : if (IsChildProcess()) {
82 0 : ContentChild* cpc = ContentChild::GetSingleton();
83 0 : if (!cpc)
84 0 : NS_RUNTIMEABORT("Content Process is NULL!");
85 0 : return cpc;
86 : }
87 :
88 0 : return nsnull;
89 : }
90 :
91 :
92 : #define ENSURE_NOT_CHILD_PROCESS_(onError) \
93 : PR_BEGIN_MACRO \
94 : if (IsChildProcess()) { \
95 : NS_ERROR("Cannot perform action in content process!"); \
96 : onError \
97 : } \
98 : PR_END_MACRO
99 :
100 : #define ENSURE_NOT_CHILD_PROCESS \
101 : ENSURE_NOT_CHILD_PROCESS_({ return NS_ERROR_NOT_AVAILABLE; })
102 :
103 : #define ENSURE_NOT_CHILD_PROCESS_NORET \
104 : ENSURE_NOT_CHILD_PROCESS_()
105 :
106 : ////////////////////////////////////////////////////////////////////////////////
107 :
108 : #define PL_ARENA_CONST_ALIGN_MASK 3
109 : #include "plarena.h"
110 :
111 : static PLArenaPool *gHostArena = nsnull;
112 :
113 : // making sHostArena 512b for nice allocation
114 : // growing is quite cheap
115 : #define HOST_ARENA_SIZE 512
116 :
117 : // equivalent to strdup() - does no error checking,
118 : // we're assuming we're only called with a valid pointer
119 : static char *
120 445 : ArenaStrDup(const char* str, PLArenaPool* aArena)
121 : {
122 : void* mem;
123 445 : const PRUint32 size = strlen(str) + 1;
124 445 : PL_ARENA_ALLOCATE(mem, aArena, size);
125 445 : if (mem)
126 445 : memcpy(mem, str, size);
127 445 : return static_cast<char*>(mem);
128 : }
129 :
130 445 : nsHostEntry::nsHostEntry(const char* aHost)
131 : {
132 445 : mHost = ArenaStrDup(aHost, gHostArena);
133 445 : }
134 :
135 : // XXX this can fail on OOM
136 0 : nsHostEntry::nsHostEntry(const nsHostEntry& toCopy)
137 : : mHost(toCopy.mHost)
138 0 : , mPermissions(toCopy.mPermissions)
139 : {
140 0 : }
141 :
142 : ////////////////////////////////////////////////////////////////////////////////
143 : // nsPermissionManager Implementation
144 :
145 : static const char kPermissionsFileName[] = "permissions.sqlite";
146 : #define HOSTS_SCHEMA_VERSION 2
147 :
148 : static const char kHostpermFileName[] = "hostperm.1";
149 :
150 : static const char kPermissionChangeNotification[] = PERM_CHANGE_NOTIFICATION;
151 :
152 13777 : NS_IMPL_ISUPPORTS3(nsPermissionManager, nsIPermissionManager, nsIObserver, nsISupportsWeakReference)
153 :
154 400 : nsPermissionManager::nsPermissionManager()
155 400 : : mLargestID(0)
156 : {
157 400 : }
158 :
159 1194 : nsPermissionManager::~nsPermissionManager()
160 : {
161 398 : RemoveAllFromMemory();
162 398 : gPermissionManager = nsnull;
163 1592 : }
164 :
165 : // static
166 : nsIPermissionManager*
167 400 : nsPermissionManager::GetXPCOMSingleton()
168 : {
169 400 : if (gPermissionManager) {
170 0 : NS_ADDREF(gPermissionManager);
171 0 : return gPermissionManager;
172 : }
173 :
174 : // Create a new singleton nsPermissionManager.
175 : // We AddRef only once since XPCOM has rules about the ordering of module
176 : // teardowns - by the time our module destructor is called, it's too late to
177 : // Release our members, since GC cycles have already been completed and
178 : // would result in serious leaks.
179 : // See bug 209571.
180 400 : gPermissionManager = new nsPermissionManager();
181 400 : if (gPermissionManager) {
182 400 : NS_ADDREF(gPermissionManager);
183 400 : if (NS_FAILED(gPermissionManager->Init())) {
184 0 : NS_RELEASE(gPermissionManager);
185 : }
186 : }
187 :
188 400 : return gPermissionManager;
189 : }
190 :
191 : nsresult
192 400 : nsPermissionManager::Init()
193 : {
194 : nsresult rv;
195 :
196 400 : if (!mHostTable.Init()) {
197 0 : return NS_ERROR_OUT_OF_MEMORY;
198 : }
199 :
200 400 : mObserverService = do_GetService("@mozilla.org/observer-service;1", &rv);
201 400 : if (NS_SUCCEEDED(rv)) {
202 400 : mObserverService->AddObserver(this, "profile-before-change", true);
203 400 : mObserverService->AddObserver(this, "profile-do-change", true);
204 : }
205 :
206 400 : if (IsChildProcess()) {
207 : // Get the permissions from the parent process
208 0 : InfallibleTArray<IPC::Permission> perms;
209 0 : ChildProcess()->SendReadPermissions(&perms);
210 :
211 0 : for (PRUint32 i = 0; i < perms.Length(); i++) {
212 0 : const IPC::Permission &perm = perms[i];
213 : AddInternal(perm.host, perm.type, perm.capability, 0, perm.expireType,
214 0 : perm.expireTime, eNotify, eNoDBOperation);
215 : }
216 :
217 : // Stop here; we don't need the DB in the child process
218 0 : return NS_OK;
219 : }
220 :
221 : // ignore failure here, since it's non-fatal (we can run fine without
222 : // persistent storage - e.g. if there's no profile).
223 : // XXX should we tell the user about this?
224 400 : InitDB(false);
225 :
226 400 : return NS_OK;
227 : }
228 :
229 : nsresult
230 401 : nsPermissionManager::InitDB(bool aRemoveFile)
231 : {
232 802 : nsCOMPtr<nsIFile> permissionsFile;
233 401 : NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(permissionsFile));
234 401 : if (!permissionsFile)
235 113 : return NS_ERROR_UNEXPECTED;
236 :
237 288 : nsresult rv = permissionsFile->AppendNative(NS_LITERAL_CSTRING(kPermissionsFileName));
238 288 : NS_ENSURE_SUCCESS(rv, rv);
239 :
240 288 : if (aRemoveFile) {
241 1 : bool exists = false;
242 1 : rv = permissionsFile->Exists(&exists);
243 1 : NS_ENSURE_SUCCESS(rv, rv);
244 1 : if (exists) {
245 1 : rv = permissionsFile->Remove(false);
246 1 : NS_ENSURE_SUCCESS(rv, rv);
247 : }
248 : }
249 :
250 576 : nsCOMPtr<mozIStorageService> storage = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
251 288 : if (!storage)
252 0 : return NS_ERROR_UNEXPECTED;
253 :
254 : // cache a connection to the hosts database
255 288 : rv = storage->OpenDatabase(permissionsFile, getter_AddRefs(mDBConn));
256 288 : NS_ENSURE_SUCCESS(rv, rv);
257 :
258 : bool ready;
259 288 : mDBConn->GetConnectionReady(&ready);
260 288 : if (!ready) {
261 : // delete and try again
262 0 : rv = permissionsFile->Remove(false);
263 0 : NS_ENSURE_SUCCESS(rv, rv);
264 :
265 0 : rv = storage->OpenDatabase(permissionsFile, getter_AddRefs(mDBConn));
266 0 : NS_ENSURE_SUCCESS(rv, rv);
267 :
268 0 : mDBConn->GetConnectionReady(&ready);
269 0 : if (!ready)
270 0 : return NS_ERROR_UNEXPECTED;
271 : }
272 :
273 288 : bool tableExists = false;
274 288 : mDBConn->TableExists(NS_LITERAL_CSTRING("moz_hosts"), &tableExists);
275 288 : if (!tableExists) {
276 288 : rv = CreateTable();
277 288 : NS_ENSURE_SUCCESS(rv, rv);
278 :
279 : } else {
280 : // table already exists; check the schema version before reading
281 : PRInt32 dbSchemaVersion;
282 0 : rv = mDBConn->GetSchemaVersion(&dbSchemaVersion);
283 0 : NS_ENSURE_SUCCESS(rv, rv);
284 :
285 0 : switch (dbSchemaVersion) {
286 : // upgrading.
287 : // every time you increment the database schema, you need to implement
288 : // the upgrading code from the previous version to the new one.
289 : // fall through to current version
290 :
291 : case 1:
292 : {
293 : // previous non-expiry version of database. Upgrade it by adding the
294 : // expiration columns
295 0 : rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
296 0 : "ALTER TABLE moz_hosts ADD expireType INTEGER"));
297 0 : NS_ENSURE_SUCCESS(rv, rv);
298 :
299 0 : rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
300 0 : "ALTER TABLE moz_hosts ADD expireTime INTEGER"));
301 0 : NS_ENSURE_SUCCESS(rv, rv);
302 :
303 0 : rv = mDBConn->SetSchemaVersion(HOSTS_SCHEMA_VERSION);
304 0 : NS_ENSURE_SUCCESS(rv, rv);
305 : }
306 :
307 : // fall through to the next upgrade
308 :
309 : // current version.
310 : case HOSTS_SCHEMA_VERSION:
311 0 : break;
312 :
313 : case 0:
314 : {
315 0 : NS_WARNING("couldn't get schema version!");
316 :
317 : // the table may be usable; someone might've just clobbered the schema
318 : // version. we can treat this case like a downgrade using the codepath
319 : // below, by verifying the columns we care about are all there. for now,
320 : // re-set the schema version in the db, in case the checks succeed (if
321 : // they don't, we're dropping the table anyway).
322 0 : rv = mDBConn->SetSchemaVersion(HOSTS_SCHEMA_VERSION);
323 0 : NS_ENSURE_SUCCESS(rv, rv);
324 : }
325 : // fall through to downgrade check
326 :
327 : // downgrading.
328 : // if columns have been added to the table, we can still use the ones we
329 : // understand safely. if columns have been deleted or altered, just
330 : // blow away the table and start from scratch! if you change the way
331 : // a column is interpreted, make sure you also change its name so this
332 : // check will catch it.
333 : default:
334 : {
335 : // check if all the expected columns exist
336 0 : nsCOMPtr<mozIStorageStatement> stmt;
337 0 : rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
338 : "SELECT host, type, permission, expireType, expireTime FROM moz_hosts"),
339 0 : getter_AddRefs(stmt));
340 0 : if (NS_SUCCEEDED(rv))
341 : break;
342 :
343 : // our columns aren't there - drop the table!
344 0 : rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DROP TABLE moz_hosts"));
345 0 : NS_ENSURE_SUCCESS(rv, rv);
346 :
347 0 : rv = CreateTable();
348 0 : NS_ENSURE_SUCCESS(rv, rv);
349 : }
350 0 : break;
351 : }
352 : }
353 :
354 : // make operations on the table asynchronous, for performance
355 288 : mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("PRAGMA synchronous = OFF"));
356 :
357 : // cache frequently used statements (for insertion, deletion, and updating)
358 576 : rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
359 : "INSERT INTO moz_hosts "
360 : "(id, host, type, permission, expireType, expireTime) "
361 576 : "VALUES (?1, ?2, ?3, ?4, ?5, ?6)"), getter_AddRefs(mStmtInsert));
362 288 : NS_ENSURE_SUCCESS(rv, rv);
363 :
364 576 : rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
365 : "DELETE FROM moz_hosts "
366 576 : "WHERE id = ?1"), getter_AddRefs(mStmtDelete));
367 288 : NS_ENSURE_SUCCESS(rv, rv);
368 :
369 576 : rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
370 : "UPDATE moz_hosts "
371 : "SET permission = ?2, expireType= ?3, expireTime = ?4 WHERE id = ?1"),
372 576 : getter_AddRefs(mStmtUpdate));
373 288 : NS_ENSURE_SUCCESS(rv, rv);
374 :
375 : // check whether to import or just read in the db
376 288 : if (tableExists)
377 0 : return Read();
378 :
379 288 : return Import();
380 : }
381 :
382 : // sets the schema version and creates the moz_hosts table.
383 : nsresult
384 288 : nsPermissionManager::CreateTable()
385 : {
386 : // set the schema version, before creating the table
387 288 : nsresult rv = mDBConn->SetSchemaVersion(HOSTS_SCHEMA_VERSION);
388 288 : if (NS_FAILED(rv)) return rv;
389 :
390 : // create the table
391 : // SQL also lives in automation.py.in. If you change this SQL change that
392 : // one too.
393 576 : return mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
394 : "CREATE TABLE moz_hosts ("
395 : " id INTEGER PRIMARY KEY"
396 : ",host TEXT"
397 : ",type TEXT"
398 : ",permission INTEGER"
399 : ",expireType INTEGER"
400 : ",expireTime INTEGER"
401 288 : ")"));
402 : }
403 :
404 : NS_IMETHODIMP
405 484 : nsPermissionManager::Add(nsIURI *aURI,
406 : const char *aType,
407 : PRUint32 aPermission,
408 : PRUint32 aExpireType,
409 : PRInt64 aExpireTime)
410 : {
411 484 : ENSURE_NOT_CHILD_PROCESS;
412 :
413 484 : NS_ENSURE_ARG_POINTER(aURI);
414 484 : NS_ENSURE_ARG_POINTER(aType);
415 484 : NS_ENSURE_TRUE(aExpireType == nsIPermissionManager::EXPIRE_NEVER ||
416 : aExpireType == nsIPermissionManager::EXPIRE_TIME ||
417 : aExpireType == nsIPermissionManager::EXPIRE_SESSION,
418 : NS_ERROR_INVALID_ARG);
419 :
420 : nsresult rv;
421 :
422 : // Skip addition if the permission is already expired.
423 527 : if (aExpireType == nsIPermissionManager::EXPIRE_TIME &&
424 43 : aExpireTime <= PR_Now() / 1000)
425 1 : return NS_OK;
426 :
427 966 : nsCAutoString host;
428 483 : rv = GetHost(aURI, host);
429 483 : NS_ENSURE_SUCCESS(rv, rv);
430 :
431 483 : return AddInternal(host, nsDependentCString(aType), aPermission, 0,
432 483 : aExpireType, aExpireTime, eNotify, eWriteToDB);
433 : }
434 :
435 : nsresult
436 513 : nsPermissionManager::AddInternal(const nsAFlatCString &aHost,
437 : const nsAFlatCString &aType,
438 : PRUint32 aPermission,
439 : PRInt64 aID,
440 : PRUint32 aExpireType,
441 : PRInt64 aExpireTime,
442 : NotifyOperationType aNotifyOperation,
443 : DBOperationType aDBOperation)
444 : {
445 513 : if (!IsChildProcess()) {
446 : IPC::Permission permission((aHost),
447 : (aType),
448 1026 : aPermission, aExpireType, aExpireTime);
449 :
450 1026 : nsTArray<ContentParent*> cplist;
451 513 : ContentParent::GetAll(cplist);
452 513 : for (PRUint32 i = 0; i < cplist.Length(); ++i) {
453 0 : ContentParent* cp = cplist[i];
454 0 : if (cp->NeedsPermissionsUpdate())
455 0 : unused << cp->SendAddPermission(permission);
456 : }
457 : }
458 :
459 513 : if (!gHostArena) {
460 218 : gHostArena = new PLArenaPool;
461 218 : if (!gHostArena)
462 0 : return NS_ERROR_OUT_OF_MEMORY;
463 218 : PL_INIT_ARENA_POOL(gHostArena, "PermissionHostArena", HOST_ARENA_SIZE);
464 : }
465 :
466 : // look up the type index
467 513 : PRInt32 typeIndex = GetTypeIndex(aType.get(), true);
468 513 : NS_ENSURE_TRUE(typeIndex != -1, NS_ERROR_OUT_OF_MEMORY);
469 :
470 : // When an entry already exists, PutEntry will return that, instead
471 : // of adding a new one
472 513 : nsHostEntry *entry = mHostTable.PutEntry(aHost.get());
473 513 : if (!entry) return NS_ERROR_FAILURE;
474 513 : if (!entry->GetKey()) {
475 0 : mHostTable.RawRemoveEntry(entry);
476 0 : return NS_ERROR_OUT_OF_MEMORY;
477 : }
478 :
479 : // figure out the transaction type, and get any existing permission value
480 : OperationType op;
481 513 : PRInt32 index = entry->GetPermissionIndex(typeIndex);
482 513 : if (index == -1) {
483 467 : if (aPermission == nsIPermissionManager::UNKNOWN_ACTION)
484 17 : op = eOperationNone;
485 : else
486 450 : op = eOperationAdding;
487 :
488 : } else {
489 46 : nsPermissionEntry oldPermissionEntry = entry->GetPermissions()[index];
490 :
491 : // remove the permission if the permission is UNKNOWN, update the
492 : // permission if its value or expire type have changed OR if the time has
493 : // changed and the expire type is time, otherwise, don't modify. There's
494 : // no need to modify a permission that doesn't expire with time when the
495 : // only thing changed is the expire time.
496 46 : if (aPermission == oldPermissionEntry.mPermission &&
497 : aExpireType == oldPermissionEntry.mExpireType &&
498 : (aExpireType != nsIPermissionManager::EXPIRE_TIME ||
499 : aExpireTime == oldPermissionEntry.mExpireTime))
500 0 : op = eOperationNone;
501 46 : else if (aPermission == nsIPermissionManager::UNKNOWN_ACTION)
502 13 : op = eOperationRemoving;
503 : else
504 33 : op = eOperationChanging;
505 : }
506 :
507 : // do the work for adding, deleting, or changing a permission:
508 : // update the in-memory list, write to the db, and notify consumers.
509 : PRInt64 id;
510 513 : switch (op) {
511 : case eOperationNone:
512 : {
513 : // nothing to do
514 17 : return NS_OK;
515 : }
516 :
517 : case eOperationAdding:
518 : {
519 450 : if (aDBOperation == eWriteToDB) {
520 : // we'll be writing to the database - generate a known unique id
521 450 : id = ++mLargestID;
522 : } else {
523 : // we're reading from the database - use the id already assigned
524 0 : id = aID;
525 : }
526 :
527 450 : entry->GetPermissions().AppendElement(nsPermissionEntry(typeIndex, aPermission, id, aExpireType, aExpireTime));
528 :
529 450 : if (aDBOperation == eWriteToDB && aExpireType != nsIPermissionManager::EXPIRE_SESSION)
530 450 : UpdateDB(op, mStmtInsert, id, aHost, aType, aPermission, aExpireType, aExpireTime);
531 :
532 450 : if (aNotifyOperation == eNotify) {
533 : NotifyObserversWithPermission(aHost,
534 450 : mTypeArray[typeIndex],
535 : aPermission,
536 : aExpireType,
537 : aExpireTime,
538 900 : NS_LITERAL_STRING("added").get());
539 : }
540 :
541 450 : break;
542 : }
543 :
544 : case eOperationRemoving:
545 : {
546 13 : nsPermissionEntry oldPermissionEntry = entry->GetPermissions()[index];
547 13 : id = oldPermissionEntry.mID;
548 13 : entry->GetPermissions().RemoveElementAt(index);
549 :
550 : // If no more types are present, remove the entry
551 13 : if (entry->GetPermissions().IsEmpty())
552 9 : mHostTable.RawRemoveEntry(entry);
553 :
554 13 : if (aDBOperation == eWriteToDB)
555 26 : UpdateDB(op, mStmtDelete, id, EmptyCString(), EmptyCString(), 0,
556 26 : nsIPermissionManager::EXPIRE_NEVER, 0);
557 :
558 13 : if (aNotifyOperation == eNotify) {
559 : NotifyObserversWithPermission(aHost,
560 13 : mTypeArray[typeIndex],
561 : oldPermissionEntry.mPermission,
562 : oldPermissionEntry.mExpireType,
563 : oldPermissionEntry.mExpireTime,
564 26 : NS_LITERAL_STRING("deleted").get());
565 : }
566 :
567 13 : break;
568 : }
569 :
570 : case eOperationChanging:
571 : {
572 33 : id = entry->GetPermissions()[index].mID;
573 33 : entry->GetPermissions()[index].mPermission = aPermission;
574 :
575 33 : if (aDBOperation == eWriteToDB && aExpireType != nsIPermissionManager::EXPIRE_SESSION)
576 33 : UpdateDB(op, mStmtUpdate, id, EmptyCString(), EmptyCString(), aPermission, aExpireType, aExpireTime);
577 :
578 33 : if (aNotifyOperation == eNotify) {
579 : NotifyObserversWithPermission(aHost,
580 33 : mTypeArray[typeIndex],
581 : aPermission,
582 : aExpireType,
583 : aExpireTime,
584 66 : NS_LITERAL_STRING("changed").get());
585 : }
586 :
587 33 : break;
588 : }
589 : }
590 :
591 496 : return NS_OK;
592 : }
593 :
594 : NS_IMETHODIMP
595 30 : nsPermissionManager::Remove(const nsACString &aHost,
596 : const char *aType)
597 : {
598 30 : ENSURE_NOT_CHILD_PROCESS;
599 :
600 30 : NS_ENSURE_ARG_POINTER(aType);
601 :
602 : // AddInternal() handles removal, just let it do the work
603 30 : return AddInternal(PromiseFlatCString(aHost),
604 30 : nsDependentCString(aType),
605 : nsIPermissionManager::UNKNOWN_ACTION,
606 : 0,
607 : nsIPermissionManager::EXPIRE_NEVER,
608 : 0,
609 : eNotify,
610 30 : eWriteToDB);
611 : }
612 :
613 : NS_IMETHODIMP
614 8 : nsPermissionManager::RemoveAll()
615 : {
616 8 : ENSURE_NOT_CHILD_PROCESS;
617 :
618 8 : nsresult rv = RemoveAllInternal();
619 8 : NotifyObservers(nsnull, NS_LITERAL_STRING("cleared").get());
620 8 : return rv;
621 : }
622 :
623 : void
624 300 : nsPermissionManager::CloseDB()
625 : {
626 : // Null the statements, this will finalize them.
627 300 : mStmtInsert = nsnull;
628 300 : mStmtDelete = nsnull;
629 300 : mStmtUpdate = nsnull;
630 300 : if (mDBConn) {
631 576 : mozilla::DebugOnly<nsresult> rv = mDBConn->Close();
632 288 : MOZ_ASSERT(NS_SUCCEEDED(rv));
633 288 : mDBConn = nsnull;
634 : }
635 300 : }
636 :
637 : nsresult
638 8 : nsPermissionManager::RemoveAllInternal()
639 : {
640 8 : RemoveAllFromMemory();
641 :
642 : // clear the db
643 8 : if (mDBConn) {
644 7 : nsresult rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_hosts"));
645 7 : if (NS_FAILED(rv)) {
646 1 : CloseDB();
647 1 : rv = InitDB(true);
648 1 : return rv;
649 : }
650 : }
651 :
652 7 : return NS_OK;
653 : }
654 :
655 : NS_IMETHODIMP
656 5085 : nsPermissionManager::TestExactPermission(nsIURI *aURI,
657 : const char *aType,
658 : PRUint32 *aPermission)
659 : {
660 5085 : return CommonTestPermission(aURI, aType, aPermission, true);
661 : }
662 :
663 : NS_IMETHODIMP
664 39192 : nsPermissionManager::TestPermission(nsIURI *aURI,
665 : const char *aType,
666 : PRUint32 *aPermission)
667 : {
668 39192 : return CommonTestPermission(aURI, aType, aPermission, false);
669 : }
670 :
671 : nsresult
672 44277 : nsPermissionManager::CommonTestPermission(nsIURI *aURI,
673 : const char *aType,
674 : PRUint32 *aPermission,
675 : bool aExactHostMatch)
676 : {
677 44277 : NS_ENSURE_ARG_POINTER(aURI);
678 44277 : NS_ENSURE_ARG_POINTER(aType);
679 :
680 : // set the default
681 44277 : *aPermission = nsIPermissionManager::UNKNOWN_ACTION;
682 :
683 88554 : nsCAutoString host;
684 44277 : nsresult rv = GetHost(aURI, host);
685 : // No host doesn't mean an error. Just return the default. Unless this is
686 : // a file uri. In that case use a magic host.
687 44277 : if (NS_FAILED(rv)) {
688 : bool isFile;
689 25 : rv = aURI->SchemeIs("file", &isFile);
690 25 : NS_ENSURE_SUCCESS(rv, rv);
691 25 : if (isFile) {
692 7 : host.AssignLiteral("<file>");
693 : }
694 : else {
695 18 : return NS_OK;
696 : }
697 : }
698 :
699 44259 : PRInt32 typeIndex = GetTypeIndex(aType, false);
700 : // If type == -1, the type isn't known,
701 : // so just return NS_OK
702 44259 : if (typeIndex == -1) return NS_OK;
703 :
704 304 : nsHostEntry *entry = GetHostEntry(host, typeIndex, aExactHostMatch);
705 304 : if (entry)
706 39 : *aPermission = entry->GetPermission(typeIndex).mPermission;
707 :
708 304 : return NS_OK;
709 : }
710 :
711 : // Get hostentry for given host string and permission type.
712 : // walk up the domain if needed.
713 : // return null if nothing found.
714 : // Also accepts host on the format "<foo>". This will perform an exact match
715 : // lookup as the string doesn't contain any dots.
716 : nsHostEntry *
717 304 : nsPermissionManager::GetHostEntry(const nsAFlatCString &aHost,
718 : PRUint32 aType,
719 : bool aExactHostMatch)
720 : {
721 304 : PRUint32 offset = 0;
722 : nsHostEntry *entry;
723 304 : PRInt64 now = PR_Now() / 1000;
724 :
725 568 : do {
726 676 : entry = mHostTable.GetEntry(aHost.get() + offset);
727 676 : if (entry) {
728 40 : nsPermissionEntry permEntry = entry->GetPermission(aType);
729 :
730 : // if the entry is expired, remove and keep looking for others.
731 40 : if (permEntry.mExpireType == nsIPermissionManager::EXPIRE_TIME &&
732 : permEntry.mExpireTime <= now)
733 1 : Remove(aHost, mTypeArray[aType].get());
734 39 : else if (permEntry.mPermission != nsIPermissionManager::UNKNOWN_ACTION)
735 39 : break;
736 :
737 : // reset entry, to be able to return null on failure
738 1 : entry = nsnull;
739 : }
740 637 : if (aExactHostMatch)
741 69 : break; // do not try super domains
742 :
743 568 : offset = aHost.FindChar('.', offset) + 1;
744 :
745 : // walk up the domaintree (we stop as soon as we find a match,
746 : // which will be the most specific domain we have an entry for).
747 : } while (offset > 0);
748 304 : return entry;
749 : }
750 :
751 : // helper struct for passing arguments into hash enumeration callback.
752 : struct nsGetEnumeratorData
753 : {
754 66 : nsGetEnumeratorData(nsCOMArray<nsIPermission> *aArray, const nsTArray<nsCString> *aTypes)
755 : : array(aArray)
756 66 : , types(aTypes) {}
757 :
758 : nsCOMArray<nsIPermission> *array;
759 : const nsTArray<nsCString> *types;
760 : };
761 :
762 : static PLDHashOperator
763 11 : AddPermissionsToList(nsHostEntry *entry, void *arg)
764 : {
765 11 : nsGetEnumeratorData *data = static_cast<nsGetEnumeratorData *>(arg);
766 :
767 24 : for (PRUint32 i = 0; i < entry->GetPermissions().Length(); ++i) {
768 13 : nsPermissionEntry &permEntry = entry->GetPermissions()[i];
769 :
770 13 : nsPermission *perm = new nsPermission(entry->GetHost(),
771 13 : data->types->ElementAt(permEntry.mType),
772 : permEntry.mPermission,
773 : permEntry.mExpireType,
774 39 : permEntry.mExpireTime);
775 :
776 13 : data->array->AppendObject(perm);
777 : }
778 :
779 11 : return PL_DHASH_NEXT;
780 : }
781 :
782 66 : NS_IMETHODIMP nsPermissionManager::GetEnumerator(nsISimpleEnumerator **aEnum)
783 : {
784 : // roll an nsCOMArray of all our permissions, then hand out an enumerator
785 132 : nsCOMArray<nsIPermission> array;
786 66 : nsGetEnumeratorData data(&array, &mTypeArray);
787 :
788 66 : mHostTable.EnumerateEntries(AddPermissionsToList, &data);
789 :
790 66 : return NS_NewArrayEnumerator(aEnum, array);
791 : }
792 :
793 299 : NS_IMETHODIMP nsPermissionManager::Observe(nsISupports *aSubject, const char *aTopic, const PRUnichar *someData)
794 : {
795 299 : ENSURE_NOT_CHILD_PROCESS;
796 :
797 299 : if (!nsCRT::strcmp(aTopic, "profile-before-change")) {
798 : // The profile is about to change,
799 : // or is going away because the application is shutting down.
800 299 : if (!nsCRT::strcmp(someData, NS_LITERAL_STRING("shutdown-cleanse").get())) {
801 : // clear the permissions file
802 0 : RemoveAllInternal();
803 : } else {
804 299 : RemoveAllFromMemory();
805 : }
806 299 : CloseDB();
807 : }
808 0 : else if (!nsCRT::strcmp(aTopic, "profile-do-change")) {
809 : // the profile has already changed; init the db from the new location
810 0 : InitDB(false);
811 : }
812 :
813 299 : return NS_OK;
814 : }
815 :
816 : //*****************************************************************************
817 : //*** nsPermissionManager private methods
818 : //*****************************************************************************
819 :
820 : nsresult
821 705 : nsPermissionManager::RemoveAllFromMemory()
822 : {
823 705 : mLargestID = 0;
824 705 : mTypeArray.Clear();
825 705 : mHostTable.Clear();
826 705 : if (gHostArena) {
827 218 : PL_FinishArenaPool(gHostArena);
828 218 : delete gHostArena;
829 : }
830 705 : gHostArena = nsnull;
831 705 : return NS_OK;
832 : }
833 :
834 : // Returns -1 on failure
835 : PRInt32
836 44772 : nsPermissionManager::GetTypeIndex(const char *aType,
837 : bool aAdd)
838 : {
839 49829 : for (PRUint32 i = 0; i < mTypeArray.Length(); ++i)
840 5642 : if (mTypeArray[i].Equals(aType))
841 585 : return i;
842 :
843 44187 : if (!aAdd) {
844 : // Not found, but that is ok - we were just looking.
845 43955 : return -1;
846 : }
847 :
848 : // This type was not registered before.
849 : // append it to the array, without copy-constructing the string
850 232 : nsCString *elem = mTypeArray.AppendElement();
851 232 : if (!elem)
852 0 : return -1;
853 :
854 232 : elem->Assign(aType);
855 232 : return mTypeArray.Length() - 1;
856 : }
857 :
858 : // wrapper function for mangling (host,type,perm,expireType,expireTime)
859 : // set into an nsIPermission.
860 : void
861 496 : nsPermissionManager::NotifyObserversWithPermission(const nsACString &aHost,
862 : const nsCString &aType,
863 : PRUint32 aPermission,
864 : PRUint32 aExpireType,
865 : PRInt64 aExpireTime,
866 : const PRUnichar *aData)
867 : {
868 : nsCOMPtr<nsIPermission> permission =
869 992 : new nsPermission(aHost, aType, aPermission, aExpireType, aExpireTime);
870 496 : if (permission)
871 496 : NotifyObservers(permission, aData);
872 496 : }
873 :
874 : // notify observers that the permission list changed. there are four possible
875 : // values for aData:
876 : // "deleted" means a permission was deleted. aPermission is the deleted permission.
877 : // "added" means a permission was added. aPermission is the added permission.
878 : // "changed" means a permission was altered. aPermission is the new permission.
879 : // "cleared" means the entire permission list was cleared. aPermission is null.
880 : void
881 504 : nsPermissionManager::NotifyObservers(nsIPermission *aPermission,
882 : const PRUnichar *aData)
883 : {
884 504 : if (mObserverService)
885 504 : mObserverService->NotifyObservers(aPermission,
886 : kPermissionChangeNotification,
887 504 : aData);
888 504 : }
889 :
890 : nsresult
891 0 : nsPermissionManager::Read()
892 : {
893 0 : ENSURE_NOT_CHILD_PROCESS;
894 :
895 : nsresult rv;
896 :
897 : // delete expired permissions before we read in the db
898 : {
899 : // this deletion has its own scope so the write lock is released when done.
900 0 : nsCOMPtr<mozIStorageStatement> stmtDeleteExpired;
901 0 : rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
902 : "DELETE FROM moz_hosts WHERE expireType = ?1 AND expireTime <= ?2"),
903 0 : getter_AddRefs(stmtDeleteExpired));
904 0 : NS_ENSURE_SUCCESS(rv, rv);
905 :
906 0 : rv = stmtDeleteExpired->BindInt32ByIndex(0, nsIPermissionManager::EXPIRE_TIME);
907 0 : NS_ENSURE_SUCCESS(rv, rv);
908 :
909 0 : rv = stmtDeleteExpired->BindInt64ByIndex(1, PR_Now() / 1000);
910 0 : NS_ENSURE_SUCCESS(rv, rv);
911 :
912 : bool hasResult;
913 0 : rv = stmtDeleteExpired->ExecuteStep(&hasResult);
914 0 : NS_ENSURE_SUCCESS(rv, rv);
915 : }
916 :
917 0 : nsCOMPtr<mozIStorageStatement> stmt;
918 0 : rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
919 : "SELECT id, host, type, permission, expireType, expireTime "
920 0 : "FROM moz_hosts"), getter_AddRefs(stmt));
921 0 : NS_ENSURE_SUCCESS(rv, rv);
922 :
923 : PRInt64 id;
924 0 : nsCAutoString host, type;
925 : PRUint32 permission;
926 : PRUint32 expireType;
927 : PRInt64 expireTime;
928 : bool hasResult;
929 0 : while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
930 : // explicitly set our entry id counter for use in AddInternal(),
931 : // and keep track of the largest id so we know where to pick up.
932 0 : id = stmt->AsInt64(0);
933 0 : if (id > mLargestID)
934 0 : mLargestID = id;
935 :
936 0 : rv = stmt->GetUTF8String(1, host);
937 0 : NS_ENSURE_SUCCESS(rv, rv);
938 :
939 0 : rv = stmt->GetUTF8String(2, type);
940 0 : NS_ENSURE_SUCCESS(rv, rv);
941 :
942 0 : permission = stmt->AsInt32(3);
943 0 : expireType = stmt->AsInt32(4);
944 :
945 : // convert into PRInt64 value (milliseconds)
946 0 : expireTime = stmt->AsInt64(5);
947 :
948 : rv = AddInternal(host, type, permission, id, expireType, expireTime,
949 0 : eDontNotify, eNoDBOperation);
950 0 : NS_ENSURE_SUCCESS(rv, rv);
951 : }
952 :
953 0 : return NS_OK;
954 : }
955 :
956 : static const char kMatchTypeHost[] = "host";
957 :
958 : nsresult
959 288 : nsPermissionManager::Import()
960 : {
961 288 : ENSURE_NOT_CHILD_PROCESS;
962 :
963 : nsresult rv;
964 :
965 576 : nsCOMPtr<nsIFile> permissionsFile;
966 288 : rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(permissionsFile));
967 288 : if (NS_FAILED(rv)) return rv;
968 :
969 288 : rv = permissionsFile->AppendNative(NS_LITERAL_CSTRING(kHostpermFileName));
970 288 : NS_ENSURE_SUCCESS(rv, rv);
971 :
972 576 : nsCOMPtr<nsIInputStream> fileInputStream;
973 288 : rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream),
974 288 : permissionsFile);
975 288 : if (NS_FAILED(rv)) return rv;
976 :
977 2 : nsCOMPtr<nsILineInputStream> lineInputStream = do_QueryInterface(fileInputStream, &rv);
978 1 : NS_ENSURE_SUCCESS(rv, rv);
979 :
980 : // start a transaction on the storage db, to optimize insertions.
981 : // transaction will automically commit on completion
982 2 : mozStorageTransaction transaction(mDBConn, true);
983 :
984 : /* format is:
985 : * matchtype \t type \t permission \t host
986 : * Only "host" is supported for matchtype
987 : * type is a string that identifies the type of permission (e.g. "cookie")
988 : * permission is an integer between 1 and 15
989 : */
990 :
991 2 : nsCAutoString buffer;
992 1 : bool isMore = true;
993 3 : while (isMore && NS_SUCCEEDED(lineInputStream->ReadLine(buffer, &isMore))) {
994 1 : if (buffer.IsEmpty() || buffer.First() == '#') {
995 1 : continue;
996 : }
997 :
998 0 : nsTArray<nsCString> lineArray;
999 :
1000 : // Split the line at tabs
1001 0 : ParseString(buffer, '\t', lineArray);
1002 :
1003 0 : if (lineArray[0].EqualsLiteral(kMatchTypeHost) &&
1004 0 : lineArray.Length() == 4) {
1005 :
1006 : PRInt32 error;
1007 0 : PRUint32 permission = lineArray[2].ToInteger(&error);
1008 0 : if (error)
1009 0 : continue;
1010 :
1011 : // hosts might be encoded in UTF8; switch them to ACE to be consistent
1012 0 : if (!IsASCII(lineArray[3])) {
1013 0 : rv = NormalizeToACE(lineArray[3]);
1014 0 : if (NS_FAILED(rv))
1015 0 : continue;
1016 : }
1017 :
1018 0 : rv = AddInternal(lineArray[3], lineArray[1], permission, 0,
1019 0 : nsIPermissionManager::EXPIRE_NEVER, 0, eDontNotify, eWriteToDB);
1020 0 : NS_ENSURE_SUCCESS(rv, rv);
1021 : }
1022 : }
1023 :
1024 : // we're done importing - delete the old file
1025 1 : permissionsFile->Remove(false);
1026 :
1027 1 : return NS_OK;
1028 : }
1029 :
1030 : nsresult
1031 0 : nsPermissionManager::NormalizeToACE(nsCString &aHost)
1032 : {
1033 : // lazily init the IDN service
1034 0 : if (!mIDNService) {
1035 : nsresult rv;
1036 0 : mIDNService = do_GetService(NS_IDNSERVICE_CONTRACTID, &rv);
1037 0 : NS_ENSURE_SUCCESS(rv, rv);
1038 : }
1039 :
1040 0 : return mIDNService->ConvertUTF8toACE(aHost, aHost);
1041 : }
1042 :
1043 : nsresult
1044 44760 : nsPermissionManager::GetHost(nsIURI *aURI, nsACString &aResult)
1045 : {
1046 89520 : nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(aURI);
1047 44760 : if (!innerURI) return NS_ERROR_FAILURE;
1048 :
1049 44760 : nsresult rv = innerURI->GetAsciiHost(aResult);
1050 :
1051 44760 : if (NS_FAILED(rv) || aResult.IsEmpty())
1052 25 : return NS_ERROR_UNEXPECTED;
1053 :
1054 44735 : return NS_OK;
1055 : }
1056 :
1057 : void
1058 496 : nsPermissionManager::UpdateDB(OperationType aOp,
1059 : mozIStorageStatement* aStmt,
1060 : PRInt64 aID,
1061 : const nsACString &aHost,
1062 : const nsACString &aType,
1063 : PRUint32 aPermission,
1064 : PRUint32 aExpireType,
1065 : PRInt64 aExpireTime)
1066 : {
1067 496 : ENSURE_NOT_CHILD_PROCESS_NORET;
1068 :
1069 : nsresult rv;
1070 :
1071 : // no statement is ok - just means we don't have a profile
1072 496 : if (!aStmt)
1073 22 : return;
1074 :
1075 474 : switch (aOp) {
1076 : case eOperationAdding:
1077 : {
1078 434 : rv = aStmt->BindInt64ByIndex(0, aID);
1079 434 : if (NS_FAILED(rv)) break;
1080 :
1081 434 : rv = aStmt->BindUTF8StringByIndex(1, aHost);
1082 434 : if (NS_FAILED(rv)) break;
1083 :
1084 434 : rv = aStmt->BindUTF8StringByIndex(2, aType);
1085 434 : if (NS_FAILED(rv)) break;
1086 :
1087 434 : rv = aStmt->BindInt32ByIndex(3, aPermission);
1088 434 : if (NS_FAILED(rv)) break;
1089 :
1090 434 : rv = aStmt->BindInt32ByIndex(4, aExpireType);
1091 434 : if (NS_FAILED(rv)) break;
1092 :
1093 434 : rv = aStmt->BindInt64ByIndex(5, aExpireTime);
1094 434 : break;
1095 : }
1096 :
1097 : case eOperationRemoving:
1098 : {
1099 7 : rv = aStmt->BindInt64ByIndex(0, aID);
1100 7 : break;
1101 : }
1102 :
1103 : case eOperationChanging:
1104 : {
1105 33 : rv = aStmt->BindInt64ByIndex(0, aID);
1106 33 : if (NS_FAILED(rv)) break;
1107 :
1108 33 : rv = aStmt->BindInt32ByIndex(1, aPermission);
1109 33 : if (NS_FAILED(rv)) break;
1110 :
1111 33 : rv = aStmt->BindInt32ByIndex(2, aExpireType);
1112 33 : if (NS_FAILED(rv)) break;
1113 :
1114 33 : rv = aStmt->BindInt64ByIndex(3, aExpireTime);
1115 33 : break;
1116 : }
1117 :
1118 : default:
1119 : {
1120 0 : NS_NOTREACHED("need a valid operation in UpdateDB()!");
1121 0 : rv = NS_ERROR_UNEXPECTED;
1122 0 : break;
1123 : }
1124 : }
1125 :
1126 474 : if (NS_SUCCEEDED(rv)) {
1127 : bool hasResult;
1128 474 : rv = aStmt->ExecuteStep(&hasResult);
1129 474 : aStmt->Reset();
1130 : }
1131 :
1132 474 : if (NS_FAILED(rv))
1133 0 : NS_WARNING("db change failed!");
1134 : }
1135 :
|