nsresult nsMsgSearchAdapter::EncodeImapTerm()

in mailnews/search/src/nsMsgSearchAdapter.cpp [214:539]


nsresult nsMsgSearchAdapter::EncodeImapTerm(nsIMsgSearchTerm* term,
                                            bool reallyDredd,
                                            const char16_t* srcCharset,
                                            const char16_t* destCharset,
                                            char** ppOutTerm) {
  NS_ENSURE_ARG_POINTER(term);
  NS_ENSURE_ARG_POINTER(ppOutTerm);

  nsresult err = NS_OK;
  bool useNot = false;
  bool useQuotes = false;
  bool ignoreValue = false;
  nsAutoCString arbitraryHeader;
  const char* whichMnemonic = nullptr;
  const char* orHeaderMnemonic = nullptr;

  *ppOutTerm = nullptr;

  nsCOMPtr<nsIMsgSearchValue> searchValue;
  nsresult rv = term->GetValue(getter_AddRefs(searchValue));

  NS_ENSURE_SUCCESS(rv, rv);

  nsMsgSearchOpValue op;
  term->GetOp(&op);

  if (op == nsMsgSearchOp::DoesntContain || op == nsMsgSearchOp::Isnt)
    useNot = true;

  nsMsgSearchAttribValue attrib;
  term->GetAttrib(&attrib);

  switch (attrib) {
    case nsMsgSearchAttrib::ToOrCC:
      orHeaderMnemonic = m_kImapCC;
      // fall through to case nsMsgSearchAttrib::To:
      [[fallthrough]];
    case nsMsgSearchAttrib::To:
      whichMnemonic = m_kImapTo;
      break;
    case nsMsgSearchAttrib::CC:
      whichMnemonic = m_kImapCC;
      break;
    case nsMsgSearchAttrib::Sender:
      whichMnemonic = m_kImapFrom;
      break;
    case nsMsgSearchAttrib::Subject:
      whichMnemonic = m_kImapSubject;
      break;
    case nsMsgSearchAttrib::Body:
      whichMnemonic = m_kImapBody;
      break;
    case nsMsgSearchAttrib::AgeInDays:  // added for searching online for age in
                                        // days...
      // for AgeInDays, we are actually going to perform a search by date, so
      // convert the operations for age to the IMAP mnemonics that we would use
      // for date!
      {
        // If we have a future date, the > and < are reversed.
        // e.g. ageInDays > 2 means more than 2 days old ("date before X")
        // whereas
        //      ageInDays > -2 should be more than 2 days in the future ("date
        //      after X")
        int32_t ageInDays;
        searchValue->GetAge(&ageInDays);
        bool dateInFuture = (ageInDays < 0);
        switch (op) {
          case nsMsgSearchOp::IsGreaterThan:
            whichMnemonic = (!dateInFuture) ? m_kImapBefore : m_kImapSince;
            break;
          case nsMsgSearchOp::IsLessThan:
            whichMnemonic = (!dateInFuture) ? m_kImapSince : m_kImapBefore;
            break;
          case nsMsgSearchOp::Is:
            whichMnemonic = m_kImapSentOn;
            break;
          default:
            NS_ASSERTION(false, "invalid search operator");
            return NS_ERROR_INVALID_ARG;
        }
      }
      break;
    case nsMsgSearchAttrib::Size:
      switch (op) {
        case nsMsgSearchOp::IsGreaterThan:
          whichMnemonic = m_kImapSizeLarger;
          break;
        case nsMsgSearchOp::IsLessThan:
          whichMnemonic = m_kImapSizeSmaller;
          break;
        default:
          NS_ASSERTION(false, "invalid search operator");
          return NS_ERROR_INVALID_ARG;
      }
      break;
    case nsMsgSearchAttrib::Date:
      switch (op) {
        case nsMsgSearchOp::IsBefore:
          whichMnemonic = m_kImapBefore;
          break;
        case nsMsgSearchOp::IsAfter:
          whichMnemonic = m_kImapSince;
          break;
        case nsMsgSearchOp::Isnt: /* we've already added the "Not" so just
                                     process it like it was a date is search */
        case nsMsgSearchOp::Is:
          whichMnemonic = m_kImapSentOn;
          break;
        default:
          NS_ASSERTION(false, "invalid search operator");
          return NS_ERROR_INVALID_ARG;
      }
      break;
    case nsMsgSearchAttrib::AnyText:
      whichMnemonic = m_kImapAnyText;
      break;
    case nsMsgSearchAttrib::Keywords:
      whichMnemonic = m_kImapKeyword;
      break;
    case nsMsgSearchAttrib::MsgStatus:
      useNot = false;      // bizarrely, NOT SEEN is wrong, but UNSEEN is right.
      ignoreValue = true;  // the mnemonic is all we need
      uint32_t status;
      searchValue->GetStatus(&status);

      switch (status) {
        case nsMsgMessageFlags::Read:
          whichMnemonic =
              op == nsMsgSearchOp::Is ? m_kImapSeen : m_kImapNotSeen;
          break;
        case nsMsgMessageFlags::Replied:
          whichMnemonic =
              op == nsMsgSearchOp::Is ? m_kImapAnswered : m_kImapNotAnswered;
          break;
        case nsMsgMessageFlags::New:
          whichMnemonic = op == nsMsgSearchOp::Is ? m_kImapNew : m_kImapNotNew;
          break;
        case nsMsgMessageFlags::Marked:
          whichMnemonic =
              op == nsMsgSearchOp::Is ? m_kImapFlagged : m_kImapNotFlagged;
          break;
        default:
          NS_ASSERTION(false, "invalid search operator");
          return NS_ERROR_INVALID_ARG;
      }
      break;
    default:
      if (attrib > nsMsgSearchAttrib::OtherHeader &&
          attrib < nsMsgSearchAttrib::kNumMsgSearchAttributes) {
        nsCString arbitraryHeaderTerm;
        term->GetArbitraryHeader(arbitraryHeaderTerm);
        if (!arbitraryHeaderTerm.IsEmpty()) {
          arbitraryHeader.AssignLiteral(" \"");
          arbitraryHeader.Append(arbitraryHeaderTerm);
          arbitraryHeader.AppendLiteral("\" ");
          whichMnemonic = arbitraryHeader.get();
        } else
          return NS_ERROR_FAILURE;
      } else {
        NS_ASSERTION(false, "invalid search operator");
        return NS_ERROR_INVALID_ARG;
      }
  }

  char* value = nullptr;
  char dateBuf[100];
  dateBuf[0] = '\0';

  bool valueWasAllocated = false;
  if (attrib == nsMsgSearchAttrib::Date) {
    // note that there used to be code here that encoded an RFC822 date for imap
    // searches. The IMAP RFC 2060 is misleading to the point that it looks like
    // it requires an RFC822 date but really it expects dd-mmm-yyyy, like dredd,
    // and refers to the RFC822 date only in that the dd-mmm-yyyy date will
    // match the RFC822 date within the message.

    PRTime adjustedDate;
    searchValue->GetDate(&adjustedDate);
    if (whichMnemonic == m_kImapSince) {
      // it looks like the IMAP server searches on Since includes the date in
      // question... our UI presents Is, IsGreater and IsLessThan. For the
      // IsGreater case (m_kImapSince) we need to adjust the date so we get
      // greater than and not greater than or equal to which is what the IMAP
      // server wants to search on won't work on Mac.
      adjustedDate += PR_USEC_PER_DAY;
    }

    PRExplodedTime exploded;
    PR_ExplodeTime(adjustedDate, PR_LocalTimeParameters, &exploded);
    PR_FormatTimeUSEnglish(dateBuf, sizeof(dateBuf), "%d-%b-%Y", &exploded);
    //    strftime (dateBuf, sizeof(dateBuf), "%d-%b-%Y", localtime (/*
    //    &term->m_value.u.date */ &adjustedDate));
    value = dateBuf;
  } else {
    if (attrib == nsMsgSearchAttrib::AgeInDays) {
      // okay, take the current date, subtract off the age in days, then do an
      // appropriate Date search on the resulting day.
      int32_t ageInDays;

      searchValue->GetAge(&ageInDays);

      PRTime now = PR_Now();
      PRTime matchDay = now - ageInDays * PR_USEC_PER_DAY;

      PRExplodedTime exploded;
      PR_ExplodeTime(matchDay, PR_LocalTimeParameters, &exploded);
      PR_FormatTimeUSEnglish(dateBuf, sizeof(dateBuf), "%d-%b-%Y", &exploded);
      //      strftime (dateBuf, sizeof(dateBuf), "%d-%b-%Y", localtime
      //      (&matchDay));
      value = dateBuf;
    } else if (attrib == nsMsgSearchAttrib::Size) {
      uint32_t sizeValue;
      nsAutoCString searchTermValue;
      searchValue->GetSize(&sizeValue);

      // Multiply by 1024 to get into kb resolution
      sizeValue *= 1024;

      // Ensure that greater than is really greater than
      // in kb resolution.
      if (op == nsMsgSearchOp::IsGreaterThan) sizeValue += 1024;

      searchTermValue.AppendInt(sizeValue);

      value = ToNewCString(searchTermValue);
      valueWasAllocated = true;
    } else

        if (IS_STRING_ATTRIBUTE(attrib)) {
      char16_t*
          convertedValue;  // = reallyDredd ? MSG_EscapeSearchUrl
                           // (term->m_value.u.string) :
                           // msg_EscapeImapSearchProtocol(term->m_value.u.string);
      nsString searchTermValue;
      searchValue->GetStr(searchTermValue);
      // Ugly switch for Korean mail/news charsets.
      // We want to do this here because here is where
      // we know what charset we want to use.
#ifdef DOING_CHARSET
      if (reallyDredd)
        dest_csid = INTL_DefaultNewsCharSetID(dest_csid);
      else
        dest_csid = INTL_DefaultMailCharSetID(dest_csid);
#endif

      // do all sorts of crazy escaping
      convertedValue = reallyDredd
                           ? EscapeSearchUrl(searchTermValue.get())
                           : EscapeImapSearchProtocol(searchTermValue.get());
      useQuotes =
          ((!reallyDredd ||
            (nsDependentString(convertedValue).FindChar(char16_t(' ')) !=
             -1)) &&
           (attrib != nsMsgSearchAttrib::Keywords));
      // now convert to char* and escape quoted_specials
      nsAutoCString valueStr;
      nsresult rv = nsMsgI18NConvertFromUnicode(
          NS_LossyConvertUTF16toASCII(destCharset),
          nsDependentString(convertedValue), valueStr);
      if (NS_SUCCEEDED(rv)) {
        const char* vptr = valueStr.get();
        // max escaped length is one extra character for every character in the
        // cmd.
        mozilla::UniquePtr<char[]> newValue =
            mozilla::MakeUnique<char[]>(2 * strlen(vptr) + 1);
        if (newValue) {
          char* p = newValue.get();
          while (1) {
            char ch = *vptr++;
            if (!ch) break;
            if ((useQuotes ? ch == '"' : 0) || ch == '\\') *p++ = '\\';
            *p++ = ch;
          }
          *p = '\0';
          value = strdup(newValue.get());  // realloc down to smaller size
        }
      } else
        value = strdup("");
      free(convertedValue);
      valueWasAllocated = true;
    }
  }

  // this should be rewritten to use nsCString
  int subLen = (value ? strlen(value) : 0) + (useNot ? strlen(m_kImapNot) : 0) +
               strlen(m_kImapHeader);
  int len =
      strlen(whichMnemonic) + subLen + (useQuotes ? 2 : 0) +
      (orHeaderMnemonic
           ? (subLen + strlen(m_kImapOr) + strlen(orHeaderMnemonic) + 2 /*""*/)
           : 0) +
      10;  // add slough for imap string literals
  char* encoding = new char[len];
  if (encoding) {
    encoding[0] = '\0';
    // Remember: if ToOrCC and useNot then the expression becomes NOT To AND Not
    // CC as opposed to (NOT TO) || (NOT CC)
    if (orHeaderMnemonic && !useNot) PL_strcat(encoding, m_kImapOr);
    if (useNot) PL_strcat(encoding, m_kImapNot);
    if (!arbitraryHeader.IsEmpty()) PL_strcat(encoding, m_kImapHeader);
    PL_strcat(encoding, whichMnemonic);
    if (!ignoreValue)
      err = EncodeImapValue(encoding, value, useQuotes, reallyDredd);

    if (orHeaderMnemonic) {
      if (useNot) PL_strcat(encoding, m_kImapNot);

      PL_strcat(encoding, m_kImapHeader);

      PL_strcat(encoding, orHeaderMnemonic);
      if (!ignoreValue)
        err = EncodeImapValue(encoding, value, useQuotes, reallyDredd);
    }

    // kmcentee, don't let the encoding end with whitespace,
    // this throws off later url STRCMP
    if (*encoding && *(encoding + strlen(encoding) - 1) == ' ')
      *(encoding + strlen(encoding) - 1) = '\0';
  }

  if (value && valueWasAllocated) free(value);

  *ppOutTerm = encoding;

  return err;
}