/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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 "nsAbManager.h" #include "nsAbBaseCID.h" #include "nsAddrDatabase.h" #include "nsIAbMDBDirectory.h" #include "nsIOutputStream.h" #include "nsNetUtil.h" #include "nsMsgI18N.h" #include "nsIStringBundle.h" #include "nsMsgUtils.h" #include "nsAppDirectoryServiceDefs.h" #include "plstr.h" #include "prmem.h" #include "nsIServiceManager.h" #include "mozIDOMWindow.h" #include "nsIFilePicker.h" #include "plbase64.h" #include "nsIWindowWatcher.h" #include "nsDirectoryServiceUtils.h" #include "nsVCard.h" #include "nsVCardObj.h" #include "nsIAbLDAPAttributeMap.h" #include "nsICommandLine.h" #include "nsIFile.h" #include "nsIMutableArray.h" #include "nsArrayUtils.h" #include "nsDirectoryServiceUtils.h" #include "nsIObserverService.h" #include "nsDirPrefs.h" #include "nsThreadUtils.h" #include "nsIAbDirFactory.h" #include "nsComponentManagerUtils.h" #include "nsIIOService.h" #include "nsAbQueryStringToExpression.h" #include "mozilla/ArrayUtils.h" #include "mozilla/Services.h" using namespace mozilla; struct ExportAttributesTableStruct { const char* abPropertyName; uint32_t plainTextStringID; }; // our schema is not fixed yet, but we still want some sort of objectclass // for now, use obsolete in the class name, hinting that this will change // see bugs bug #116692 and #118454 #define MOZ_AB_OBJECTCLASS "mozillaAbPersonAlpha" // for now, the oder of the attributes with true for includeForPlainText // should be in the same order as they are in the import code // see importMsgProperties and nsImportStringBundle. // // XXX todo, merge with what's in nsAbLDAPProperties.cpp, so we can // use this for LDAP and LDIF export // // here's how we're coming up with the ldapPropertyName values // if they are specified in RFC 2798, use them // else use the 4.x LDIF attribute names (for example, "xmozillanickname" // as we want to allow export from mozilla back to 4.x, and other apps // are probably out there that can handle 4.x LDIF) // else use the MOZ_AB_LDIF_PREFIX prefix, see nsIAddrDatabase.idl const ExportAttributesTableStruct EXPORT_ATTRIBUTES_TABLE[] = { {kFirstNameProperty, 2100}, {kLastNameProperty, 2101}, {kDisplayNameProperty, 2102}, {kNicknameProperty, 2103}, {kPriEmailProperty, 2104}, {k2ndEmailProperty, 2105}, {kScreenNameProperty, 2136}, {kPreferMailFormatProperty, 0}, {kLastModifiedDateProperty, 0}, {kWorkPhoneProperty, 2106}, {kWorkPhoneTypeProperty, 0}, {kHomePhoneProperty, 2107}, {kHomePhoneTypeProperty, 0}, {kFaxProperty, 2108}, {kFaxTypeProperty, 0}, {kPagerProperty, 2109}, {kPagerTypeProperty, 0}, {kCellularProperty, 2110}, {kCellularTypeProperty, 0}, {kHomeAddressProperty, 2111}, {kHomeAddress2Property, 2112}, {kHomeCityProperty, 2113}, {kHomeStateProperty, 2114}, {kHomeZipCodeProperty, 2115}, {kHomeCountryProperty, 2116}, {kWorkAddressProperty, 2117}, {kWorkAddress2Property, 2118}, {kWorkCityProperty, 2119}, {kWorkStateProperty, 2120}, {kWorkZipCodeProperty, 2121}, {kWorkCountryProperty, 2122}, {kJobTitleProperty, 2123}, {kDepartmentProperty, 2124}, {kCompanyProperty, 2125}, {kWorkWebPageProperty, 2126}, {kHomeWebPageProperty, 2127}, {kBirthYearProperty, 2128}, // unused for now {kBirthMonthProperty, 2129}, // unused for now {kBirthDayProperty, 2130}, // unused for now {kCustom1Property, 2131}, {kCustom2Property, 2132}, {kCustom3Property, 2133}, {kCustom4Property, 2134}, {kNotesProperty, 2135}, {kAnniversaryYearProperty, 0}, {kAnniversaryMonthProperty, 0}, {kAnniversaryDayProperty, 0}, {kSpouseNameProperty, 0}, {kFamilyNameProperty, 0}, }; // // nsAbManager // nsAbManager::nsAbManager() { } nsAbManager::~nsAbManager() { } NS_IMPL_ISUPPORTS(nsAbManager, nsIAbManager, nsICommandLineHandler, nsIObserver) nsresult nsAbManager::Init() { NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_FAILURE); nsCOMPtr observerService = mozilla::services::GetObserverService(); NS_ENSURE_TRUE(observerService, NS_ERROR_UNEXPECTED); nsresult rv = observerService->AddObserver(this, "profile-do-change", false); NS_ENSURE_SUCCESS(rv, rv); rv = observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } NS_IMETHODIMP nsAbManager::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData) { // The nsDirPrefs code caches all the directories that it got // from the first profiles prefs.js. // When we profile switch, we need to force it to shut down. // We'll re-load all the directories from the second profiles prefs.js // that happens in nsAbBSDirectory::GetChildNodes() // when we call DIR_GetDirectories(). if (!strcmp(aTopic, "profile-do-change")) { DIR_ShutDown(); return NS_OK; } if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { DIR_ShutDown(); nsCOMPtr observerService = mozilla::services::GetObserverService(); NS_ENSURE_TRUE(observerService, NS_ERROR_UNEXPECTED); nsresult rv = observerService->RemoveObserver(this, "profile-do-change"); NS_ENSURE_SUCCESS(rv, rv); rv = observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; } // // nsIAbManager // NS_IMETHODIMP nsAbManager::GetDirectories(nsISimpleEnumerator **aResult) { NS_ENSURE_ARG_POINTER(aResult); // We cache the top level AB to ensure that nsIAbDirectory items are not // created and dumped every time GetDirectories is called. This was causing // performance problems, especially with the content policy on messages // with lots of urls. nsresult rv; nsCOMPtr rootAddressBook; rv = GetRootDirectory(getter_AddRefs(rootAddressBook)); NS_ENSURE_SUCCESS(rv, rv); return rootAddressBook->GetChildNodes(aResult); } nsresult nsAbManager::GetRootDirectory(nsIAbDirectory **aResult) { // We cache the top level AB to ensure that nsIAbDirectory items are not // created and dumped every time GetDirectories is called. This was causing // performance problems, especially with the content policy on messages // with lots of urls. nsresult rv; if (!mCacheTopLevelAb) { nsCOMPtr rootAddressBook(do_GetService(NS_ABDIRECTORY_CONTRACTID, &rv)); NS_ENSURE_SUCCESS(rv, rv); mCacheTopLevelAb = rootAddressBook; } NS_IF_ADDREF(*aResult = mCacheTopLevelAb); return NS_OK; } NS_IMETHODIMP nsAbManager::GetDirectoryFromId(const nsACString &aDirPrefId, nsIAbDirectory **aResult) { NS_ENSURE_ARG_POINTER(aResult); nsCOMPtr enumerator; nsresult rv = GetDirectories(getter_AddRefs(enumerator)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr support; nsCOMPtr directory; bool hasMore = false; while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) { rv = enumerator->GetNext(getter_AddRefs(support)); NS_ENSURE_SUCCESS(rv, rv); directory = do_QueryInterface(support, &rv); if (NS_FAILED(rv)) { NS_WARNING("Unable to select Address book nsAbManager::GetDirectoryFromId()"); continue; } nsCString dirPrefId; directory->GetDirPrefId(dirPrefId); if (dirPrefId.Equals(aDirPrefId)) { directory.forget(aResult); return NS_OK; } } return NS_OK; } NS_IMETHODIMP nsAbManager::GetDirectory(const nsACString &aURI, nsIAbDirectory **aResult) { NS_ENSURE_ARG_POINTER(aResult); nsresult rv; nsCOMPtr directory; // Was the directory root requested? if (aURI.EqualsLiteral(kAllDirectoryRoot)) { rv = GetRootDirectory(getter_AddRefs(directory)); NS_ENSURE_SUCCESS(rv, rv); NS_IF_ADDREF(*aResult = directory); return NS_OK; } // Do we have a copy of this directory already within our look-up table? if (!mAbStore.Get(aURI, getter_AddRefs(directory))) { // The directory wasn't in our look-up table, so we need to instantiate // it. First, extract the scheme from the URI... nsAutoCString scheme; int32_t colon = aURI.FindChar(':'); if (colon <= 0) return NS_ERROR_MALFORMED_URI; scheme = Substring(aURI, 0, colon); // Construct the appropriate nsIAbDirectory... nsAutoCString contractID; contractID.AssignLiteral(NS_AB_DIRECTORY_TYPE_CONTRACTID_PREFIX); contractID.Append(scheme); directory = do_CreateInstance(contractID.get(), &rv); NS_ENSURE_SUCCESS(rv, rv); // Init it with the URI rv = directory->Init(PromiseFlatCString(aURI).get()); NS_ENSURE_SUCCESS(rv, rv); // Check if this directory was initiated with a search query. If so, // we don't cache it. bool isQuery = false; rv = directory->GetIsQuery(&isQuery); NS_ENSURE_SUCCESS(rv, rv); if (!isQuery) mAbStore.Put(aURI, directory); } NS_IF_ADDREF(*aResult = directory); return NS_OK; } NS_IMETHODIMP nsAbManager::NewAddressBook(const nsAString &aDirName, const nsACString &aURI, const uint32_t aType, const nsACString &aPrefName, nsACString &aResult) { nsresult rv; nsCOMPtr parentDir; rv = GetRootDirectory(getter_AddRefs(parentDir)); NS_ENSURE_SUCCESS(rv, rv); return parentDir->CreateNewDirectory(aDirName, aURI, aType, aPrefName, aResult); } NS_IMETHODIMP nsAbManager::DeleteAddressBook(const nsACString &aURI) { // Find the address book nsresult rv; nsCOMPtr directory; rv = GetDirectory(aURI, getter_AddRefs(directory)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr rootDirectory; rv = GetRootDirectory(getter_AddRefs(rootDirectory)); NS_ENSURE_SUCCESS(rv, rv); // Go through each of the children of the address book // (so, the mailing lists) and remove their entries from // the look up table. nsCOMPtr enumerator; rv = directory->GetChildNodes(getter_AddRefs(enumerator)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr item; nsCOMPtr childDirectory; bool hasMore = false; while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) { rv = enumerator->GetNext(getter_AddRefs(item)); NS_ENSURE_SUCCESS(rv, rv); childDirectory = do_QueryInterface(item, &rv); if (NS_SUCCEEDED(rv)) { nsCString childURI; rv = childDirectory->GetURI(childURI); NS_ENSURE_SUCCESS(rv, rv); mAbStore.Remove(childURI); } } mAbStore.Remove(aURI); bool isMailList; rv = directory->GetIsMailList(&isMailList); NS_ENSURE_SUCCESS(rv, rv); if (!isMailList) // If we're not a mailing list, then our parent // must be the root address book directory. return rootDirectory->DeleteDirectory(directory); nsCString parentUri; parentUri.Append(aURI); int32_t pos = parentUri.RFindChar('/'); // If we didn't find a /, we're in trouble. if (pos == -1) return NS_ERROR_FAILURE; parentUri = StringHead(parentUri, pos); nsCOMPtr parentDirectory; rv = GetDirectory(parentUri, getter_AddRefs(parentDirectory)); NS_ENSURE_SUCCESS(rv, rv); return parentDirectory->DeleteDirectory(directory); } NS_IMETHODIMP nsAbManager::AddAddressBookListener(nsIAbListener *aListener, abListenerNotifyFlagValue aNotifyFlags) { NS_ENSURE_ARG_POINTER(aListener); abListener newListener(aListener, aNotifyFlags); mListeners.AppendElementUnlessExists(newListener); return NS_OK; } NS_IMETHODIMP nsAbManager::RemoveAddressBookListener(nsIAbListener *aListener) { NS_ENSURE_ARG_POINTER(aListener); mListeners.RemoveElement(aListener); return NS_OK; } #define NOTIFY_AB_LISTENERS(propertyflag_, propertyfunc_, params_) \ PR_BEGIN_MACRO \ nsTObserverArray::ForwardIterator iter(mListeners); \ while (iter.HasMore()) { \ const abListener &abL = iter.GetNext(); \ if (abL.mNotifyFlags & nsIAbListener::propertyflag_) \ abL.mListener->propertyfunc_ params_; \ } \ PR_END_MACRO NS_IMETHODIMP nsAbManager::NotifyItemPropertyChanged(nsISupports *aItem, const char *aProperty, const char16_t* aOldValue, const char16_t* aNewValue) { NOTIFY_AB_LISTENERS(itemChanged, OnItemPropertyChanged, (aItem, aProperty, aOldValue, aNewValue)); return NS_OK; } NS_IMETHODIMP nsAbManager::NotifyDirectoryItemAdded(nsIAbDirectory *aParentDirectory, nsISupports *aItem) { NOTIFY_AB_LISTENERS(itemAdded, OnItemAdded, (aParentDirectory, aItem)); return NS_OK; } NS_IMETHODIMP nsAbManager::NotifyDirectoryItemDeleted(nsIAbDirectory *aParentDirectory, nsISupports *aItem) { NOTIFY_AB_LISTENERS(directoryItemRemoved, OnItemRemoved, (aParentDirectory, aItem)); return NS_OK; } NS_IMETHODIMP nsAbManager::NotifyDirectoryDeleted(nsIAbDirectory *aParentDirectory, nsISupports *aDirectory) { NOTIFY_AB_LISTENERS(directoryRemoved, OnItemRemoved, (aParentDirectory, aDirectory)); return NS_OK; } NS_IMETHODIMP nsAbManager::GetUserProfileDirectory(nsIFile **userDir) { NS_ENSURE_ARG_POINTER(userDir); *userDir = nullptr; nsresult rv; nsCOMPtr profileDir; nsAutoCString pathBuf; rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(profileDir)); NS_ENSURE_SUCCESS(rv, rv); profileDir.forget(userDir); return NS_OK; } NS_IMETHODIMP nsAbManager::MailListNameExists(const char16_t *aName, bool *aExists) { nsresult rv; NS_ENSURE_ARG_POINTER(aExists); *aExists = false; // now get the top-level book nsCOMPtr topDirectory; rv = GetRootDirectory(getter_AddRefs(topDirectory)); // now go through the address books nsCOMPtr enumerator; rv = topDirectory->GetChildNodes(getter_AddRefs(enumerator)); NS_ENSURE_SUCCESS(rv, rv); bool hasMore; while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) { nsCOMPtr item; rv = enumerator->GetNext(getter_AddRefs(item)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr directory = do_QueryInterface(item, &rv); if (NS_FAILED(rv)) continue; rv = directory->HasMailListWithName(aName, aExists); if (NS_SUCCEEDED(rv) && *aExists) return NS_OK; } *aExists = false; return NS_OK; } #define CSV_DELIM "," #define CSV_DELIM_LEN 1 #define TAB_DELIM "\t" #define TAB_DELIM_LEN 1 #define CSV_FILE_EXTENSION ".csv" #define TAB_FILE_EXTENSION ".tab" #define TXT_FILE_EXTENSION ".txt" #define VCF_FILE_EXTENSION ".vcf" #define LDIF_FILE_EXTENSION ".ldi" #define LDIF_FILE_EXTENSION2 ".ldif" enum ADDRESSBOOK_EXPORT_FILE_TYPE { CSV_EXPORT_TYPE = 0, CSV_EXPORT_TYPE_UTF8 = 1, TAB_EXPORT_TYPE = 2, TAB_EXPORT_TYPE_UTF8 = 3, VCF_EXPORT_TYPE = 4, LDIF_EXPORT_TYPE = 5, }; NS_IMETHODIMP nsAbManager::ExportAddressBook(mozIDOMWindowProxy *aParentWin, nsIAbDirectory *aDirectory) { NS_ENSURE_ARG_POINTER(aParentWin); nsresult rv; nsCOMPtr filePicker = do_CreateInstance("@mozilla.org/filepicker;1", &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr bundleService = mozilla::services::GetStringBundleService(); NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); nsCOMPtr bundle; rv = bundleService->CreateBundle("chrome://messenger/locale/addressbook/addressBook.properties", getter_AddRefs(bundle)); NS_ENSURE_SUCCESS(rv, rv); nsString dirName; aDirectory->GetDirName(dirName); const char16_t *formatStrings[] = { dirName.get() }; nsString title; rv = bundle->FormatStringFromName(u"ExportAddressBookNameTitle", formatStrings, ArrayLength(formatStrings), getter_Copies(title)); NS_ENSURE_SUCCESS(rv, rv); rv = filePicker->Init(aParentWin, title, nsIFilePicker::modeSave); NS_ENSURE_SUCCESS(rv, rv); filePicker->SetDefaultString(dirName); nsString filterString; // CSV: System charset and UTF-8. rv = bundle->GetStringFromName(u"CSVFilesSysCharset", getter_Copies(filterString)); NS_ENSURE_SUCCESS(rv, rv); rv = filePicker->AppendFilter(filterString, NS_LITERAL_STRING("*.csv")); NS_ENSURE_SUCCESS(rv, rv); rv = bundle->GetStringFromName(u"CSVFilesUTF8", getter_Copies(filterString)); NS_ENSURE_SUCCESS(rv, rv); rv = filePicker->AppendFilter(filterString, NS_LITERAL_STRING("*.csv")); NS_ENSURE_SUCCESS(rv, rv); // Tab separated: System charset and UTF-8. rv = bundle->GetStringFromName(u"TABFilesSysCharset", getter_Copies(filterString)); NS_ENSURE_SUCCESS(rv, rv); rv = filePicker->AppendFilter(filterString, NS_LITERAL_STRING("*.tab; *.txt")); NS_ENSURE_SUCCESS(rv, rv); rv = bundle->GetStringFromName(u"TABFilesUTF8", getter_Copies(filterString)); NS_ENSURE_SUCCESS(rv, rv); rv = filePicker->AppendFilter(filterString, NS_LITERAL_STRING("*.tab; *.txt")); NS_ENSURE_SUCCESS(rv, rv); rv = bundle->GetStringFromName(u"VCFFiles", getter_Copies(filterString)); NS_ENSURE_SUCCESS(rv, rv); rv = filePicker->AppendFilter(filterString, NS_LITERAL_STRING("*.vcf")); NS_ENSURE_SUCCESS(rv, rv); rv = bundle->GetStringFromName(u"LDIFFiles", getter_Copies(filterString)); NS_ENSURE_SUCCESS(rv, rv); rv = filePicker->AppendFilter(filterString, NS_LITERAL_STRING("*.ldi; *.ldif")); NS_ENSURE_SUCCESS(rv, rv); int16_t dialogResult; filePicker->Show(&dialogResult); if (dialogResult == nsIFilePicker::returnCancel) return rv; nsCOMPtr localFile; rv = filePicker->GetFile(getter_AddRefs(localFile)); NS_ENSURE_SUCCESS(rv, rv); if (dialogResult == nsIFilePicker::returnReplace) { // be extra safe and only delete when the file is really a file bool isFile; rv = localFile->IsFile(&isFile); if (NS_SUCCEEDED(rv) && isFile) { rv = localFile->Remove(false /* recursive delete */); NS_ENSURE_SUCCESS(rv, rv); } } // The type of export is determined by the drop-down in // the file picker dialog. int32_t exportType; rv = filePicker->GetFilterIndex(&exportType); NS_ENSURE_SUCCESS(rv,rv); nsAutoString fileName; rv = localFile->GetLeafName(fileName); NS_ENSURE_SUCCESS(rv, rv); switch ( exportType ) { default: case LDIF_EXPORT_TYPE: // ldif // If filename does not have the correct ext, add one. if ((MsgFind(fileName, LDIF_FILE_EXTENSION, true, fileName.Length() - strlen(LDIF_FILE_EXTENSION)) == -1) && (MsgFind(fileName, LDIF_FILE_EXTENSION2, true, fileName.Length() - strlen(LDIF_FILE_EXTENSION2)) == -1)) { // Add the extension and build a new localFile. fileName.AppendLiteral(LDIF_FILE_EXTENSION2); localFile->SetLeafName(fileName); } rv = ExportDirectoryToLDIF(aDirectory, localFile); break; case CSV_EXPORT_TYPE: // csv case CSV_EXPORT_TYPE_UTF8: // If filename does not have the correct ext, add one. if (MsgFind(fileName, CSV_FILE_EXTENSION, true, fileName.Length() - strlen(CSV_FILE_EXTENSION)) == -1) { // Add the extension and build a new localFile. fileName.AppendLiteral(CSV_FILE_EXTENSION); localFile->SetLeafName(fileName); } rv = ExportDirectoryToDelimitedText(aDirectory, CSV_DELIM, CSV_DELIM_LEN, localFile, exportType==CSV_EXPORT_TYPE_UTF8); break; case TAB_EXPORT_TYPE: // tab & text case TAB_EXPORT_TYPE_UTF8: // If filename does not have the correct ext, add one. if ((MsgFind(fileName, TXT_FILE_EXTENSION, true, fileName.Length() - strlen(TXT_FILE_EXTENSION)) == -1) && (MsgFind(fileName, TAB_FILE_EXTENSION, true, fileName.Length() - strlen(TAB_FILE_EXTENSION)) == -1)) { // Add the extension and build a new localFile. fileName.AppendLiteral(TXT_FILE_EXTENSION); localFile->SetLeafName(fileName); } rv = ExportDirectoryToDelimitedText(aDirectory, TAB_DELIM, TAB_DELIM_LEN, localFile, exportType==TAB_EXPORT_TYPE_UTF8); break; case VCF_EXPORT_TYPE: // vCard // If filename does not have the correct ext, add one. if (MsgFind(fileName, VCF_FILE_EXTENSION, true, fileName.Length() - strlen(VCF_FILE_EXTENSION)) == -1) { // Add the extension and build a new localFile. fileName.AppendLiteral(VCF_FILE_EXTENSION); localFile->SetLeafName(fileName); } rv = ExportDirectoryToVCard(aDirectory, localFile); break; }; return rv; } nsresult nsAbManager::ExportDirectoryToDelimitedText(nsIAbDirectory *aDirectory, const char *aDelim, uint32_t aDelimLen, nsIFile *aLocalFile, bool useUTF8) { nsCOMPtr cardsEnumerator; nsCOMPtr card; nsresult rv; nsCOMPtr outputStream; rv = MsgNewBufferedFileOutputStream(getter_AddRefs(outputStream), aLocalFile, PR_CREATE_FILE | PR_WRONLY | PR_TRUNCATE, 0664); // the desired file may be read only if (NS_FAILED(rv)) return rv; uint32_t i; uint32_t writeCount; uint32_t length; nsCOMPtr bundleService = mozilla::services::GetStringBundleService(); NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); nsCOMPtr bundle; rv = bundleService->CreateBundle("chrome://messenger/locale/importMsgs.properties", getter_AddRefs(bundle)); NS_ENSURE_SUCCESS(rv, rv); nsCString revisedName; nsString columnName; for (i = 0; i < ArrayLength(EXPORT_ATTRIBUTES_TABLE); i++) { if (EXPORT_ATTRIBUTES_TABLE[i].plainTextStringID != 0) { // We don't need to truncate the string here as getter_Copies will // do that for us. if (NS_FAILED(bundle->GetStringFromID(EXPORT_ATTRIBUTES_TABLE[i].plainTextStringID, getter_Copies(columnName)))) columnName.AppendInt(EXPORT_ATTRIBUTES_TABLE[i].plainTextStringID); rv = nsMsgI18NConvertFromUnicode(useUTF8 ? "UTF-8" : nsMsgI18NFileSystemCharset(), columnName, revisedName); NS_ENSURE_SUCCESS(rv,rv); rv = outputStream->Write(revisedName.get(), revisedName.Length(), &writeCount); NS_ENSURE_SUCCESS(rv,rv); if (revisedName.Length() != writeCount) return NS_ERROR_FAILURE; if (i < ArrayLength(EXPORT_ATTRIBUTES_TABLE) - 1) { rv = outputStream->Write(aDelim, aDelimLen, &writeCount); NS_ENSURE_SUCCESS(rv,rv); if (aDelimLen != writeCount) return NS_ERROR_FAILURE; } } } rv = outputStream->Write(MSG_LINEBREAK, MSG_LINEBREAK_LEN, &writeCount); NS_ENSURE_SUCCESS(rv,rv); if (MSG_LINEBREAK_LEN != writeCount) return NS_ERROR_FAILURE; rv = aDirectory->GetChildCards(getter_AddRefs(cardsEnumerator)); if (NS_SUCCEEDED(rv) && cardsEnumerator) { nsCOMPtr item; bool more; while (NS_SUCCEEDED(cardsEnumerator->HasMoreElements(&more)) && more) { rv = cardsEnumerator->GetNext(getter_AddRefs(item)); if (NS_SUCCEEDED(rv)) { nsCOMPtr card = do_QueryInterface(item, &rv); NS_ENSURE_SUCCESS(rv,rv); bool isMailList; rv = card->GetIsMailList(&isMailList); NS_ENSURE_SUCCESS(rv,rv); if (isMailList) { // .tab, .txt and .csv aren't able to export mailing lists // use LDIF for that. } else { nsString value; nsCString valueCStr; for (i = 0; i < ArrayLength(EXPORT_ATTRIBUTES_TABLE); i++) { if (EXPORT_ATTRIBUTES_TABLE[i].plainTextStringID != 0) { rv = card->GetPropertyAsAString(EXPORT_ATTRIBUTES_TABLE[i].abPropertyName, value); if (NS_FAILED(rv)) value.Truncate(); // If a string contains at least one comma, tab or double quote then // we need to quote the entire string. Also if double quote is part // of the string we need to quote the double quote(s) as well. nsAutoString newValue(value); bool needsQuotes = false; if(newValue.FindChar('"') != -1) { needsQuotes = true; int32_t match = 0; uint32_t offset = 0; nsString oldSubstr = NS_LITERAL_STRING("\""); nsString newSubstr = NS_LITERAL_STRING("\"\""); while (offset < newValue.Length()) { match = newValue.Find(oldSubstr, offset); if (match == -1) break; newValue.Replace(offset + match, oldSubstr.Length(), newSubstr); offset += (match + newSubstr.Length()); } } if (!needsQuotes && (newValue.FindChar(',') != -1 || newValue.FindChar('\x09') != -1)) needsQuotes = true; // Make sure we quote if containing CR/LF. if (newValue.FindChar('\r') != -1 || newValue.FindChar('\n') != -1) needsQuotes = true; if (needsQuotes) { newValue.Insert(NS_LITERAL_STRING("\""), 0); newValue.AppendLiteral("\""); } rv = nsMsgI18NConvertFromUnicode(useUTF8 ? "UTF-8" : nsMsgI18NFileSystemCharset(), newValue, valueCStr); NS_ENSURE_SUCCESS(rv,rv); if (NS_FAILED(rv)) { NS_ERROR("failed to convert string to system charset. use LDIF"); valueCStr = "?"; } length = valueCStr.Length(); if (length) { rv = outputStream->Write(valueCStr.get(), length, &writeCount); NS_ENSURE_SUCCESS(rv,rv); if (length != writeCount) return NS_ERROR_FAILURE; } valueCStr = ""; } else { // something we don't support for the current export // for example, .tab doesn't export preferred html format continue; // go to next field } if (i < ArrayLength(EXPORT_ATTRIBUTES_TABLE) - 1) { rv = outputStream->Write(aDelim, aDelimLen, &writeCount); NS_ENSURE_SUCCESS(rv,rv); if (aDelimLen != writeCount) return NS_ERROR_FAILURE; } } // write out the linebreak that separates the cards rv = outputStream->Write(MSG_LINEBREAK, MSG_LINEBREAK_LEN, &writeCount); NS_ENSURE_SUCCESS(rv,rv); if (MSG_LINEBREAK_LEN != writeCount) return NS_ERROR_FAILURE; } } } } rv = outputStream->Flush(); NS_ENSURE_SUCCESS(rv,rv); rv = outputStream->Close(); NS_ENSURE_SUCCESS(rv,rv); return NS_OK; } nsresult nsAbManager::ExportDirectoryToVCard(nsIAbDirectory *aDirectory, nsIFile *aLocalFile) { nsCOMPtr cardsEnumerator; nsCOMPtr card; nsresult rv; nsCOMPtr outputStream; rv = MsgNewBufferedFileOutputStream(getter_AddRefs(outputStream), aLocalFile, PR_CREATE_FILE | PR_WRONLY | PR_TRUNCATE, 0664); // the desired file may be read only if (NS_FAILED(rv)) return rv; uint32_t writeCount; uint32_t length; rv = aDirectory->GetChildCards(getter_AddRefs(cardsEnumerator)); if (NS_SUCCEEDED(rv) && cardsEnumerator) { nsCOMPtr item; bool more; while (NS_SUCCEEDED(cardsEnumerator->HasMoreElements(&more)) && more) { rv = cardsEnumerator->GetNext(getter_AddRefs(item)); if (NS_SUCCEEDED(rv)) { nsCOMPtr card = do_QueryInterface(item, &rv); NS_ENSURE_SUCCESS(rv,rv); bool isMailList; rv = card->GetIsMailList(&isMailList); NS_ENSURE_SUCCESS(rv,rv); if (isMailList) { // we don't know how to export mailing lists to vcf // use LDIF for that. } else { nsCString escapedValue; rv = card->TranslateTo(NS_LITERAL_CSTRING("vcard"), escapedValue); NS_ENSURE_SUCCESS(rv,rv); nsCString valueCStr; MsgUnescapeString(escapedValue, 0, valueCStr); length = valueCStr.Length(); rv = outputStream->Write(valueCStr.get(), length, &writeCount); NS_ENSURE_SUCCESS(rv,rv); if (length != writeCount) return NS_ERROR_FAILURE; } } } } rv = outputStream->Flush(); NS_ENSURE_SUCCESS(rv,rv); rv = outputStream->Close(); NS_ENSURE_SUCCESS(rv,rv); return NS_OK; } nsresult nsAbManager::ExportDirectoryToLDIF(nsIAbDirectory *aDirectory, nsIFile *aLocalFile) { nsCOMPtr cardsEnumerator; nsCOMPtr card; nsresult rv; nsCOMPtr outputStream; rv = MsgNewBufferedFileOutputStream(getter_AddRefs(outputStream), aLocalFile, PR_CREATE_FILE | PR_WRONLY | PR_TRUNCATE, 0664); // the desired file may be read only if (NS_FAILED(rv)) return rv; // Get the default attribute map for ldap. We use the default attribute // map rather than one for a specific server because if people want an // ldif export using a servers specific schema, then they can use ldapsearch nsCOMPtr mapSrv = do_GetService("@mozilla.org/addressbook/ldap-attribute-map-service;1", &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr attrMap; rv = mapSrv->GetMapForPrefBranch(NS_LITERAL_CSTRING("ldap_2.servers.default.attrmap"), getter_AddRefs(attrMap)); NS_ENSURE_SUCCESS(rv, rv); uint32_t i; uint32_t writeCount; uint32_t length; rv = aDirectory->GetChildCards(getter_AddRefs(cardsEnumerator)); if (NS_SUCCEEDED(rv) && cardsEnumerator) { nsCOMPtr item; bool more; while (NS_SUCCEEDED(cardsEnumerator->HasMoreElements(&more)) && more) { rv = cardsEnumerator->GetNext(getter_AddRefs(item)); if (NS_SUCCEEDED(rv)) { nsCOMPtr card = do_QueryInterface(item, &rv); NS_ENSURE_SUCCESS(rv,rv); bool isMailList; rv = card->GetIsMailList(&isMailList); NS_ENSURE_SUCCESS(rv,rv); if (isMailList) { nsCString mailListCStr; rv = AppendLDIFForMailList(card, attrMap, mailListCStr); NS_ENSURE_SUCCESS(rv,rv); length = mailListCStr.Length(); rv = outputStream->Write(mailListCStr.get(), length, &writeCount); NS_ENSURE_SUCCESS(rv,rv); if (length != writeCount) return NS_ERROR_FAILURE; } else { nsString value; nsCString valueCStr; rv = AppendBasicLDIFForCard(card, attrMap, valueCStr); NS_ENSURE_SUCCESS(rv,rv); length = valueCStr.Length(); rv = outputStream->Write(valueCStr.get(), length, &writeCount); NS_ENSURE_SUCCESS(rv,rv); if (length != writeCount) return NS_ERROR_FAILURE; valueCStr.Truncate(); nsAutoCString ldapAttribute; for (i = 0; i < ArrayLength(EXPORT_ATTRIBUTES_TABLE); i++) { if (NS_SUCCEEDED(attrMap->GetFirstAttribute(nsDependentCString(EXPORT_ATTRIBUTES_TABLE[i].abPropertyName), ldapAttribute)) && !ldapAttribute.IsEmpty()) { rv = card->GetPropertyAsAString(EXPORT_ATTRIBUTES_TABLE[i].abPropertyName, value); if (NS_FAILED(rv)) value.Truncate(); if (!PL_strcmp(EXPORT_ATTRIBUTES_TABLE[i].abPropertyName, kPreferMailFormatProperty)) { if (value.EqualsLiteral("html")) value.AssignLiteral("true"); else if (value.EqualsLiteral("plaintext")) value.AssignLiteral("false"); else value.Truncate(); // unknown. } if (!value.IsEmpty()) { rv = AppendProperty(ldapAttribute.get(), value.get(), valueCStr); NS_ENSURE_SUCCESS(rv,rv); valueCStr += MSG_LINEBREAK; } else valueCStr.Truncate(); length = valueCStr.Length(); if (length) { rv = outputStream->Write(valueCStr.get(), length, &writeCount); NS_ENSURE_SUCCESS(rv,rv); if (length != writeCount) return NS_ERROR_FAILURE; } valueCStr.Truncate(); } else { // something we don't support yet // ldif doesn't export multiple addresses } } // write out the linebreak that separates the cards rv = outputStream->Write(MSG_LINEBREAK, MSG_LINEBREAK_LEN, &writeCount); NS_ENSURE_SUCCESS(rv,rv); if (MSG_LINEBREAK_LEN != writeCount) return NS_ERROR_FAILURE; } } } } rv = outputStream->Flush(); NS_ENSURE_SUCCESS(rv,rv); rv = outputStream->Close(); NS_ENSURE_SUCCESS(rv,rv); return NS_OK; } nsresult nsAbManager::AppendLDIFForMailList(nsIAbCard *aCard, nsIAbLDAPAttributeMap *aAttrMap, nsACString &aResult) { nsresult rv; nsString attrValue; rv = AppendDNForCard("dn", aCard, aAttrMap, aResult); NS_ENSURE_SUCCESS(rv,rv); aResult += MSG_LINEBREAK \ "objectclass: top" MSG_LINEBREAK \ "objectclass: groupOfNames" MSG_LINEBREAK; rv = aCard->GetDisplayName(attrValue); NS_ENSURE_SUCCESS(rv,rv); nsAutoCString ldapAttributeName; rv = aAttrMap->GetFirstAttribute(NS_LITERAL_CSTRING(kDisplayNameProperty), ldapAttributeName); NS_ENSURE_SUCCESS(rv, rv); rv = AppendProperty(ldapAttributeName.get(), attrValue.get(), aResult); NS_ENSURE_SUCCESS(rv,rv); aResult += MSG_LINEBREAK; rv = aAttrMap->GetFirstAttribute(NS_LITERAL_CSTRING(kNicknameProperty), ldapAttributeName); NS_ENSURE_SUCCESS(rv, rv); rv = aCard->GetPropertyAsAString(kNicknameProperty, attrValue); if (NS_SUCCEEDED(rv) && !attrValue.IsEmpty()) { rv = AppendProperty(ldapAttributeName.get(), attrValue.get(), aResult); NS_ENSURE_SUCCESS(rv,rv); aResult += MSG_LINEBREAK; } rv = aAttrMap->GetFirstAttribute(NS_LITERAL_CSTRING(kNotesProperty), ldapAttributeName); NS_ENSURE_SUCCESS(rv, rv); rv = aCard->GetPropertyAsAString(kNotesProperty, attrValue); if (NS_SUCCEEDED(rv) && !attrValue.IsEmpty()) { rv = AppendProperty(ldapAttributeName.get(), attrValue.get(), aResult); NS_ENSURE_SUCCESS(rv,rv); aResult += MSG_LINEBREAK; } nsCString mailListURI; rv = aCard->GetMailListURI(getter_Copies(mailListURI)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr mailList; rv = GetDirectory(mailListURI, getter_AddRefs(mailList)); NS_ENSURE_SUCCESS(rv,rv); nsCOMPtr addresses; rv = mailList->GetAddressLists(getter_AddRefs(addresses)); if (addresses) { uint32_t total = 0; addresses->GetLength(&total); if (total) { uint32_t i; for (i = 0; i < total; i++) { nsCOMPtr listCard = do_QueryElementAt(addresses, i, &rv); NS_ENSURE_SUCCESS(rv,rv); rv = AppendDNForCard("member", listCard, aAttrMap, aResult); NS_ENSURE_SUCCESS(rv,rv); aResult += MSG_LINEBREAK; } } } aResult += MSG_LINEBREAK; return NS_OK; } nsresult nsAbManager::AppendDNForCard(const char *aProperty, nsIAbCard *aCard, nsIAbLDAPAttributeMap *aAttrMap, nsACString &aResult) { nsString email; nsString displayName; nsAutoCString ldapAttributeName; nsresult rv = aCard->GetPrimaryEmail(email); NS_ENSURE_SUCCESS(rv,rv); rv = aCard->GetDisplayName(displayName); NS_ENSURE_SUCCESS(rv,rv); nsString cnStr; rv = aAttrMap->GetFirstAttribute(NS_LITERAL_CSTRING(kDisplayNameProperty), ldapAttributeName); NS_ENSURE_SUCCESS(rv, rv); if (!displayName.IsEmpty()) { cnStr += NS_ConvertUTF8toUTF16(ldapAttributeName).get(); cnStr.AppendLiteral("="); cnStr.Append(displayName); if (!email.IsEmpty()) { cnStr.AppendLiteral(","); } } rv = aAttrMap->GetFirstAttribute(NS_LITERAL_CSTRING(kPriEmailProperty), ldapAttributeName); NS_ENSURE_SUCCESS(rv, rv); if (!email.IsEmpty()) { cnStr += NS_ConvertUTF8toUTF16(ldapAttributeName).get(); cnStr.AppendLiteral("="); cnStr.Append(email); } rv = AppendProperty(aProperty, cnStr.get(), aResult); NS_ENSURE_SUCCESS(rv,rv); return rv; } nsresult nsAbManager::AppendBasicLDIFForCard(nsIAbCard *aCard, nsIAbLDAPAttributeMap *aAttrMap, nsACString &aResult) { nsresult rv = AppendDNForCard("dn", aCard, aAttrMap, aResult); NS_ENSURE_SUCCESS(rv,rv); aResult += MSG_LINEBREAK \ "objectclass: top" MSG_LINEBREAK \ "objectclass: person" MSG_LINEBREAK \ "objectclass: organizationalPerson" MSG_LINEBREAK \ "objectclass: inetOrgPerson" MSG_LINEBREAK \ "objectclass: " MOZ_AB_OBJECTCLASS MSG_LINEBREAK; return rv; } bool nsAbManager::IsSafeLDIFString(const char16_t *aStr) { // follow RFC 2849 to determine if something is safe "as is" for LDIF if (aStr[0] == char16_t(' ') || aStr[0] == char16_t(':') || aStr[0] == char16_t('<')) return false; uint32_t i; uint32_t len = NS_strlen(aStr); for (i=0; iSetPropertyAsAUTF8String(cardPropName, nsDependentCString(cardPropValue)); } else { if (!PL_strcmp(cardPropValue, "TRUE")) aCard->SetPropertyAsUint32(cardPropName, nsIAbPreferMailFormat::html); else if (!PL_strcmp(cardPropValue, "FALSE")) aCard->SetPropertyAsUint32(cardPropName, nsIAbPreferMailFormat::plaintext); else aCard->SetPropertyAsUint32(cardPropName, nsIAbPreferMailFormat::unknown); } PR_FREEIF(cardPropValue); return; } static void convertFromVObject(VObject *vObj, nsIAbCard *aCard) { if (vObj) { VObjectIterator t; convertNameValue(vObj, aCard); initPropIterator(&t, vObj); while (moreIteration(&t)) { VObject * nextObject = nextVObject(&t); convertFromVObject(nextObject, aCard); } } return; } NS_IMETHODIMP nsAbManager::EscapedVCardToAbCard(const char *aEscapedVCardStr, nsIAbCard **aCard) { NS_ENSURE_ARG_POINTER(aEscapedVCardStr); NS_ENSURE_ARG_POINTER(aCard); nsCOMPtr cardFromVCard = do_CreateInstance(NS_ABCARDPROPERTY_CONTRACTID); if (!cardFromVCard) return NS_ERROR_FAILURE; // aEscapedVCardStr will be "" the first time, before you have a vCard if (*aEscapedVCardStr != '\0') { nsCString unescapedData; MsgUnescapeString(nsDependentCString(aEscapedVCardStr), 0, unescapedData); VObject *vObj = parse_MIME(unescapedData.get(), unescapedData.Length()); if (vObj) { convertFromVObject(vObj, cardFromVCard); cleanVObject(vObj); } else NS_WARNING("Parse of vCard failed"); } NS_IF_ADDREF(*aCard = cardFromVCard); return NS_OK; } NS_IMETHODIMP nsAbManager::Handle(nsICommandLine* aCmdLine) { nsresult rv; bool found; rv = aCmdLine->HandleFlag(NS_LITERAL_STRING("addressbook"), false, &found); NS_ENSURE_SUCCESS(rv, rv); if (!found) return NS_OK; nsCOMPtr wwatch (do_GetService(NS_WINDOWWATCHER_CONTRACTID)); NS_ENSURE_TRUE(wwatch, NS_ERROR_FAILURE); nsCOMPtr opened; wwatch->OpenWindow(nullptr, "chrome://messenger/content/addressbook/addressbook.xul", "_blank", "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar", nullptr, getter_AddRefs(opened)); aCmdLine->SetPreventDefault(true); return NS_OK; } NS_IMETHODIMP nsAbManager::GetHelpInfo(nsACString& aResult) { aResult.Assign(NS_LITERAL_CSTRING(" -addressbook Open the address book at startup.\n")); return NS_OK; } NS_IMETHODIMP nsAbManager::GenerateUUID(const nsACString &aDirectoryId, const nsACString &aLocalId, nsACString &uuid) { uuid.Assign(aDirectoryId); uuid.Append('#'); uuid.Append(aLocalId); return NS_OK; } NS_IMETHODIMP nsAbManager::ConvertQueryStringToExpression(const nsACString &aQueryString, nsIAbBooleanExpression **_retval) { NS_ENSURE_ARG_POINTER(_retval); return nsAbQueryStringToExpression::Convert(aQueryString, _retval); }