Source code

Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsEncryptedMsgURIsService.h"
#include "mozilla/Assertions.h"
#include "nsIMsgMailNewsUrl.h"
#include "nsIURL.h"
#include "nsNetUtil.h"
// This service must treat different URI representations of the same message as
// identical. The same message is named by URIs varying along two independent
// dimensions:
//
// 1. Two consumer families use two kinds of URI: the content policy and the
// banner look up the necko/display URL (mailbox:///folder?...&number=nn);
// copy-decrypted-to and compose look up the message URI
// (mailbox-message://folder#nn). These do not reduce to a common key, so
// producers register both forms per message, one for each family. This is
// why there are two registrations; normalizing does not remove the need
// for them.
//
// 2. Within the necko/display family, one message still appears under several
// forms: file:// vs mailbox:// (a message opened from a file is registered
// as file:// but displayed as mailbox://) and with or without volatile
// query bits (?type=...). NormalizeURI collapses these to one key so the
// necko registration matches whatever necko form a consumer uses.
//
// Normalization (dimension 2) does not bridge the two families (dimension 1);
// the two registrations do. It uses nsIMsgMessageUrl::normalizedSpec (the same
// canonicalization nsMsgContentPolicy uses for principals), which strips the
// volatile query bits. file:// URLs are not mailnews message URLs, so they are
// first mapped to their mailbox display form (mailbox:///<path>?number=0).
//
// normalizedSpec also strips the ref, where a message URI carries its key
// (mailbox-message://folder#nn). Reducing such a URI would drop the key and
// collapse distinct messages, so normalization is restricted to ref-less URIs;
// message URIs key on their raw spec, which is safe because their producer and
// consumer use the identical string. The file mapping is ref-independent.
// Anything else keys on its raw spec.
//
// Keying on the raw spec is the fallback of every step below, so aOut is set
// to it first and only replaced once a normalized form has been obtained. A
// normalized form is accepted only if it is non-empty: an empty key would
// match every other URI whose normalization degrades the same way.
static void NormalizeURI(const nsACString& aURI, nsACString& aOut) {
aOut = aURI;
nsCOMPtr<nsIURI> uri;
if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), aURI))) {
return;
}
bool hasRef = false;
if (NS_FAILED(uri->GetHasRef(&hasRef))) {
NS_WARNING("Cannot get ref, keying on the raw spec");
return;
}
nsAutoCString scheme;
if (NS_FAILED(uri->GetScheme(scheme))) {
NS_WARNING("Cannot get scheme, keying on the raw spec");
return;
}
#ifdef DEBUG
// Enforce the ref convention normalization relies on: message URIs carry
// their key in the ref, display/necko URLs do not. file: is excluded (its
// mapping is ref-independent).
if (scheme.EqualsLiteral("mailbox-message") ||
scheme.EqualsLiteral("imap-message") ||
scheme.EqualsLiteral("news-message")) {
MOZ_ASSERT(hasRef,
"message URI without a ref: it would be normalized and could "
"collapse distinct messages");
} else if (scheme.EqualsLiteral("mailbox") || scheme.EqualsLiteral("imap") ||
scheme.EqualsLiteral("news") || scheme.EqualsLiteral("nntp")) {
MOZ_ASSERT(!hasRef,
"display URL with a ref: normalization would be skipped");
}
#endif
if (!hasRef) {
nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(uri);
if (msgUrl) {
nsAutoCString normalized;
if (NS_SUCCEEDED(msgUrl->GetNormalizedSpec(normalized)) &&
!normalized.IsEmpty()) {
aOut = normalized;
return;
}
NS_WARNING("No normalized spec for message URL, keying on the raw spec");
return;
}
}
// A .eml opened from a file is a single message (number 0) displayed as
// mailbox:///<path>?number=0; map the file: URL to that form. GetFilePath
// drops any query/ref, so this is applied regardless of the ref.
if (scheme.EqualsLiteral("file")) {
nsCOMPtr<nsIURL> url = do_QueryInterface(uri);
nsAutoCString filePath;
if (url && NS_SUCCEEDED(url->GetFilePath(filePath)) &&
!filePath.IsEmpty()) {
nsAutoCString mailboxSpec("mailbox://"_ns);
mailboxSpec.Append(filePath);
mailboxSpec.AppendLiteral("?number=0");
nsCOMPtr<nsIURI> mailboxUri;
if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(mailboxUri), mailboxSpec))) {
nsCOMPtr<nsIMsgMessageUrl> mailboxMsgUrl =
do_QueryInterface(mailboxUri);
nsAutoCString normalized;
if (mailboxMsgUrl &&
NS_SUCCEEDED(mailboxMsgUrl->GetNormalizedSpec(normalized)) &&
!normalized.IsEmpty()) {
aOut = normalized;
return;
}
}
}
NS_WARNING(
"Cannot map file URL to its mailbox form, keying on the raw spec");
}
// Everything else (message URIs, non-message schemes) keys on its exact spec,
// already in aOut.
}
NS_IMPL_ISUPPORTS(nsEncryptedMsgURIsService, nsIEncryptedMsgURIsService)
nsEncryptedMsgURIsService::nsEncryptedMsgURIsService() {}
nsEncryptedMsgURIsService::~nsEncryptedMsgURIsService() {}
NS_IMETHODIMP nsEncryptedMsgURIsService::RememberEncrypted(
const nsACString& uri, bool aIsIntegrityProtected) {
if (uri.IsEmpty()) {
return NS_ERROR_INVALID_ARG;
}
nsAutoCString key;
NormalizeURI(uri, key);
// An empty key would match unrelated lookups.
if (key.IsEmpty()) {
return NS_ERROR_FAILURE;
}
// Appended per registration, keeping duplicates; see mEncryptedURIs.
mEncryptedURIs.AppendElement(key);
if (aIsIntegrityProtected) {
// Only mark as integrity protected if no earlier registration of this URI
// claimed the opposite.
if (!mIntegrityViolatedURIs.Contains(key)) {
mIntegrityProtectedURIs.Insert(key);
}
} else {
// A registration without integrity protection permanently (for the
// lifetime of the remembered URI) removes the integrity claim. Producers
// offering different integrity guarantees can register the same URI in an
// unspecified order, so the more restrictive claim must win regardless of
// which one arrives last.
mIntegrityProtectedURIs.Remove(key);
mIntegrityViolatedURIs.Insert(key);
}
return NS_OK;
}
NS_IMETHODIMP nsEncryptedMsgURIsService::ForgetEncrypted(
const nsACString& uri) {
if (uri.IsEmpty()) {
return NS_ERROR_INVALID_ARG;
}
nsAutoCString key;
NormalizeURI(uri, key);
if (key.IsEmpty()) {
return NS_ERROR_FAILURE;
}
// Removes a single entry, balancing one RememberEncrypted() call.
mEncryptedURIs.RemoveElement(key);
// Entries of other registrations may remain, and the URI is then still
// encrypted. Discard the integrity state together with the last entry, so
// that a later unrelated display of the same URI starts from a clean slate.
if (!mEncryptedURIs.Contains(key)) {
mIntegrityProtectedURIs.Remove(key);
mIntegrityViolatedURIs.Remove(key);
}
return NS_OK;
}
NS_IMETHODIMP nsEncryptedMsgURIsService::IsEncrypted(const nsACString& uri,
bool* _retval) {
NS_ENSURE_ARG_POINTER(_retval);
*_retval = false;
if (uri.IsEmpty()) {
return NS_OK;
}
nsAutoCString key;
NormalizeURI(uri, key);
if (key.IsEmpty()) {
return NS_OK;
}
*_retval = mEncryptedURIs.Contains(key);
return NS_OK;
}
NS_IMETHODIMP nsEncryptedMsgURIsService::IsEncryptedWithoutIntegrity(
const nsACString& uri, bool* _retval) {
NS_ENSURE_ARG_POINTER(_retval);
*_retval = false;
if (uri.IsEmpty()) {
return NS_OK;
}
nsAutoCString key;
NormalizeURI(uri, key);
if (key.IsEmpty()) {
return NS_OK;
}
*_retval =
mEncryptedURIs.Contains(key) && (!mIntegrityProtectedURIs.Contains(key) ||
mIntegrityViolatedURIs.Contains(key));
return NS_OK;
}