xref: /core/sal/osl/w32/file_url.cxx (revision ab9b67bb)
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 <sal/config.h>
21 #include <sal/log.hxx>
22 
23 #include <algorithm>
24 
25 #include <systools/win32/uwinapi.h>
26 
27 #include "file_url.hxx"
28 #include "file_error.hxx"
29 
30 #include <rtl/alloc.h>
31 #include <rtl/ustring.hxx>
32 #include <osl/mutex.h>
33 #include <o3tl/char16_t2wchar_t.hxx>
34 
35 #include "path_helper.hxx"
36 
37 #define WSTR_SYSTEM_ROOT_PATH               L"\\\\.\\"
38 #define WSTR_LONG_PATH_PREFIX               L"\\\\?\\"
39 #define WSTR_LONG_PATH_PREFIX_UNC           L"\\\\?\\UNC\\"
40 
41 // FileURL functions
42 
43 oslMutex g_CurrentDirectoryMutex = nullptr; /* Initialized in dllentry.c */
44 
45 static bool IsValidFilePathComponent(
46     sal_Unicode const * lpComponent, sal_Unicode const **lppComponentEnd,
47     DWORD dwFlags)
48 {
49         sal_Unicode const * lpComponentEnd = nullptr;
50         sal_Unicode const * lpCurrent = lpComponent;
51         bool    bValid = true;  /* Assume success */
52         sal_Unicode cLast = 0;
53 
54         /* Path component length must not exceed MAX_PATH even if long path with "\\?\" prefix is used */
55 
56         while ( !lpComponentEnd && lpCurrent && lpCurrent - lpComponent < MAX_PATH )
57         {
58             switch ( *lpCurrent )
59             {
60                 /* Both backslash and slash determine the end of a path component */
61             case '\0':
62             case '/':
63             case '\\':
64                 switch ( cLast )
65                 {
66                     /* Component must not end with '.' or blank and can't be empty */
67 
68                 case '.':
69                     if ( dwFlags & VALIDATEPATH_ALLOW_ELLIPSE )
70                     {
71                         if ( (dwFlags & VALIDATEPATH_ALLOW_INVALID_SPACE_AND_PERIOD) ||
72                              1 == lpCurrent - lpComponent )
73                         {
74                             /* Either do allow periods anywhere, or current directory */
75                             lpComponentEnd = lpCurrent;
76                             break;
77                         }
78                         else if ( 2 == lpCurrent - lpComponent && '.' == *lpComponent )
79                         {
80                             /* Parent directory is O.K. */
81                             lpComponentEnd = lpCurrent;
82                             break;
83                         }
84                     }
85                     [[fallthrough]];
86                 case 0:
87                 case ' ':
88                     if ( dwFlags & VALIDATEPATH_ALLOW_INVALID_SPACE_AND_PERIOD )
89                         lpComponentEnd = lpCurrent;
90                     else
91                     {
92                         lpComponentEnd = lpCurrent - 1;
93                         bValid = false;
94                     }
95                     break;
96                 default:
97                     lpComponentEnd = lpCurrent;
98                     break;
99                 }
100                 break;
101                 /* The following characters are reserved */
102             case '?':
103             case '*':
104             case '<':
105             case '>':
106             case '\"':
107             case '|':
108             case ':':
109                 lpComponentEnd = lpCurrent;
110                 bValid = false;
111                 break;
112             default:
113                 /* Characters below ASCII 32 are not allowed */
114                 if ( *lpCurrent < ' ' )
115                 {
116                     lpComponentEnd = lpCurrent;
117                     bValid = false;
118                 }
119                 break;
120             }
121             cLast = *lpCurrent++;
122         }
123 
124         /*  If we don't reached the end of the component the length of the component was to long
125             ( See condition of while loop ) */
126         if ( !lpComponentEnd )
127         {
128             bValid = false;
129             lpComponentEnd = lpCurrent;
130         }
131 
132         if ( bValid )
133         {
134             // Empty components are not allowed
135             if ( lpComponentEnd - lpComponent < 1 )
136                 bValid = false;
137 
138             // If we reached the end of the string nullptr is returned
139             else if ( !*lpComponentEnd )
140                 lpComponentEnd = nullptr;
141 
142         }
143 
144         if ( lppComponentEnd )
145             *lppComponentEnd = lpComponentEnd;
146 
147         return bValid;
148 }
149 
150 static sal_Int32 countInitialSeparators(sal_Unicode const * path) {
151     sal_Unicode const * p = path;
152     while (*p == '\\' || *p == '/') {
153         ++p;
154     }
155     return p - path;
156 }
157 
158 DWORD IsValidFilePath(rtl_uString *path, DWORD dwFlags, rtl_uString **corrected)
159 {
160         sal_Unicode const * lpszPath = path->buffer;
161         sal_Unicode const * lpComponent = lpszPath;
162         bool    bValid = true;
163         DWORD   dwPathType = PATHTYPE_ERROR;
164         sal_Int32 nLength = rtl_uString_getLength( path );
165 
166         if ( dwFlags & VALIDATEPATH_ALLOW_RELATIVE )
167             dwFlags |= VALIDATEPATH_ALLOW_ELLIPSE;
168 
169         DWORD   dwCandidatPathType = PATHTYPE_ERROR;
170 
171         if ( 0 == rtl_ustr_shortenedCompareIgnoreAsciiCase_WithLength( path->buffer, nLength, o3tl::toU(WSTR_LONG_PATH_PREFIX_UNC), SAL_N_ELEMENTS(WSTR_LONG_PATH_PREFIX_UNC) - 1, SAL_N_ELEMENTS(WSTR_LONG_PATH_PREFIX_UNC) - 1 ) )
172         {
173             /* This is long path in UNC notation */
174             lpComponent = lpszPath + SAL_N_ELEMENTS(WSTR_LONG_PATH_PREFIX_UNC) - 1;
175             dwCandidatPathType = PATHTYPE_ABSOLUTE_UNC | PATHTYPE_IS_LONGPATH;
176         }
177         else if ( 0 == rtl_ustr_shortenedCompareIgnoreAsciiCase_WithLength( path->buffer, nLength, o3tl::toU(WSTR_LONG_PATH_PREFIX), SAL_N_ELEMENTS(WSTR_LONG_PATH_PREFIX) - 1, SAL_N_ELEMENTS(WSTR_LONG_PATH_PREFIX) - 1 ) )
178         {
179             /* This is long path */
180             lpComponent = lpszPath + SAL_N_ELEMENTS(WSTR_LONG_PATH_PREFIX) - 1;
181 
182             if ( iswalpha( lpComponent[0] ) && ':' == lpComponent[1] )
183             {
184                 lpComponent += 2;
185                 dwCandidatPathType = PATHTYPE_ABSOLUTE_LOCAL | PATHTYPE_IS_LONGPATH;
186             }
187         }
188         else if ( 2 == countInitialSeparators( lpszPath ) )
189         {
190             /* The UNC path notation */
191             lpComponent = lpszPath + 2;
192             dwCandidatPathType = PATHTYPE_ABSOLUTE_UNC;
193         }
194         else if ( iswalpha( lpszPath[0] ) && ':' == lpszPath[1] )
195         {
196             /* Local path verification. Must start with <drive>: */
197             lpComponent = lpszPath + 2;
198             dwCandidatPathType = PATHTYPE_ABSOLUTE_LOCAL;
199         }
200 
201         if ( ( dwCandidatPathType & PATHTYPE_MASK_TYPE ) == PATHTYPE_ABSOLUTE_UNC )
202         {
203             bValid = IsValidFilePathComponent( lpComponent, &lpComponent, VALIDATEPATH_ALLOW_ELLIPSE );
204 
205             /* So far we have a valid servername. Now let's see if we also have a network resource */
206 
207             dwPathType = dwCandidatPathType;
208 
209             if ( bValid )
210             {
211                 if ( lpComponent &&  !*++lpComponent )
212                     lpComponent = nullptr;
213 
214                 if ( !lpComponent )
215                 {
216                     dwPathType |= PATHTYPE_IS_SERVER;
217                 }
218                 else
219                 {
220                     /* Now test the network resource */
221 
222                     bValid = IsValidFilePathComponent( lpComponent, &lpComponent, 0 );
223 
224                     /* If we now reached the end of the path, everything is O.K. */
225 
226                     if ( bValid && (!lpComponent || !*++lpComponent ) )
227                     {
228                         lpComponent = nullptr;
229                         dwPathType |= PATHTYPE_IS_VOLUME;
230                     }
231                 }
232             }
233         }
234         else if (  ( dwCandidatPathType & PATHTYPE_MASK_TYPE ) == PATHTYPE_ABSOLUTE_LOCAL )
235         {
236             if ( 1 == countInitialSeparators( lpComponent ) )
237                 lpComponent++;
238             else if ( *lpComponent )
239                 bValid = false;
240 
241             dwPathType = dwCandidatPathType;
242 
243             /* Now we are behind the backslash or it was a simple drive without backslash */
244 
245             if ( bValid && !*lpComponent )
246             {
247                 lpComponent = nullptr;
248                 dwPathType |= PATHTYPE_IS_VOLUME;
249             }
250         }
251         else if ( dwFlags & VALIDATEPATH_ALLOW_RELATIVE )
252         {
253             /* Can be a relative path */
254             lpComponent = lpszPath;
255 
256             /* Relative path can start with a backslash */
257 
258             if ( 1 == countInitialSeparators( lpComponent ) )
259             {
260                 lpComponent++;
261                 if ( !*lpComponent )
262                     lpComponent = nullptr;
263             }
264 
265             dwPathType = PATHTYPE_RELATIVE;
266         }
267         else
268         {
269             /* Anything else is an error */
270             bValid = false;
271             lpComponent = lpszPath;
272         }
273 
274         /* Now validate each component of the path */
275         rtl_uString * lastCorrected = path;
276         while ( bValid && lpComponent )
277         {
278             // Correct path by merging consecutive slashes:
279             if (*lpComponent == '\\' && corrected != nullptr) {
280                 sal_Int32 i = lpComponent - lpszPath;
281                 rtl_uString_newReplaceStrAt(corrected, lastCorrected, i, 1, nullptr);
282                     //TODO: handle out-of-memory
283                 lastCorrected = *corrected;
284                 lpszPath = (*corrected)->buffer;
285                 lpComponent = lpszPath + i;
286             }
287 
288             bValid = IsValidFilePathComponent( lpComponent, &lpComponent, dwFlags | VALIDATEPATH_ALLOW_INVALID_SPACE_AND_PERIOD);
289 
290             if ( bValid && lpComponent )
291             {
292                 lpComponent++;
293 
294                 /* If the string behind the backslash is empty, we've done */
295 
296                 if ( !*lpComponent )
297                     lpComponent = nullptr;
298             }
299         }
300 
301         /* The path can be longer than MAX_PATH only in case it has the longpath prefix */
302         if ( bValid && !( dwPathType &  PATHTYPE_IS_LONGPATH ) && rtl_ustr_getLength( lpszPath ) >= MAX_PATH )
303         {
304             bValid = false;
305         }
306 
307         return bValid ? dwPathType : PATHTYPE_ERROR;
308 }
309 
310 static sal_Int32 PathRemoveFileSpec(LPWSTR lpPath, LPWSTR lpFileName, sal_Int32 nFileBufLen )
311 {
312     sal_Int32 nRemoved = 0;
313 
314     if ( nFileBufLen )
315     {
316         lpFileName[0] = 0;
317         LPWSTR  lpLastBkSlash = wcsrchr( lpPath, '\\' );
318         LPWSTR  lpLastSlash = wcsrchr( lpPath, '/' );
319         LPWSTR  lpLastDelimiter = std::max(lpLastSlash, lpLastBkSlash);
320 
321         if ( lpLastDelimiter )
322         {
323                 sal_Int32 nDelLen = wcslen( lpLastDelimiter );
324                 if ( 1 == nDelLen )
325                 {
326                     if ( lpLastDelimiter > lpPath && *(lpLastDelimiter - 1) != ':' )
327                     {
328                         *lpLastDelimiter = 0;
329                         *lpFileName = 0;
330                         nRemoved = nDelLen;
331                     }
332                 }
333                 else if ( nDelLen && nDelLen - 1 < nFileBufLen )
334                 {
335                     wcscpy( lpFileName, lpLastDelimiter + 1 );
336                     *(++lpLastDelimiter) = 0;
337                     nRemoved = nDelLen - 1;
338                 }
339         }
340     }
341 
342     return nRemoved;
343 }
344 
345 // Undocumented in SHELL32.DLL ordinal 32
346 static LPWSTR PathAddBackslash(LPWSTR lpPath, sal_uInt32 nBufLen)
347 {
348     LPWSTR  lpEndPath = nullptr;
349 
350     if ( lpPath )
351     {
352             std::size_t nLen = wcslen(lpPath);
353 
354             if ( !nLen || ( lpPath[nLen-1] != '\\' && lpPath[nLen-1] != '/' && nLen < nBufLen - 1 ) )
355             {
356                 lpEndPath = lpPath + nLen;
357                 *lpEndPath++ = '\\';
358                 *lpEndPath = 0;
359             }
360     }
361     return lpEndPath;
362 }
363 
364 // Same as GetLongPathName but also 95/NT4
365 static DWORD GetCaseCorrectPathNameEx(
366     LPWSTR  lpszPath,   // path buffer to convert
367     sal_uInt32 cchBuffer,      // size of path buffer
368     DWORD   nSkipLevels,
369     bool bCheckExistence )
370 {
371         ::osl::LongPathBuffer< WCHAR > szFile( MAX_PATH + 1 );
372         sal_Int32 nRemoved = PathRemoveFileSpec( lpszPath, szFile, MAX_PATH + 1 );
373         sal_Int32 nLastStepRemoved = nRemoved;
374         while ( nLastStepRemoved && szFile[0] == 0 )
375         {
376             // remove separators
377             nLastStepRemoved = PathRemoveFileSpec( lpszPath, szFile, MAX_PATH + 1 );
378             nRemoved += nLastStepRemoved;
379         }
380 
381         if ( nRemoved )
382         {
383             bool bSkipThis = false;
384 
385             if ( 0 == wcscmp( szFile, L".." ) )
386             {
387                 bSkipThis = true;
388                 nSkipLevels += 1;
389             }
390             else if ( 0 == wcscmp( szFile, L"." ) )
391             {
392                 bSkipThis = true;
393             }
394             else if ( nSkipLevels )
395             {
396                 bSkipThis = true;
397                 nSkipLevels--;
398             }
399             else
400                 bSkipThis = false;
401 
402             if ( !GetCaseCorrectPathNameEx( lpszPath, cchBuffer, nSkipLevels, bCheckExistence ) )
403                 return 0;
404 
405             PathAddBackslash( lpszPath, cchBuffer );
406 
407             /* Analyze parent if not only a trailing backslash was cutted but a real file spec */
408             if ( !bSkipThis )
409             {
410                 if ( bCheckExistence )
411                 {
412                     ::osl::LongPathBuffer< WCHAR > aShortPath( MAX_LONG_PATH );
413                     wcscpy( aShortPath, lpszPath );
414                     wcscat( aShortPath, szFile );
415 
416                     WIN32_FIND_DATAW aFindFileData;
417                     HANDLE  hFind = FindFirstFileW( aShortPath, &aFindFileData );
418 
419                     if ( IsValidHandle(hFind) )
420                     {
421                         wcscat( lpszPath, aFindFileData.cFileName[0] ? aFindFileData.cFileName : aFindFileData.cAlternateFileName );
422 
423                         FindClose( hFind );
424                     }
425                     else
426                         lpszPath[0] = 0;
427                 }
428                 else
429                 {
430                     /* add the segment name back */
431                     wcscat( lpszPath, szFile );
432                 }
433             }
434         }
435         else
436         {
437             /* File specification can't be removed therefore the short path is either a drive
438                or a network share. If still levels to skip are left, the path specification
439                tries to travel below the file system root */
440             if ( nSkipLevels )
441                     lpszPath[0] = 0;
442             else
443                 _wcsupr( lpszPath );
444         }
445 
446         return wcslen( lpszPath );
447 }
448 
449 DWORD GetCaseCorrectPathName(
450     LPCWSTR lpszShortPath,  // file name
451     LPWSTR  lpszLongPath,   // path buffer
452     sal_uInt32 cchBuffer,      // size of path buffer
453     bool bCheckExistence
454 )
455 {
456     /* Special handling for "\\.\" as system root */
457     if ( lpszShortPath && 0 == wcscmp( lpszShortPath, WSTR_SYSTEM_ROOT_PATH ) )
458     {
459         if ( cchBuffer >= SAL_N_ELEMENTS(WSTR_SYSTEM_ROOT_PATH) )
460         {
461             wcscpy( lpszLongPath, WSTR_SYSTEM_ROOT_PATH );
462             return SAL_N_ELEMENTS(WSTR_SYSTEM_ROOT_PATH) - 1;
463         }
464         else
465         {
466             return SAL_N_ELEMENTS(WSTR_SYSTEM_ROOT_PATH) - 1;
467         }
468     }
469     else if ( lpszShortPath )
470     {
471         if ( wcslen( lpszShortPath ) <= cchBuffer )
472         {
473             wcscpy( lpszLongPath, lpszShortPath );
474             return GetCaseCorrectPathNameEx( lpszLongPath, cchBuffer, 0, bCheckExistence );
475         }
476     }
477 
478     return 0;
479 }
480 
481 static bool osl_decodeURL_( rtl_String* strUTF8, rtl_uString** pstrDecodedURL )
482 {
483     sal_Char        *pBuffer;
484     const sal_Char  *pSrcEnd;
485     const sal_Char  *pSrc;
486     sal_Char        *pDest;
487     sal_Int32       nSrcLen;
488     bool        bValidEncoded = true;   /* Assume success */
489 
490     /* The resulting decoded string length is shorter or equal to the source length */
491 
492     nSrcLen = rtl_string_getLength(strUTF8);
493     pBuffer = static_cast<sal_Char*>(malloc((nSrcLen + 1) * sizeof(sal_Char)));
494 
495     pDest = pBuffer;
496     pSrc = rtl_string_getStr(strUTF8);
497     pSrcEnd = pSrc + nSrcLen;
498 
499     /* Now decode the URL what should result in an UTF8 string */
500     while ( bValidEncoded && pSrc < pSrcEnd )
501     {
502         switch ( *pSrc )
503         {
504         case '%':
505             {
506                 sal_Char    aToken[3];
507                 sal_Char    aChar;
508 
509                 pSrc++;
510                 aToken[0] = *pSrc++;
511                 aToken[1] = *pSrc++;
512                 aToken[2] = 0;
513 
514                 aChar = static_cast<sal_Char>(strtoul( aToken, nullptr, 16 ));
515 
516                 /* The chars are path delimiters and must not be encoded */
517 
518                 if ( 0 == aChar || '\\' == aChar || '/' == aChar || ':' == aChar )
519                     bValidEncoded = false;
520                 else
521                     *pDest++ = aChar;
522             }
523             break;
524         case '\0':
525         case '#':
526         case '?':
527             bValidEncoded = false;
528             break;
529         default:
530             *pDest++ = *pSrc++;
531             break;
532         }
533     }
534 
535     *pDest++ = 0;
536 
537     if ( bValidEncoded )
538     {
539         rtl_string2UString( pstrDecodedURL, pBuffer, rtl_str_getLength(pBuffer), RTL_TEXTENCODING_UTF8, OSTRING_TO_OUSTRING_CVTFLAGS );
540         OSL_ASSERT(*pstrDecodedURL != nullptr);
541     }
542 
543     free( pBuffer );
544 
545     return bValidEncoded;
546 }
547 
548 static void osl_encodeURL_( rtl_uString *strURL, rtl_String **pstrEncodedURL )
549 {
550     /* Encode non ascii characters within the URL */
551 
552     rtl_String      *strUTF8 = nullptr;
553     sal_Char        *pszEncodedURL;
554     const sal_Char  *pURLScan;
555     sal_Char        *pURLDest;
556     sal_Int32       nURLScanLen;
557     sal_Int32       nURLScanCount;
558 
559     rtl_uString2String( &strUTF8, rtl_uString_getStr( strURL ), rtl_uString_getLength( strURL ), RTL_TEXTENCODING_UTF8, OUSTRING_TO_OSTRING_CVTFLAGS );
560 
561     pszEncodedURL = static_cast<sal_Char*>(malloc( (rtl_string_getLength( strUTF8 ) * 3 + 1)  * sizeof(sal_Char) ));
562 
563     pURLDest = pszEncodedURL;
564     pURLScan = rtl_string_getStr( strUTF8 );
565     nURLScanLen = rtl_string_getLength( strUTF8 );
566     nURLScanCount = 0;
567 
568     while ( nURLScanCount < nURLScanLen )
569     {
570         sal_Char cCurrent = *pURLScan;
571         switch ( cCurrent )
572         {
573         default:
574             if (!( ( cCurrent >= 'a' && cCurrent <= 'z' ) || ( cCurrent >= 'A' && cCurrent <= 'Z' ) || ( cCurrent >= '0' && cCurrent <= '9' ) ) )
575             {
576                 sprintf( pURLDest, "%%%02X", static_cast<unsigned char>(cCurrent) );
577                 pURLDest += 3;
578                 break;
579             }
580             [[fallthrough]];
581         case '!':
582         case '\'':
583         case '(':
584         case ')':
585         case '*':
586         case '-':
587         case '.':
588         case '_':
589         case '~':
590         case '$':
591         case '&':
592         case '+':
593         case ',':
594         case '=':
595         case '@':
596         case ':':
597         case '/':
598         case '\\':
599         case '|':
600             *pURLDest++ = cCurrent;
601             break;
602         case 0:
603             break;
604         }
605 
606         pURLScan++;
607         nURLScanCount++;
608     }
609 
610     *pURLDest = 0;
611 
612     rtl_string_release( strUTF8 );
613     rtl_string_newFromStr( pstrEncodedURL, pszEncodedURL );
614     free( pszEncodedURL );
615 }
616 
617 oslFileError osl_getSystemPathFromFileURL_( rtl_uString *strURL, rtl_uString **pustrPath, bool bAllowRelative )
618 {
619     rtl_String          *strUTF8 = nullptr;
620     rtl_uString         *strDecodedURL = nullptr;
621     rtl_uString         *strTempPath = nullptr;
622     sal_uInt32          nDecodedLen;
623     bool            bValidEncoded;
624     oslFileError        nError = osl_File_E_INVAL;  /* Assume failure */
625 
626     /*  If someone hasn't encoded the complete URL we convert it to UTF8 now to prevent from
627         having a mixed encoded URL later */
628 
629     rtl_uString2String( &strUTF8, rtl_uString_getStr( strURL ), rtl_uString_getLength( strURL ), RTL_TEXTENCODING_UTF8, OUSTRING_TO_OSTRING_CVTFLAGS );
630 
631     /* If the length of strUTF8 and strURL differs it indicates that the URL was not correct encoded */
632 
633     SAL_WARN_IF(
634         strUTF8->length != strURL->length &&
635         0 == rtl_ustr_ascii_shortenedCompareIgnoreAsciiCase_WithLength( strURL->buffer, strURL->length, "file:\\\\", 7 )
636         , "sal.osl"
637         ,"osl_getSystemPathFromFileURL: \"" << OUString(strURL) << "\" is not encoded !!!");
638 
639     bValidEncoded = osl_decodeURL_( strUTF8, &strDecodedURL );
640 
641     /* Release the encoded UTF8 string */
642     rtl_string_release( strUTF8 );
643 
644     if ( bValidEncoded )
645     {
646         /* Replace backslashes and pipes */
647 
648         rtl_uString_newReplace( &strDecodedURL, strDecodedURL, '/', '\\' );
649         rtl_uString_newReplace( &strDecodedURL, strDecodedURL, '|', ':' );
650 
651         const sal_Unicode *pDecodedURL = rtl_uString_getStr( strDecodedURL );
652         nDecodedLen = rtl_uString_getLength( strDecodedURL );
653 
654         /* Must start with "file://" */
655         if ( 0 == rtl_ustr_ascii_shortenedCompareIgnoreAsciiCase_WithLength( pDecodedURL, nDecodedLen, "file:\\\\", 7 ) )
656         {
657             sal_uInt32  nSkip;
658 
659             if ( 0 == rtl_ustr_ascii_shortenedCompareIgnoreAsciiCase_WithLength( pDecodedURL, nDecodedLen, "file:\\\\\\", 8 ) )
660                 nSkip = 8;
661             else if (
662                 0 == rtl_ustr_ascii_shortenedCompareIgnoreAsciiCase_WithLength( pDecodedURL, nDecodedLen, "file:\\\\localhost\\", 17 ) ||
663                 0 == rtl_ustr_ascii_shortenedCompareIgnoreAsciiCase_WithLength( pDecodedURL, nDecodedLen, "file:\\\\127.0.0.1\\", 17 )
664                       )
665                 nSkip = 17;
666             else
667                 nSkip = 5;
668 
669             /* Indicates local root */
670             if ( nDecodedLen == nSkip )
671                 rtl_uString_newFromStr_WithLength( &strTempPath, o3tl::toU(WSTR_SYSTEM_ROOT_PATH), SAL_N_ELEMENTS(WSTR_SYSTEM_ROOT_PATH) - 1 );
672             else
673             {
674                 /* do not separate the directory and file case, so the maximal path length without prefix is MAX_PATH-12 */
675                 if ( nDecodedLen - nSkip <= MAX_PATH - 12 )
676                 {
677                     rtl_uString_newFromStr_WithLength( &strTempPath, pDecodedURL + nSkip, nDecodedLen - nSkip );
678                 }
679                 else
680                 {
681                     ::osl::LongPathBuffer< sal_Unicode > aBuf( MAX_LONG_PATH );
682                     sal_uInt32 nNewLen = GetCaseCorrectPathName( o3tl::toW(pDecodedURL) + nSkip,
683                                                                  o3tl::toW(aBuf),
684                                                                  aBuf.getBufSizeInSymbols(),
685                                                                  false );
686 
687                     if ( nNewLen <= MAX_PATH - 12
688                       || 0 == rtl_ustr_shortenedCompareIgnoreAsciiCase_WithLength( pDecodedURL + nSkip, nDecodedLen - nSkip, o3tl::toU(WSTR_SYSTEM_ROOT_PATH), SAL_N_ELEMENTS(WSTR_SYSTEM_ROOT_PATH) - 1, SAL_N_ELEMENTS(WSTR_SYSTEM_ROOT_PATH) - 1 )
689                       || 0 == rtl_ustr_shortenedCompareIgnoreAsciiCase_WithLength( pDecodedURL + nSkip, nDecodedLen - nSkip, o3tl::toU(WSTR_LONG_PATH_PREFIX), SAL_N_ELEMENTS(WSTR_LONG_PATH_PREFIX) - 1, SAL_N_ELEMENTS(WSTR_LONG_PATH_PREFIX) - 1 ) )
690                     {
691                         rtl_uString_newFromStr_WithLength( &strTempPath, aBuf, nNewLen );
692                     }
693                     else if ( pDecodedURL[nSkip] == '\\' && pDecodedURL[nSkip+1] == '\\' )
694                     {
695                         /* it should be an UNC path, use the according prefix */
696                         rtl_uString *strSuffix = nullptr;
697                         rtl_uString *strPrefix = nullptr;
698                         rtl_uString_newFromStr_WithLength( &strPrefix, o3tl::toU(WSTR_LONG_PATH_PREFIX_UNC), SAL_N_ELEMENTS( WSTR_LONG_PATH_PREFIX_UNC ) - 1 );
699                         rtl_uString_newFromStr_WithLength( &strSuffix, aBuf + 2, nNewLen - 2 );
700 
701                         rtl_uString_newConcat( &strTempPath, strPrefix, strSuffix );
702 
703                         rtl_uString_release( strPrefix );
704                         rtl_uString_release( strSuffix );
705                     }
706                     else
707                     {
708                         rtl_uString *strSuffix = nullptr;
709                         rtl_uString *strPrefix = nullptr;
710                         rtl_uString_newFromStr_WithLength( &strPrefix, o3tl::toU(WSTR_LONG_PATH_PREFIX), SAL_N_ELEMENTS( WSTR_LONG_PATH_PREFIX ) - 1 );
711                         rtl_uString_newFromStr_WithLength( &strSuffix, aBuf, nNewLen );
712 
713                         rtl_uString_newConcat( &strTempPath, strPrefix, strSuffix );
714 
715                         rtl_uString_release( strPrefix );
716                         rtl_uString_release( strSuffix );
717                     }
718                 }
719             }
720 
721             if ( IsValidFilePath( strTempPath, VALIDATEPATH_ALLOW_ELLIPSE, &strTempPath ) )
722                 nError = osl_File_E_None;
723         }
724         else if ( bAllowRelative )  /* This maybe a relative file URL */
725         {
726             /* In future the relative path could be converted to absolute if it is too long */
727             rtl_uString_assign( &strTempPath, strDecodedURL );
728 
729             if ( IsValidFilePath( strTempPath, VALIDATEPATH_ALLOW_RELATIVE | VALIDATEPATH_ALLOW_ELLIPSE, &strTempPath ) )
730                 nError = osl_File_E_None;
731         }
732         else
733           SAL_INFO_IF(nError, "sal.osl",
734               "osl_getSystemPathFromFileURL: \"" << OUString(strURL) << "\" is not an absolute FileURL");
735 
736     }
737 
738     if ( strDecodedURL )
739         rtl_uString_release( strDecodedURL );
740 
741     if ( osl_File_E_None == nError )
742         rtl_uString_assign( pustrPath, strTempPath );
743 
744     if ( strTempPath )
745         rtl_uString_release( strTempPath );
746 
747     SAL_INFO_IF(nError, "sal.osl",
748         "osl_getSystemPathFromFileURL: \"" << OUString(strURL) << "\" is not a FileURL");
749 
750     return nError;
751 }
752 
753 oslFileError osl_getFileURLFromSystemPath( rtl_uString* strPath, rtl_uString** pstrURL )
754 {
755     oslFileError nError = osl_File_E_INVAL; /* Assume failure */
756     rtl_uString *strTempURL = nullptr;
757     DWORD dwPathType = PATHTYPE_ERROR;
758 
759     if (strPath)
760         dwPathType = IsValidFilePath(strPath, VALIDATEPATH_ALLOW_RELATIVE, nullptr);
761 
762     if (dwPathType)
763     {
764         rtl_uString *strTempPath = nullptr;
765 
766         if ( dwPathType & PATHTYPE_IS_LONGPATH )
767         {
768             rtl_uString *strBuffer = nullptr;
769             sal_uInt32 nIgnore = 0;
770             sal_uInt32 nLength = 0;
771 
772             /* the path has the longpath prefix, lets remove it */
773             switch ( dwPathType & PATHTYPE_MASK_TYPE )
774             {
775                 case PATHTYPE_ABSOLUTE_UNC:
776                     nIgnore = SAL_N_ELEMENTS( WSTR_LONG_PATH_PREFIX_UNC ) - 1;
777                     OSL_ENSURE( nIgnore == 8, "Unexpected long path UNC prefix!" );
778 
779                     /* generate the normal UNC path */
780                     nLength = rtl_uString_getLength( strPath );
781                     rtl_uString_newFromStr_WithLength( &strBuffer, strPath->buffer + nIgnore - 2, nLength - nIgnore + 2 );
782                     strBuffer->buffer[0] = '\\';
783 
784                     rtl_uString_newReplace( &strTempPath, strBuffer, '\\', '/' );
785                     rtl_uString_release( strBuffer );
786                     break;
787 
788                 case PATHTYPE_ABSOLUTE_LOCAL:
789                     nIgnore = SAL_N_ELEMENTS( WSTR_LONG_PATH_PREFIX ) - 1;
790                     OSL_ENSURE( nIgnore == 4, "Unexpected long path prefix!" );
791 
792                     /* generate the normal path */
793                     nLength = rtl_uString_getLength( strPath );
794                     rtl_uString_newFromStr_WithLength( &strBuffer, strPath->buffer + nIgnore, nLength - nIgnore );
795 
796                     rtl_uString_newReplace( &strTempPath, strBuffer, '\\', '/' );
797                     rtl_uString_release( strBuffer );
798                     break;
799 
800                 default:
801                     OSL_FAIL( "Unexpected long path format!" );
802                     rtl_uString_newReplace( &strTempPath, strPath, '\\', '/' );
803                     break;
804             }
805         }
806         else
807         {
808             /* Replace backslashes */
809             rtl_uString_newReplace( &strTempPath, strPath, '\\', '/' );
810         }
811 
812         switch ( dwPathType & PATHTYPE_MASK_TYPE )
813         {
814         case PATHTYPE_RELATIVE:
815             rtl_uString_assign( &strTempURL, strTempPath );
816             nError = osl_File_E_None;
817             break;
818         case PATHTYPE_ABSOLUTE_UNC:
819             rtl_uString_newFromAscii( &strTempURL, "file:" );
820             rtl_uString_newConcat( &strTempURL, strTempURL, strTempPath );
821             nError = osl_File_E_None;
822             break;
823         case PATHTYPE_ABSOLUTE_LOCAL:
824             rtl_uString_newFromAscii( &strTempURL, "file:///" );
825             rtl_uString_newConcat( &strTempURL, strTempURL, strTempPath );
826             nError = osl_File_E_None;
827             break;
828         default:
829             break;
830         }
831 
832         /* Release temp path */
833         rtl_uString_release( strTempPath );
834     }
835 
836     if ( osl_File_E_None == nError )
837     {
838         rtl_String  *strEncodedURL = nullptr;
839 
840         /* Encode the URL */
841         osl_encodeURL_( strTempURL, &strEncodedURL );
842 
843         /* Provide URL via unicode string */
844         rtl_string2UString( pstrURL, rtl_string_getStr(strEncodedURL), rtl_string_getLength(strEncodedURL), RTL_TEXTENCODING_ASCII_US, OUSTRING_TO_OSTRING_CVTFLAGS );
845         OSL_ASSERT(*pstrURL != nullptr);
846         rtl_string_release( strEncodedURL );
847     }
848 
849     /* Release temp URL */
850     if ( strTempURL )
851         rtl_uString_release( strTempURL );
852 
853     SAL_INFO_IF(nError, "sal.osl",
854         "osl_getFileURLFromSystemPath: \"" << OUString(strPath) << "\" is not a systemPath");
855     return nError;
856 }
857 
858 oslFileError SAL_CALL osl_getSystemPathFromFileURL(
859     rtl_uString *ustrURL, rtl_uString **pustrPath)
860 {
861     return osl_getSystemPathFromFileURL_( ustrURL, pustrPath, true );
862 }
863 
864 oslFileError SAL_CALL osl_searchFileURL(
865     rtl_uString *ustrFileName,
866     rtl_uString *ustrSystemSearchPath,
867     rtl_uString **pustrPath)
868 {
869     rtl_uString     *ustrUNCPath = nullptr;
870     rtl_uString     *ustrSysPath = nullptr;
871     oslFileError    error;
872 
873     /* First try to interpret the file name as an URL even a relative one */
874     error = osl_getSystemPathFromFileURL_( ustrFileName, &ustrUNCPath, true );
875 
876     /* So far we either have an UNC path or something invalid
877        Now create a system path */
878     if ( osl_File_E_None == error )
879         error = osl_getSystemPathFromFileURL_( ustrUNCPath, &ustrSysPath, true );
880 
881     if ( osl_File_E_None == error )
882     {
883         DWORD   nBufferLength;
884         DWORD   dwResult;
885         LPWSTR  lpBuffer = nullptr;
886         LPWSTR  lpszFilePart;
887 
888         /* Repeat calling SearchPath ...
889            Start with MAX_PATH for the buffer. In most cases this
890            will be enough and does not force the loop to run twice */
891         dwResult = MAX_PATH;
892 
893         do
894         {
895             /* If search path is empty use a nullptr pointer instead according to MSDN documentation of SearchPath */
896             LPCWSTR lpszSearchPath = ustrSystemSearchPath && ustrSystemSearchPath->length ? o3tl::toW(ustrSystemSearchPath->buffer) : nullptr;
897             LPCWSTR lpszSearchFile = o3tl::toW(ustrSysPath->buffer);
898 
899             /* Allocate space for buffer according to previous returned count of required chars */
900             /* +1 is not necessary if we follow MSDN documentation but for robustness we do so */
901             nBufferLength = dwResult + 1;
902             lpBuffer = lpBuffer ?
903                 static_cast<LPWSTR>(realloc(lpBuffer, nBufferLength * sizeof(WCHAR))) :
904                 static_cast<LPWSTR>(malloc(nBufferLength * sizeof(WCHAR)));
905 
906             dwResult = SearchPathW( lpszSearchPath, lpszSearchFile, nullptr, nBufferLength, lpBuffer, &lpszFilePart );
907         } while ( dwResult && dwResult >= nBufferLength );
908 
909         /*  ... until an error occurs or buffer is large enough.
910             dwResult == nBufferLength can not happen according to documentation but lets be robust ;-) */
911 
912         if ( dwResult )
913         {
914             rtl_uString_newFromStr( &ustrSysPath, o3tl::toU(lpBuffer) );
915             error = osl_getFileURLFromSystemPath( ustrSysPath, pustrPath );
916         }
917         else
918         {
919             WIN32_FIND_DATAW aFindFileData;
920             HANDLE  hFind;
921 
922             /* something went wrong, perhaps the path was absolute */
923             error = oslTranslateFileError( GetLastError() );
924 
925             hFind = FindFirstFileW( o3tl::toW(ustrSysPath->buffer), &aFindFileData );
926 
927             if ( IsValidHandle(hFind) )
928             {
929                 error = osl_getFileURLFromSystemPath( ustrSysPath, pustrPath );
930                 FindClose( hFind );
931             }
932         }
933 
934         free( lpBuffer );
935     }
936 
937     if ( ustrSysPath )
938         rtl_uString_release( ustrSysPath );
939 
940     if ( ustrUNCPath )
941         rtl_uString_release( ustrUNCPath );
942 
943     return error;
944 }
945 
946 oslFileError SAL_CALL osl_getAbsoluteFileURL( rtl_uString* ustrBaseURL, rtl_uString* ustrRelativeURL, rtl_uString** pustrAbsoluteURL )
947 {
948     oslFileError    eError;
949     rtl_uString     *ustrRelSysPath = nullptr;
950     rtl_uString     *ustrBaseSysPath = nullptr;
951 
952     if ( ustrBaseURL && ustrBaseURL->length )
953     {
954         eError = osl_getSystemPathFromFileURL_( ustrBaseURL, &ustrBaseSysPath, false );
955         OSL_ENSURE( osl_File_E_None == eError, "osl_getAbsoluteFileURL called with relative or invalid base URL" );
956 
957         eError = osl_getSystemPathFromFileURL_( ustrRelativeURL, &ustrRelSysPath, true );
958     }
959     else
960     {
961         eError = osl_getSystemPathFromFileURL_( ustrRelativeURL, &ustrRelSysPath, false );
962         OSL_ENSURE( osl_File_E_None == eError, "osl_getAbsoluteFileURL called with empty base URL and/or invalid relative URL" );
963     }
964 
965     if ( !eError )
966     {
967         ::osl::LongPathBuffer< sal_Unicode > aBuffer( MAX_LONG_PATH );
968         ::osl::LongPathBuffer< sal_Unicode > aCurrentDir( MAX_LONG_PATH );
969         LPWSTR  lpFilePart = nullptr;
970         DWORD   dwResult;
971 
972 /*@@@ToDo
973   Bad, bad hack, this only works if the base path
974   really exists which is not necessary according
975   to RFC2396
976   The whole FileURL implementation should be merged
977   with the rtl/uri class.
978 */
979         if ( ustrBaseSysPath )
980         {
981             osl_acquireMutex( g_CurrentDirectoryMutex );
982 
983             GetCurrentDirectoryW( aCurrentDir.getBufSizeInSymbols(), o3tl::toW(aCurrentDir) );
984             SetCurrentDirectoryW( o3tl::toW(ustrBaseSysPath->buffer) );
985         }
986 
987         dwResult = GetFullPathNameW( o3tl::toW(ustrRelSysPath->buffer), aBuffer.getBufSizeInSymbols(), o3tl::toW(aBuffer), &lpFilePart );
988 
989         if ( ustrBaseSysPath )
990         {
991             SetCurrentDirectoryW( o3tl::toW(aCurrentDir) );
992 
993             osl_releaseMutex( g_CurrentDirectoryMutex );
994         }
995 
996         if ( dwResult )
997         {
998             if ( dwResult >= aBuffer.getBufSizeInSymbols() )
999                 eError = osl_File_E_INVAL;
1000             else
1001             {
1002                 rtl_uString *ustrAbsSysPath = nullptr;
1003 
1004                 rtl_uString_newFromStr( &ustrAbsSysPath, aBuffer );
1005 
1006                 eError = osl_getFileURLFromSystemPath( ustrAbsSysPath, pustrAbsoluteURL );
1007 
1008                 if ( ustrAbsSysPath )
1009                     rtl_uString_release( ustrAbsSysPath );
1010             }
1011         }
1012         else
1013             eError = oslTranslateFileError( GetLastError() );
1014     }
1015 
1016     if ( ustrBaseSysPath )
1017         rtl_uString_release( ustrBaseSysPath );
1018 
1019     if ( ustrRelSysPath )
1020         rtl_uString_release( ustrRelSysPath );
1021 
1022     return  eError;
1023 }
1024 
1025 oslFileError SAL_CALL osl_getCanonicalName( rtl_uString *strRequested, rtl_uString **strValid )
1026 {
1027     rtl_uString_newFromString(strValid, strRequested);
1028     return osl_File_E_None;
1029 }
1030 
1031 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1032