1 /*************************************************************************
2  *
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * Copyright 2000, 2010 Oracle and/or its affiliates.
6  *
7  * OpenOffice.org - a multi-platform office productivity suite
8  *
9  * This file is part of OpenOffice.org.
10  *
11  * OpenOffice.org is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License version 3
13  * only, as published by the Free Software Foundation.
14  *
15  * OpenOffice.org is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License version 3 for more details
19  * (a copy is included in the LICENSE file that accompanied this code).
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * version 3 along with OpenOffice.org.  If not, see
23  * <http://www.openoffice.org/license.html>
24  * for a copy of the LGPLv3 License.
25  *
26  ************************************************************************/
27 
28 // MARKER(update_precomp.py): autogen include statement, do not remove
29 #include "precompiled_unotools.hxx"
30 
31 #include "unotools/configpathes.hxx"
32 #include <rtl/ustring.hxx>
33 #include <rtl/ustrbuf.hxx>
34 #include <osl/diagnose.h>
35 
36 //----------------------------------------------------------------------------
37 namespace utl
38 {
39 //----------------------------------------------------------------------------
40 
41     using ::rtl::OUString;
42     using ::rtl::OUStringBuffer;
43 
44 //----------------------------------------------------------------------------
45 
46 static
47 void lcl_resolveCharEntities(OUString & aLocalString)
48 {
49     sal_Int32 nEscapePos=aLocalString.indexOf('&');
50     if (nEscapePos < 0) return;
51 
52     OUStringBuffer aResult;
53     sal_Int32 nStart = 0;
54 
55     do
56     {
57         sal_Unicode ch = 0;
58         if (aLocalString.matchAsciiL(RTL_CONSTASCII_STRINGPARAM("&amp;"),nEscapePos))
59             ch = '&';
60 
61         else if (aLocalString.matchAsciiL(RTL_CONSTASCII_STRINGPARAM("&apos;"),nEscapePos))
62             ch = '\'';
63 
64         else if (aLocalString.matchAsciiL(RTL_CONSTASCII_STRINGPARAM("&quot;"),nEscapePos))
65             ch = '"';
66 
67         OSL_ENSURE(ch,"Configuration path contains '&' that is not part of a valid character escape");
68         if (ch)
69         {
70             aResult.append(aLocalString.copy(nStart,nEscapePos-nStart)).append(ch);
71 
72             sal_Int32 nEscapeEnd=aLocalString.indexOf(';',nEscapePos);
73             nStart = nEscapeEnd+1;
74             nEscapePos=aLocalString.indexOf('&',nStart);
75         }
76         else
77         {
78             nEscapePos=aLocalString.indexOf('&',nEscapePos+1);
79         }
80     }
81     while ( nEscapePos > 0);
82 
83     aResult.append(aLocalString.copy(nStart));
84 
85     aLocalString = aResult.makeStringAndClear();
86 }
87 
88 //----------------------------------------------------------------------------
89 sal_Bool splitLastFromConfigurationPath(OUString const& _sInPath,
90                                         OUString& _rsOutPath,
91                                         OUString& _rsLocalName)
92 {
93     sal_Int32 nStart,nEnd;
94 
95     sal_Int32 nPos = _sInPath.getLength()-1;
96 
97     // strip trailing slash
98     if (nPos > 0 && _sInPath[ nPos ] == sal_Unicode('/'))
99     {
100         OSL_ENSURE(false, "Invalid config path: trailing '/' is not allowed");
101         --nPos;
102     }
103 
104     // check for predicate ['xxx'] or ["yyy"]
105     if (nPos  > 0 && _sInPath[ nPos ] == sal_Unicode(']'))
106     {
107         sal_Unicode chQuote = _sInPath[--nPos];
108 
109         if (chQuote == '\'' || chQuote == '\"')
110         {
111             nEnd = nPos;
112             nPos = _sInPath.lastIndexOf(chQuote,nEnd);
113             nStart = nPos + 1;
114             --nPos; // nPos = rInPath.lastIndexOf('[',nPos);
115         }
116         else // allow [xxx]
117         {
118             nEnd = nPos + 1;
119             nPos = _sInPath.lastIndexOf('[',nEnd);
120             nStart = nPos + 1;
121         }
122 
123         OSL_ENSURE(nPos >= 0 && _sInPath[nPos] == '[', "Invalid config path: unmatched quotes or brackets");
124         if (nPos >= 0 && _sInPath[nPos] == '[')
125         {
126             nPos =  _sInPath.lastIndexOf('/',nPos);
127         }
128         else // defined behavior for invalid pathes
129         {
130             nStart = 0, nEnd = _sInPath.getLength();
131             nPos = -1;
132         }
133 
134     }
135     else
136     {
137         nEnd = nPos+1;
138         nPos = _sInPath.lastIndexOf('/',nEnd);
139         nStart = nPos + 1;
140     }
141     OSL_ASSERT( -1 <= nPos &&
142                 nPos < nStart &&
143                 nStart < nEnd &&
144                 nEnd <= _sInPath.getLength() );
145 
146     OSL_ASSERT(nPos == -1 || _sInPath[nPos] == '/');
147     OSL_ENSURE(nPos != 0 , "Invalid config child path: immediate child of root");
148 
149     _rsLocalName = _sInPath.copy(nStart, nEnd-nStart);
150     _rsOutPath = (nPos > 0) ? _sInPath.copy(0,nPos) : OUString();
151     lcl_resolveCharEntities(_rsLocalName);
152 
153     return nPos >= 0;
154 }
155 
156 //----------------------------------------------------------------------------
157 OUString extractFirstFromConfigurationPath(OUString const& _sInPath, OUString* _sOutPath)
158 {
159     sal_Int32 nSep      = _sInPath.indexOf('/');
160     sal_Int32 nBracket  = _sInPath.indexOf('[');
161 
162     sal_Int32 nStart    = nBracket + 1;
163     sal_Int32 nEnd      = nSep;
164 
165     if (0 <= nBracket) // found a bracket-quoted relative path
166     {
167         if (nSep < 0 || nBracket < nSep) // and the separator comes after it
168         {
169             sal_Unicode chQuote = _sInPath[nStart];
170             if (chQuote == '\'' || chQuote == '\"')
171             {
172                 ++nStart;
173                 nEnd      = _sInPath.indexOf(chQuote, nStart+1);
174                 nBracket  = nEnd+1;
175             }
176             else
177             {
178                 nEnd = _sInPath.indexOf(']',nStart);
179                 nBracket = nEnd;
180             }
181             OSL_ENSURE(nEnd > nStart && _sInPath[nBracket] == ']', "Invalid config path: improper mismatch of quote or bracket");
182             OSL_ENSURE((nBracket+1 == _sInPath.getLength() && nSep == -1) || (_sInPath[nBracket+1] == '/' && nSep == nBracket+1), "Invalid config path: brackets not followed by slash");
183         }
184         else // ... but our initial element name is in simple form
185             nStart = 0;
186     }
187 
188     OUString sResult = (nEnd >= 0) ? _sInPath.copy(nStart, nEnd-nStart) : _sInPath;
189     lcl_resolveCharEntities(sResult);
190 
191     if (_sOutPath != 0)
192     {
193         *_sOutPath = (nSep >= 0) ? _sInPath.copy(nSep + 1) : OUString();
194     }
195 
196     return sResult;
197 }
198 
199 //----------------------------------------------------------------------------
200 
201 // find the position after the prefix in the nested path
202 static inline
203 sal_Int32 lcl_findPrefixEnd(OUString const& _sNestedPath, OUString const& _sPrefixPath)
204 {
205     // TODO: currently handles only exact prefix matches
206     sal_Int32 nPrefixLength = _sPrefixPath.getLength();
207 
208     OSL_ENSURE(nPrefixLength == 0 || _sPrefixPath[nPrefixLength-1] != '/',
209                 "Cannot handle slash-terminated prefix pathes");
210 
211     sal_Bool bIsPrefix;
212     if (_sNestedPath.getLength() > nPrefixLength)
213     {
214         bIsPrefix = _sNestedPath[nPrefixLength] == '/' &&
215                     _sNestedPath.compareTo(_sPrefixPath,nPrefixLength) == 0;
216         ++nPrefixLength;
217     }
218     else if (_sNestedPath.getLength() == nPrefixLength)
219     {
220         bIsPrefix = _sNestedPath.equals(_sPrefixPath);
221     }
222     else
223     {
224         bIsPrefix = false;
225     }
226 
227     return bIsPrefix ? nPrefixLength : 0;
228 }
229 
230 //----------------------------------------------------------------------------
231 sal_Bool isPrefixOfConfigurationPath(OUString const& _sNestedPath,
232                                      OUString const& _sPrefixPath)
233 {
234     return _sPrefixPath.getLength() == 0 || lcl_findPrefixEnd(_sNestedPath,_sPrefixPath) != 0;
235 }
236 
237 //----------------------------------------------------------------------------
238 OUString dropPrefixFromConfigurationPath(OUString const& _sNestedPath,
239                                          OUString const& _sPrefixPath)
240 {
241     if ( sal_Int32 nPrefixEnd = lcl_findPrefixEnd(_sNestedPath,_sPrefixPath) )
242     {
243 	    return _sNestedPath.copy(nPrefixEnd);
244     }
245     else
246     {
247         OSL_ENSURE(_sPrefixPath.getLength() == 0,  "Path does not start with expected prefix");
248 
249         return _sNestedPath;
250     }
251 }
252 
253 //----------------------------------------------------------------------------
254 static
255 OUString lcl_wrapName(const OUString& _sContent, const OUString& _sType)
256 {
257     const sal_Unicode * const pBeginContent = _sContent.getStr();
258     const sal_Unicode * const pEndContent   = pBeginContent + _sContent.getLength();
259 
260     OSL_PRECOND(_sType.getLength(), "Unexpected config type name: empty");
261     OSL_PRECOND(pBeginContent <= pEndContent, "Invalid config name: empty");
262 
263     if (pBeginContent == pEndContent)
264         return _sType;
265 
266     rtl::OUStringBuffer aNormalized(_sType.getLength() + _sContent.getLength() + 4); // reserve approximate size initially
267 
268     // prefix: type, opening bracket and quote
269     aNormalized.append( _sType ).appendAscii( RTL_CONSTASCII_STRINGPARAM("['") );
270 
271     // content: copy over each char and handle escaping
272     for(const sal_Unicode* pCur = pBeginContent; pCur != pEndContent; ++pCur)
273     {
274         // append (escape if needed)
275         switch(*pCur)
276         {
277         case sal_Unicode('&') : aNormalized.appendAscii( RTL_CONSTASCII_STRINGPARAM("&amp;") ); break;
278         case sal_Unicode('\''): aNormalized.appendAscii( RTL_CONSTASCII_STRINGPARAM("&apos;") ); break;
279         case sal_Unicode('\"'): aNormalized.appendAscii( RTL_CONSTASCII_STRINGPARAM("&quot;") ); break;
280 
281         default: aNormalized.append( *pCur );
282         }
283     }
284 
285     // suffix: closing quote and bracket
286     aNormalized.appendAscii( RTL_CONSTASCII_STRINGPARAM("']") );
287 
288     return aNormalized.makeStringAndClear();
289 }
290 
291 //----------------------------------------------------------------------------
292 
293 OUString wrapConfigurationElementName(OUString const& _sElementName)
294 {
295     return lcl_wrapName(_sElementName, OUString(RTL_CONSTASCII_USTRINGPARAM("*")) );
296 }
297 
298 //----------------------------------------------------------------------------
299 
300 OUString wrapConfigurationElementName(OUString const& _sElementName,
301                                       OUString const& _sTypeName)
302 {
303     // todo: check that _sTypeName is valid
304     return lcl_wrapName(_sElementName, _sTypeName);
305 }
306 
307 //----------------------------------------------------------------------------
308 } // namespace utl
309