text: /*
  * Copyright (c) 2003-2021 Rony Shapiro <ronys@pwsafe.org>.
  * All rights reserved. Use of the code is allowed under the
  * Artistic License 2.0 terms, as specified in the LICENSE file
  * distributed with this code, or available from
  * http://www.opensource.org/licenses/artistic-license-2.0.php
  */
  #ifndef __PWSFILTERS_H
  #define __PWSFILTERS_H
  
  /**
   * \file PWSFilters.h
   * types used for defining and working with filters of pwsafe
   * entries. Note that the structures are st_*, and they currently
   * have a wrapper class PWSFilters.
   */
  
  #include "StringX.h"
  #include "StringXStream.h"
  #include "Match.h"
  #include "ItemData.h"
  #include "ItemAtt.h"
  #include "Proxy.h"
  
  #include <iostream>
  #include <string>
  #include <vector>
  #include <map>
  #include <time.h> // for time_t
  
  enum FilterType {DFTYPE_INVALID = 0,
                   DFTYPE_MAIN,
                   DFTYPE_PWHISTORY, 
                   DFTYPE_PWPOLICY,
                   DFTYPE_ATTACHMENT
  };
  
  // All the fields that we can use for filtering entries:
  
  enum FieldType {
    // PWS Test fields
    FT_GROUPTITLE    = CItemData::NAME, // reusing depreciated NAME for Group.Title combination
    FT_GROUP         = CItemData::GROUP,
    FT_TITLE         = CItemData::TITLE,
    FT_USER          = CItemData::USER,
    FT_NOTES         = CItemData::NOTES,
    FT_PASSWORD      = CItemData::PASSWORD,
    FT_CTIME         = CItemData::CTIME,
    FT_PMTIME        = CItemData::PMTIME,
    FT_ATIME         = CItemData::ATIME,
    FT_XTIME         = CItemData::XTIME,
    FT_RMTIME        = CItemData::RMTIME,
    FT_URL           = CItemData::URL,
    FT_AUTOTYPE      = CItemData::AUTOTYPE,
    FT_PWHIST        = CItemData::PWHIST,
    FT_POLICY        = CItemData::POLICY,
    FT_XTIME_INT     = CItemData::XTIME_INT,
    FT_RUNCMD        = CItemData::RUNCMD,
    FT_DCA           = CItemData::DCA,
    FT_SHIFTDCA      = CItemData::SHIFTDCA,
    FT_EMAIL         = CItemData::EMAIL,
    FT_PROTECTED     = CItemData::PROTECTED,
    FT_SYMBOLS       = CItemData::SYMBOLS,
    FT_POLICYNAME    = CItemData::POLICYNAME,
    FT_KBSHORTCUT    = CItemData::KBSHORTCUT,
    FT_END           = CItemData::END,
  
    // Internal fields purely for filters
    FT_ENTRYSIZE     = CItemData::ENTRYSIZE,     // 0x100
    FT_ENTRYTYPE     = CItemData::ENTRYTYPE,     // 0x101,
    FT_ENTRYSTATUS   = CItemData::ENTRYSTATUS,   // 0x102,
    FT_UNKNOWNFIELDS = CItemData::UNKNOWNFIELDS, // 0x103,
    FT_PASSWORDLEN   = CItemData::PASSWORDLEN,   // 0x104
  
    // Password History Test fields
    HT_PRESENT       = 0x200,
    HT_ACTIVE,
    HT_NUM,
    HT_MAX,
    HT_CHANGEDATE,
    HT_PASSWORDS,
    HT_END,
  
    // Password Policy Test fields
    PT_PRESENT       = 0x300,
    PT_LENGTH,
    PT_LOWERCASE,
    PT_UPPERCASE,
    PT_DIGITS,
    PT_SYMBOLS,
    PT_EASYVISION,
    PT_PRONOUNCEABLE,
    PT_HEXADECIMAL,
    PT_END,
  
    // Attachment Test fields
    AT_PRESENT       = CItemAtt::START_ATT,
    AT_TITLE         = CItemAtt::ATTTITLE,
    AT_CTIME         = CItemAtt::ATTCTIME,
    AT_MEDIATYPE     = CItemAtt::MEDIATYPE,
    AT_FILENAME      = CItemAtt::FILENAME,
    AT_FILEPATH      = CItemAtt::FILEPATH,
    AT_FILECTIME     = CItemAtt::FILECTIME,
    AT_FILEMTIME     = CItemAtt::FILEMTIME,
    AT_FILEATIME     = CItemAtt::FILEATIME,
    AT_END           = CItemAtt::LAST_SEARCHABLE,
    FT_ATTACHMENT    = CItemAtt::LAST_SEARCHABLE + 1, // not searchable
  
    FT_INVALID       = 0xffff
  };
  
  // Logical connection with previous filter - brackets not allowed
  enum LogicConnect {LC_INVALID = 0, LC_AND = 1, LC_OR = 2};
  
  // This defines a single row of a filter
  // Filters consist of one or more rows. The relation with the previous
  // row is defined by LogicConnect ltype.
  struct st_FilterRow {
    bool bFilterActive;
    bool bFilterComplete;
    // Item in ComboBox selected is found via FieldType
    FieldType ftype;
    // and matchtype
    PWSMatch::MatchType mtype;
    // User selected rule
    PWSMatch::MatchRule rule;
  
    // Following should really be a union or subclassed, since
    // only one is used, and which is defined uniquely by the ftype.
  
    // if filter type is a 'relative date', integer or size
    int fnum1, fnum2;
    // if filter type is an 'absolute date'
    time_t fdate1, fdate2;
    // fdatetype = 0 for absolute date (using fdate1 & 2) "the default" or
    // fdatetype = 1 for relative date (using fnum1 & 2)
    // if relative, values can be between -3650 & +3650 (i.e. 10 years either side of now)
    int fdatetype;
    // if filter type is a string
    StringX fstring;
    bool fcase; // case sensitive?
    // if filter type is Double-Click Action or Shift-Double-Click Action
    short fdca;
    // if filter type is a entrytype
    CItemData::EntryType etype;
    // if filter type is a entrystatus
    CItemData::EntryStatus estatus;
    // if filter type is a entrysize
    int funit;  // 0 = Bytes, 1 = KBytes, 2 = MBytes
    
    // Logical connection with previous filter (if any). Brackets unsupported
    LogicConnect ltype;
  
    st_FilterRow()
      : bFilterActive(true), bFilterComplete(false),
      ftype(FT_INVALID), mtype(PWSMatch::MT_INVALID), rule(PWSMatch::MR_INVALID),
      fnum1(0), fnum2(0),
      fdate1(time_t(0)), fdate2(time_t(0)), fdatetype(0),
      fstring(_T("")), fcase(false), fdca(-2),
      etype(CItemData::ET_INVALID),
      estatus(CItemData::ES_INVALID), funit(-1),
      ltype(LC_INVALID)
    {}
  
    st_FilterRow(const st_FilterRow &that)
      : bFilterActive(that.bFilterActive), bFilterComplete(that.bFilterComplete),
      ftype(that.ftype), mtype(that.mtype), rule(that.rule),
      fnum1(that.fnum1), fnum2(that.fnum2),
      fdate1(that.fdate1), fdate2(that.fdate2), fdatetype(that.fdatetype),
      fstring(that.fstring), fcase(that.fcase), fdca(that.fdca),
      etype(that.etype), estatus(that.estatus), funit(that.funit),
      ltype(that.ltype)
    {}
  
    st_FilterRow &operator=(const st_FilterRow &that)
    {
      if (this != &that) {
        bFilterActive = that.bFilterActive;
        bFilterComplete = that.bFilterComplete;
        ftype = that.ftype;
        mtype = that.mtype;
        rule = that.rule;
        fnum1 = that.fnum1;
        fnum2 = that.fnum2;
        fdate1 = that.fdate1;
        fdate2 = that.fdate2;
        fdatetype = that.fdatetype;
        fstring = that.fstring;
        fcase = that.fcase;
        fdca = that.fdca;
        etype = that.etype;
        estatus = that.estatus;
        funit = that.funit;
        ltype = that.ltype;
      }
      return *this;
    }
  
    bool operator==(const st_FilterRow &that) const
    {
      if (this != &that) {
        if (bFilterActive != that.bFilterActive ||
            bFilterComplete != that.bFilterComplete ||
            ftype != that.ftype ||
            mtype != that.mtype ||
            rule != that.rule ||
            fnum1 != that.fnum1 ||
            fnum2 != that.fnum2 ||
            fdate1 != that.fdate1 ||
            fdate2 != that.fdate2 ||
            fdatetype != that.fdatetype ||
            fstring != that.fstring ||
            fcase != that.fcase ||
            fdca != that.fdca ||
            etype != that.etype ||
            estatus != that.estatus ||
            funit != that.funit ||
            ltype != that.ltype)
        return false;
      }
      return true;
    }
  
    bool operator!=(const st_FilterRow &that) const
    {return !(*this == that);}
  
    void Empty()
    {
      bFilterActive = true;
      bFilterComplete = false;
      ftype = FT_INVALID;
      mtype = PWSMatch::MT_INVALID;
      rule = PWSMatch::MR_INVALID;
      fnum1 = fnum2 = 0;
      fdate1 = fdate2 = time_t(0);
      fdatetype = 0;
      fstring = _T("");
      fcase = false;
      fdca = -2;
      etype = CItemData::ET_INVALID;
      estatus = CItemData::ES_INVALID;
      funit = -1;
      ltype = LC_INVALID;
    }
    void SetFilterComplete() {bFilterComplete = true;}
    void ClearFilterComplete() {bFilterComplete = false;}
  };
  
  // The following structure is needed for entry filtering
  // Basically a named vector of filter rows, along with
  // some accounting information
  
  typedef std::vector<st_FilterRow> vFilterRows;
  typedef std::vector<int> vfiltergroup;
  typedef std::vector<vfiltergroup> vfiltergroups;
  
  struct st_filters {
    // Filter name
    stringT fname;
    // Counters
    int num_Mactive, num_Hactive, num_Pactive, num_Aactive;
    // Main filters
    vFilterRows vMfldata;
    // PW history filters
    vFilterRows vHfldata;
    // PW Policy filters
    vFilterRows vPfldata;
    // Attachment filters
    vFilterRows vAfldata;
  
    st_filters()
      : fname(_T("")), num_Mactive(0), num_Hactive(0), num_Pactive(0), num_Aactive(0)
    {}
  
    st_filters(const st_filters &that)
      : fname(that.fname),
      num_Mactive(that.num_Mactive), 
      num_Hactive(that.num_Hactive), num_Pactive(that.num_Pactive), num_Aactive(that.num_Aactive),
      vMfldata(that.vMfldata), vHfldata(that.vHfldata),
      vPfldata(that.vPfldata), vAfldata(that.vAfldata)
    {}
  
    st_filters &operator=(const st_filters &that)
    {
      if (this != &that) {
        fname = that.fname;
        num_Mactive = that.num_Mactive;
        num_Hactive = that.num_Hactive;
        num_Pactive = that.num_Pactive;
        num_Aactive = that.num_Aactive;
        vMfldata = that.vMfldata;
        vHfldata = that.vHfldata;
        vPfldata = that.vPfldata;
        vAfldata = that.vAfldata;
      }
      return *this;
    }
  
    bool operator==(const st_filters &that) const
    {
      if (this != &that) {
        if (fname != that.fname ||
            num_Mactive != that.num_Mactive ||
            num_Hactive != that.num_Hactive ||
            num_Pactive != that.num_Pactive ||
            num_Aactive != that.num_Aactive ||
            vMfldata != that.vMfldata ||
            vHfldata != that.vHfldata ||
            vPfldata != that.vPfldata ||
            vAfldata != that.vAfldata)
        return false;
      }
      return true;
    }
  
    bool operator!=(const st_filters &that) const
    {return !(*this == that);}
  
    bool IsActive() const {return (num_Mactive + num_Hactive +
                                   num_Pactive + num_Aactive) != 0;}
  
    void Empty()
    {
      fname = _T("");
      num_Mactive = num_Hactive = num_Pactive = num_Aactive = 0;
      vMfldata.clear();
      vHfldata.clear();
      vPfldata.clear();
      vAfldata.clear();
    }
  };
  
  enum FilterPool {FPOOL_DATABASE = 1, FPOOL_AUTOLOAD, FPOOL_IMPORTED, FPOOL_SESSION,
                   FPOOL_LAST};
  
  struct st_Filterkey {
    FilterPool fpool;
    stringT cs_filtername;
  
    bool operator==(const st_Filterkey& that) const
    {
      if (this != &that) {
        if (fpool != that.fpool &&
            cs_filtername != that.cs_filtername)
          return false;
      }
      return true;
    }
  
    bool operator!=(const st_Filterkey& that) const
    {
      return !(*this == that);
    }
  };
  
  // Following is for map<> compare function
  struct ltfk {
    bool operator()(const st_Filterkey &fk1, const st_Filterkey &fk2) const
    {
      if (fk1.fpool != fk2.fpool)
        return static_cast<int>(fk1.fpool) < static_cast<int>(fk2.fpool);
  
      return fk1.cs_filtername.compare(fk2.cs_filtername) < 0;
    }
  };
  
  struct PWSfileHeader;
  class PWScore;
  
  class PWSFilters : public std::map<st_Filterkey, st_filters, ltfk> {
   public:
    typedef std::pair<st_Filterkey, st_filters> Pair;
    
    int WriteFilterXMLFile(const StringX &filename, const PWSfileHeader &hdr,
                           const StringX &currentfile);
    int WriteFilterXMLFile(coStringXStream &os, const PWSfileHeader &hdr,
                           const StringX &currentfile, const bool bWithFormatting = false);
    int ImportFilterXMLFile(const FilterPool fpool,
                            const StringX &strXMLData,
                            const stringT &strXMLFileName,
                            const stringT &strXSDFileName, stringT &strErrors,
                            Asker *pAsker, Reporter *pReporter = nullptr);
  
    static stringT GetFilterDescription(const st_FilterRow &st_fldata);
   private:
    std::string GetFilterXMLHeader(const StringX &currentfile,
                                   const PWSfileHeader &hdr);
  };
  
  class PWSFilterManager {
   public:
    PWSFilterManager();
    void CreateGroups();
    bool PassesFiltering(const CItemData &ci, const PWScore &core);
    bool PassesEmptyGroupFiltering(const StringX &sxGroup);
    void SetFindFilter(const bool &bFilter) { m_bFindFilterActive = bFilter; }
    void SetFilterFindEntries(UUIDVector *pvFoundUUIDs);
  
    // predefined filters accessors, use by assigning to m_currentfilter
    const st_filters &GetExpireFilter() const {return m_expirefilter;}
    const st_filters &GetUnsavedFilter() const {return m_unsavedfilter;}
    const st_filters &GetFoundFilter() const { return m_lastfoundfilter; }
  
    st_filters m_currentfilter;
    size_t GetFindFilterSize() { return m_vFltrFoundUUIDs.size(); }
    
   private:
     bool PassesPWHFiltering(const CItemData *pci) const;
     bool PassesPWPFiltering(const CItemData *pci) const;
     bool PassesAttFiltering(const CItemData *pci, const PWScore &core) const;
  
     vfiltergroups m_vMflgroups, m_vHflgroups, m_vPflgroups, m_vAflgroups;
  
     // predefined filters, set up at c'tor
     st_filters m_expirefilter, m_unsavedfilter, m_lastfoundfilter;
  
     // Filter on Find results
     bool m_bFindFilterActive;
     // Vector of found entries' UUID for advance search to display only those
     // entries satisfying a search
     UUIDVector m_vFltrFoundUUIDs;
  };
  
  #endif  /* __PWSFILTERS_H */
  