1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include "file_url.hxx"
31 #include <osl/file.hxx>
32 #include <osl/security.hxx>
33 #include <osl/diagnose.h>
34 #include <osl/thread.h>
35 #include <osl/process.h>
37 #include <rtl/character.hxx>
39 #include <rtl/uri.hxx>
40 #include <rtl/ustring.hxx>
41 #include <rtl/ustrbuf.h>
42 #include <rtl/textcvt.h>
43 #include <sal/log.hxx>
45 #include "file_error_transl.hxx"
46 #include "file_path_helper.hxx"
48 #include "uunxapi.hxx"
54 This file contains the part that handles File URLs.
56 File URLs as scheme specific notion of URIs
57 (RFC2396) may be handled platform independent, but
58 will not in osl which is considered wrong.
59 Future version of osl should handle File URLs this
60 way. In rtl/uri there is already an URI parser etc.
61 so this code should be consolidated.
69 // A slightly modified version of Pchar in rtl/source/uri.c, but without
71 const sal_Bool uriCharClass
[128] = {
72 false, false, false, false, false, false, false, false,
73 false, false, false, false, false, false, false, false,
74 false, false, false, false, false, false, false, false,
75 false, false, false, false, false, false, false, false,
76 false, true, false, false, true, false, true, true, // !"#$%&'
77 true, true, true, true, true, true, true, true, // ()*+,-./
78 true, true, true, true, true, true, true, true, // 01234567
79 true, true, true, false, false, true, false, false, // 89:;<=>?
80 true, true, true, true, true, true, true, true, // @ABCDEFG
81 true, true, true, true, true, true, true, true, // HIJKLMNO
82 true, true, true, true, true, true, true, true, // PQRSTUVW
83 true, true, true, false, false, false, false, true, // XYZ[\]^_
84 false, true, true, true, true, true, true, true, // `abcdefg
85 true, true, true, true, true, true, true, true, // hijklmno
86 true, true, true, true, true, true, true, true, // pqrstuvw
87 true, true, true, false, false, false, true, false}; // xyz{|}~
91 oslFileError SAL_CALL
osl_getCanonicalName( rtl_uString
* ustrFileURL
, rtl_uString
** pustrValidURL
)
93 OSL_FAIL("osl_getCanonicalName not implemented");
95 rtl_uString_newFromString(pustrValidURL
, ustrFileURL
);
96 return osl_File_E_None
;
101 oslFileError
getSystemPathFromFileUrl(
102 OUString
const & url
, OUString
* path
, bool resolveHome
)
104 assert(path
!= nullptr);
105 // For compatibility with assumptions in other parts of the code base,
106 // assume that anything starting with a slash is a system path instead of a
107 // (relative) file URL (except if it starts with two slashes, in which case
108 // it is a relative URL with an authority component):
110 || (url
[0] == '/' && (url
.getLength() == 1 || url
[1] != '/')))
112 return osl_File_E_INVAL
;
114 // Check for non file scheme:
116 if (rtl::isAsciiAlpha(url
[0])) {
117 for (sal_Int32 j
= 1; j
!= url
.getLength(); ++j
) {
120 if (rtl_ustr_ascii_compareIgnoreAsciiCase_WithLengths(
121 url
.pData
->buffer
, j
,
122 RTL_CONSTASCII_STRINGPARAM("file"))
125 return osl_File_E_INVAL
;
129 } if (!rtl::isAsciiAlphanumeric(c
) && c
!= '+' && c
!= '-'
136 // Handle query or fragment:
137 if (url
.indexOf('?', i
) != -1 || url
.indexOf('#', i
) != -1)
138 return osl_File_E_INVAL
;
140 if (url
.getLength() - i
>= 2 && url
[i
] == '/' && url
[i
+ 1] == '/')
143 sal_Int32 j
= url
.indexOf('/', i
);
147 && (rtl_ustr_ascii_compareIgnoreAsciiCase_WithLengths(
148 url
.pData
->buffer
+ i
, j
- i
,
149 RTL_CONSTASCII_STRINGPARAM("localhost"))
151 && (rtl_ustr_ascii_compareIgnoreAsciiCase_WithLengths(
152 url
.pData
->buffer
+ i
, j
- i
,
153 RTL_CONSTASCII_STRINGPARAM("127.0.0.1"))
156 return osl_File_E_INVAL
;
160 // Handle empty path:
161 if (i
== url
.getLength())
164 return osl_File_E_None
;
166 // Path must not contain %2F:
167 if (url
.indexOf("%2F", i
) != -1 || url
.indexOf("%2f", i
) != -1)
168 return osl_File_E_INVAL
;
170 *path
= rtl::Uri::decode(
171 url
.copy(i
), rtl_UriDecodeWithCharset
, RTL_TEXTENCODING_UTF8
);
172 // Path must not contain %2F:
173 if (path
->indexOf('\0') != -1)
174 return osl_File_E_INVAL
;
176 // Handle ~ notation:
177 if (resolveHome
&& path
->getLength() >= 2 && (*path
)[1] == '~')
179 sal_Int32 j
= path
->indexOf('/', 2);
181 j
= path
->getLength();
186 if (!osl::Security().getHomeDir(home
))
188 SAL_WARN("sal.file", "osl::Security::getHomeDir failed");
189 return osl_File_E_INVAL
;
192 i
= url
.indexOf('/', i
+ 1);
199 //TODO: cheesy way of ensuring home's path ends in slash:
200 if (!home
.isEmpty() && home
[home
.getLength() - 1] != '/')
204 home
= rtl::Uri::convertRelToAbs(home
, url
.copy(i
));
206 catch (rtl::MalformedUriException
& e
)
208 SAL_WARN("sal.file", "rtl::MalformedUriException " << e
.getMessage());
209 return osl_File_E_INVAL
;
211 return getSystemPathFromFileUrl(home
, path
, false);
213 // FIXME: replace ~user with user's home directory
214 return osl_File_E_INVAL
;
216 return osl_File_E_None
;
221 oslFileError SAL_CALL
osl_getSystemPathFromFileURL( rtl_uString
*ustrFileURL
, rtl_uString
**pustrSystemPath
)
227 e
= getSystemPathFromFileUrl(
228 OUString::unacquired(&ustrFileURL
), &path
, true);
230 catch (std::length_error
&)
232 e
= osl_File_E_RANGE
;
235 if (e
== osl_File_E_None
)
236 rtl_uString_assign(pustrSystemPath
, path
.pData
);
241 oslFileError SAL_CALL
osl_getFileURLFromSystemPath( rtl_uString
*ustrSystemPath
, rtl_uString
**pustrFileURL
)
243 static const sal_Unicode pDoubleSlash
[2] = { '/', '/' };
245 rtl_uString
*pTmp
= nullptr;
248 if( ustrSystemPath
->length
== 0 )
249 return osl_File_E_INVAL
;
251 /* temporary hack: if already file url, return ustrSystemPath */
253 if( rtl_ustr_ascii_shortenedCompare_WithLength( ustrSystemPath
->buffer
, ustrSystemPath
->length
,"file:", 5 ) == 0 )
254 return osl_File_E_INVAL
;
256 /* check if system path starts with ~ or ~user and replace it with the appropriate home dir */
257 if( ustrSystemPath
->buffer
[0] == '~' )
259 /* check if another user is specified */
260 if( ( ustrSystemPath
->length
== 1 ) ||
261 ( ustrSystemPath
->buffer
[1] == '/' ) )
263 /* osl_getHomeDir returns file URL */
264 oslSecurity pSecurity
= osl_getCurrentSecurity();
265 osl_getHomeDir( pSecurity
, &pTmp
);
266 osl_freeSecurityHandle( pSecurity
);
269 return osl_File_E_INVAL
;
271 /* remove "file://" prefix */
272 rtl_uString_newFromStr_WithLength( &pTmp
, pTmp
->buffer
+ 7, pTmp
->length
- 7 );
274 /* replace '~' in original string */
275 rtl_uString_newReplaceStrAt( &pTmp
, ustrSystemPath
, 0, 1, pTmp
);
279 /* FIXME: replace ~user with users home directory */
280 return osl_File_E_INVAL
;
284 /* check if initial string contains repeated '/' characters */
285 nIndex
= rtl_ustr_indexOfStr_WithLength( ustrSystemPath
->buffer
, ustrSystemPath
->length
, pDoubleSlash
, 2 );
289 sal_Int32 nDeleted
= 0;
291 /* if pTmp is not already allocated, copy ustrSystemPath for modification */
292 if( pTmp
== nullptr )
293 rtl_uString_newFromString( &pTmp
, ustrSystemPath
);
295 /* adapt index to pTmp */
296 nIndex
+= pTmp
->length
- ustrSystemPath
->length
;
298 /* replace repeated '/' characters with a single '/' */
299 for( nSrcIndex
= nIndex
+ 1; nSrcIndex
< pTmp
->length
; nSrcIndex
++ )
301 if( (pTmp
->buffer
[nSrcIndex
] == '/') && (pTmp
->buffer
[nIndex
] == '/') )
304 pTmp
->buffer
[++nIndex
] = pTmp
->buffer
[nSrcIndex
];
307 /* adjust length member */
308 pTmp
->length
-= nDeleted
;
311 if( pTmp
== nullptr )
312 rtl_uString_assign( &pTmp
, ustrSystemPath
);
314 /* file URLs must be URI encoded */
315 rtl_uriEncode( pTmp
, uriCharClass
, rtl_UriEncodeIgnoreEscapes
, RTL_TEXTENCODING_UTF8
, pustrFileURL
);
317 rtl_uString_release( pTmp
);
319 /* absolute urls should start with 'file://' */
320 if( (*pustrFileURL
)->buffer
[0] == '/' )
322 rtl_uString
*pProtocol
= nullptr;
324 rtl_uString_newFromAscii( &pProtocol
, "file://" );
325 rtl_uString_newConcat( pustrFileURL
, pProtocol
, *pustrFileURL
);
326 rtl_uString_release( pProtocol
);
329 return osl_File_E_None
;
333 * relative URLs are not accepted
335 oslFileError
osl_getSystemPathFromFileURL_Ex(
336 rtl_uString
*ustrFileURL
, rtl_uString
**pustrSystemPath
)
338 rtl_uString
* temp
= nullptr;
339 oslFileError osl_error
= osl_getSystemPathFromFileURL(ustrFileURL
, &temp
);
341 if (osl_error
== osl_File_E_None
)
343 if (temp
->buffer
[0] == '/')
345 *pustrSystemPath
= temp
;
349 rtl_uString_release(temp
);
350 osl_error
= osl_File_E_INVAL
;
360 /** Helper function, return a pinter to the final '\0'
364 sal_Unicode
* ustrtoend(sal_Unicode
* pStr
)
366 return (pStr
+ rtl_ustr_getLength(pStr
));
369 sal_Unicode
* ustrchrcat(const sal_Unicode chr
, sal_Unicode
* d
)
371 sal_Unicode
* p
= ustrtoend(d
);
377 bool _islastchr(sal_Unicode
* pStr
, sal_Unicode Chr
)
379 sal_Unicode
* p
= ustrtoend(pStr
);
386 Remove the last part of a path, a path that has
387 only a '/' or no '/' at all will be returned
391 sal_Unicode
* _rmlastpathtoken(sal_Unicode
* aPath
)
393 /* we may always skip -2 because we
394 may at least stand on a '/' but
395 either there is no other character
396 before this '/' or it's another
397 character than the '/'
399 sal_Unicode
* p
= ustrtoend(aPath
) - 2;
401 /* move back to the next path separator
402 or to the start of the string */
403 while ((p
> aPath
) && (*p
!= '/'))
422 oslFileError
_osl_resolvepath(
423 /*inout*/ sal_Unicode
* path
,
424 /*inout*/ bool* failed
)
426 oslFileError ferr
= osl_File_E_None
;
430 char unresolved_path
[PATH_MAX
];
431 if (!UnicodeToText(unresolved_path
, sizeof(unresolved_path
), path
, rtl_ustr_getLength(path
)))
432 return oslTranslateFileError(ENAMETOOLONG
);
434 char resolved_path
[PATH_MAX
];
435 if (realpath(unresolved_path
, resolved_path
))
437 if (!TextToUnicode(resolved_path
, strlen(resolved_path
), path
, PATH_MAX
))
438 return oslTranslateFileError(ENAMETOOLONG
);
442 if (EACCES
== errno
|| ENOTDIR
== errno
|| ENOENT
== errno
)
445 ferr
= oslTranslateFileError(errno
);
453 Works even with non existing paths. The resulting path must not exceed
454 PATH_MAX else osl_File_E_NAMETOOLONG is the result
457 oslFileError
osl_getAbsoluteFileURL_impl_(const OUString
& unresolved_path
, OUString
& resolved_path
)
459 /* the given unresolved path must not exceed PATH_MAX */
460 if (unresolved_path
.getLength() >= (PATH_MAX
- 2))
461 return oslTranslateFileError(ENAMETOOLONG
);
463 sal_Unicode path_resolved_so_far
[PATH_MAX
];
464 const sal_Unicode
* punresolved
= unresolved_path
.getStr();
465 sal_Unicode
* presolvedsf
= path_resolved_so_far
;
467 /* reserve space for leading '/' and trailing '\0'
468 do not exceed this limit */
469 sal_Unicode
* sentinel
= path_resolved_so_far
+ PATH_MAX
- 2;
471 /* if realpath fails with error ENOTDIR, EACCES or ENOENT
472 we will not call it again, because _osl_realpath should also
473 work with non existing directories etc. */
474 bool realpath_failed
= false;
477 path_resolved_so_far
[0] = '\0';
479 while (*punresolved
!= '\0')
481 /* ignore '/.' , skip one part back when '/..' */
482 if ((*punresolved
== '.') && (*presolvedsf
== '/'))
484 if (*(punresolved
+ 1) == '\0')
489 if (*(punresolved
+ 1) == '/')
494 if ((*(punresolved
+ 1) == '.') && (*(punresolved
+ 2) == '\0' || (*(punresolved
+ 2) == '/')))
496 _rmlastpathtoken(path_resolved_so_far
);
498 presolvedsf
= ustrtoend(path_resolved_so_far
) - 1;
500 if (*(punresolved
+ 2) == '/')
508 /* a file or directory name may start with '.' */
509 if ((presolvedsf
= ustrtoend(path_resolved_so_far
)) > sentinel
)
510 return oslTranslateFileError(ENAMETOOLONG
);
512 ustrchrcat(*punresolved
++, path_resolved_so_far
);
514 if (*punresolved
== '\0' && !realpath_failed
)
516 ferr
= _osl_resolvepath(
517 path_resolved_so_far
,
520 if (ferr
!= osl_File_E_None
)
524 else if (*punresolved
== '/')
526 if ((presolvedsf
= ustrtoend(path_resolved_so_far
)) > sentinel
)
527 return oslTranslateFileError(ENAMETOOLONG
);
529 ustrchrcat(*punresolved
++, path_resolved_so_far
);
531 if (!realpath_failed
)
533 ferr
= _osl_resolvepath(
534 path_resolved_so_far
,
537 if (ferr
!= osl_File_E_None
)
540 if (!_islastchr(path_resolved_so_far
, '/'))
542 if ((presolvedsf
= ustrtoend(path_resolved_so_far
)) > sentinel
)
543 return oslTranslateFileError(ENAMETOOLONG
);
545 ustrchrcat('/', path_resolved_so_far
);
549 else // any other character
551 if ((presolvedsf
= ustrtoend(path_resolved_so_far
)) > sentinel
)
552 return oslTranslateFileError(ENAMETOOLONG
);
554 ustrchrcat(*punresolved
++, path_resolved_so_far
);
556 if (*punresolved
== '\0' && !realpath_failed
)
558 ferr
= _osl_resolvepath(
559 path_resolved_so_far
,
562 if (ferr
!= osl_File_E_None
)
568 sal_Int32 len
= rtl_ustr_getLength(path_resolved_so_far
);
570 OSL_ASSERT(len
< PATH_MAX
);
572 resolved_path
= OUString(path_resolved_so_far
, len
);
574 return osl_File_E_None
;
579 oslFileError
osl_getAbsoluteFileURL(
580 rtl_uString
* ustrBaseDirURL
,
581 rtl_uString
* ustrRelativeURL
,
582 rtl_uString
** pustrAbsoluteURL
)
584 /* Work around the below call to getSystemPathFromFileURL rejecting input
585 that starts with "/" (for whatever reason it behaves that way; but
586 changing that would start to break lots of tests at least) */
587 OUString
relUrl(ustrRelativeURL
);
588 if (relUrl
.startsWith("//"))
589 relUrl
= "file:" + relUrl
;
590 else if (relUrl
.startsWith("/"))
591 relUrl
= "file://" + relUrl
;
593 OUString unresolved_path
;
595 FileBase::RC frc
= FileBase::getSystemPathFromFileURL(relUrl
, unresolved_path
);
596 if (frc
!= FileBase::E_None
)
597 return oslFileError(frc
);
599 if (systemPathIsRelativePath(unresolved_path
))
602 oslFileError rc
= osl_getSystemPathFromFileURL_Ex(ustrBaseDirURL
, &base_path
.pData
);
603 if (rc
!= osl_File_E_None
)
607 systemPathMakeAbsolutePath(base_path
, unresolved_path
, abs_path
);
609 unresolved_path
= abs_path
;
612 OUString resolved_path
;
613 oslFileError rc
= osl_getAbsoluteFileURL_impl_(unresolved_path
, resolved_path
);
614 if (rc
== osl_File_E_None
)
616 rc
= osl_getFileURLFromSystemPath(resolved_path
.pData
, pustrAbsoluteURL
);
617 OSL_ASSERT(osl_File_E_None
== rc
);
627 No separate error code if unicode to text conversion or getenv fails because for the
628 caller there is no difference why a file could not be found in $PATH
630 bool find_in_PATH(const OUString
& file_path
, OUString
& result
)
633 OUString
path("PATH");
636 if (osl_getEnvironment(path
.pData
, &env_path
.pData
) == osl_Process_E_None
)
637 bfound
= osl::searchPath(file_path
, env_path
, result
);
647 No separate error code if unicode to text conversion or getcwd fails because for the
648 caller there is no difference why a file could not be found in CDW
650 bool find_in_CWD(const OUString
& file_path
, OUString
& result
)
655 if (osl_getProcessWorkingDir(&cwd_url
.pData
) == osl_Process_E_None
)
658 FileBase::getSystemPathFromFileURL(cwd_url
, cwd
);
659 bfound
= osl::searchPath(file_path
, cwd
, result
);
664 bool find_in_searchPath(const OUString
& file_path
, rtl_uString
* search_path
, OUString
& result
)
666 return (search_path
&& osl::searchPath(file_path
, OUString(search_path
), result
));
671 oslFileError
osl_searchFileURL(rtl_uString
* ustrFilePath
, rtl_uString
* ustrSearchPath
, rtl_uString
** pustrURL
)
673 OSL_PRECOND(ustrFilePath
&& pustrURL
, "osl_searchFileURL: invalid parameter");
678 // try to interpret search path as file url else assume it's a system path list
679 rc
= FileBase::getSystemPathFromFileURL(ustrFilePath
, file_path
);
680 if (rc
== FileBase::E_INVAL
)
681 file_path
= ustrFilePath
;
682 else if (rc
!= FileBase::E_None
)
683 return oslFileError(rc
);
688 if (find_in_searchPath(file_path
, ustrSearchPath
, result
) ||
689 osl::detail::find_in_PATH(file_path
, result
) ||
690 find_in_CWD(file_path
, result
))
694 if (osl::realpath(result
, resolved
))
696 oslFileError osl_error
= osl_getFileURLFromSystemPath(resolved
.pData
, pustrURL
);
697 SAL_WARN_IF(osl_File_E_None
!= osl_error
, "sal.file", "osl_getFileURLFromSystemPath failed");
701 return bfound
? osl_File_E_None
: osl_File_E_NOENT
;
704 oslFileError
FileURLToPath(char * buffer
, size_t bufLen
, rtl_uString
* ustrFileURL
)
706 rtl_uString
* ustrSystemPath
= nullptr;
707 oslFileError osl_error
= osl_getSystemPathFromFileURL(ustrFileURL
, &ustrSystemPath
);
709 if(osl_error
!= osl_File_E_None
)
712 osl_systemPathRemoveSeparator(ustrSystemPath
);
714 /* convert unicode path to text */
715 if(!UnicodeToText( buffer
, bufLen
, ustrSystemPath
->buffer
, ustrSystemPath
->length
))
716 osl_error
= oslTranslateFileError(errno
);
718 rtl_uString_release(ustrSystemPath
);
725 class UnicodeToTextConverter_Impl
727 rtl_UnicodeToTextConverter
const m_converter
;
729 UnicodeToTextConverter_Impl()
730 : m_converter (rtl_createUnicodeToTextConverter (osl_getThreadTextEncoding()))
733 ~UnicodeToTextConverter_Impl()
735 rtl_destroyUnicodeToTextConverter (m_converter
);
738 static UnicodeToTextConverter_Impl
& getInstance()
740 static UnicodeToTextConverter_Impl g_theConverter
;
741 return g_theConverter
;
745 sal_Unicode
const * pSrcBuf
, sal_Size nSrcChars
, sal_Char
* pDstBuf
, sal_Size nDstBytes
,
746 sal_uInt32 nFlags
, sal_uInt32
* pInfo
, sal_Size
* pSrcCvtChars
)
748 OSL_ASSERT(m_converter
!= nullptr);
749 return rtl_convertUnicodeToText (
750 m_converter
, nullptr, pSrcBuf
, nSrcChars
, pDstBuf
, nDstBytes
, nFlags
, pInfo
, pSrcCvtChars
);
755 int UnicodeToText( char * buffer
, size_t bufLen
, const sal_Unicode
* uniText
, sal_Int32 uniTextLen
)
757 sal_uInt32 nInfo
= 0;
758 sal_Size nSrcChars
= 0;
760 sal_Size nDestBytes
= UnicodeToTextConverter_Impl::getInstance().convert (
761 uniText
, uniTextLen
, buffer
, bufLen
,
762 OUSTRING_TO_OSTRING_CVTFLAGS
| RTL_UNICODETOTEXT_FLAGS_FLUSH
, &nInfo
, &nSrcChars
);
764 if( nInfo
& RTL_UNICODETOTEXT_INFO_DESTBUFFERTOSMALL
)
770 /* ensure trailing '\0' */
771 buffer
[nDestBytes
] = '\0';
777 class TextToUnicodeConverter_Impl
779 rtl_TextToUnicodeConverter
const m_converter
;
781 TextToUnicodeConverter_Impl()
782 : m_converter (rtl_createTextToUnicodeConverter (osl_getThreadTextEncoding()))
785 ~TextToUnicodeConverter_Impl()
787 rtl_destroyTextToUnicodeConverter (m_converter
);
791 static TextToUnicodeConverter_Impl
& getInstance()
793 static TextToUnicodeConverter_Impl g_theConverter
;
794 return g_theConverter
;
798 sal_Char
const * pSrcBuf
, sal_Size nSrcBytes
, sal_Unicode
* pDstBuf
, sal_Size nDstChars
,
799 sal_uInt32 nFlags
, sal_uInt32
* pInfo
, sal_Size
* pSrcCvtBytes
)
801 OSL_ASSERT(m_converter
!= nullptr);
802 return rtl_convertTextToUnicode (
803 m_converter
, nullptr, pSrcBuf
, nSrcBytes
, pDstBuf
, nDstChars
, nFlags
, pInfo
, pSrcCvtBytes
);
810 size_t text_buffer_size
,
811 sal_Unicode
* unic_text
,
812 sal_Int32 unic_text_buffer_size
)
814 sal_uInt32 nInfo
= 0;
815 sal_Size nSrcChars
= 0;
817 sal_Size nDestBytes
= TextToUnicodeConverter_Impl::getInstance().convert(
818 text
, text_buffer_size
, unic_text
, unic_text_buffer_size
,
819 OSTRING_TO_OUSTRING_CVTFLAGS
| RTL_TEXTTOUNICODE_FLAGS_FLUSH
, &nInfo
, &nSrcChars
);
821 if (nInfo
& RTL_TEXTTOUNICODE_INFO_DESTBUFFERTOOSMALL
)
827 /* ensure trailing '\0' */
828 unic_text
[nDestBytes
] = '\0';
832 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */