~ubuntu-branches/debian/experimental/nzbget/experimental

« back to all changes in this revision

Viewing changes to daemon/feed/FeedFilter.cpp

  • Committer: Package Import Robot
  • Author(s): Andreas Moog
  • Date: 2014-12-25 12:58:06 UTC
  • mfrom: (1.2.1) (3.1.4 sid)
  • Revision ID: package-import@ubuntu.com-20141225125806-vnzgajhm7mju9933
Tags: 14.1+dfsg-1
* New Upstream release (Closes: #768863)
* debian/patches:
  - Remove 0010_unnecessary_gcryptdep.patch, included upstream
  - Refresh remaining patches
* debian/control:
  - Remove no longer needed build-depends on libpar2-dev and libsigc++-dev
* debian/nzbget.conf
  - Update sample configuration file to include new options introduced by
    new upstream version.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 *  This file is part of nzbget
 
3
 *
 
4
 *  Copyright (C) 2013-2014 Andrey Prygunkov <hugbug@users.sourceforge.net>
 
5
 *
 
6
 *  This program is free software; you can redistribute it and/or modify
 
7
 *  it under the terms of the GNU General Public License as published by
 
8
 *  the Free Software Foundation; either version 2 of the License, or
 
9
 *  (at your option) any later version.
 
10
 *
 
11
 *  This program is distributed in the hope that it will be useful,
 
12
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
13
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
14
 *  GNU General Public License for more details.
 
15
 *
 
16
 *  You should have received a copy of the GNU General Public License
 
17
 *  along with this program; if not, write to the Free Software
 
18
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
19
 *
 
20
 * $Revision: 1086 $
 
21
 * $Date: 2014-08-11 00:14:03 +0200 (Mon, 11 Aug 2014) $
 
22
 *
 
23
 */
 
24
 
 
25
 
 
26
#ifdef HAVE_CONFIG_H
 
27
#include "config.h"
 
28
#endif
 
29
 
 
30
#ifdef WIN32
 
31
#include "win32.h"
 
32
#endif
 
33
#include <stdlib.h>
 
34
#include <string.h>
 
35
#include <stdio.h>
 
36
#include <ctype.h>
 
37
 
 
38
#include "nzbget.h"
 
39
#include "Log.h"
 
40
#include "DownloadInfo.h"
 
41
#include "Util.h"
 
42
#include "FeedFilter.h"
 
43
 
 
44
 
 
45
FeedFilter::Term::Term()
 
46
{
 
47
        m_szField = NULL;
 
48
        m_szParam = NULL;
 
49
        m_bFloat = false;
 
50
        m_iIntParam = 0;
 
51
        m_fFloatParam = 0.0;
 
52
        m_pRegEx = NULL;
 
53
        m_pRefValues = NULL;
 
54
}
 
55
 
 
56
FeedFilter::Term::~Term()
 
57
{
 
58
        free(m_szField);
 
59
        free(m_szParam);
 
60
        delete m_pRegEx;
 
61
}
 
62
 
 
63
bool FeedFilter::Term::Match(FeedItemInfo* pFeedItemInfo)
 
64
{
 
65
        const char* szStrValue = NULL;
 
66
        long long iIntValue = 0;
 
67
 
 
68
        if (!GetFieldData(m_szField, pFeedItemInfo, &szStrValue, &iIntValue))
 
69
        {
 
70
                return false;
 
71
        }
 
72
 
 
73
        bool bMatch = MatchValue(szStrValue, iIntValue);
 
74
 
 
75
        if (m_bPositive != bMatch)
 
76
        {
 
77
                return false;
 
78
        }
 
79
 
 
80
        return true;
 
81
}
 
82
 
 
83
bool FeedFilter::Term::MatchValue(const char* szStrValue, long long iIntValue)
 
84
{
 
85
        double fFloatValue = (double)iIntValue;
 
86
        char szIntBuf[100];
 
87
 
 
88
        if (m_eCommand < fcEqual && !szStrValue)
 
89
        {
 
90
                snprintf(szIntBuf, 100, "%lld", iIntValue);
 
91
                szIntBuf[100-1] = '\0';
 
92
                szStrValue = szIntBuf;
 
93
        }
 
94
 
 
95
        else if (m_eCommand >= fcEqual && szStrValue)
 
96
        {
 
97
                fFloatValue = atof(szStrValue);
 
98
                iIntValue = (long long)fFloatValue;
 
99
        }
 
100
 
 
101
        switch (m_eCommand)
 
102
        {
 
103
                case fcText:
 
104
                        return MatchText(szStrValue);
 
105
 
 
106
                case fcRegex:
 
107
                        return MatchRegex(szStrValue);
 
108
 
 
109
                case fcEqual:
 
110
                        return m_bFloat ? fFloatValue == m_fFloatParam : iIntValue == m_iIntParam;
 
111
 
 
112
                case fcLess:
 
113
                        return m_bFloat ? fFloatValue < m_fFloatParam : iIntValue < m_iIntParam;
 
114
 
 
115
                case fcLessEqual:
 
116
                        return m_bFloat ? fFloatValue <= m_fFloatParam : iIntValue <= m_iIntParam;
 
117
 
 
118
                case fcGreater:
 
119
                        return m_bFloat ? fFloatValue > m_fFloatParam : iIntValue > m_iIntParam;
 
120
 
 
121
                case fcGreaterEqual:
 
122
                        return m_bFloat ? fFloatValue >= m_fFloatParam : iIntValue >= m_iIntParam;
 
123
 
 
124
                default:
 
125
                        return false;
 
126
        }
 
127
}
 
128
 
 
129
bool FeedFilter::Term::MatchText(const char* szStrValue)
 
130
{
 
131
        const char* WORD_SEPARATORS = " !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
 
132
 
 
133
        // first check if we should make word-search or substring-search
 
134
        int iParamLen = strlen(m_szParam);
 
135
        bool bSubstr = iParamLen >= 2 && m_szParam[0] == '*' && m_szParam[iParamLen-1] == '*';
 
136
        if (!bSubstr)
 
137
        {
 
138
                for (const char* p = m_szParam; *p; p++)
 
139
                {
 
140
                        char ch = *p;
 
141
                        if (strchr(WORD_SEPARATORS, ch) && ch != '*' && ch != '?' && ch != '#')
 
142
                        {
 
143
                                bSubstr = true;
 
144
                                break;
 
145
                        }
 
146
                }
 
147
        }
 
148
 
 
149
        bool bMatch = false;
 
150
 
 
151
        if (!bSubstr)
 
152
        {
 
153
                // Word-search
 
154
 
 
155
                // split szStrValue into tokens
 
156
                Tokenizer tok(szStrValue, WORD_SEPARATORS);
 
157
                while (const char* szWord = tok.Next())
 
158
                {
 
159
                        WildMask mask(m_szParam, m_pRefValues != NULL);
 
160
                        bMatch = mask.Match(szWord);
 
161
                        if (bMatch)
 
162
                        {
 
163
                                FillWildMaskRefValues(szWord, &mask, 0);
 
164
                                break;
 
165
                        }
 
166
                }
 
167
        }
 
168
        else
 
169
        {
 
170
                // Substring-search
 
171
 
 
172
                int iRefOffset = 1;
 
173
                const char* szFormat = "*%s*";
 
174
                if (iParamLen >= 2 && m_szParam[0] == '*' && m_szParam[iParamLen-1] == '*')
 
175
                {
 
176
                        szFormat = "%s";
 
177
                        iRefOffset = 0;
 
178
                }
 
179
                else if (iParamLen >= 1 && m_szParam[0] == '*')
 
180
                {
 
181
                        szFormat = "%s*";
 
182
                        iRefOffset = 0;
 
183
                }
 
184
                else if (iParamLen >= 1 && m_szParam[iParamLen-1] == '*')
 
185
                {
 
186
                        szFormat = "*%s";
 
187
                }
 
188
 
 
189
                int iMaskLen = strlen(m_szParam) + 2 + 1;
 
190
                char* szMask = (char*)malloc(iMaskLen);
 
191
                snprintf(szMask, iMaskLen, szFormat, m_szParam);
 
192
                szMask[iMaskLen-1] = '\0';
 
193
 
 
194
                WildMask mask(szMask, m_pRefValues != NULL);
 
195
                bMatch = mask.Match(szStrValue);
 
196
 
 
197
                if (bMatch)
 
198
                {
 
199
                        FillWildMaskRefValues(szStrValue, &mask, iRefOffset);
 
200
                }
 
201
 
 
202
                free(szMask);
 
203
        }
 
204
 
 
205
        return bMatch;
 
206
}
 
207
 
 
208
bool FeedFilter::Term::MatchRegex(const char* szStrValue)
 
209
{
 
210
        if (!m_pRegEx)
 
211
        {
 
212
                m_pRegEx = new RegEx(m_szParam, m_pRefValues == NULL ? 0 : 100);
 
213
        }
 
214
 
 
215
        bool bFound = m_pRegEx->Match(szStrValue);
 
216
        if (bFound)
 
217
        {
 
218
                FillRegExRefValues(szStrValue, m_pRegEx);
 
219
        }
 
220
        return bFound;
 
221
}
 
222
 
 
223
bool FeedFilter::Term::Compile(char* szToken)
 
224
{
 
225
        debug("Token: %s", szToken);
 
226
 
 
227
        char ch = szToken[0];
 
228
 
 
229
        m_bPositive = ch != '-';
 
230
        if (ch == '-' || ch == '+')
 
231
        {
 
232
                szToken++;
 
233
                ch = szToken[0];
 
234
        }
 
235
 
 
236
        char ch2= szToken[1];
 
237
        if ((ch == '(' || ch == ')' || ch == '|') && (ch2 == ' ' || ch2 == '\0'))
 
238
        {
 
239
                switch (ch)
 
240
                {
 
241
                        case '(':
 
242
                                m_eCommand = fcOpeningBrace;
 
243
                                return true;
 
244
                        case ')':
 
245
                                m_eCommand = fcClosingBrace;
 
246
                                return true;
 
247
                        case '|':
 
248
                                m_eCommand = fcOrOperator;
 
249
                                return true;
 
250
                }
 
251
        }
 
252
 
 
253
        char *szField = NULL;
 
254
        m_eCommand = fcText;
 
255
 
 
256
        char* szColon = NULL;
 
257
        if (ch != '@' && ch != '$' && ch != '<' && ch != '>' && ch != '=')
 
258
        {
 
259
                szColon = strchr(szToken, ':');
 
260
        }
 
261
        if (szColon)
 
262
        {
 
263
                szField = szToken;
 
264
                szColon[0] = '\0';
 
265
                szToken = szColon + 1;
 
266
                ch = szToken[0];
 
267
        }
 
268
 
 
269
        if (ch == '\0')
 
270
        {
 
271
                return false;
 
272
        }
 
273
 
 
274
        ch2= szToken[1];
 
275
 
 
276
        if (ch == '@')
 
277
        {
 
278
                m_eCommand = fcText;
 
279
                szToken++;
 
280
        }
 
281
        else if (ch == '$')
 
282
        {
 
283
                m_eCommand = fcRegex;
 
284
                szToken++;
 
285
        }
 
286
        else if (ch == '=')
 
287
        {
 
288
                m_eCommand = fcEqual;
 
289
                szToken++;
 
290
        }
 
291
        else if (ch == '<' && ch2 == '=')
 
292
        {
 
293
                m_eCommand = fcLessEqual;
 
294
                szToken += 2;
 
295
        }
 
296
        else if (ch == '>' && ch2 == '=')
 
297
        {
 
298
                m_eCommand = fcGreaterEqual;
 
299
                szToken += 2;
 
300
        }
 
301
        else if (ch == '<')
 
302
        {
 
303
                m_eCommand = fcLess;
 
304
                szToken++;
 
305
        }
 
306
        else if (ch == '>')
 
307
        {
 
308
                m_eCommand = fcGreater;
 
309
                szToken++;
 
310
        }
 
311
 
 
312
        debug("%s, Field: %s, Command: %i, Param: %s", (m_bPositive ? "Positive" : "Negative"), szField, m_eCommand, szToken);
 
313
 
 
314
        const char* szStrValue;
 
315
        long long iIntValue;
 
316
        if (!GetFieldData(szField, NULL, &szStrValue, &iIntValue))
 
317
        {
 
318
                return false;
 
319
        }
 
320
 
 
321
        if (szField && !ParseParam(szField, szToken))
 
322
        {
 
323
                return false;
 
324
        }
 
325
 
 
326
        m_szField = szField ? strdup(szField) : NULL;
 
327
        m_szParam = strdup(szToken);
 
328
 
 
329
        return true;
 
330
}
 
331
 
 
332
/*
 
333
 * If pFeedItemInfo is NULL, only field name is validated
 
334
 */
 
335
bool FeedFilter::Term::GetFieldData(const char* szField, FeedItemInfo* pFeedItemInfo,
 
336
        const char** StrValue, long long* IntValue)
 
337
{
 
338
        *StrValue = NULL;
 
339
        *IntValue = 0;
 
340
 
 
341
        if (!szField || !strcasecmp(szField, "title"))
 
342
        {
 
343
                *StrValue = pFeedItemInfo ? pFeedItemInfo->GetTitle() : NULL;
 
344
                return true;
 
345
        }
 
346
        else if (!strcasecmp(szField, "filename"))
 
347
        {
 
348
                *StrValue = pFeedItemInfo ? pFeedItemInfo->GetFilename() : NULL;
 
349
                return true;
 
350
        }
 
351
        else if (!strcasecmp(szField, "category"))
 
352
        {
 
353
                *StrValue = pFeedItemInfo ? pFeedItemInfo->GetCategory() : NULL;
 
354
                return true;
 
355
        }
 
356
        else if (!strcasecmp(szField, "link") || !strcasecmp(szField, "url"))
 
357
        {
 
358
                *StrValue = pFeedItemInfo ? pFeedItemInfo->GetUrl() : NULL;
 
359
                return true;
 
360
        }
 
361
        else if (!strcasecmp(szField, "size"))
 
362
        {
 
363
                *IntValue = pFeedItemInfo ? pFeedItemInfo->GetSize() : 0;
 
364
                return true;
 
365
        }
 
366
        else if (!strcasecmp(szField, "age"))
 
367
        {
 
368
                *IntValue = pFeedItemInfo ? time(NULL) - pFeedItemInfo->GetTime() : 0;
 
369
                return true;
 
370
        }
 
371
        else if (!strcasecmp(szField, "imdbid"))
 
372
        {
 
373
                *IntValue = pFeedItemInfo ? pFeedItemInfo->GetImdbId() : 0;
 
374
                return true;
 
375
        }
 
376
        else if (!strcasecmp(szField, "rageid"))
 
377
        {
 
378
                *IntValue = pFeedItemInfo ? pFeedItemInfo->GetRageId() : 0;
 
379
                return true;
 
380
        }
 
381
        else if (!strcasecmp(szField, "description"))
 
382
        {
 
383
                *StrValue = pFeedItemInfo ? pFeedItemInfo->GetDescription() : NULL;
 
384
                return true;
 
385
        }
 
386
        else if (!strcasecmp(szField, "season"))
 
387
        {
 
388
                *IntValue = pFeedItemInfo ? pFeedItemInfo->GetSeasonNum() : 0;
 
389
                return true;
 
390
        }
 
391
        else if (!strcasecmp(szField, "episode"))
 
392
        {
 
393
                *IntValue = pFeedItemInfo ? pFeedItemInfo->GetEpisodeNum() : 0;
 
394
                return true;
 
395
        }
 
396
        else if (!strcasecmp(szField, "priority"))
 
397
        {
 
398
                *IntValue = pFeedItemInfo ? pFeedItemInfo->GetPriority() : 0;
 
399
                return true;
 
400
        }
 
401
        else if (!strcasecmp(szField, "dupekey"))
 
402
        {
 
403
                *StrValue = pFeedItemInfo ? pFeedItemInfo->GetDupeKey() : NULL;
 
404
                return true;
 
405
        }
 
406
        else if (!strcasecmp(szField, "dupescore"))
 
407
        {
 
408
                *IntValue = pFeedItemInfo ? pFeedItemInfo->GetDupeScore() : 0;
 
409
                return true;
 
410
        }
 
411
        else if (!strcasecmp(szField, "dupestatus"))
 
412
        {
 
413
                *StrValue = pFeedItemInfo ? pFeedItemInfo->GetDupeStatus() : NULL;
 
414
                return true;
 
415
        }
 
416
        else if (!strncasecmp(szField, "attr-", 5))
 
417
        {
 
418
                if (pFeedItemInfo)
 
419
                {
 
420
                        FeedItemInfo::Attr* pAttr = pFeedItemInfo->GetAttributes()->Find(szField + 5);
 
421
                        *StrValue = pAttr ? pAttr->GetValue() : NULL;
 
422
                }
 
423
                return true;
 
424
        }
 
425
 
 
426
        return false;
 
427
}
 
428
 
 
429
bool FeedFilter::Term::ParseParam(const char* szField, const char* szParam)
 
430
{
 
431
        if (!strcasecmp(szField, "size"))
 
432
        {
 
433
                return ParseSizeParam(szParam);
 
434
        }
 
435
        else if (!strcasecmp(szField, "age"))
 
436
        {
 
437
                return ParseAgeParam(szParam);
 
438
        }
 
439
        else if (m_eCommand >= fcEqual)
 
440
        {
 
441
                return ParseNumericParam(szParam);
 
442
        }
 
443
 
 
444
        return true;
 
445
}
 
446
 
 
447
bool FeedFilter::Term::ParseSizeParam(const char* szParam)
 
448
{
 
449
        double fParam = atof(szParam);
 
450
 
 
451
        const char* p;
 
452
        for (p = szParam; *p && ((*p >= '0' && *p <='9') || *p == '.'); p++) ;
 
453
        if (*p)
 
454
        {
 
455
                if (!strcasecmp(p, "K") || !strcasecmp(p, "KB"))
 
456
                {
 
457
                        m_iIntParam = (long long)(fParam*1024);
 
458
                }
 
459
                else if (!strcasecmp(p, "M") || !strcasecmp(p, "MB"))
 
460
                {
 
461
                        m_iIntParam = (long long)(fParam*1024*1024);
 
462
                }
 
463
                else if (!strcasecmp(p, "G") || !strcasecmp(p, "GB"))
 
464
                {
 
465
                        m_iIntParam = (long long)(fParam*1024*1024*1024);
 
466
                }
 
467
                else
 
468
                {
 
469
                        return false;
 
470
                }
 
471
        }
 
472
        else
 
473
        {
 
474
                m_iIntParam = (long long)fParam;
 
475
        }
 
476
 
 
477
        return true;
 
478
}
 
479
 
 
480
bool FeedFilter::Term::ParseAgeParam(const char* szParam)
 
481
{
 
482
        double fParam = atof(szParam);
 
483
 
 
484
        const char* p;
 
485
        for (p = szParam; *p && ((*p >= '0' && *p <='9') || *p == '.'); p++) ;
 
486
        if (*p)
 
487
        {
 
488
                if (!strcasecmp(p, "m"))
 
489
                {
 
490
                        // minutes
 
491
                        m_iIntParam = (long long)(fParam*60);
 
492
                }
 
493
                else if (!strcasecmp(p, "h"))
 
494
                {
 
495
                        // hours
 
496
                        m_iIntParam = (long long)(fParam*60*60);
 
497
                }
 
498
                else if (!strcasecmp(p, "d"))
 
499
                {
 
500
                        // days
 
501
                        m_iIntParam = (long long)(fParam*60*60*24);
 
502
                }
 
503
                else
 
504
                {
 
505
                        return false;
 
506
                }
 
507
        }
 
508
        else
 
509
        {
 
510
                // days by default
 
511
                m_iIntParam = (long long)(fParam*60*60*24);
 
512
        }
 
513
 
 
514
        return true;
 
515
}
 
516
 
 
517
bool FeedFilter::Term::ParseNumericParam(const char* szParam)
 
518
{
 
519
        m_fFloatParam = atof(szParam);
 
520
        m_iIntParam = (long long)m_fFloatParam;
 
521
        m_bFloat = strchr(szParam, '.');
 
522
        
 
523
        const char* p;
 
524
        for (p = szParam; *p && ((*p >= '0' && *p <='9') || *p == '.') ; p++) ;
 
525
        if (*p)
 
526
        {
 
527
                return false;
 
528
        }
 
529
        
 
530
        return true;
 
531
}
 
532
 
 
533
void FeedFilter::Term::FillWildMaskRefValues(const char* szStrValue, WildMask* pMask, int iRefOffset)
 
534
{
 
535
        if (!m_pRefValues)
 
536
        {
 
537
                return;
 
538
        }
 
539
 
 
540
        for (int i = iRefOffset; i < pMask->GetMatchCount(); i++)
 
541
        {
 
542
                int iLen = pMask->GetMatchLen(i);
 
543
                char* szValue = (char*)malloc(iLen + 1);
 
544
                strncpy(szValue, szStrValue + pMask->GetMatchStart(i), iLen);
 
545
                szValue[iLen] = '\0';
 
546
 
 
547
                m_pRefValues->push_back(szValue);
 
548
        }
 
549
}
 
550
 
 
551
void FeedFilter::Term::FillRegExRefValues(const char* szStrValue, RegEx* pRegEx)
 
552
{
 
553
        if (!m_pRefValues)
 
554
        {
 
555
                return;
 
556
        }
 
557
 
 
558
        for (int i = 1; i < pRegEx->GetMatchCount(); i++)
 
559
        {
 
560
                int iLen = pRegEx->GetMatchLen(i);
 
561
                char* szValue = (char*)malloc(iLen + 1);
 
562
                strncpy(szValue, szStrValue + pRegEx->GetMatchStart(i), iLen);
 
563
                szValue[iLen] = '\0';
 
564
 
 
565
                m_pRefValues->push_back(szValue);
 
566
        }
 
567
}
 
568
 
 
569
 
 
570
FeedFilter::Rule::Rule()
 
571
{
 
572
        m_eCommand = frAccept;
 
573
        m_bIsValid = false;
 
574
        m_szCategory = NULL;
 
575
        m_iPriority = 0;
 
576
        m_iAddPriority = 0;
 
577
        m_bPause = false;
 
578
        m_szDupeKey = NULL;
 
579
        m_szAddDupeKey = NULL;
 
580
        m_iDupeScore = 0;
 
581
        m_iAddDupeScore = 0;
 
582
        m_eDupeMode = dmScore;
 
583
        m_szRageId = NULL;
 
584
        m_szSeries = NULL;
 
585
        m_bHasCategory = false;
 
586
        m_bHasPriority = false;
 
587
        m_bHasAddPriority = false;
 
588
        m_bHasPause = false;
 
589
        m_bHasDupeScore = false;
 
590
        m_bHasAddDupeScore = false;
 
591
        m_bHasDupeKey = false;
 
592
        m_bHasAddDupeKey = false;
 
593
        m_bHasDupeMode = false;
 
594
        m_bHasRageId = false;
 
595
        m_bHasSeries = false;
 
596
        m_bPatCategory = false;
 
597
        m_bPatDupeKey = false;
 
598
        m_bPatAddDupeKey = false;
 
599
        m_szPatCategory = NULL;
 
600
        m_szPatDupeKey = NULL;
 
601
        m_szPatAddDupeKey = NULL;
 
602
}
 
603
 
 
604
FeedFilter::Rule::~Rule()
 
605
{
 
606
        free(m_szCategory);
 
607
        free(m_szDupeKey);
 
608
        free(m_szAddDupeKey);
 
609
        free(m_szRageId);
 
610
        free(m_szSeries);
 
611
        free(m_szPatCategory);
 
612
        free(m_szPatDupeKey);
 
613
        free(m_szPatAddDupeKey);
 
614
 
 
615
        for (TermList::iterator it = m_Terms.begin(); it != m_Terms.end(); it++)
 
616
        {
 
617
                delete *it;
 
618
        }
 
619
 
 
620
        for (RefValues::iterator it = m_RefValues.begin(); it != m_RefValues.end(); it++)
 
621
        {
 
622
                delete *it;
 
623
        }
 
624
}
 
625
 
 
626
void FeedFilter::Rule::Compile(char* szRule)
 
627
{
 
628
        debug("Compiling rule: %s", szRule);
 
629
 
 
630
        m_bIsValid = true;
 
631
 
 
632
        char* szFilter3 = Util::Trim(szRule);
 
633
 
 
634
        char* szTerm = CompileCommand(szFilter3);
 
635
        if (!szTerm)
 
636
        {
 
637
                m_bIsValid = false;
 
638
                return;
 
639
        }
 
640
        if (m_eCommand == frComment)
 
641
        {
 
642
                return;
 
643
        }
 
644
 
 
645
        szTerm = Util::Trim(szTerm);
 
646
 
 
647
        for (char* p = szTerm; *p && m_bIsValid; p++)
 
648
        {
 
649
                char ch = *p;
 
650
                if (ch == ' ')
 
651
                {
 
652
                        *p = '\0';
 
653
                        m_bIsValid = CompileTerm(szTerm);
 
654
                        szTerm = p + 1;
 
655
                        while (*szTerm == ' ') szTerm++;
 
656
                        p = szTerm;
 
657
                }
 
658
        }
 
659
 
 
660
        m_bIsValid = m_bIsValid && CompileTerm(szTerm);
 
661
 
 
662
        if (m_bIsValid && m_bPatCategory)
 
663
        {
 
664
                m_szPatCategory = m_szCategory;
 
665
                m_szCategory = NULL;
 
666
        }
 
667
        if (m_bIsValid && m_bPatDupeKey)
 
668
        {
 
669
                m_szPatDupeKey = m_szDupeKey;
 
670
                m_szDupeKey = NULL;
 
671
        }
 
672
        if (m_bIsValid && m_bPatAddDupeKey)
 
673
        {
 
674
                m_szPatAddDupeKey = m_szAddDupeKey;
 
675
                m_szAddDupeKey = NULL;
 
676
        }
 
677
}
 
678
 
 
679
/* Checks if the rule starts with command and compiles it.
 
680
 * Returns a pointer to the next (first) term or NULL in a case of compilation error.
 
681
 */
 
682
char* FeedFilter::Rule::CompileCommand(char* szRule)
 
683
{
 
684
        if (!strncasecmp(szRule, "A:", 2) || !strncasecmp(szRule, "Accept:", 7) ||
 
685
                !strncasecmp(szRule, "A(", 2) || !strncasecmp(szRule, "Accept(", 7))
 
686
        {
 
687
                m_eCommand = frAccept;
 
688
                szRule += szRule[1] == ':' || szRule[1] == '(' ? 2 : 7;
 
689
        }
 
690
        else if (!strncasecmp(szRule, "O(", 2) || !strncasecmp(szRule, "Options(", 8))
 
691
        {
 
692
                m_eCommand = frOptions;
 
693
                szRule += szRule[1] == ':' || szRule[1] == '(' ? 2 : 8;
 
694
        }
 
695
        else if (!strncasecmp(szRule, "R:", 2) || !strncasecmp(szRule, "Reject:", 7))
 
696
        {
 
697
                m_eCommand = frReject;
 
698
                szRule += szRule[1] == ':' || szRule[1] == '(' ? 2 : 7;
 
699
        }
 
700
        else if (!strncasecmp(szRule, "Q:", 2) || !strncasecmp(szRule, "Require:", 8))
 
701
        {
 
702
                m_eCommand = frRequire;
 
703
                szRule += szRule[1] == ':' || szRule[1] == '(' ? 2 : 8;
 
704
        }
 
705
        else if (*szRule == '#')
 
706
        {
 
707
                m_eCommand = frComment;
 
708
                return szRule;
 
709
        }
 
710
        else
 
711
        {
 
712
                // not a command
 
713
                return szRule;
 
714
        }
 
715
 
 
716
        if ((m_eCommand == frAccept || m_eCommand == frOptions) && szRule[-1] == '(')
 
717
        {
 
718
                szRule = CompileOptions(szRule);
 
719
        }
 
720
 
 
721
        return szRule;
 
722
}
 
723
 
 
724
char* FeedFilter::Rule::CompileOptions(char* szRule)
 
725
{
 
726
        char* p = strchr(szRule, ')');
 
727
        if (!p)
 
728
        {
 
729
                // error
 
730
                return NULL;
 
731
        }
 
732
 
 
733
        // split command into tokens
 
734
        *p = '\0';
 
735
        Tokenizer tok(szRule, ",", true);
 
736
        while (char* szOption = tok.Next())
 
737
        {
 
738
                const char* szValue = "";
 
739
                char* szColon = strchr(szOption, ':');
 
740
                if (szColon)
 
741
                {
 
742
                        *szColon = '\0';
 
743
                        szValue = Util::Trim(szColon + 1);
 
744
                }
 
745
 
 
746
                if (!strcasecmp(szOption, "category") || !strcasecmp(szOption, "cat") || !strcasecmp(szOption, "c"))
 
747
                {
 
748
                        m_bHasCategory = true;
 
749
                        free(m_szCategory);
 
750
                        m_szCategory = strdup(szValue);
 
751
                        m_bPatCategory = strstr(szValue, "${");
 
752
                }
 
753
                else if (!strcasecmp(szOption, "pause") || !strcasecmp(szOption, "p"))
 
754
                {
 
755
                        m_bHasPause = true;
 
756
                        m_bPause = !*szValue || !strcasecmp(szValue, "yes") || !strcasecmp(szValue, "y");
 
757
                        if (!m_bPause && !(!strcasecmp(szValue, "no") || !strcasecmp(szValue, "n")))
 
758
                        {
 
759
                                // error
 
760
                                return NULL;
 
761
                        }
 
762
                }
 
763
                else if (!strcasecmp(szOption, "priority") || !strcasecmp(szOption, "pr") || !strcasecmp(szOption, "r"))
 
764
                {
 
765
                        if (!strchr("0123456789-+", *szValue))
 
766
                        {
 
767
                                // error
 
768
                                return NULL;
 
769
                        }
 
770
                        m_bHasPriority = true;
 
771
                        m_iPriority = atoi(szValue);
 
772
                }
 
773
                else if (!strcasecmp(szOption, "priority+") || !strcasecmp(szOption, "pr+") || !strcasecmp(szOption, "r+"))
 
774
                {
 
775
                        if (!strchr("0123456789-+", *szValue))
 
776
                        {
 
777
                                // error
 
778
                                return NULL;
 
779
                        }
 
780
                        m_bHasAddPriority = true;
 
781
                        m_iAddPriority = atoi(szValue);
 
782
                }
 
783
                else if (!strcasecmp(szOption, "dupescore") || !strcasecmp(szOption, "ds") || !strcasecmp(szOption, "s"))
 
784
                {
 
785
                        if (!strchr("0123456789-+", *szValue))
 
786
                        {
 
787
                                // error
 
788
                                return NULL;
 
789
                        }
 
790
                        m_bHasDupeScore = true;
 
791
                        m_iDupeScore = atoi(szValue);
 
792
                }
 
793
                else if (!strcasecmp(szOption, "dupescore+") || !strcasecmp(szOption, "ds+") || !strcasecmp(szOption, "s+"))
 
794
                {
 
795
                        if (!strchr("0123456789-+", *szValue))
 
796
                        {
 
797
                                // error
 
798
                                return NULL;
 
799
                        }
 
800
                        m_bHasAddDupeScore = true;
 
801
                        m_iAddDupeScore = atoi(szValue);
 
802
                }
 
803
                else if (!strcasecmp(szOption, "dupekey") || !strcasecmp(szOption, "dk") || !strcasecmp(szOption, "k"))
 
804
                {
 
805
                        m_bHasDupeKey = true;
 
806
                        free(m_szDupeKey);
 
807
                        m_szDupeKey = strdup(szValue);
 
808
                        m_bPatDupeKey = strstr(szValue, "${");
 
809
                }
 
810
                else if (!strcasecmp(szOption, "dupekey+") || !strcasecmp(szOption, "dk+") || !strcasecmp(szOption, "k+"))
 
811
                {
 
812
                        m_bHasAddDupeKey = true;
 
813
                        free(m_szAddDupeKey);
 
814
                        m_szAddDupeKey = strdup(szValue);
 
815
                        m_bPatAddDupeKey = strstr(szValue, "${");
 
816
                }
 
817
                else if (!strcasecmp(szOption, "dupemode") || !strcasecmp(szOption, "dm") || !strcasecmp(szOption, "m"))
 
818
                {
 
819
                        m_bHasDupeMode = true;
 
820
                        if (!strcasecmp(szValue, "score") || !strcasecmp(szValue, "s"))
 
821
                        {
 
822
                                m_eDupeMode = dmScore;
 
823
                        }
 
824
                        else if (!strcasecmp(szValue, "all") || !strcasecmp(szValue, "a"))
 
825
                        {
 
826
                                m_eDupeMode = dmAll;
 
827
                        }
 
828
                        else if (!strcasecmp(szValue, "force") || !strcasecmp(szValue, "f"))
 
829
                        {
 
830
                                m_eDupeMode = dmForce;
 
831
                        }
 
832
                        else
 
833
                        {
 
834
                                // error
 
835
                                return NULL;
 
836
                        }
 
837
                }
 
838
                else if (!strcasecmp(szOption, "rageid"))
 
839
                {
 
840
                        m_bHasRageId = true;
 
841
                        free(m_szRageId);
 
842
                        m_szRageId = strdup(szValue);
 
843
                }
 
844
                else if (!strcasecmp(szOption, "series"))
 
845
                {
 
846
                        m_bHasSeries = true;
 
847
                        free(m_szSeries);
 
848
                        m_szSeries = strdup(szValue);
 
849
                }
 
850
 
 
851
                // for compatibility with older version we support old commands too
 
852
                else if (!strcasecmp(szOption, "paused") || !strcasecmp(szOption, "unpaused"))
 
853
                {
 
854
                        m_bHasPause = true;
 
855
                        m_bPause = !strcasecmp(szOption, "paused");
 
856
                }
 
857
                else if (strchr("0123456789-+", *szOption))
 
858
                {
 
859
                        m_bHasPriority = true;
 
860
                        m_iPriority = atoi(szOption);
 
861
                }
 
862
                else
 
863
                {
 
864
                        m_bHasCategory = true;
 
865
                        free(m_szCategory);
 
866
                        m_szCategory = strdup(szOption);
 
867
                }
 
868
        }
 
869
 
 
870
        szRule = p + 1;
 
871
        if (*szRule == ':')
 
872
        {
 
873
                szRule++;
 
874
        }
 
875
 
 
876
        return szRule;
 
877
}
 
878
 
 
879
bool FeedFilter::Rule::CompileTerm(char* szTerm)
 
880
{
 
881
        Term* pTerm = new Term();
 
882
        pTerm->SetRefValues(m_bPatCategory || m_bPatDupeKey || m_bPatAddDupeKey ? &m_RefValues : NULL);
 
883
        if (pTerm->Compile(szTerm))
 
884
        {
 
885
                m_Terms.push_back(pTerm);
 
886
                return true;
 
887
        }
 
888
        else
 
889
        {
 
890
                delete pTerm;
 
891
                return false;
 
892
        }
 
893
}
 
894
 
 
895
bool FeedFilter::Rule::Match(FeedItemInfo* pFeedItemInfo)
 
896
{
 
897
        for (RefValues::iterator it = m_RefValues.begin(); it != m_RefValues.end(); it++)
 
898
        {
 
899
                delete *it;
 
900
        }
 
901
        m_RefValues.clear();
 
902
 
 
903
        if (!MatchExpression(pFeedItemInfo))
 
904
        {
 
905
                return false;
 
906
        }
 
907
 
 
908
        if (m_bPatCategory)
 
909
        {
 
910
                ExpandRefValues(pFeedItemInfo, &m_szCategory, m_szPatCategory);
 
911
        }
 
912
        if (m_bPatDupeKey)
 
913
        {
 
914
                ExpandRefValues(pFeedItemInfo, &m_szDupeKey, m_szPatDupeKey);
 
915
        }
 
916
        if (m_bPatAddDupeKey)
 
917
        {
 
918
                ExpandRefValues(pFeedItemInfo, &m_szAddDupeKey, m_szPatAddDupeKey);
 
919
        }
 
920
 
 
921
        return true;
 
922
}
 
923
 
 
924
bool FeedFilter::Rule::MatchExpression(FeedItemInfo* pFeedItemInfo)
 
925
{
 
926
        char* expr = (char*)malloc(m_Terms.size() + 1);
 
927
 
 
928
        int index = 0;
 
929
        for (TermList::iterator it = m_Terms.begin(); it != m_Terms.end(); it++, index++)
 
930
        {
 
931
                Term* pTerm = *it;
 
932
                switch (pTerm->GetCommand())
 
933
                {
 
934
                        case fcOpeningBrace:
 
935
                                expr[index] = '(';
 
936
                                break;
 
937
 
 
938
                        case fcClosingBrace:
 
939
                                expr[index] = ')';
 
940
                                break;
 
941
 
 
942
                        case fcOrOperator:
 
943
                                expr[index] = '|';
 
944
                                break;
 
945
 
 
946
                        default:
 
947
                                expr[index] = pTerm->Match(pFeedItemInfo) ? 'T' : 'F';
 
948
                                break;
 
949
                }
 
950
        }
 
951
        expr[index] = '\0';
 
952
 
 
953
        // reduce result tree to one element (may be longer if expression has syntax errors)
 
954
        for (int iOldLen = 0, iNewLen = strlen(expr); iNewLen != iOldLen; iOldLen = iNewLen, iNewLen = strlen(expr))
 
955
        {
 
956
                // NOTE: there are no operator priorities.
 
957
                // the order of operators "OR" and "AND" is not defined, they can be checked in any order.
 
958
                // "OR" and "AND" should not be mixed in one group; instead braces should be used to define priorities.
 
959
                Util::ReduceStr(expr, "TT", "T");
 
960
                Util::ReduceStr(expr, "TF", "F");
 
961
                Util::ReduceStr(expr, "FT", "F");
 
962
                Util::ReduceStr(expr, "FF", "F");
 
963
                Util::ReduceStr(expr, "||", "|");
 
964
                Util::ReduceStr(expr, "(|", "(");
 
965
                Util::ReduceStr(expr, "|)", ")");
 
966
                Util::ReduceStr(expr, "T|T", "T");
 
967
                Util::ReduceStr(expr, "T|F", "T");
 
968
                Util::ReduceStr(expr, "F|T", "T");
 
969
                Util::ReduceStr(expr, "F|F", "F");
 
970
                Util::ReduceStr(expr, "(T)", "T");
 
971
                Util::ReduceStr(expr, "(F)", "F");
 
972
        }
 
973
 
 
974
        bool bMatch = *expr && *expr == 'T' && expr[1] == '\0';
 
975
        free(expr);
 
976
        return bMatch;
 
977
}
 
978
 
 
979
void FeedFilter::Rule::ExpandRefValues(FeedItemInfo* pFeedItemInfo, char** pDestStr, char* pPatStr)
 
980
{
 
981
        free(*pDestStr);
 
982
 
 
983
        *pDestStr = strdup(pPatStr);
 
984
        char* curvalue = *pDestStr;
 
985
 
 
986
        int iAttempts = 0;
 
987
        while (char* dollar = strstr(curvalue, "${"))
 
988
        {
 
989
                iAttempts++;
 
990
                if (iAttempts > 100)
 
991
                {
 
992
                        break; // error
 
993
                }
 
994
 
 
995
                char* end = strchr(dollar, '}');
 
996
                if (!end)
 
997
                {
 
998
                        break; // error
 
999
                }
 
1000
 
 
1001
                int varlen = (int)(end - dollar - 2);
 
1002
                char variable[101];
 
1003
                int maxlen = varlen < 100 ? varlen : 100;
 
1004
                strncpy(variable, dollar + 2, maxlen);
 
1005
                variable[maxlen] = '\0';
 
1006
 
 
1007
                const char* varvalue = GetRefValue(pFeedItemInfo, variable);
 
1008
                if (!varvalue)
 
1009
                {
 
1010
                        break; // error
 
1011
                }
 
1012
 
 
1013
                int newlen = strlen(varvalue);
 
1014
                char* newvalue = (char*)malloc(strlen(curvalue) - varlen - 3 + newlen + 1);
 
1015
                strncpy(newvalue, curvalue, dollar - curvalue);
 
1016
                strncpy(newvalue + (dollar - curvalue), varvalue, newlen);
 
1017
                strcpy(newvalue + (dollar - curvalue) + newlen, end + 1);
 
1018
                free(curvalue);
 
1019
                curvalue = newvalue;
 
1020
                *pDestStr = curvalue;
 
1021
        }
 
1022
}
 
1023
 
 
1024
const char* FeedFilter::Rule::GetRefValue(FeedItemInfo* pFeedItemInfo, const char* szVarName)
 
1025
{
 
1026
        if (!strcasecmp(szVarName, "season"))
 
1027
        {
 
1028
                pFeedItemInfo->GetSeasonNum(); // needed to parse title
 
1029
                return pFeedItemInfo->GetSeason() ? pFeedItemInfo->GetSeason() : "";
 
1030
        }
 
1031
        else if (!strcasecmp(szVarName, "episode"))
 
1032
        {
 
1033
                pFeedItemInfo->GetEpisodeNum(); // needed to parse title
 
1034
                return pFeedItemInfo->GetEpisode() ? pFeedItemInfo->GetEpisode() : "";
 
1035
        }
 
1036
 
 
1037
        int iIndex = atoi(szVarName) - 1;
 
1038
        if (iIndex >= 0 && iIndex < (int)m_RefValues.size())
 
1039
        {
 
1040
                return m_RefValues[iIndex];
 
1041
        }
 
1042
 
 
1043
        return NULL;
 
1044
}
 
1045
 
 
1046
FeedFilter::FeedFilter(const char* szFilter)
 
1047
{
 
1048
        Compile(szFilter);
 
1049
}
 
1050
 
 
1051
FeedFilter::~FeedFilter()
 
1052
{
 
1053
        for (RuleList::iterator it = m_Rules.begin(); it != m_Rules.end(); it++)
 
1054
        {
 
1055
                delete *it;
 
1056
        }
 
1057
}
 
1058
 
 
1059
void FeedFilter::Compile(const char* szFilter)
 
1060
{
 
1061
        debug("Compiling filter: %s", szFilter);
 
1062
 
 
1063
        char* szFilter2 = strdup(szFilter);
 
1064
        char* szRule = szFilter2;
 
1065
 
 
1066
        for (char* p = szRule; *p; p++)
 
1067
        {
 
1068
                char ch = *p;
 
1069
                if (ch == '%')
 
1070
                {
 
1071
                        *p = '\0';
 
1072
                        CompileRule(szRule);
 
1073
                        szRule = p + 1;
 
1074
                }
 
1075
        }
 
1076
 
 
1077
        CompileRule(szRule);
 
1078
 
 
1079
        free(szFilter2);
 
1080
}
 
1081
 
 
1082
void FeedFilter::CompileRule(char* szRule)
 
1083
{
 
1084
        Rule* pRule = new Rule();
 
1085
        m_Rules.push_back(pRule);
 
1086
        pRule->Compile(szRule);
 
1087
}
 
1088
 
 
1089
void FeedFilter::Match(FeedItemInfo* pFeedItemInfo)
 
1090
{
 
1091
        int index = 0;
 
1092
        for (RuleList::iterator it = m_Rules.begin(); it != m_Rules.end(); it++)
 
1093
        {
 
1094
                Rule* pRule = *it;
 
1095
                index++;
 
1096
                if (pRule->IsValid())
 
1097
                {
 
1098
                        bool bMatch = pRule->Match(pFeedItemInfo);
 
1099
                        switch (pRule->GetCommand())
 
1100
                        {
 
1101
                                case frAccept:
 
1102
                                case frOptions:
 
1103
                                        if (bMatch)
 
1104
                                        {
 
1105
                                                pFeedItemInfo->SetMatchStatus(FeedItemInfo::msAccepted);
 
1106
                                                pFeedItemInfo->SetMatchRule(index);
 
1107
                                                ApplyOptions(pRule, pFeedItemInfo);
 
1108
                                                if (pRule->GetCommand() == frAccept)
 
1109
                                                {
 
1110
                                                        return;
 
1111
                                                }
 
1112
                                        }
 
1113
                                        break;
 
1114
 
 
1115
                                case frReject:
 
1116
                                        if (bMatch)
 
1117
                                        {
 
1118
                                                pFeedItemInfo->SetMatchStatus(FeedItemInfo::msRejected);
 
1119
                                                pFeedItemInfo->SetMatchRule(index);
 
1120
                                                return;
 
1121
                                        }
 
1122
                                        break;
 
1123
 
 
1124
                                case frRequire:
 
1125
                                        if (!bMatch)
 
1126
                                        {
 
1127
                                                pFeedItemInfo->SetMatchStatus(FeedItemInfo::msRejected);
 
1128
                                                pFeedItemInfo->SetMatchRule(index);
 
1129
                                                return;
 
1130
                                        }
 
1131
                                        break;
 
1132
 
 
1133
                                case frComment:
 
1134
                                        break;
 
1135
                        }
 
1136
                }
 
1137
        }
 
1138
 
 
1139
        pFeedItemInfo->SetMatchStatus(FeedItemInfo::msIgnored);
 
1140
        pFeedItemInfo->SetMatchRule(0);
 
1141
}
 
1142
 
 
1143
void FeedFilter::ApplyOptions(Rule* pRule, FeedItemInfo* pFeedItemInfo)
 
1144
{
 
1145
        if (pRule->HasPause())
 
1146
        {
 
1147
                pFeedItemInfo->SetPauseNzb(pRule->GetPause());
 
1148
        }
 
1149
        if (pRule->HasCategory())
 
1150
        {
 
1151
                pFeedItemInfo->SetAddCategory(pRule->GetCategory());
 
1152
        }
 
1153
        if (pRule->HasPriority())
 
1154
        {
 
1155
                pFeedItemInfo->SetPriority(pRule->GetPriority());
 
1156
        }
 
1157
        if (pRule->HasAddPriority())
 
1158
        {
 
1159
                pFeedItemInfo->SetPriority(pFeedItemInfo->GetPriority() + pRule->GetAddPriority());
 
1160
        }
 
1161
        if (pRule->HasDupeScore())
 
1162
        {
 
1163
                pFeedItemInfo->SetDupeScore(pRule->GetDupeScore());
 
1164
        }
 
1165
        if (pRule->HasAddDupeScore())
 
1166
        {
 
1167
                pFeedItemInfo->SetDupeScore(pFeedItemInfo->GetDupeScore() + pRule->GetAddDupeScore());
 
1168
        }
 
1169
        if (pRule->HasRageId() || pRule->HasSeries())
 
1170
        {
 
1171
                pFeedItemInfo->BuildDupeKey(pRule->GetRageId(), pRule->GetSeries());
 
1172
        }
 
1173
        if (pRule->HasDupeKey())
 
1174
        {
 
1175
                pFeedItemInfo->SetDupeKey(pRule->GetDupeKey());
 
1176
        }
 
1177
        if (pRule->HasAddDupeKey())
 
1178
        {
 
1179
                pFeedItemInfo->AppendDupeKey(pRule->GetAddDupeKey());
 
1180
        }
 
1181
        if (pRule->HasDupeMode())
 
1182
        {
 
1183
                pFeedItemInfo->SetDupeMode(pRule->GetDupeMode());
 
1184
        }
 
1185
}