/* -*- Mode: C++; tab-width: 4; 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 "msgCore.h" #include "nsMsgSearchAdapter.h" #include "nsUnicharUtils.h" #include "nsMsgSearchScopeTerm.h" #include "nsMsgResultElement.h" #include "nsMsgSearchTerm.h" #include "nsIMsgHdr.h" #include "nsMsgSearchNews.h" #include "nsIDBFolderInfo.h" #include "prprf.h" #include "nsIMsgDatabase.h" #include "nsMemory.h" #include // Disable deprecation warnings generated by nsISupportsArray and associated // classes. #if defined(__GNUC__) #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #elif defined(_MSC_VER) #pragma warning (disable : 4996) #endif #include "nsISupportsArray.h" // Implementation of search for IMAP mail folders // Implementation of search for newsgroups //----------------------------------------------------------------------------- //----------- Adapter class for searching XPAT-capable news servers ----------- //----------------------------------------------------------------------------- const char *nsMsgSearchNews::m_kNntpFrom = "FROM "; const char *nsMsgSearchNews::m_kNntpSubject = "SUBJECT "; const char *nsMsgSearchNews::m_kTermSeparator = "/"; nsMsgSearchNews::nsMsgSearchNews (nsMsgSearchScopeTerm *scope, nsISupportsArray *termList) : nsMsgSearchAdapter (scope, termList) { } nsMsgSearchNews::~nsMsgSearchNews () { } nsresult nsMsgSearchNews::ValidateTerms () { nsresult err = nsMsgSearchAdapter::ValidateTerms (); if (NS_OK == err) { err = Encode (&m_encoding); } return err; } nsresult nsMsgSearchNews::Search (bool *aDone) { // the state machine runs in the news: handler nsresult err = NS_ERROR_NOT_IMPLEMENTED; return err; } char16_t *nsMsgSearchNews::EncodeToWildmat (const char16_t *value) { // Here we take advantage of XPAT's use of the wildmat format, which allows // a case-insensitive match by specifying each case possibility for each character // So, "FooBar" is encoded as "[Ff][Oo][Bb][Aa][Rr]" char16_t *caseInsensitiveValue = (char16_t*) moz_xmalloc(sizeof(char16_t) * ((4 * NS_strlen(value)) + 1)); if (caseInsensitiveValue) { char16_t *walkValue = caseInsensitiveValue; while (*value) { if (isalpha(*value)) { *walkValue++ = (char16_t)'['; *walkValue++ = ToUpperCase((char16_t)*value); *walkValue++ = ToLowerCase((char16_t)*value); *walkValue++ = (char16_t)']'; } else *walkValue++ = *value; value++; } *walkValue = 0; } return caseInsensitiveValue; } char *nsMsgSearchNews::EncodeTerm (nsIMsgSearchTerm *term) { // Develop an XPAT-style encoding for the search term NS_ASSERTION(term, "null term"); if (!term) return nullptr; // Find a string to represent the attribute const char *attribEncoding = nullptr; nsMsgSearchAttribValue attrib; term->GetAttrib(&attrib); switch (attrib) { case nsMsgSearchAttrib::Sender: attribEncoding = m_kNntpFrom; break; case nsMsgSearchAttrib::Subject: attribEncoding = m_kNntpSubject; break; default: nsCString header; term->GetArbitraryHeader(header); if (header.IsEmpty()) { NS_ASSERTION(false,"malformed search"); // malformed search term? return nullptr; } attribEncoding = header.get(); } // Build a string to represent the string pattern bool leadingStar = false; bool trailingStar = false; nsMsgSearchOpValue op; term->GetOp(&op); switch (op) { case nsMsgSearchOp::Contains: leadingStar = true; trailingStar = true; break; case nsMsgSearchOp::Is: break; case nsMsgSearchOp::BeginsWith: trailingStar = true; break; case nsMsgSearchOp::EndsWith: leadingStar = true; break; default: NS_ASSERTION(false,"malformed search"); // malformed search term? return nullptr; } // ### i18N problem Get the csid from FE, which is the correct csid for term // int16 wincsid = INTL_GetCharSetID(INTL_DefaultTextWidgetCsidSel); // Do INTL_FormatNNTPXPATInRFC1522Format trick for non-ASCII string // unsigned char *intlNonRFC1522Value = INTL_FormatNNTPXPATInNonRFC1522Format (wincsid, (unsigned char*)term->m_value.u.string); nsCOMPtr searchValue; nsresult rv = term->GetValue(getter_AddRefs(searchValue)); if (NS_FAILED(rv) || !searchValue) return nullptr; nsString intlNonRFC1522Value; rv = searchValue->GetStr(intlNonRFC1522Value); if (NS_FAILED(rv) || intlNonRFC1522Value.IsEmpty()) return nullptr; char16_t *caseInsensitiveValue = EncodeToWildmat (intlNonRFC1522Value.get()); if (!caseInsensitiveValue) return nullptr; // TO DO: Do INTL_FormatNNTPXPATInRFC1522Format trick for non-ASCII string // Unfortunately, we currently do not handle xxx or xxx search in XPAT // Need to add the INTL_FormatNNTPXPATInRFC1522Format call after we can do that // so we should search a string in either RFC1522 format and non-RFC1522 format char16_t *escapedValue = EscapeSearchUrl (caseInsensitiveValue); free(caseInsensitiveValue); if (!escapedValue) return nullptr; nsAutoCString pattern; if (leadingStar) pattern.Append('*'); pattern.Append(NS_ConvertUTF16toUTF8(escapedValue)); if (trailingStar) pattern.Append('*'); // Combine the XPAT command syntax with the attribute and the pattern to // form the term encoding const char xpatTemplate[] = "XPAT %s 1- %s"; int termLength = (sizeof(xpatTemplate) - 1) + strlen(attribEncoding) + pattern.Length() + 1; char *termEncoding = new char [termLength]; if (termEncoding) PR_snprintf (termEncoding, termLength, xpatTemplate, attribEncoding, pattern.get()); return termEncoding; } nsresult nsMsgSearchNews::GetEncoding(char **result) { NS_ENSURE_ARG(result); *result = ToNewCString(m_encoding); return (*result) ? NS_OK : NS_ERROR_OUT_OF_MEMORY; } nsresult nsMsgSearchNews::Encode (nsCString *outEncoding) { NS_ASSERTION(outEncoding, "no out encoding"); if (!outEncoding) return NS_ERROR_NULL_POINTER; nsresult err = NS_OK; uint32_t numTerms; m_searchTerms->Count(&numTerms); char **intermediateEncodings = new char * [numTerms]; if (intermediateEncodings) { // Build an XPAT command for each term int encodingLength = 0; uint32_t i; for (i = 0; i < numTerms; i++) { nsCOMPtr pTerm; m_searchTerms->QueryElementAt(i, NS_GET_IID(nsIMsgSearchTerm), (void **)getter_AddRefs(pTerm)); // set boolean OR term if any of the search terms are an OR...this only works if we are using // homogeneous boolean operators. bool isBooleanOpAnd; pTerm->GetBooleanAnd(&isBooleanOpAnd); m_ORSearch = !isBooleanOpAnd; intermediateEncodings[i] = EncodeTerm (pTerm); if (intermediateEncodings[i]) encodingLength += strlen(intermediateEncodings[i]) + strlen(m_kTermSeparator); } encodingLength += strlen("?search"); // Combine all the term encodings into one big encoding char *encoding = new char [encodingLength + 1]; if (encoding) { PL_strcpy (encoding, "?search"); m_searchTerms->Count(&numTerms); for (i = 0; i < numTerms; i++) { if (intermediateEncodings[i]) { PL_strcat (encoding, m_kTermSeparator); PL_strcat (encoding, intermediateEncodings[i]); delete [] intermediateEncodings[i]; } } *outEncoding = encoding; } else err = NS_ERROR_OUT_OF_MEMORY; } else err = NS_ERROR_OUT_OF_MEMORY; delete [] intermediateEncodings; return err; } NS_IMETHODIMP nsMsgSearchNews::AddHit(nsMsgKey key) { m_candidateHits.AppendElement(key); return NS_OK; } /* void CurrentUrlDone (in nsresult exitCode); */ NS_IMETHODIMP nsMsgSearchNews::CurrentUrlDone(nsresult exitCode) { CollateHits(); ReportHits(); return NS_OK; } #if 0 // need to switch this to a notify stop loading handler, I think. void nsMsgSearchNews::PreExitFunction (URL_Struct * /*url*/, int status, MWContext *context) { MSG_SearchFrame *frame = MSG_SearchFrame::FromContext (context); nsMsgSearchNews *adapter = (nsMsgSearchNews*) frame->GetRunningAdapter(); adapter->CollateHits(); adapter->ReportHits(); if (status == MK_INTERRUPTED) { adapter->Abort(); frame->EndCylonMode(); } else { frame->m_idxRunningScope++; if (frame->m_idxRunningScope >= frame->m_scopeList.Count()) frame->EndCylonMode(); } } #endif // 0 void nsMsgSearchNews::CollateHits() { // Since the XPAT commands are processed one at a time, the result set for the // entire query is the intersection of results for each XPAT command if an AND search, // otherwise we want the union of all the search hits (minus the duplicates of course). uint32_t size = m_candidateHits.Length(); if (!size) return; // Sort the article numbers first, so it's easy to tell how many hits // on a given article we got m_candidateHits.Sort(); // For an OR search we only need to count the first occurrence of a candidate. uint32_t termCount = 1; if (!m_ORSearch) { // We have a traditional AND search which must be collated. In order to // get promoted into the hits list, a candidate article number must appear // in the results of each XPAT command. So if we fire 3 XPAT commands (one // per search term), the article number must appear 3 times. If it appears // fewer than 3 times, it matched some search terms, but not all. m_searchTerms->Count(&termCount); } uint32_t candidateCount = 0; uint32_t candidate = m_candidateHits[0]; for (uint32_t index = 0; index < size; ++index) { uint32_t possibleCandidate = m_candidateHits[index]; if (candidate == possibleCandidate) { ++candidateCount; } else { candidateCount = 1; candidate = possibleCandidate; } if (candidateCount == termCount) m_hits.AppendElement(candidate); } } void nsMsgSearchNews::ReportHits () { nsCOMPtr db; nsCOMPtr folderInfo; nsCOMPtr scopeFolder; nsresult err = m_scope->GetFolder(getter_AddRefs(scopeFolder)); if (NS_SUCCEEDED(err) && scopeFolder) { err = scopeFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db)); } if (db) { uint32_t size = m_hits.Length(); for (uint32_t i = 0; i < size; ++i) { nsCOMPtr header; db->GetMsgHdrForKey(m_hits.ElementAt(i), getter_AddRefs(header)); if (header) ReportHit(header, scopeFolder); } } } // ### this should take an nsIMsgFolder instead of a string location. void nsMsgSearchNews::ReportHit (nsIMsgDBHdr *pHeaders, nsIMsgFolder *folder) { // this is totally filched from msg_SearchOfflineMail until I decide whether the // right thing is to get them from the db or from NNTP nsCOMPtr session; nsCOMPtr scopeFolder; m_scope->GetFolder(getter_AddRefs(scopeFolder)); m_scope->GetSearchSession(getter_AddRefs(session)); if (session) session->AddSearchHit (pHeaders, scopeFolder); } nsresult nsMsgSearchValidityManager::InitNewsTable() { NS_ASSERTION (nullptr == m_newsTable,"don't call this twice!"); nsresult rv = NewTable (getter_AddRefs(m_newsTable)); if (NS_SUCCEEDED(rv)) { m_newsTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1); m_newsTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1); m_newsTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1); m_newsTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1); m_newsTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1); m_newsTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1); m_newsTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1); m_newsTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1); m_newsTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1); m_newsTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1); m_newsTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1); m_newsTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1); m_newsTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1); m_newsTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1); m_newsTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1); m_newsTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1); #if 0 // Size should be handled after the fact... m_newsTable->SetAvailable (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1); m_newsTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1); m_newsTable->SetAvailable (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1); m_newsTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1); #endif m_newsTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1); m_newsTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1); m_newsTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1); m_newsTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1); m_newsTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1); m_newsTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1); m_newsTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1); m_newsTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1); } return rv; } nsresult nsMsgSearchValidityManager::InitNewsFilterTable() { NS_ASSERTION (nullptr == m_newsFilterTable, "news filter table already initted"); nsresult rv = NewTable (getter_AddRefs(m_newsFilterTable)); if (NS_SUCCEEDED(rv)) { m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1); m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1); m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1); m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1); m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1); m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1); m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1); m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1); m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1); m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1); m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1); m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1); m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1); m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1); m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1); m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1); m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1); m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1); m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1); m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1); m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1); m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1); m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1); m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1); m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1); m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1); m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1); m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1); m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1); m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1); m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1); m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1); m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1); m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1); m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1); m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1); m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1); m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1); m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1); m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1); m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1); m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1); m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1); m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1); m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1); m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1); m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1); m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1); m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1); m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1); m_newsFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1); m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1); } return rv; }