1 /*************************************************************************** 2 * Copyright (C) 2005-2020 by the Quassel Project * 3 * devel@quassel-irc.org * 4 * * 5 * This program is free software; you can redistribute it and/or modify * 6 * it under the terms of the GNU General Public License as published by * 7 * the Free Software Foundation; either version 2 of the License, or * 8 * (at your option) version 3. * 9 * * 10 * This program is distributed in the hope that it will be useful, * 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 * GNU General Public License for more details. * 14 * * 15 * You should have received a copy of the GNU General Public License * 16 * along with this program; if not, write to the * 17 * Free Software Foundation, Inc., * 18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * 19 ***************************************************************************/ 20 21 #pragma once 22 23 #include "common-export.h" 24 25 #include <utility> 26 27 #include <QRegExp> 28 #include <QString> 29 #include <QStringList> 30 31 #include "expressionmatch.h" 32 #include "message.h" 33 #include "syncableobject.h" 34 35 class COMMON_EXPORT IgnoreListManager : public SyncableObject 36 { 37 Q_OBJECT 38 SYNCABLE_OBJECT 39 40 public: 41 inline IgnoreListManager(QObject* parent = nullptr) SyncableObject(parent)42 : SyncableObject(parent) 43 { 44 setAllowClientUpdates(true); 45 } 46 47 enum IgnoreType 48 { 49 SenderIgnore, 50 MessageIgnore, 51 CtcpIgnore 52 }; 53 54 enum StrictnessType 55 { 56 UnmatchedStrictness = 0, 57 SoftStrictness = 1, 58 HardStrictness = 2 59 }; 60 61 enum ScopeType 62 { 63 GlobalScope, 64 NetworkScope, 65 ChannelScope, 66 }; 67 68 /** 69 * Individual ignore list rule 70 */ 71 class COMMON_EXPORT IgnoreListItem 72 { 73 public: 74 /** 75 * Construct an empty ignore rule 76 */ 77 IgnoreListItem() = default; 78 79 /** 80 * Construct an ignore rule with the given parameters 81 * 82 * CAUTION: For legacy reasons, "contents" doubles as the identifier for the ignore rule. 83 * Duplicate entries are not allowed. 84 * 85 * @param type Type of ignore rule 86 * @param contents String representing a message contents expression to match 87 * @param isRegEx True if regular expression, otherwise false 88 * @param strictness Strictness of ignore rule 89 * @param scope What to match scope rule against 90 * @param scopeRule String representing a scope rule expression to match 91 * @param isEnabled True if enabled, otherwise false 92 */ IgnoreListItem(IgnoreType type,QString contents,bool isRegEx,StrictnessType strictness,ScopeType scope,QString scopeRule,bool isEnabled)93 IgnoreListItem( 94 IgnoreType type, QString contents, bool isRegEx, StrictnessType strictness, ScopeType scope, QString scopeRule, bool isEnabled) 95 : _contents(std::move(contents)) 96 , _isRegEx(isRegEx) 97 , _strictness(strictness) 98 , _scope(scope) 99 , _scopeRule(std::move(scopeRule)) 100 , _isEnabled(isEnabled) 101 { 102 // Allow passing empty "contents" as they can happen when editing an ignore rule 103 104 // Handle CTCP ignores 105 setType(type); 106 107 _cacheInvalid = true; 108 // Cache expression matches on construction 109 // 110 // This provides immediate feedback on errors when loading the rule. If profiling shows 111 // this as a performance bottleneck, this can be removed in deference to caching on 112 // first use. 113 // 114 // Inversely, if needed for validity checks, caching can be done on every update below 115 // instead of on first use. 116 determineExpressions(); 117 } 118 119 /** 120 * Gets the type of this ignore rule 121 * 122 * @return IgnoreType of the rule 123 */ type()124 inline IgnoreType type() const { return _type; } 125 /** 126 * Sets the type of this ignore rule 127 * 128 * @param type IgnoreType of the rule 129 */ setType(IgnoreType type)130 inline void setType(IgnoreType type) 131 { 132 // Handle CTCP ignores 133 if (type == CtcpIgnore) { 134 // This is not performance-intensive; sticking with QRegExp for Qt 4 is fine 135 // Split based on whitespace characters 136 QStringList split(contents().split(QRegExp("\\s+"), QString::SkipEmptyParts)); 137 // Match on the first item, handling empty rules/matches 138 if (!split.isEmpty()) { 139 // Take the first item as the sender 140 _cacheCtcpSender = split.takeFirst(); 141 // Track the rest as CTCP types to ignore 142 _cacheCtcpTypes = split; 143 } 144 else { 145 // No match found - this can happen if a pure whitespace CTCP ignore rule is 146 // created. Fall back to matching all senders. 147 if (_isRegEx) { 148 // RegEx match everything 149 _cacheCtcpSender = ".*"; 150 } 151 else { 152 // Wildcard match everything 153 _cacheCtcpSender = "*"; 154 } 155 // Clear the types (split is already empty) 156 _cacheCtcpTypes = split; 157 } 158 } 159 _type = type; 160 } 161 162 /** 163 * Gets the message contents this rule matches 164 * 165 * NOTE: Use IgnoreListItem::contentsMatcher() for performing matches 166 * 167 * CAUTION: For legacy reasons, "contents" doubles as the identifier for the ignore rule. 168 * Duplicate entries are not allowed. 169 * 170 * @return String representing a phrase or expression to match 171 */ contents()172 inline QString contents() const { return _contents; } 173 /** 174 * Sets the message contents this rule matches 175 * 176 * @param contents String representing a phrase or expression to match 177 */ setContents(const QString & contents)178 inline void setContents(const QString& contents) 179 { 180 // Allow passing empty "contents" as they can happen when editing an ignore rule 181 _contents = contents; 182 _cacheInvalid = true; 183 } 184 185 /** 186 * Gets if this is a regular expression rule 187 * 188 * @return True if regular expression, otherwise false 189 */ isRegEx()190 inline bool isRegEx() const { return _isRegEx; } 191 /** 192 * Sets if this rule is a regular expression rule 193 * 194 * @param isRegEx True if regular expression, otherwise false 195 */ setIsRegEx(bool isRegEx)196 inline void setIsRegEx(bool isRegEx) 197 { 198 _isRegEx = isRegEx; 199 _cacheInvalid = true; 200 } 201 202 /** 203 * Gets the strictness of this ignore rule 204 * 205 * @return StrictnessType of the rule 206 */ strictness()207 inline StrictnessType strictness() const { return _strictness; } 208 /** 209 * Sets the strictness of this ignore rule 210 * 211 * @param strictness StrictnessType of the rule 212 */ setStrictness(StrictnessType strictness)213 inline void setStrictness(StrictnessType strictness) { _strictness = strictness; } 214 215 /** 216 * Gets what to match scope rule against 217 * 218 * @return ScopeType of the rule 219 */ scope()220 inline ScopeType scope() const { return _scope; } 221 /** 222 * Sets what to match scope rule against 223 * 224 * @param type ScopeType of the rule 225 */ setScope(ScopeType scope)226 inline void setScope(ScopeType scope) { _scope = scope; } 227 228 /** 229 * Gets the scope rule this rule matches 230 * 231 * NOTE: Use IgnoreListItem::scopeRuleMatcher() for performing matches 232 * 233 * @return String representing a phrase or expression to match 234 */ scopeRule()235 inline QString scopeRule() const { return _scopeRule; } 236 /** 237 * Sets the scope rule this rule matches 238 * 239 * @param scopeRule String representing a phrase or expression to match 240 */ setScopeRule(const QString & scopeRule)241 inline void setScopeRule(const QString& scopeRule) 242 { 243 _scopeRule = scopeRule; 244 _cacheInvalid = true; 245 } 246 247 /** 248 * Gets if this rule is enabled and active 249 * 250 * @return True if enabled, otherwise false 251 */ isEnabled()252 inline bool isEnabled() const { return _isEnabled; } 253 /** 254 * Sets if this rule is enabled and active 255 * 256 * @param isEnabled True if enabled, otherwise false 257 */ setIsEnabled(bool isEnabled)258 inline void setIsEnabled(bool isEnabled) { _isEnabled = isEnabled; } 259 260 /** 261 * Gets the ignored CTCP types for CTCP ignores 262 * 263 * @return List of CTCP types to ignore, or empty for all 264 */ ctcpTypes()265 inline QStringList ctcpTypes() const { return _cacheCtcpTypes; } 266 267 /** 268 * Gets the expression matcher for the message contents, caching if needed 269 * 270 * @return Expression matcher to compare with message contents 271 */ contentsMatcher()272 inline ExpressionMatch contentsMatcher() const 273 { 274 if (_cacheInvalid) { 275 determineExpressions(); 276 } 277 return _contentsMatch; 278 } 279 280 /** 281 * Gets the expression matcher for the scope, caching if needed 282 * 283 * @return Expression matcher to compare with scope 284 */ scopeRuleMatcher()285 inline ExpressionMatch scopeRuleMatcher() const 286 { 287 if (_cacheInvalid) { 288 determineExpressions(); 289 } 290 return _scopeRuleMatch; 291 } 292 293 /** 294 * Gets the expression matcher for the message contents, caching if needed 295 * 296 * @return Expression matcher to compare with message contents 297 */ senderCTCPMatcher()298 inline ExpressionMatch senderCTCPMatcher() const 299 { 300 if (_cacheInvalid) { 301 determineExpressions(); 302 } 303 return _ctcpSenderMatch; 304 } 305 306 bool operator!=(const IgnoreListItem& other) const; 307 308 private: 309 /** 310 * Update internal cache of expression matching if needed 311 */ 312 void determineExpressions() const; 313 314 IgnoreType _type = {}; 315 QString _contents = {}; 316 bool _isRegEx = false; 317 StrictnessType _strictness = {}; 318 ScopeType _scope = {}; 319 QString _scopeRule = {}; 320 bool _isEnabled = true; 321 322 QString _cacheCtcpSender = {}; ///< For CTCP rules, precalculate sender 323 QStringList _cacheCtcpTypes = {}; ///< For CTCP rules, precalculate types 324 325 // These represent internal cache and should be safe to mutate in 'const' functions 326 // See https://stackoverflow.com/questions/3141087/what-is-meant-with-const-at-end-of-function-declaration 327 mutable bool _cacheInvalid = true; ///< If true, match cache needs redone 328 mutable ExpressionMatch _contentsMatch = {}; ///< Expression match cache for message 329 mutable ExpressionMatch _scopeRuleMatch = {}; ///< Expression match cache for scope rule 330 mutable ExpressionMatch _ctcpSenderMatch = {}; ///< Expression match cache for CTCP nick 331 }; 332 333 using IgnoreList = QList<IgnoreListItem>; 334 335 int indexOf(const QString& ignore) const; contains(const QString & ignore)336 inline bool contains(const QString& ignore) const { return indexOf(ignore) != -1; } isEmpty()337 inline bool isEmpty() const { return _ignoreList.isEmpty(); } count()338 inline int count() const { return _ignoreList.count(); } removeAt(int index)339 inline void removeAt(int index) { _ignoreList.removeAt(index); } 340 inline IgnoreListItem& operator[](int i) { return _ignoreList[i]; } 341 inline const IgnoreListItem& operator[](int i) const { return _ignoreList.at(i); } ignoreList()342 inline const IgnoreList& ignoreList() const { return _ignoreList; } 343 344 //! Check if a message matches the IgnoreRule 345 /** This method checks if a message matches the users ignorelist. 346 * \param msg The Message that should be checked 347 * \param network The networkname the message belongs to 348 * \return UnmatchedStrictness, HardStrictness or SoftStrictness representing the match type 349 */ 350 inline StrictnessType match(const Message& msg, const QString& network = QString()) 351 { 352 return _match(msg.contents(), msg.sender(), msg.type(), network, msg.bufferInfo().bufferName()); 353 } 354 355 bool ctcpMatch(const QString sender, const QString& network, const QString& type = QString()); 356 357 // virtual void addIgnoreListItem(const IgnoreListItem &item); 358 359 public slots: 360 virtual QVariantMap initIgnoreList() const; 361 virtual void initSetIgnoreList(const QVariantMap& ignoreList); 362 363 //! Request removal of an ignore rule based on the rule itself. 364 /** Use this method if you want to remove a single ignore rule 365 * and get that synced with the core immediately. 366 * \param ignoreRule A valid ignore rule 367 */ requestRemoveIgnoreListItem(const QString & ignoreRule)368 virtual inline void requestRemoveIgnoreListItem(const QString& ignoreRule) { REQUEST(ARG(ignoreRule)) } 369 virtual void removeIgnoreListItem(const QString& ignoreRule); 370 371 //! Request toggling of "isActive" flag of a given ignore rule. 372 /** Use this method if you want to toggle the "isActive" flag of a single ignore rule 373 * and get that synced with the core immediately. 374 * \param ignoreRule A valid ignore rule 375 */ requestToggleIgnoreRule(const QString & ignoreRule)376 virtual inline void requestToggleIgnoreRule(const QString& ignoreRule) { REQUEST(ARG(ignoreRule)) } 377 virtual void toggleIgnoreRule(const QString& ignoreRule); 378 379 //! Request an IgnoreListItem to be added to the ignore list 380 /** Items added to the list with this method, get immediately synced with the core 381 * \param type The IgnoreType of the new rule 382 * \param ignoreRule The rule itself 383 * \param isRegEx Signals if the rule should be interpreted as a regular expression 384 * \param strictness Th StrictnessType that should be applied 385 * \param scope The ScopeType that should be set 386 * \param scopeRule A string of semi-colon separated network- or channelnames 387 * \param isActive Signals if the rule is enabled or not 388 */ requestAddIgnoreListItem(int type,const QString & ignoreRule,bool isRegEx,int strictness,int scope,const QString & scopeRule,bool isActive)389 virtual inline void requestAddIgnoreListItem( 390 int type, const QString& ignoreRule, bool isRegEx, int strictness, int scope, const QString& scopeRule, bool isActive) 391 { 392 REQUEST(ARG(type), ARG(ignoreRule), ARG(isRegEx), ARG(strictness), ARG(scope), ARG(scopeRule), ARG(isActive)) 393 } 394 395 virtual void addIgnoreListItem( 396 int type, const QString& ignoreRule, bool isRegEx, int strictness, int scope, const QString& scopeRule, bool isActive); 397 398 protected: setIgnoreList(const QList<IgnoreListItem> & ignoreList)399 void setIgnoreList(const QList<IgnoreListItem>& ignoreList) { _ignoreList = ignoreList; } 400 401 StrictnessType _match( 402 const QString& msgContents, const QString& msgSender, Message::Type msgType, const QString& network, const QString& bufferName); 403 404 signals: 405 void ignoreAdded(IgnoreType type, 406 const QString& ignoreRule, 407 bool isRegex, 408 StrictnessType strictness, 409 ScopeType scope, 410 const QVariant& scopeRule, 411 bool isActive); 412 413 private: 414 IgnoreList _ignoreList; 415 }; 416