Merge pull request #4676 from jmarshallnz/dont_set_scraper_on_tvshow_on_nfo
[vuplus_xbmc] / xbmc / utils / RegExp.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://xbmc.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, or (at your option)
8  *  any later version.
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 XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include <stdlib.h>
22 #include <string.h>
23 #include <algorithm> 
24 #include "RegExp.h"
25 #include "StdString.h"
26 #include "log.h"
27 #include "utils/StringUtils.h"
28 #include "utils/Utf8Utils.h"
29
30 using namespace PCRE;
31
32 #ifndef PCRE_UCP
33 #define PCRE_UCP 0
34 #endif // PCRE_UCP
35
36 #ifdef PCRE_CONFIG_JIT
37 #define PCRE_HAS_JIT_CODE 1
38 #endif
39
40 #ifndef PCRE_STUDY_JIT_COMPILE
41 #define PCRE_STUDY_JIT_COMPILE 0
42 #endif
43 #ifndef PCRE_INFO_JIT
44 // some unused number
45 #define PCRE_INFO_JIT 2048
46 #endif
47 #ifndef PCRE_HAS_JIT_CODE
48 #define pcre_free_study(x) pcre_free((x))
49 #endif
50
51 int CRegExp::m_Utf8Supported = -1;
52 int CRegExp::m_UcpSupported  = -1;
53 int CRegExp::m_JitSupported  = -1;
54
55
56 CRegExp::CRegExp(bool caseless /*= false*/, CRegExp::utf8Mode utf8 /*= asciiOnly*/)
57 {
58   InitValues(caseless, utf8);
59 }
60
61 void CRegExp::InitValues(bool caseless /*= false*/, CRegExp::utf8Mode utf8 /*= asciiOnly*/)
62 {
63   m_utf8Mode    = utf8;
64   m_re          = NULL;
65   m_sd          = NULL;
66   m_iOptions    = PCRE_DOTALL | PCRE_NEWLINE_ANY;
67   if(caseless)
68     m_iOptions |= PCRE_CASELESS;
69   if (m_utf8Mode == forceUtf8)
70   {
71     if (IsUtf8Supported())
72       m_iOptions |= PCRE_UTF8;
73     if (AreUnicodePropertiesSupported())
74       m_iOptions |= PCRE_UCP;
75   }
76
77   m_offset      = 0;
78   m_jitCompiled = false;
79   m_bMatched    = false;
80   m_iMatchCount = 0;
81   m_jitStack    = NULL;
82
83   memset(m_iOvector, 0, sizeof(m_iOvector));
84 }
85
86 CRegExp::CRegExp(bool caseless, CRegExp::utf8Mode utf8, const char *re, studyMode study /*= NoStudy*/)
87 {
88   if (utf8 == autoUtf8)
89     utf8 = requireUtf8(re) ? forceUtf8 : asciiOnly;
90
91   InitValues(caseless, utf8);
92   RegComp(re, study);
93 }
94
95 bool CRegExp::requireUtf8(const std::string& regexp)
96 {
97   // enable UTF-8 mode if regexp string has UTF-8 multibyte sequences
98   if (CUtf8Utils::checkStrForUtf8(regexp) == CUtf8Utils::utf8string)
99     return true;
100
101   // check for explicit Unicode Properties (\p, \P, \X) and for Unicode character codes (greater than 0xFF) in form \x{hhh..}
102   // note: PCRE change meaning of \w, \s, \d (and \W, \S, \D) when Unicode Properties are enabled,
103   //       but in auto mode we enable UNP for US-ASCII regexp only if regexp contains explicit \p, \P, \X or Unicode character code
104   const char* const regexpC = regexp.c_str();
105   const size_t len = regexp.length();
106   size_t pos = 0;
107
108   while (pos < len)
109   {
110     const char chr = regexpC[pos];
111     if (chr == '\\')
112     {
113       const char nextChr = regexpC[pos + 1];
114
115       if (nextChr == 'p' || nextChr == 'P' || nextChr == 'X')
116         return true; // found Unicode Properties
117       else if (nextChr == 'Q')
118         pos = regexp.find("\\E", pos + 2); // skip all literals in "\Q...\E"
119       else if (nextChr == 'x' && regexpC[pos + 2] == '{')
120       { // Unicode character with hex code
121         if (readCharXCode(regexp, pos) >= 0x100)
122           return true; // found Unicode character code
123       }
124       else if (nextChr == '\\' || nextChr == '(' || nextChr == ')'
125                || nextChr == '[' || nextChr == ']')
126                pos++; // exclude next character from analyze
127
128     } // chr != '\\'
129     else if (chr == '(' && regexpC[pos + 1] == '?' && regexpC[pos + 2] == '#') // comment in regexp
130       pos = regexp.find(')', pos); // skip comment
131     else if (chr == '[')
132     {
133       if (isCharClassWithUnicode(regexp, pos))
134         return true;
135     }
136
137     if (pos == std::string::npos) // check results of regexp.find() and isCharClassWithUnicode
138       return false;
139
140     pos++;
141   }
142
143   // no Unicode Properties was found
144   return false;
145 }
146
147 inline int CRegExp::readCharXCode(const std::string& regexp, size_t& pos)
148 {
149   // read hex character code in form "\x{hh..}"
150   // 'pos' must point to '\'
151   if (pos >= regexp.length())
152     return -1;
153   const char* const regexpC = regexp.c_str();
154   if (regexpC[pos] != '\\' || regexpC[pos + 1] != 'x' || regexpC[pos + 2] != '{')
155     return -1;
156
157   pos++;
158   const size_t startPos = pos; // 'startPos' points to 'x'
159   const size_t closingBracketPos = regexp.find('}', startPos + 2);
160   if (closingBracketPos == std::string::npos)
161     return 0; // return character zero code, leave 'pos' at 'x'
162
163   pos++; // 'pos' points to '{'
164   int chCode = 0;
165   while (++pos < closingBracketPos)
166   {
167     const int xdigitVal = StringUtils::asciixdigitvalue(regexpC[pos]);
168     if (xdigitVal >= 0)
169       chCode = chCode * 16 + xdigitVal;
170     else
171     { // found non-hexdigit
172       pos = startPos; // reset 'pos' to 'startPos', process "{hh..}" as non-code
173       return 0; // return character zero code
174     }
175   }
176
177   return chCode;
178 }
179
180 bool CRegExp::isCharClassWithUnicode(const std::string& regexp, size_t& pos)
181 {
182   const char* const regexpC = regexp.c_str();
183   const size_t len = regexp.length();
184   if (pos > len || regexpC[pos] != '[')
185     return false;
186
187   // look for Unicode character code "\x{hhh..}" and Unicode properties "\P", "\p" and "\X"
188   // find end (terminating ']') of character class (like "[a-h45]")
189   // detect nested POSIX classes like "[[:lower:]]" and escaped brackets like "[\]]"
190   bool needUnicode = false;
191   while (++pos < len)
192   {
193     if (regexpC[pos] == '[' && regexpC[pos + 1] == ':')
194     { // possible POSIX character class, like "[:alpha:]"
195       const size_t nextClosingBracketPos = regexp.find(']', pos + 2); // don't care about "\]", as it produce error if used inside POSIX char class
196
197       if (nextClosingBracketPos == std::string::npos)
198       { // error in regexp: no closing ']' for character class
199         pos = std::string::npos;
200         return needUnicode;
201       }
202       else if (regexpC[nextClosingBracketPos - 1] == ':')
203         pos = nextClosingBracketPos; // skip POSIX character class
204       // if ":]" is not found, process "[:..." as part of normal character class
205     }
206     else if (regexpC[pos] == ']')
207       return needUnicode; // end of character class
208     else if (regexpC[pos] == '\\')
209     {
210       const char nextChar = regexpC[pos + 1];
211       if (nextChar == ']' || nextChar == '[')
212         pos++; // skip next character
213       else if (nextChar == 'Q')
214       {
215         pos = regexp.find("\\E", pos + 2);
216         if (pos == std::string::npos)
217           return needUnicode; // error in regexp: no closing "\E" after "\Q" in character class
218         else
219           pos++; // skip "\E"
220       }
221       else if (nextChar == 'p' || nextChar == 'P' || nextChar == 'X')
222         needUnicode = true; // don't care about property name as it can contain only ASCII chars
223       else if (nextChar == 'x')
224       {
225         if (readCharXCode(regexp, pos) >= 0x100)
226           needUnicode = true;
227       }
228     }
229   }
230   pos = std::string::npos; // closing square bracket was not found
231
232   return needUnicode;
233 }
234
235
236 CRegExp::CRegExp(const CRegExp& re)
237 {
238   m_re = NULL;
239   m_sd = NULL;
240   m_jitStack = NULL;
241   m_utf8Mode = re.m_utf8Mode;
242   m_iOptions = re.m_iOptions;
243   *this = re;
244 }
245
246 const CRegExp& CRegExp::operator=(const CRegExp& re)
247 {
248   size_t size;
249   Cleanup();
250   m_jitCompiled = false;
251   m_pattern = re.m_pattern;
252   if (re.m_re)
253   {
254     if (pcre_fullinfo(re.m_re, NULL, PCRE_INFO_SIZE, &size) >= 0)
255     {
256       if ((m_re = (pcre*)malloc(size)))
257       {
258         memcpy(m_re, re.m_re, size);
259         memcpy(m_iOvector, re.m_iOvector, OVECCOUNT*sizeof(int));
260         m_offset = re.m_offset;
261         m_iMatchCount = re.m_iMatchCount;
262         m_bMatched = re.m_bMatched;
263         m_subject = re.m_subject;
264         m_iOptions = re.m_iOptions;
265       }
266       else
267         CLog::Log(LOGSEVERE, "%s: Failed to allocate memory", __FUNCTION__);
268     }
269   }
270   return *this;
271 }
272
273 CRegExp::~CRegExp()
274 {
275   Cleanup();
276 }
277
278 bool CRegExp::RegComp(const char *re, studyMode study /*= NoStudy*/)
279 {
280   if (!re)
281     return false;
282
283   m_offset           = 0;
284   m_jitCompiled      = false;
285   m_bMatched         = false;
286   m_iMatchCount      = 0;
287   const char *errMsg = NULL;
288   int errOffset      = 0;
289   int options        = m_iOptions;
290   if (m_utf8Mode == autoUtf8 && requireUtf8(re))
291     options |= (IsUtf8Supported() ? PCRE_UTF8 : 0) | (AreUnicodePropertiesSupported() ? PCRE_UCP : 0);
292
293   Cleanup();
294
295   m_re = pcre_compile(re, options, &errMsg, &errOffset, NULL);
296   if (!m_re)
297   {
298     m_pattern.clear();
299     CLog::Log(LOGERROR, "PCRE: %s. Compilation failed at offset %d in expression '%s'",
300               errMsg, errOffset, re);
301     return false;
302   }
303
304   m_pattern = re;
305
306   if (study)
307   {
308     const bool jitCompile = (study == StudyWithJitComp) && IsJitSupported();
309     const int studyOptions = jitCompile ? PCRE_STUDY_JIT_COMPILE : 0;
310
311     m_sd = pcre_study(m_re, studyOptions, &errMsg);
312     if (errMsg != NULL)
313     {
314       CLog::Log(LOGWARNING, "%s: PCRE error \"%s\" while studying expression", __FUNCTION__, errMsg);
315       if (m_sd != NULL)
316       {
317         pcre_free_study(m_sd);
318         m_sd = NULL;
319       }
320     }
321     else if (jitCompile)
322     {
323       int jitPresent = 0;
324       m_jitCompiled = (pcre_fullinfo(m_re, m_sd, PCRE_INFO_JIT, &jitPresent) == 0 && jitPresent == 1);
325     }
326   }
327
328   return true;
329 }
330
331 int CRegExp::RegFind(const char *str, unsigned int startoffset /*= 0*/, int maxNumberOfCharsToTest /*= -1*/)
332 {
333   return PrivateRegFind(strlen(str), str, startoffset, maxNumberOfCharsToTest);
334 }
335
336 int CRegExp::PrivateRegFind(size_t bufferLen, const char *str, unsigned int startoffset /* = 0*/, int maxNumberOfCharsToTest /*= -1*/)
337 {
338   m_offset      = 0;
339   m_bMatched    = false;
340   m_iMatchCount = 0;
341
342   if (!m_re)
343   {
344     CLog::Log(LOGERROR, "PCRE: Called before compilation");
345     return -1;
346   }
347
348   if (!str)
349   {
350     CLog::Log(LOGERROR, "PCRE: Called without a string to match");
351     return -1;
352   } 
353
354   if (startoffset > bufferLen)
355   {
356     CLog::Log(LOGERROR, "%s: startoffset is beyond end of string to match", __FUNCTION__);
357     return -1;
358   }
359
360 #ifdef PCRE_HAS_JIT_CODE
361   if (m_jitCompiled && !m_jitStack)
362   {
363     m_jitStack = pcre_jit_stack_alloc(32*1024, 512*1024);
364     if (m_jitStack == NULL)
365       CLog::Log(LOGWARNING, "%s: can't allocate address space for JIT stack", __FUNCTION__);
366
367     pcre_assign_jit_stack(m_sd, NULL, m_jitStack);
368   }
369 #endif
370
371   if (maxNumberOfCharsToTest >= 0)
372     bufferLen = std::min<size_t>(bufferLen, startoffset + maxNumberOfCharsToTest);
373
374   m_subject.assign(str + startoffset, bufferLen - startoffset);
375   int rc = pcre_exec(m_re, NULL, m_subject.c_str(), m_subject.length(), 0, 0, m_iOvector, OVECCOUNT);
376
377   if (rc<1)
378   {
379     static const int fragmentLen = 80; // length of excerpt before erroneous char for log
380     switch(rc)
381     {
382     case PCRE_ERROR_NOMATCH:
383       return -1;
384
385     case PCRE_ERROR_MATCHLIMIT:
386       CLog::Log(LOGERROR, "PCRE: Match limit reached");
387       return -1;
388
389 #ifdef PCRE_ERROR_SHORTUTF8 
390     case PCRE_ERROR_SHORTUTF8:
391       {
392         const size_t startPos = (m_subject.length() > fragmentLen) ? CUtf8Utils::RFindValidUtf8Char(m_subject, m_subject.length() - fragmentLen) : 0;
393         if (startPos != std::string::npos)
394           CLog::Log(LOGERROR, "PCRE: Bad UTF-8 character at the end of string. Text before bad character: \"%s\"", m_subject.substr(startPos).c_str());
395         else
396           CLog::Log(LOGERROR, "PCRE: Bad UTF-8 character at the end of string");
397         return -1;
398       }
399 #endif
400     case PCRE_ERROR_BADUTF8:
401       {
402         const size_t startPos = (m_iOvector[0] > fragmentLen) ? CUtf8Utils::RFindValidUtf8Char(m_subject, m_iOvector[0] - fragmentLen) : 0;
403         if (m_iOvector[0] >= 0 && startPos != std::string::npos)
404           CLog::Log(LOGERROR, "PCRE: Bad UTF-8 character, error code: %d, position: %d. Text before bad char: \"%s\"", m_iOvector[1], m_iOvector[0], m_subject.substr(startPos, m_iOvector[0] - startPos + 1).c_str());
405         else
406           CLog::Log(LOGERROR, "PCRE: Bad UTF-8 character, error code: %d, position: %d", m_iOvector[1], m_iOvector[0]);
407         return -1;
408       }
409     case PCRE_ERROR_BADUTF8_OFFSET:
410       CLog::Log(LOGERROR, "PCRE: Offset is pointing to the middle of UTF-8 character");
411       return -1;
412
413     default:
414       CLog::Log(LOGERROR, "PCRE: Unknown error: %d", rc);
415       return -1;
416     }
417   }
418   m_offset = startoffset;
419   m_bMatched = true;
420   m_iMatchCount = rc;
421   return m_iOvector[0] + m_offset;
422 }
423
424 int CRegExp::GetCaptureTotal() const
425 {
426   int c = -1;
427   if (m_re)
428     pcre_fullinfo(m_re, NULL, PCRE_INFO_CAPTURECOUNT, &c);
429   return c;
430 }
431
432 std::string CRegExp::GetReplaceString(const std::string& sReplaceExp) const
433 {
434   if (!m_bMatched || sReplaceExp.empty())
435     return "";
436
437   const char* const expr = sReplaceExp.c_str();
438
439   size_t pos = sReplaceExp.find_first_of("\\&");
440   std::string result(sReplaceExp, 0, pos);
441   result.reserve(sReplaceExp.size()); // very rough estimate
442
443   while(pos != std::string::npos)
444   {
445     if (expr[pos] == '\\')
446     {
447       // string is null-terminated and current char isn't null, so it's safe to advance to next char
448       pos++; // advance to next char
449       const char nextChar = expr[pos];
450       if (nextChar == '&' || nextChar == '\\')
451       { // this is "\&" or "\\" combination
452         result.push_back(nextChar); // add '&' or '\' to result 
453         pos++; 
454       }
455       else if (isdigit(nextChar))
456       { // this is "\0" - "\9" combination
457         int subNum = nextChar - '0';
458         pos++; // advance to second next char
459         const char secondNextChar = expr[pos];
460         if (isdigit(secondNextChar))
461         { // this is "\00" - "\99" combination
462           subNum = subNum * 10 + (secondNextChar - '0');
463           pos++;
464         }
465         result.append(GetMatch(subNum));
466       }
467     }
468     else
469     { // '&' char
470       result.append(GetMatch(0));
471       pos++;
472     }
473
474     const size_t nextPos = sReplaceExp.find_first_of("\\&", pos);
475     result.append(sReplaceExp, pos, nextPos - pos);
476     pos = nextPos;
477   }
478
479   return result;
480 }
481
482 int CRegExp::GetSubStart(int iSub) const
483 {
484   if (!IsValidSubNumber(iSub))
485     return -1;
486
487   return m_iOvector[iSub*2] + m_offset;
488 }
489
490 int CRegExp::GetSubStart(const std::string& subName) const
491 {
492   return GetSubStart(GetNamedSubPatternNumber(subName.c_str()));
493 }
494
495 int CRegExp::GetSubLength(int iSub) const
496 {
497   if (!IsValidSubNumber(iSub))
498     return -1;
499
500   return m_iOvector[(iSub*2)+1] - m_iOvector[(iSub*2)];
501 }
502
503 int CRegExp::GetSubLength(const std::string& subName) const
504 {
505   return GetSubLength(GetNamedSubPatternNumber(subName.c_str()));
506 }
507
508 std::string CRegExp::GetMatch(int iSub /* = 0 */) const
509 {
510   if (!IsValidSubNumber(iSub))
511     return "";
512
513   int pos = m_iOvector[(iSub*2)];
514   int len = m_iOvector[(iSub*2)+1] - pos;
515   if (pos < 0 || len <= 0)
516     return "";
517
518   return m_subject.substr(pos, len);
519 }
520
521 std::string CRegExp::GetMatch(const std::string& subName) const
522 {
523   return GetMatch(GetNamedSubPatternNumber(subName.c_str()));
524 }
525
526 bool CRegExp::GetNamedSubPattern(const char* strName, std::string& strMatch) const
527 {
528   strMatch.clear();
529   int iSub = pcre_get_stringnumber(m_re, strName);
530   if (!IsValidSubNumber(iSub))
531     return false;
532   strMatch = GetMatch(iSub);
533   return true;
534 }
535
536 int CRegExp::GetNamedSubPatternNumber(const char* strName) const
537 {
538   return pcre_get_stringnumber(m_re, strName);
539 }
540
541 void CRegExp::DumpOvector(int iLog /* = LOGDEBUG */)
542 {
543   if (iLog < LOGDEBUG || iLog > LOGNONE)
544     return;
545
546   CStdString str = "{";
547   int size = GetSubCount(); // past the subpatterns is junk
548   for (int i = 0; i <= size; i++)
549   {
550     CStdString t = StringUtils::Format("[%i,%i]", m_iOvector[(i*2)], m_iOvector[(i*2)+1]);
551     if (i != size)
552       t += ",";
553     str += t;
554   }
555   str += "}";
556   CLog::Log(iLog, "regexp ovector=%s", str.c_str());
557 }
558
559 void CRegExp::Cleanup()
560 {
561   if (m_re)
562   {
563     pcre_free(m_re); 
564     m_re = NULL; 
565   }
566
567   if (m_sd)
568   {
569     pcre_free_study(m_sd);
570     m_sd = NULL;
571   }
572
573 #ifdef PCRE_HAS_JIT_CODE
574   if (m_jitStack)
575   {
576     pcre_jit_stack_free(m_jitStack);
577     m_jitStack = NULL;
578   }
579 #endif
580 }
581
582 inline bool CRegExp::IsValidSubNumber(int iSub) const
583 {
584   return iSub >= 0 && iSub <= m_iMatchCount && iSub <= m_MaxNumOfBackrefrences;
585 }
586
587
588 bool CRegExp::IsUtf8Supported(void)
589 {
590   if (m_Utf8Supported == -1)
591   {
592     if (pcre_config(PCRE_CONFIG_UTF8, &m_Utf8Supported) != 0)
593       m_Utf8Supported = 0;
594   }
595
596   return m_Utf8Supported == 1;
597 }
598
599 bool CRegExp::AreUnicodePropertiesSupported(void)
600 {
601 #if defined(PCRE_CONFIG_UNICODE_PROPERTIES) && PCRE_UCP != 0
602   if (m_UcpSupported == -1)
603   {
604     if (pcre_config(PCRE_CONFIG_UNICODE_PROPERTIES, &m_UcpSupported) != 0)
605       m_UcpSupported = 0;
606   }
607 #endif
608
609   return m_UcpSupported == 1;
610 }
611
612 bool CRegExp::LogCheckUtf8Support(void)
613 {
614   bool utf8FullSupport = true;
615
616   if (!CRegExp::IsUtf8Supported())
617   {
618     utf8FullSupport = false;
619     CLog::Log(LOGWARNING, "UTF-8 is not supported in PCRE lib, support for national symbols is limited!");
620   }
621
622   if (!CRegExp::AreUnicodePropertiesSupported())
623   {
624     utf8FullSupport = false;
625     CLog::Log(LOGWARNING, "Unicode properties are not enabled in PCRE lib, support for national symbols may be limited!");
626   }
627
628   if (!utf8FullSupport)
629   {
630     CLog::Log(LOGNOTICE, "Consider installing PCRE lib version 8.10 or later with enabled Unicode properties and UTF-8 support. Your PCRE lib version: %s", PCRE::pcre_version());
631 #if PCRE_UCP == 0
632     CLog::Log(LOGNOTICE, "You will need to rebuild XBMC after PCRE lib update.");
633 #endif
634   }
635
636   return utf8FullSupport;
637 }
638
639 bool CRegExp::IsJitSupported(void)
640 {
641   if (m_JitSupported == -1)
642   {
643 #ifdef PCRE_HAS_JIT_CODE
644     if (pcre_config(PCRE_CONFIG_JIT, &m_JitSupported) != 0)
645 #endif
646       m_JitSupported = 0;
647   }
648
649   return m_JitSupported == 1;
650 }