1626af730abf0f60b83e18b23d31960d1903cdb5
[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
28 using namespace PCRE;
29
30 #ifndef PCRE_UCP
31 #define PCRE_UCP 0
32 #endif // PCRE_UCP
33
34 #ifdef PCRE_CONFIG_JIT
35 #define PCRE_HAS_JIT_CODE 1
36 #endif
37
38 #ifndef PCRE_STUDY_JIT_COMPILE
39 #define PCRE_STUDY_JIT_COMPILE 0
40 #endif
41 #ifndef PCRE_INFO_JIT
42 // some unused number
43 #define PCRE_INFO_JIT 2048
44 #endif
45 #ifndef PCRE_HAS_JIT_CODE
46 #define pcre_free_study(x) pcre_free((x))
47 #endif
48
49 int CRegExp::m_Utf8Supported = -1;
50 int CRegExp::m_UcpSupported  = -1;
51 int CRegExp::m_JitSupported  = -1;
52
53
54 CRegExp::CRegExp(bool caseless /*= false*/, bool utf8 /*= false*/)
55 {
56   InitValues(caseless, utf8);
57 }
58
59 void CRegExp::InitValues(bool caseless /*= false*/, bool utf8 /*= false*/)
60 {
61   m_re          = NULL;
62   m_sd          = NULL;
63   m_iOptions    = PCRE_DOTALL | PCRE_NEWLINE_ANY;
64   if(caseless)
65     m_iOptions |= PCRE_CASELESS;
66   if (utf8)
67   {
68     if (IsUtf8Supported())
69       m_iOptions |= PCRE_UTF8;
70     if (AreUnicodePropertiesSupported())
71       m_iOptions |= PCRE_UCP;
72   }
73
74   m_offset      = 0;
75   m_jitCompiled = false;
76   m_bMatched    = false;
77   m_iMatchCount = 0;
78   m_jitStack    = NULL;
79
80   memset(m_iOvector, 0, sizeof(m_iOvector));
81 }
82
83 CRegExp::CRegExp(bool caseless, bool utf8, const char *re, studyMode study /*= NoStudy*/)
84 {
85   InitValues(caseless, utf8);
86   RegComp(re, study);
87 }
88
89 CRegExp::CRegExp(const CRegExp& re)
90 {
91   m_re = NULL;
92   m_sd = NULL;
93   m_jitStack = NULL;
94   m_iOptions = re.m_iOptions;
95   *this = re;
96 }
97
98 const CRegExp& CRegExp::operator=(const CRegExp& re)
99 {
100   size_t size;
101   Cleanup();
102   m_jitCompiled = false;
103   m_pattern = re.m_pattern;
104   if (re.m_re)
105   {
106     if (pcre_fullinfo(re.m_re, NULL, PCRE_INFO_SIZE, &size) >= 0)
107     {
108       if ((m_re = (pcre*)malloc(size)))
109       {
110         memcpy(m_re, re.m_re, size);
111         memcpy(m_iOvector, re.m_iOvector, OVECCOUNT*sizeof(int));
112         m_offset = re.m_offset;
113         m_iMatchCount = re.m_iMatchCount;
114         m_bMatched = re.m_bMatched;
115         m_subject = re.m_subject;
116         m_iOptions = re.m_iOptions;
117       }
118       else
119         CLog::Log(LOGSEVERE, "%s: Failed to allocate memory", __FUNCTION__);
120     }
121   }
122   return *this;
123 }
124
125 CRegExp::~CRegExp()
126 {
127   Cleanup();
128 }
129
130 bool CRegExp::RegComp(const char *re, studyMode study /*= NoStudy*/)
131 {
132   if (!re)
133     return false;
134
135   m_offset           = 0;
136   m_jitCompiled      = false;
137   m_bMatched         = false;
138   m_iMatchCount      = 0;
139   const char *errMsg = NULL;
140   int errOffset      = 0;
141
142   Cleanup();
143
144   m_re = pcre_compile(re, m_iOptions, &errMsg, &errOffset, NULL);
145   if (!m_re)
146   {
147     m_pattern.clear();
148     CLog::Log(LOGERROR, "PCRE: %s. Compilation failed at offset %d in expression '%s'",
149               errMsg, errOffset, re);
150     return false;
151   }
152
153   m_pattern = re;
154
155   if (study)
156   {
157     const bool jitCompile = (study == StudyWithJitComp) && IsJitSupported();
158     const int studyOptions = jitCompile ? PCRE_STUDY_JIT_COMPILE : 0;
159
160     m_sd = pcre_study(m_re, studyOptions, &errMsg);
161     if (errMsg != NULL)
162     {
163       CLog::Log(LOGWARNING, "%s: PCRE error \"%s\" while studying expression", __FUNCTION__, errMsg);
164       if (m_sd != NULL)
165       {
166         pcre_free_study(m_sd);
167         m_sd = NULL;
168       }
169     }
170     else if (jitCompile)
171     {
172       int jitPresent = 0;
173       m_jitCompiled = (pcre_fullinfo(m_re, m_sd, PCRE_INFO_JIT, &jitPresent) == 0 && jitPresent == 1);
174     }
175   }
176
177   return true;
178 }
179
180 int CRegExp::RegFind(const char *str, unsigned int startoffset /*= 0*/, int maxNumberOfCharsToTest /*= -1*/)
181 {
182   return PrivateRegFind(strlen(str), str, startoffset, maxNumberOfCharsToTest);
183 }
184
185 int CRegExp::PrivateRegFind(size_t bufferLen, const char *str, unsigned int startoffset /* = 0*/, int maxNumberOfCharsToTest /*= -1*/)
186 {
187   m_offset      = 0;
188   m_bMatched    = false;
189   m_iMatchCount = 0;
190
191   if (!m_re)
192   {
193     CLog::Log(LOGERROR, "PCRE: Called before compilation");
194     return -1;
195   }
196
197   if (!str)
198   {
199     CLog::Log(LOGERROR, "PCRE: Called without a string to match");
200     return -1;
201   } 
202
203   if (startoffset > bufferLen)
204   {
205     CLog::Log(LOGERROR, "%s: startoffset is beyond end of string to match", __FUNCTION__);
206     return -1;
207   }
208
209 #ifdef PCRE_HAS_JIT_CODE
210   if (m_jitCompiled && !m_jitStack)
211   {
212     m_jitStack = pcre_jit_stack_alloc(32*1024, 512*1024);
213     if (m_jitStack == NULL)
214       CLog::Log(LOGWARNING, "%s: can't allocate address space for JIT stack", __FUNCTION__);
215
216     pcre_assign_jit_stack(m_sd, NULL, m_jitStack);
217   }
218 #endif
219
220   if (maxNumberOfCharsToTest >= 0)
221     bufferLen = std::min<size_t>(bufferLen, startoffset + maxNumberOfCharsToTest);
222
223   m_subject.assign(str + startoffset, bufferLen - startoffset);
224   int rc = pcre_exec(m_re, NULL, m_subject.c_str(), m_subject.length(), 0, 0, m_iOvector, OVECCOUNT);
225
226   if (rc<1)
227   {
228     switch(rc)
229     {
230     case PCRE_ERROR_NOMATCH:
231       return -1;
232
233     case PCRE_ERROR_MATCHLIMIT:
234       CLog::Log(LOGERROR, "PCRE: Match limit reached");
235       return -1;
236
237 #ifdef PCRE_ERROR_SHORTUTF8 
238     case PCRE_ERROR_SHORTUTF8:
239 #endif
240     case PCRE_ERROR_BADUTF8:
241       CLog::Log(LOGERROR, "PCRE: Bad UTF-8 character");
242       return -1;
243
244     case PCRE_ERROR_BADUTF8_OFFSET:
245       CLog::Log(LOGERROR, "PCRE: Offset (%d) is pointing to the middle of UTF-8 character", startoffset);
246       return -1;
247
248     default:
249       CLog::Log(LOGERROR, "PCRE: Unknown error: %d", rc);
250       return -1;
251     }
252   }
253   m_offset = startoffset;
254   m_bMatched = true;
255   m_iMatchCount = rc;
256   return m_iOvector[0] + m_offset;
257 }
258
259 int CRegExp::GetCaptureTotal() const
260 {
261   int c = -1;
262   if (m_re)
263     pcre_fullinfo(m_re, NULL, PCRE_INFO_CAPTURECOUNT, &c);
264   return c;
265 }
266
267 std::string CRegExp::GetReplaceString(const std::string& sReplaceExp) const
268 {
269   if (!m_bMatched || sReplaceExp.empty())
270     return "";
271
272   const char* const expr = sReplaceExp.c_str();
273
274   size_t pos = sReplaceExp.find_first_of("\\&");
275   std::string result(sReplaceExp, 0, pos);
276   result.reserve(sReplaceExp.size()); // very rough estimate
277
278   while(pos != std::string::npos)
279   {
280     if (expr[pos] == '\\')
281     {
282       // string is null-terminated and current char isn't null, so it's safe to advance to next char
283       pos++; // advance to next char
284       const char nextChar = expr[pos];
285       if (nextChar == '&' || nextChar == '\\')
286       { // this is "\&" or "\\" combination
287         result.push_back(nextChar); // add '&' or '\' to result 
288         pos++; 
289       }
290       else if (isdigit(nextChar))
291       { // this is "\0" - "\9" combination
292         int subNum = nextChar - '0';
293         pos++; // advance to second next char
294         const char secondNextChar = expr[pos];
295         if (isdigit(secondNextChar))
296         { // this is "\00" - "\99" combination
297           subNum = subNum * 10 + (secondNextChar - '0');
298           pos++;
299         }
300         result.append(GetMatch(subNum));
301       }
302     }
303     else
304     { // '&' char
305       result.append(GetMatch(0));
306       pos++;
307     }
308
309     const size_t nextPos = sReplaceExp.find_first_of("\\&", pos);
310     result.append(sReplaceExp, pos, nextPos - pos);
311     pos = nextPos;
312   }
313
314   return result;
315 }
316
317 int CRegExp::GetSubStart(int iSub) const
318 {
319   if (!IsValidSubNumber(iSub))
320     return -1;
321
322   return m_iOvector[iSub*2] + m_offset;
323 }
324
325 int CRegExp::GetSubStart(const std::string& subName) const
326 {
327   return GetSubStart(GetNamedSubPatternNumber(subName.c_str()));
328 }
329
330 int CRegExp::GetSubLength(int iSub) const
331 {
332   if (!IsValidSubNumber(iSub))
333     return -1;
334
335   return m_iOvector[(iSub*2)+1] - m_iOvector[(iSub*2)];
336 }
337
338 int CRegExp::GetSubLength(const std::string& subName) const
339 {
340   return GetSubLength(GetNamedSubPatternNumber(subName.c_str()));
341 }
342
343 std::string CRegExp::GetMatch(int iSub /* = 0 */) const
344 {
345   if (!IsValidSubNumber(iSub))
346     return "";
347
348   int pos = m_iOvector[(iSub*2)];
349   int len = m_iOvector[(iSub*2)+1] - pos;
350   if (pos < 0 || len <= 0)
351     return "";
352
353   return m_subject.substr(pos, len);
354 }
355
356 std::string CRegExp::GetMatch(const std::string& subName) const
357 {
358   return GetMatch(GetNamedSubPatternNumber(subName.c_str()));
359 }
360
361 bool CRegExp::GetNamedSubPattern(const char* strName, std::string& strMatch) const
362 {
363   strMatch.clear();
364   int iSub = pcre_get_stringnumber(m_re, strName);
365   if (!IsValidSubNumber(iSub))
366     return false;
367   strMatch = GetMatch(iSub);
368   return true;
369 }
370
371 int CRegExp::GetNamedSubPatternNumber(const char* strName) const
372 {
373   return pcre_get_stringnumber(m_re, strName);
374 }
375
376 void CRegExp::DumpOvector(int iLog /* = LOGDEBUG */)
377 {
378   if (iLog < LOGDEBUG || iLog > LOGNONE)
379     return;
380
381   CStdString str = "{";
382   int size = GetSubCount(); // past the subpatterns is junk
383   for (int i = 0; i <= size; i++)
384   {
385     CStdString t;
386     t.Format("[%i,%i]", m_iOvector[(i*2)], m_iOvector[(i*2)+1]);
387     if (i != size)
388       t += ",";
389     str += t;
390   }
391   str += "}";
392   CLog::Log(iLog, "regexp ovector=%s", str.c_str());
393 }
394
395 void CRegExp::Cleanup()
396 {
397   if (m_re)
398   {
399     pcre_free(m_re); 
400     m_re = NULL; 
401   }
402
403   if (m_sd)
404   {
405     pcre_free_study(m_sd);
406     m_sd = NULL;
407   }
408
409 #ifdef PCRE_HAS_JIT_CODE
410   if (m_jitStack)
411   {
412     pcre_jit_stack_free(m_jitStack);
413     m_jitStack = NULL;
414   }
415 #endif
416 }
417
418 inline bool CRegExp::IsValidSubNumber(int iSub) const
419 {
420   return iSub >= 0 && iSub <= m_iMatchCount && iSub <= m_MaxNumOfBackrefrences;
421 }
422
423
424 bool CRegExp::IsUtf8Supported(void)
425 {
426   if (m_Utf8Supported == -1)
427   {
428     if (pcre_config(PCRE_CONFIG_UTF8, &m_Utf8Supported) != 0)
429       m_Utf8Supported = 0;
430   }
431
432   return m_Utf8Supported == 1;
433 }
434
435 bool CRegExp::AreUnicodePropertiesSupported(void)
436 {
437 #if defined(PCRE_CONFIG_UNICODE_PROPERTIES) && PCRE_UCP != 0
438   if (m_UcpSupported == -1)
439   {
440     if (pcre_config(PCRE_CONFIG_UNICODE_PROPERTIES, &m_UcpSupported) != 0)
441       m_UcpSupported = 0;
442   }
443 #endif
444
445   return m_UcpSupported == 1;
446 }
447
448 bool CRegExp::LogCheckUtf8Support(void)
449 {
450   bool utf8FullSupport = true;
451
452   if (!CRegExp::IsUtf8Supported())
453   {
454     utf8FullSupport = false;
455     CLog::Log(LOGWARNING, "UTF-8 is not supported in PCRE lib, support for national symbols is limited!");
456   }
457
458   if (!CRegExp::AreUnicodePropertiesSupported())
459   {
460     utf8FullSupport = false;
461     CLog::Log(LOGWARNING, "Unicode properties are not enabled in PCRE lib, support for national symbols may be limited!");
462   }
463
464   if (!utf8FullSupport)
465   {
466     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());
467 #if PCRE_UCP == 0
468     CLog::Log(LOGNOTICE, "You will need to rebuild XBMC after PCRE lib update.");
469 #endif
470   }
471
472   return utf8FullSupport;
473 }
474
475 bool CRegExp::IsJitSupported(void)
476 {
477   if (m_JitSupported == -1)
478   {
479 #ifdef PCRE_HAS_JIT_CODE
480     if (pcre_config(PCRE_CONFIG_JIT, &m_JitSupported) != 0)
481 #endif
482       m_JitSupported = 0;
483   }
484
485   return m_JitSupported == 1;
486 }