diff options
Diffstat (limited to 'mailnews/imap/src/nsIMAPNamespace.cpp')
-rw-r--r-- | mailnews/imap/src/nsIMAPNamespace.cpp | 650 |
1 files changed, 650 insertions, 0 deletions
diff --git a/mailnews/imap/src/nsIMAPNamespace.cpp b/mailnews/imap/src/nsIMAPNamespace.cpp new file mode 100644 index 000000000..d46352373 --- /dev/null +++ b/mailnews/imap/src/nsIMAPNamespace.cpp @@ -0,0 +1,650 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "msgCore.h" // for pre-compiled headers + +#include "nsImapCore.h" +#include "nsIMAPNamespace.h" +#include "nsImapProtocol.h" +#include "nsMsgImapCID.h" +#include "nsImapUrl.h" +#include "nsStringGlue.h" +#include "nsServiceManagerUtils.h" + +//////////////////// nsIMAPNamespace ///////////////////////////////////////////////////////////// + +static NS_DEFINE_CID(kCImapHostSessionListCID, NS_IIMAPHOSTSESSIONLIST_CID); + +nsIMAPNamespace::nsIMAPNamespace(EIMAPNamespaceType type, const char *prefix, char delimiter, bool from_prefs) +{ + m_namespaceType = type; + m_prefix = PL_strdup(prefix); + m_fromPrefs = from_prefs; + + m_delimiter = delimiter; + m_delimiterFilledIn = !m_fromPrefs; // if it's from the prefs, we can't be sure about the delimiter until we list it. +} + +nsIMAPNamespace::~nsIMAPNamespace() +{ + PR_FREEIF(m_prefix); +} + +void nsIMAPNamespace::SetDelimiter(char delimiter, bool delimiterFilledIn) +{ + m_delimiter = delimiter; + m_delimiterFilledIn = delimiterFilledIn; +} + +// returns -1 if this box is not part of this namespace, +// or the length of the prefix if it is part of this namespace +int nsIMAPNamespace::MailboxMatchesNamespace(const char *boxname) +{ + if (!boxname) return -1; + + // If the namespace is part of the boxname + if (!m_prefix || !*m_prefix) + return 0; + + if (PL_strstr(boxname, m_prefix) == boxname) + return PL_strlen(m_prefix); + + // If the boxname is part of the prefix + // (Used for matching Personal mailbox with Personal/ namespace, etc.) + if (PL_strstr(m_prefix, boxname) == m_prefix) + return PL_strlen(boxname); + return -1; +} + + +nsIMAPNamespaceList *nsIMAPNamespaceList::CreatensIMAPNamespaceList() +{ + nsIMAPNamespaceList *rv = new nsIMAPNamespaceList(); + return rv; +} + +nsIMAPNamespaceList::nsIMAPNamespaceList() +{ +} + +int nsIMAPNamespaceList::GetNumberOfNamespaces() +{ + return m_NamespaceList.Length(); +} + + +nsresult nsIMAPNamespaceList::InitFromString(const char *nameSpaceString, EIMAPNamespaceType nstype) +{ + nsresult rv = NS_OK; + if (nameSpaceString) + { + int numNamespaces = UnserializeNamespaces(nameSpaceString, nullptr, 0); + char **prefixes = (char**) PR_CALLOC(numNamespaces * sizeof(char*)); + if (prefixes) + { + int len = UnserializeNamespaces(nameSpaceString, prefixes, numNamespaces); + for (int i = 0; i < len; i++) + { + char *thisns = prefixes[i]; + char delimiter = '/'; // a guess + if (PL_strlen(thisns) >= 1) + delimiter = thisns[PL_strlen(thisns)-1]; + nsIMAPNamespace *ns = new nsIMAPNamespace(nstype, thisns, delimiter, true); + if (ns) + AddNewNamespace(ns); + PR_FREEIF(thisns); + } + PR_Free(prefixes); + } + } + + return rv; +} + +nsresult nsIMAPNamespaceList::OutputToString(nsCString &string) +{ + nsresult rv = NS_OK; + + return rv; +} + + +int nsIMAPNamespaceList::GetNumberOfNamespaces(EIMAPNamespaceType type) +{ + int nodeIndex = 0, count = 0; + for (nodeIndex = m_NamespaceList.Length() - 1; nodeIndex >= 0; nodeIndex--) + { + nsIMAPNamespace *nspace = m_NamespaceList.ElementAt(nodeIndex); + if (nspace->GetType() == type) + { + count++; + } + } + return count; +} + +int nsIMAPNamespaceList::AddNewNamespace(nsIMAPNamespace *ns) +{ + // If the namespace is from the NAMESPACE response, then we should see if there + // are any namespaces previously set by the preferences, or the default namespace. If so, remove these. + + if (!ns->GetIsNamespaceFromPrefs()) + { + int nodeIndex; + // iterate backwards because we delete elements + for (nodeIndex = m_NamespaceList.Length() - 1; nodeIndex >= 0; nodeIndex--) + { + nsIMAPNamespace *nspace = m_NamespaceList.ElementAt(nodeIndex); + // if we find existing namespace(s) that matches the + // new one, we'll just remove the old ones and let the + // new one get added when we've finished checking for + // matching namespaces or namespaces that came from prefs. + if (nspace && + (nspace->GetIsNamespaceFromPrefs() || + (!PL_strcmp(ns->GetPrefix(), nspace->GetPrefix()) && + ns->GetType() == nspace->GetType() && + ns->GetDelimiter() == nspace->GetDelimiter()))) + { + m_NamespaceList.RemoveElementAt(nodeIndex); + delete nspace; + } + } + } + + // Add the new namespace to the list. This must come after the removing code, + // or else we could never add the initial kDefaultNamespace type to the list. + m_NamespaceList.AppendElement(ns); + + return 0; +} + + +// chrisf - later, fix this to know the real concept of "default" namespace of a given type +nsIMAPNamespace *nsIMAPNamespaceList::GetDefaultNamespaceOfType(EIMAPNamespaceType type) +{ + nsIMAPNamespace *rv = 0, *firstOfType = 0; + + int nodeIndex, count = m_NamespaceList.Length(); + for (nodeIndex= 0; nodeIndex < count && !rv; nodeIndex++) + { + nsIMAPNamespace *ns = m_NamespaceList.ElementAt(nodeIndex); + if (ns->GetType() == type) + { + if (!firstOfType) + firstOfType = ns; + if (!(*(ns->GetPrefix()))) + { + // This namespace's prefix is "" + // Therefore it is the default + rv = ns; + } + } + } + if (!rv) + rv = firstOfType; + return rv; +} + +nsIMAPNamespaceList::~nsIMAPNamespaceList() +{ + ClearNamespaces(true, true, true); +} + +// ClearNamespaces removes and deletes the namespaces specified, and if there are no namespaces left, +void nsIMAPNamespaceList::ClearNamespaces(bool deleteFromPrefsNamespaces, bool deleteServerAdvertisedNamespaces, bool reallyDelete) +{ + int nodeIndex; + + // iterate backwards because we delete elements + for (nodeIndex = m_NamespaceList.Length() - 1; nodeIndex >= 0; nodeIndex--) + { + nsIMAPNamespace *ns = m_NamespaceList.ElementAt(nodeIndex); + if (ns->GetIsNamespaceFromPrefs()) + { + if (deleteFromPrefsNamespaces) + { + m_NamespaceList.RemoveElementAt(nodeIndex); + if (reallyDelete) + delete ns; + } + } + else if (deleteServerAdvertisedNamespaces) + { + m_NamespaceList.RemoveElementAt(nodeIndex); + if (reallyDelete) + delete ns; + } + } +} + +nsIMAPNamespace *nsIMAPNamespaceList::GetNamespaceNumber(int nodeIndex) +{ + NS_ASSERTION(nodeIndex >= 0 && nodeIndex < GetNumberOfNamespaces(), "invalid IMAP namespace node index"); + if (nodeIndex < 0) nodeIndex = 0; + + // XXX really could be just ElementAt; that's why we have the assertion + return m_NamespaceList.SafeElementAt(nodeIndex); +} + +nsIMAPNamespace *nsIMAPNamespaceList::GetNamespaceNumber(int nodeIndex, EIMAPNamespaceType type) +{ + int nodeCount, count = 0; + for (nodeCount = m_NamespaceList.Length() - 1; nodeCount >= 0; nodeCount--) + { + nsIMAPNamespace *nspace = m_NamespaceList.ElementAt(nodeCount); + if (nspace->GetType() == type) + { + count++; + if (count == nodeIndex) + return nspace; + } + } + return nullptr; +} + +nsIMAPNamespace *nsIMAPNamespaceList::GetNamespaceForMailbox(const char *boxname) +{ + // We want to find the LONGEST substring that matches the beginning of this mailbox's path. + // This accounts for nested namespaces (i.e. "Public/" and "Public/Users/") + + // Also, we want to match the namespace's mailbox to that namespace also: + // The Personal box will match the Personal/ namespace, etc. + + // these lists shouldn't be too long (99% chance there won't be more than 3 or 4) + // so just do a linear search + + int lengthMatched = -1; + int currentMatchedLength = -1; + nsIMAPNamespace *rv = nullptr; + int nodeIndex = 0; + + if (!PL_strcasecmp(boxname, "INBOX")) + return GetDefaultNamespaceOfType(kPersonalNamespace); + + for (nodeIndex = m_NamespaceList.Length() - 1; nodeIndex >= 0; nodeIndex--) + { + nsIMAPNamespace *nspace = m_NamespaceList.ElementAt(nodeIndex); + currentMatchedLength = nspace->MailboxMatchesNamespace(boxname); + if (currentMatchedLength > lengthMatched) + { + rv = nspace; + lengthMatched = currentMatchedLength; + } + } + + return rv; +} + +#define SERIALIZER_SEPARATORS "," + +/** + * If len is one, copies the first element of prefixes into serializedNamespaces. + * If len > 1, copies len strings from prefixes into serializedNamespaces + * as a comma-separated list of quoted strings. + */ +nsresult nsIMAPNamespaceList::SerializeNamespaces(char **prefixes, int len, + nsCString &serializedNamespaces) +{ + if (len <= 0) + return NS_OK; + + if (len == 1) + { + serializedNamespaces.Assign(prefixes[0]); + return NS_OK; + } + + for (int i = 0; i < len; i++) + { + if (i > 0) + serializedNamespaces.AppendLiteral(","); + + serializedNamespaces.AppendLiteral("\""); + serializedNamespaces.Append(prefixes[i]); + serializedNamespaces.AppendLiteral("\""); + } + return NS_OK; +} + +/* str is the string which needs to be unserialized. + If prefixes is NULL, simply returns the number of namespaces in str. (len is ignored) + If prefixes is not NULL, it should be an array of length len which is to be filled in + with newly-allocated string. Returns the number of strings filled in. +*/ +int nsIMAPNamespaceList::UnserializeNamespaces(const char *str, char **prefixes, int len) +{ + if (!str) + return 0; + if (!prefixes) + { + if (str[0] != '"') + return 1; + else + { + int count = 0; + char *ourstr = PL_strdup(str); + char *origOurStr = ourstr; + if (ourstr) + { + char *token = NS_strtok(SERIALIZER_SEPARATORS, &ourstr ); + while (token != nullptr) + { + token = NS_strtok(SERIALIZER_SEPARATORS, &ourstr ); + count++; + } + PR_Free(origOurStr); + } + return count; + } + } + else + { + if ((str[0] != '"') && (len >= 1)) + { + prefixes[0] = PL_strdup(str); + return 1; + } + else + { + int count = 0; + char *ourstr = PL_strdup(str); + char *origOurStr = ourstr; + if (ourstr) + { + char *token = NS_strtok(SERIALIZER_SEPARATORS, &ourstr ); + while ((count < len) && (token != nullptr)) + { + + char *current = PL_strdup(token), *where = current; + if (where[0] == '"') + where++; + if (where[PL_strlen(where)-1] == '"') + where[PL_strlen(where)-1] = 0; + prefixes[count] = PL_strdup(where); + PR_FREEIF(current); + token = NS_strtok(SERIALIZER_SEPARATORS, &ourstr ); + count++; + } + PR_Free(origOurStr); + } + return count; + } + } +} + + + + +char *nsIMAPNamespaceList::AllocateCanonicalFolderName(const char *onlineFolderName, char delimiter) +{ + char *canonicalPath = nullptr; + if (delimiter) + canonicalPath = nsImapUrl::ReplaceCharsInCopiedString(onlineFolderName, delimiter , '/'); + else + canonicalPath = PL_strdup(onlineFolderName); + + // eat any escape characters for escaped dir separators + if (canonicalPath) + { + char *currentEscapeSequence = strstr(canonicalPath, "\\/"); + while (currentEscapeSequence) + { + strcpy(currentEscapeSequence, currentEscapeSequence+1); + currentEscapeSequence = strstr(currentEscapeSequence+1, "\\/"); + } + } + + return canonicalPath; +} + + + +/* + GetFolderNameWithoutNamespace takes as input a folder name + in canonical form, and the namespace for the given folder. It returns an allocated + string of the folder's path with the namespace string stripped out. For instance, + when passed the folder Folders/a/b where the namespace is "Folders/", it will return + "a/b". Similarly, if the folder name is "#news/comp/mail/imap" in canonical form, + with a real delimiter of "." and a namespace of "#news.", it will return "comp/mail/imap". + The return value is always in canonical form. +*/ +char* nsIMAPNamespaceList::GetFolderNameWithoutNamespace(nsIMAPNamespace *namespaceForFolder, const char *canonicalFolderName) +{ + NS_ASSERTION(canonicalFolderName, "null folder name"); +#ifdef DEBUG + NS_ASSERTION(namespaceForFolder || !PL_strcasecmp(canonicalFolderName, "INBOX"), "need namespace or INBOX"); +#endif + + char *retFolderName = nullptr; + + if (!PL_strcasecmp(canonicalFolderName, "INBOX")) + return PL_strdup(canonicalFolderName); + + // convert the canonical path to the online path + char *convertedFolderName = nsIMAPNamespaceList::AllocateServerFolderName(canonicalFolderName, namespaceForFolder->GetDelimiter()); + if (convertedFolderName) + { + char *beginFolderPath = nullptr; + if (strlen(convertedFolderName) <= strlen(namespaceForFolder->GetPrefix())) + beginFolderPath = convertedFolderName; + else + beginFolderPath = convertedFolderName + strlen(namespaceForFolder->GetPrefix()); + NS_ASSERTION(beginFolderPath, "empty folder path"); + retFolderName = nsIMAPNamespaceList::AllocateCanonicalFolderName(beginFolderPath, namespaceForFolder->GetDelimiter()); + PR_Free(convertedFolderName); + } + + NS_ASSERTION(retFolderName, "returning null folder name"); + return retFolderName; +} + + +nsIMAPNamespace* nsIMAPNamespaceList::GetNamespaceForFolder(const char *hostName, + const char *canonicalFolderName, + char delimiter) +{ + if (!hostName || !canonicalFolderName) + return nullptr; + + nsIMAPNamespace *resultNamespace = nullptr; + nsresult rv; + char *convertedFolderName = nsIMAPNamespaceList::AllocateServerFolderName(canonicalFolderName, delimiter); + + if (convertedFolderName) + { + + nsCOMPtr<nsIImapHostSessionList> hostSessionList = + do_GetService(kCImapHostSessionListCID, &rv); + if (NS_FAILED(rv)) + return nullptr; + hostSessionList->GetNamespaceForMailboxForHost(hostName, convertedFolderName, resultNamespace); + PR_Free(convertedFolderName); + } + else + { + NS_ASSERTION(false, "couldn't get converted folder name"); + } + + return resultNamespace; +} + +/* static */ +char *nsIMAPNamespaceList::AllocateServerFolderName(const char *canonicalFolderName, char delimiter) +{ + if (delimiter) + return nsImapUrl::ReplaceCharsInCopiedString(canonicalFolderName, '/', delimiter); + else + return NS_strdup(canonicalFolderName); +} + +/* + GetFolderOwnerNameFromPath takes as inputs a folder name + in canonical form, and a namespace for that folder. + The namespace MUST be of type kOtherUsersNamespace, hence the folder MUST be + owned by another user. This function extracts the folder owner's name from the + canonical name of the folder, and returns an allocated copy of that owner's name +*/ +/* static */ +char *nsIMAPNamespaceList::GetFolderOwnerNameFromPath(nsIMAPNamespace *namespaceForFolder, const char *canonicalFolderName) +{ + if (!namespaceForFolder || !canonicalFolderName) + { + NS_ASSERTION(false,"null namespace or canonical folder name"); + return nullptr; + } + + char *rv = nullptr; + + // convert the canonical path to the online path + char *convertedFolderName = AllocateServerFolderName(canonicalFolderName, namespaceForFolder->GetDelimiter()); + if (convertedFolderName) + { +#ifdef DEBUG + NS_ASSERTION(strlen(convertedFolderName) > strlen(namespaceForFolder->GetPrefix()), "server folder name invalid"); +#endif + if (strlen(convertedFolderName) > strlen(namespaceForFolder->GetPrefix())) + { + char *owner = convertedFolderName + strlen(namespaceForFolder->GetPrefix()); + NS_ASSERTION(owner, "couldn't find folder owner"); + char *nextDelimiter = strchr(owner, namespaceForFolder->GetDelimiter()); + // if !nextDelimiter, then the path is of the form Shared/Users/chrisf (no subfolder) + if (nextDelimiter) + { + *nextDelimiter = 0; + } + rv = PL_strdup(owner); + } + PR_Free(convertedFolderName); + } + else + { + NS_ASSERTION(false, "couldn't allocate server folder name"); + } + + return rv; +} + +/* +GetFolderIsNamespace returns TRUE if the given folder is the folder representing +a namespace. +*/ + +bool nsIMAPNamespaceList::GetFolderIsNamespace(const char *hostName, + const char *canonicalFolderName, + char delimiter,nsIMAPNamespace *namespaceForFolder) +{ + NS_ASSERTION(namespaceForFolder, "null namespace"); + + bool rv = false; + + const char *prefix = namespaceForFolder->GetPrefix(); + NS_ASSERTION(prefix, "namespace has no prefix"); + if (!prefix || !*prefix) // empty namespace prefix + return false; + + char *convertedFolderName = AllocateServerFolderName(canonicalFolderName, delimiter); + if (convertedFolderName) + { + bool lastCharIsDelimiter = (prefix[strlen(prefix) - 1] == delimiter); + + if (lastCharIsDelimiter) + { + rv = ((strncmp(convertedFolderName, prefix, strlen(convertedFolderName)) == 0) && + (strlen(convertedFolderName) == strlen(prefix) - 1)); + } + else + { + rv = (strcmp(convertedFolderName, prefix) == 0); + } + + PR_Free(convertedFolderName); + } + else + { + NS_ASSERTION(false, "couldn't allocate server folder name"); + } + + return rv; +} + +/* + SuggestHierarchySeparatorForNamespace takes a namespace from libmsg + and a hierarchy delimiter. If the namespace has not been filled in from + online NAMESPACE command yet, it fills in the suggested delimiter to be + used from then on (until it is overridden by an online response). +*/ + +void nsIMAPNamespaceList::SuggestHierarchySeparatorForNamespace(nsIMAPNamespace *namespaceForFolder, char delimiterFromFolder) +{ + NS_ASSERTION(namespaceForFolder, "need namespace"); + if (namespaceForFolder && !namespaceForFolder->GetIsDelimiterFilledIn()) + namespaceForFolder->SetDelimiter(delimiterFromFolder, false); +} + + +/* + GenerateFullFolderNameWithDefaultNamespace takes a folder name in canonical form, + converts it to online form, allocates a string to contain the full online server name + including the namespace prefix of the default namespace of the given type, in the form: + PR_smprintf("%s%s", prefix, onlineServerName) if there is a NULL owner + PR_smprintf("%s%s%c%s", prefix, owner, delimiter, onlineServerName) if there is an owner + It then converts this back to canonical form and returns it (allocated) to libmsg. + It returns NULL if there is no namespace of the given type. + If nsUsed is not passed in as NULL, then *nsUsed is filled in and returned; it is the + namespace used for generating the folder name. +*/ +char *nsIMAPNamespaceList::GenerateFullFolderNameWithDefaultNamespace(const char *hostName, + const char *canonicalFolderName, + const char *owner, + EIMAPNamespaceType nsType, + nsIMAPNamespace **nsUsed) +{ + nsresult rv = NS_OK; + + nsCOMPtr<nsIImapHostSessionList> hostSession = + do_GetService(kCImapHostSessionListCID, &rv); + NS_ENSURE_SUCCESS(rv, nullptr); + nsIMAPNamespace *ns; + char *fullFolderName = nullptr; + rv = hostSession->GetDefaultNamespaceOfTypeForHost(hostName, nsType, ns); + NS_ENSURE_SUCCESS(rv, nullptr); + if (ns) + { + if (nsUsed) + *nsUsed = ns; + const char *prefix = ns->GetPrefix(); + char *convertedFolderName = AllocateServerFolderName(canonicalFolderName, ns->GetDelimiter()); + if (convertedFolderName) + { + char *convertedReturnName = nullptr; + if (owner) + { + convertedReturnName = PR_smprintf("%s%s%c%s", prefix, owner, ns->GetDelimiter(), convertedFolderName); + } + else + { + convertedReturnName = PR_smprintf("%s%s", prefix, convertedFolderName); + } + + if (convertedReturnName) + { + fullFolderName = AllocateCanonicalFolderName(convertedReturnName, ns->GetDelimiter()); + PR_Free(convertedReturnName); + } + PR_Free(convertedFolderName); + } + else + { + NS_ASSERTION(false, "couldn't allocate server folder name"); + } + } + else + { + // Could not find other users namespace on the given host + NS_WARNING("couldn't find namespace for given host"); + } + return (fullFolderName); +} + |