Line data Source code
1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /*
3 : * This file is part of the LibreOffice project.
4 : *
5 : * This Source Code Form is subject to the terms of the Mozilla Public
6 : * License, v. 2.0. If a copy of the MPL was not distributed with this
7 : * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 : *
9 : * This file incorporates work covered by the following license notice:
10 : *
11 : * Licensed to the Apache Software Foundation (ASF) under one or more
12 : * contributor license agreements. See the NOTICE file distributed
13 : * with this work for additional information regarding copyright
14 : * ownership. The ASF licenses this file to you under the Apache
15 : * License, Version 2.0 (the "License"); you may not use this file
16 : * except in compliance with the License. You may obtain a copy of
17 : * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 : */
19 :
20 : #include <regexp.hxx>
21 :
22 : #include <cstddef>
23 :
24 : #include "osl/diagnose.h"
25 : #include <com/sun/star/lang/IllegalArgumentException.hpp>
26 : #include <rtl/ustrbuf.hxx>
27 : #include <rtl/ustring.hxx>
28 : #include <comphelper/string.hxx>
29 :
30 : namespace unnamed_ucb_regexp {} using namespace unnamed_ucb_regexp;
31 : // unnamed namespaces don't work well yet...
32 :
33 : using namespace com::sun::star;
34 : using namespace ucb_impl;
35 :
36 :
37 :
38 : // Regexp
39 :
40 :
41 :
42 12118 : inline Regexp::Regexp(Kind eTheKind, OUString const & rThePrefix,
43 : bool bTheEmptyDomain, OUString const & rTheInfix,
44 : bool bTheTranslation,
45 : OUString const & rTheReversePrefix):
46 : m_eKind(eTheKind),
47 : m_aPrefix(rThePrefix),
48 : m_aInfix(rTheInfix),
49 : m_aReversePrefix(rTheReversePrefix),
50 : m_bEmptyDomain(bTheEmptyDomain),
51 12118 : m_bTranslation(bTheTranslation)
52 : {
53 : OSL_ASSERT(m_eKind == KIND_DOMAIN
54 : || (!m_bEmptyDomain && m_aInfix.isEmpty()));
55 : OSL_ASSERT(m_bTranslation || m_aReversePrefix.isEmpty());
56 12118 : }
57 :
58 :
59 : namespace unnamed_ucb_regexp {
60 :
61 1115138 : bool matchStringIgnoreCase(sal_Unicode const ** pBegin,
62 : sal_Unicode const * pEnd,
63 : OUString const & rString)
64 : {
65 1115138 : sal_Unicode const * p = *pBegin;
66 :
67 1115138 : sal_Unicode const * q = rString.getStr();
68 1115138 : sal_Unicode const * qEnd = q + rString.getLength();
69 :
70 1115138 : if (pEnd - p < qEnd - q)
71 257044 : return false;
72 :
73 5975213 : while (q != qEnd)
74 : {
75 4582160 : sal_Unicode c1 = *p++;
76 4582160 : sal_Unicode c2 = *q++;
77 4582160 : if (c1 >= 'a' && c1 <= 'z')
78 3689337 : c1 -= 'a' - 'A';
79 4582160 : if (c2 >= 'a' && c2 <= 'z')
80 3689853 : c2 -= 'a' - 'A';
81 4582160 : if (c1 != c2)
82 323135 : return false;
83 : }
84 :
85 534959 : *pBegin = p;
86 534959 : return true;
87 : }
88 :
89 : }
90 :
91 1115138 : bool Regexp::matches(OUString const & rString,
92 : OUString * pTranslation, bool * pTranslated) const
93 : {
94 1115138 : sal_Unicode const * pBegin = rString.getStr();
95 1115138 : sal_Unicode const * pEnd = pBegin + rString.getLength();
96 :
97 1115138 : bool bMatches = false;
98 :
99 1115138 : sal_Unicode const * p = pBegin;
100 1115138 : if (matchStringIgnoreCase(&p, pEnd, m_aPrefix))
101 : {
102 534959 : sal_Unicode const * pBlock1Begin = p;
103 534959 : sal_Unicode const * pBlock1End = pEnd;
104 :
105 534959 : sal_Unicode const * pBlock2Begin = 0;
106 534959 : sal_Unicode const * pBlock2End = 0;
107 :
108 534959 : switch (m_eKind)
109 : {
110 : case KIND_PREFIX:
111 534959 : bMatches = true;
112 534959 : break;
113 :
114 : case KIND_AUTHORITY:
115 0 : bMatches = p == pEnd || *p == '/' || *p == '?' || *p == '#';
116 0 : break;
117 :
118 : case KIND_DOMAIN:
119 0 : if (!m_bEmptyDomain)
120 : {
121 0 : if (p == pEnd || *p == '/' || *p == '?' || *p == '#')
122 : break;
123 0 : ++p;
124 : }
125 : for (;;)
126 : {
127 0 : sal_Unicode const * q = p;
128 0 : if (matchStringIgnoreCase(&q, pEnd, m_aInfix)
129 0 : && (q == pEnd || *q == '/' || *q == '?' || *q == '#'))
130 : {
131 0 : bMatches = true;
132 0 : pBlock1End = p;
133 0 : pBlock2Begin = q;
134 0 : pBlock2End = pEnd;
135 0 : break;
136 : }
137 :
138 0 : if (p == pEnd)
139 0 : break;
140 :
141 0 : sal_Unicode c = *p++;
142 0 : if (c == '/' || c == '?' || c == '#')
143 : break;
144 0 : }
145 0 : break;
146 : }
147 :
148 534959 : if (bMatches)
149 : {
150 534959 : if (m_bTranslation)
151 : {
152 0 : if (pTranslation)
153 : {
154 0 : OUStringBuffer aBuffer(m_aReversePrefix);
155 0 : aBuffer.append(pBlock1Begin, pBlock1End - pBlock1Begin);
156 0 : aBuffer.append(m_aInfix);
157 0 : aBuffer.append(pBlock2Begin, pBlock2End - pBlock2Begin);
158 0 : *pTranslation = aBuffer.makeStringAndClear();
159 : }
160 0 : if (pTranslated)
161 0 : *pTranslated = true;
162 : }
163 : else
164 : {
165 534959 : if (pTranslation)
166 0 : *pTranslation = rString;
167 534959 : if (pTranslated)
168 0 : *pTranslated = false;
169 : }
170 : }
171 : }
172 :
173 1115138 : return bMatches;
174 : }
175 :
176 :
177 : namespace unnamed_ucb_regexp {
178 :
179 17706 : bool isScheme(OUString const & rString, bool bColon)
180 : {
181 : using comphelper::string::isalphaAscii;
182 : using comphelper::string::isdigitAscii;
183 : // Return true if rString matches <scheme> (plus a trailing ":" if bColon
184 : // is true) from RFC 2396:
185 17706 : sal_Unicode const * p = rString.getStr();
186 17706 : sal_Unicode const * pEnd = p + rString.getLength();
187 17706 : if (p != pEnd && isalphaAscii(*p))
188 17338 : for (++p;;)
189 : {
190 148126 : if (p == pEnd)
191 11756 : return !bColon;
192 136370 : sal_Unicode c = *p++;
193 295100 : if (!(isalphaAscii(c) || isdigitAscii(c)
194 158730 : || c == '+' || c == '-' || c == '.'))
195 5582 : return bColon && c == ':' && p == pEnd;
196 130788 : }
197 368 : return false;
198 : }
199 :
200 0 : void appendStringLiteral(OUStringBuffer * pBuffer,
201 : OUString const & rString)
202 : {
203 : OSL_ASSERT(pBuffer);
204 :
205 0 : pBuffer->append('"');
206 0 : sal_Unicode const * p = rString.getStr();
207 0 : sal_Unicode const * pEnd = p + rString.getLength();
208 0 : while (p != pEnd)
209 : {
210 0 : sal_Unicode c = *p++;
211 0 : if (c == '"' || c == '\\')
212 0 : pBuffer->append('\\');
213 0 : pBuffer->append(c);
214 : }
215 0 : pBuffer->append('"');
216 0 : }
217 :
218 : }
219 :
220 5588 : OUString Regexp::getRegexp(bool bReverse) const
221 : {
222 5588 : if (m_bTranslation)
223 : {
224 0 : OUStringBuffer aBuffer;
225 0 : if (bReverse)
226 : {
227 0 : if (!m_aReversePrefix.isEmpty())
228 0 : appendStringLiteral(&aBuffer, m_aReversePrefix);
229 : }
230 : else
231 : {
232 0 : if (!m_aPrefix.isEmpty())
233 0 : appendStringLiteral(&aBuffer, m_aPrefix);
234 : }
235 0 : switch (m_eKind)
236 : {
237 : case KIND_PREFIX:
238 0 : aBuffer.append("(.*)");
239 0 : break;
240 :
241 : case KIND_AUTHORITY:
242 0 : aBuffer.append("(([/?#].*)?)");
243 0 : break;
244 :
245 : case KIND_DOMAIN:
246 0 : aBuffer.append("([^/?#]");
247 0 : aBuffer.append(sal_Unicode(m_bEmptyDomain ? '*' : '+'));
248 0 : if (!m_aInfix.isEmpty())
249 0 : appendStringLiteral(&aBuffer, m_aInfix);
250 0 : aBuffer.append("([/?#].*)?)");
251 0 : break;
252 : }
253 0 : aBuffer.append("->");
254 0 : if (bReverse)
255 : {
256 0 : if (!m_aPrefix.isEmpty())
257 0 : appendStringLiteral(&aBuffer, m_aPrefix);
258 : }
259 : else
260 : {
261 0 : if (!m_aReversePrefix.isEmpty())
262 0 : appendStringLiteral(&aBuffer, m_aReversePrefix);
263 : }
264 0 : aBuffer.append("\\1");
265 0 : return aBuffer.makeStringAndClear();
266 : }
267 5588 : else if (m_eKind == KIND_PREFIX && isScheme(m_aPrefix, true))
268 5582 : return m_aPrefix.copy(0, m_aPrefix.getLength() - 1);
269 : else
270 : {
271 6 : OUStringBuffer aBuffer;
272 6 : if (!m_aPrefix.isEmpty())
273 0 : appendStringLiteral(&aBuffer, m_aPrefix);
274 6 : switch (m_eKind)
275 : {
276 : case KIND_PREFIX:
277 6 : aBuffer.append(".*");
278 6 : break;
279 :
280 : case KIND_AUTHORITY:
281 0 : aBuffer.append("([/?#].*)?");
282 0 : break;
283 :
284 : case KIND_DOMAIN:
285 0 : aBuffer.append("[^/?#]");
286 0 : aBuffer.append( m_bEmptyDomain ? '*' : '+' );
287 0 : if (!m_aInfix.isEmpty())
288 0 : appendStringLiteral(&aBuffer, m_aInfix);
289 0 : aBuffer.append("([/?#].*)?");
290 0 : break;
291 : }
292 6 : return aBuffer.makeStringAndClear();
293 : }
294 : }
295 :
296 :
297 : namespace unnamed_ucb_regexp {
298 :
299 362 : bool matchString(sal_Unicode const ** pBegin, sal_Unicode const * pEnd,
300 : sal_Char const * pString, size_t nStringLength)
301 : {
302 362 : sal_Unicode const * p = *pBegin;
303 :
304 362 : unsigned char const * q = reinterpret_cast< unsigned char const * >(pString);
305 362 : unsigned char const * qEnd = q + nStringLength;
306 :
307 362 : if (pEnd - p < qEnd - q)
308 0 : return false;
309 :
310 1448 : while (q != qEnd)
311 : {
312 724 : sal_Unicode c1 = *p++;
313 724 : sal_Unicode c2 = *q++;
314 724 : if (c1 != c2)
315 0 : return false;
316 : }
317 :
318 362 : *pBegin = p;
319 362 : return true;
320 : }
321 :
322 362 : bool scanStringLiteral(sal_Unicode const ** pBegin, sal_Unicode const * pEnd,
323 : OUString * pString)
324 : {
325 362 : sal_Unicode const * p = *pBegin;
326 :
327 362 : if (p == pEnd || *p++ != '"')
328 362 : return false;
329 :
330 0 : OUStringBuffer aBuffer;
331 : for (;;)
332 : {
333 0 : if (p == pEnd)
334 0 : return false;
335 0 : sal_Unicode c = *p++;
336 0 : if (c == '"')
337 0 : break;
338 0 : if (c == '\\')
339 : {
340 0 : if (p == pEnd)
341 0 : return false;
342 0 : c = *p++;
343 0 : if (c != '"' && c != '\\')
344 0 : return false;
345 : }
346 0 : aBuffer.append(c);
347 0 : }
348 :
349 0 : *pBegin = p;
350 0 : *pString = aBuffer.makeStringAndClear();
351 0 : return true;
352 : }
353 :
354 : }
355 :
356 12118 : Regexp Regexp::parse(OUString const & rRegexp)
357 : {
358 : // Detect an input of '<scheme>' as an abbreviation of '"<scheme>:".*'
359 : // where <scheme> is as defined in RFC 2396:
360 12118 : if (isScheme(rRegexp, false))
361 : return Regexp(Regexp::KIND_PREFIX,
362 23512 : rRegexp + ":",
363 : false,
364 : OUString(),
365 : false,
366 35268 : OUString());
367 :
368 362 : sal_Unicode const * p = rRegexp.getStr();
369 362 : sal_Unicode const * pEnd = p + rRegexp.getLength();
370 :
371 362 : OUString aPrefix;
372 362 : scanStringLiteral(&p, pEnd, &aPrefix);
373 :
374 362 : if (p == pEnd)
375 0 : throw lang::IllegalArgumentException();
376 :
377 : // This and the matchString() calls below are some of the few places where
378 : // RTL_CONSTASCII_STRINGPARAM() should NOT be removed.
379 : // (c.f. https://gerrit.libreoffice.org/3117)
380 362 : if (matchString(&p, pEnd, RTL_CONSTASCII_STRINGPARAM(".*")))
381 : {
382 362 : if (p != pEnd)
383 0 : throw lang::IllegalArgumentException();
384 :
385 : return Regexp(Regexp::KIND_PREFIX, aPrefix, false, OUString(),
386 362 : false, OUString());
387 : }
388 0 : else if (matchString(&p, pEnd, RTL_CONSTASCII_STRINGPARAM("(.*)->")))
389 : {
390 0 : OUString aReversePrefix;
391 0 : scanStringLiteral(&p, pEnd, &aReversePrefix);
392 :
393 0 : if (!matchString(&p, pEnd, RTL_CONSTASCII_STRINGPARAM("\\1"))
394 0 : || p != pEnd)
395 0 : throw lang::IllegalArgumentException();
396 :
397 : return Regexp(Regexp::KIND_PREFIX, aPrefix, false, OUString(),
398 0 : true, aReversePrefix);
399 : }
400 0 : else if (matchString(&p, pEnd, RTL_CONSTASCII_STRINGPARAM("([/?#].*)?")))
401 : {
402 0 : if (p != pEnd)
403 0 : throw lang::IllegalArgumentException();
404 :
405 : return Regexp(Regexp::KIND_AUTHORITY, aPrefix, false, OUString(),
406 0 : false, OUString());
407 : }
408 0 : else if (matchString(&p, pEnd,
409 0 : RTL_CONSTASCII_STRINGPARAM("(([/?#].*)?)->")))
410 : {
411 0 : OUString aReversePrefix;
412 0 : if (!(scanStringLiteral(&p, pEnd, &aReversePrefix)
413 0 : && matchString(&p, pEnd, RTL_CONSTASCII_STRINGPARAM("\\1"))
414 0 : && p == pEnd))
415 0 : throw lang::IllegalArgumentException();
416 :
417 : return Regexp(Regexp::KIND_AUTHORITY, aPrefix, false, OUString(),
418 0 : true, aReversePrefix);
419 : }
420 : else
421 : {
422 0 : bool bOpen = false;
423 0 : if (p != pEnd && *p == '(')
424 : {
425 0 : ++p;
426 0 : bOpen = true;
427 : }
428 :
429 0 : if (!matchString(&p, pEnd, RTL_CONSTASCII_STRINGPARAM("[^/?#]")))
430 0 : throw lang::IllegalArgumentException();
431 :
432 0 : if (p == pEnd || (*p != '*' && *p != '+'))
433 0 : throw lang::IllegalArgumentException();
434 0 : bool bEmptyDomain = *p++ == '*';
435 :
436 0 : OUString aInfix;
437 0 : scanStringLiteral(&p, pEnd, &aInfix);
438 :
439 0 : if (!matchString(&p, pEnd, RTL_CONSTASCII_STRINGPARAM("([/?#].*)?")))
440 0 : throw lang::IllegalArgumentException();
441 :
442 0 : OUString aReversePrefix;
443 0 : if (bOpen
444 0 : && !(matchString(&p, pEnd, RTL_CONSTASCII_STRINGPARAM(")->"))
445 0 : && scanStringLiteral(&p, pEnd, &aReversePrefix)
446 0 : && matchString(&p, pEnd, RTL_CONSTASCII_STRINGPARAM("\\1"))))
447 0 : throw lang::IllegalArgumentException();
448 :
449 0 : if (p != pEnd)
450 0 : throw lang::IllegalArgumentException();
451 :
452 : return Regexp(Regexp::KIND_DOMAIN, aPrefix, bEmptyDomain, aInfix,
453 0 : bOpen, aReversePrefix);
454 362 : }
455 : }
456 :
457 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|