1// Licensed to the .NET Foundation under one or more agreements.
2// The .NET Foundation licenses this file to you under the MIT license.
3// See the LICENSE file in the project root for more information.
4/***
5*splitpath.c - break down path name into components
6*
7
8*
9*Purpose:
10* To provide support for accessing the individual components of an
11* arbitrary path name
12*
13*******************************************************************************/
14#include "stdafx.h"
15#include "winwrap.h"
16#include "utilcode.h"
17#include "sstring.h"
18
19
20/***
21*SplitPath() - split a path name into its individual components
22*
23*Purpose:
24* to split a path name into its individual components
25*
26*Entry:
27* path - pointer to path name to be parsed
28* drive - pointer to buffer for drive component, if any
29* dir - pointer to buffer for subdirectory component, if any
30* fname - pointer to buffer for file base name component, if any
31* ext - pointer to buffer for file name extension component, if any
32*
33*Exit:
34* drive - pointer to drive string. Includes ':' if a drive was given.
35* dir - pointer to subdirectory string. Includes leading and trailing
36* '/' or '\', if any.
37* fname - pointer to file base name
38* ext - pointer to file extension, if any. Includes leading '.'.
39*
40*Exceptions:
41*
42*******************************************************************************/
43
44void SplitPath(
45 const WCHAR *path,
46 __inout_z __inout_ecount_opt(driveSizeInWords) WCHAR *drive, int driveSizeInWords,
47 __inout_z __inout_ecount_opt(dirSizeInWords) WCHAR *dir, int dirSizeInWords,
48 __inout_z __inout_ecount_opt(fnameSizeInWords) WCHAR *fname, size_t fnameSizeInWords,
49 __inout_z __inout_ecount_opt(extSizeInWords) WCHAR *ext, size_t extSizeInWords)
50{
51 WRAPPER_NO_CONTRACT;
52
53 LPCWSTR _wszDrive, _wszDir, _wszFileName, _wszExt;
54 size_t _cchDrive, _cchDir, _cchFileName, _cchExt;
55
56 SplitPathInterior(path,
57 &_wszDrive, &_cchDrive,
58 &_wszDir, &_cchDir,
59 &_wszFileName, &_cchFileName,
60 &_wszExt, &_cchExt);
61
62 if (drive && _wszDrive)
63 wcsncpy_s(drive, driveSizeInWords, _wszDrive, min(_cchDrive, _MAX_DRIVE));
64
65 if (dir && _wszDir)
66 wcsncpy_s(dir, dirSizeInWords, _wszDir, min(_cchDir, _MAX_DIR));
67
68 if (fname && _wszFileName)
69 wcsncpy_s(fname, fnameSizeInWords, _wszFileName, min(_cchFileName, _MAX_FNAME));
70
71 if (ext && _wszExt)
72 wcsncpy_s(ext, extSizeInWords, _wszExt, min(_cchExt, _MAX_EXT));
73}
74
75//*******************************************************************************
76// A much more sensible version that just points to each section of the string.
77//*******************************************************************************
78void SplitPathInterior(
79 __in LPCWSTR wszPath,
80 __out_opt LPCWSTR *pwszDrive, __out_opt size_t *pcchDrive,
81 __out_opt LPCWSTR *pwszDir, __out_opt size_t *pcchDir,
82 __out_opt LPCWSTR *pwszFileName, __out_opt size_t *pcchFileName,
83 __out_opt LPCWSTR *pwszExt, __out_opt size_t *pcchExt)
84{
85 LIMITED_METHOD_CONTRACT;
86
87 // Arguments must come in valid pairs
88 _ASSERTE(!!pwszDrive == !!pcchDrive);
89 _ASSERTE(!!pwszDir == !!pcchDir);
90 _ASSERTE(!!pwszFileName == !!pcchFileName);
91 _ASSERTE(!!pwszExt == !!pcchExt);
92
93 WCHAR *p;
94 LPCWSTR last_slash = NULL, dot = NULL;
95
96 /* we assume that the path argument has the following form, where any
97 * or all of the components may be missing.
98 *
99 * <drive><dir><fname><ext>
100 *
101 * and each of the components has the following expected form(s)
102 *
103 * drive:
104 * 0 to _MAX_DRIVE-1 characters, the last of which, if any, is a
105 * ':'
106 * dir:
107 * 0 to _MAX_DIR-1 characters in the form of an absolute path
108 * (leading '/' or '\') or relative path, the last of which, if
109 * any, must be a '/' or '\'. E.g -
110 * absolute path:
111 * \top\next\last\ ; or
112 * /top/next/last/
113 * relative path:
114 * top\next\last\ ; or
115 * top/next/last/
116 * Mixed use of '/' and '\' within a path is also tolerated
117 * fname:
118 * 0 to _MAX_FNAME-1 characters not including the '.' character
119 * ext:
120 * 0 to _MAX_EXT-1 characters where, if any, the first must be a
121 * '.'
122 *
123 */
124
125 /* extract drive letter and :, if any */
126
127 if ((wcslen(wszPath) > (_MAX_DRIVE - 2)) && (*(wszPath + _MAX_DRIVE - 2) == _T(':'))) {
128 if (pwszDrive && pcchDrive) {
129 *pwszDrive = wszPath;
130 *pcchDrive = _MAX_DRIVE - 1;
131 }
132 wszPath += _MAX_DRIVE - 1;
133 }
134 else if (pwszDrive && pcchDrive) {
135 *pwszDrive = NULL;
136 *pcchDrive = 0;
137 }
138
139 /* extract path string, if any. Path now points to the first character
140 * of the path, if any, or the filename or extension, if no path was
141 * specified. Scan ahead for the last occurence, if any, of a '/' or
142 * '\' path separator character. If none is found, there is no path.
143 * We will also note the last '.' character found, if any, to aid in
144 * handling the extension.
145 */
146
147 for (last_slash = NULL, p = (WCHAR *)wszPath; *p; p++) {
148#ifdef _MBCS
149 if (_ISLEADBYTE (*p))
150 p++;
151 else {
152#endif /* _MBCS */
153 if (*p == _T('/') || *p == _T('\\'))
154 /* point to one beyond for later copy */
155 last_slash = p + 1;
156 else if (*p == _T('.'))
157 dot = p;
158#ifdef _MBCS
159 }
160#endif /* _MBCS */
161 }
162
163 if (last_slash) {
164 /* found a path - copy up through last_slash or max. characters
165 * allowed, whichever is smaller
166 */
167
168 if (pwszDir && pcchDir) {
169 *pwszDir = wszPath;
170 *pcchDir = last_slash - wszPath;
171 }
172 wszPath = last_slash;
173 }
174 else if (pwszDir && pcchDir) {
175 *pwszDir = NULL;
176 *pcchDir = 0;
177 }
178
179 /* extract file name and extension, if any. Path now points to the
180 * first character of the file name, if any, or the extension if no
181 * file name was given. Dot points to the '.' beginning the extension,
182 * if any.
183 */
184
185 if (dot && (dot >= wszPath)) {
186 /* found the marker for an extension - copy the file name up to
187 * the '.'.
188 */
189 if (pwszFileName && pcchFileName) {
190 *pwszFileName = wszPath;
191 *pcchFileName = dot - wszPath;
192 }
193 /* now we can get the extension - remember that p still points
194 * to the terminating nul character of path.
195 */
196 if (pwszExt && pcchExt) {
197 *pwszExt = dot;
198 *pcchExt = p - dot;
199 }
200 }
201 else {
202 /* found no extension, give empty extension and copy rest of
203 * string into fname.
204 */
205 if (pwszFileName && pcchFileName) {
206 *pwszFileName = wszPath;
207 *pcchFileName = p - wszPath;
208 }
209 if (pwszExt && pcchExt) {
210 *pwszExt = NULL;
211 *pcchExt = 0;
212 }
213 }
214}
215
216/***
217*SplitPath() - split a path name into its individual components
218*
219*Purpose:
220* to split a path name into its individual components
221*
222*Entry:
223* path - SString representing the path name to be parsed
224* drive - Out SString for drive component
225* dir - Out SString for subdirectory component
226* fname - Out SString for file base name component
227* ext - Out SString for file name extension component
228*
229*Exit:
230* drive - Drive string. Includes ':' if a drive was given.
231* dir - Subdirectory string. Includes leading and trailing
232* '/' or '\', if any.
233* fname - File base name
234* ext - File extension, if any. Includes leading '.'.
235*
236*Exceptions:
237*
238*******************************************************************************/
239
240void SplitPath(__in SString const &path,
241 __inout_opt SString *drive,
242 __inout_opt SString *dir,
243 __inout_opt SString *fname,
244 __inout_opt SString *ext)
245{
246 LPCWSTR wzDrive, wzDir, wzFname, wzExt;
247 size_t cchDrive, cchDir, cchFname, cchExt;
248
249 SplitPathInterior(path,
250 &wzDrive, &cchDrive,
251 &wzDir, &cchDir,
252 &wzFname, &cchFname,
253 &wzExt, &cchExt);
254
255 if (drive != NULL)
256 drive->Set(wzDrive, (COUNT_T)cchDrive);
257
258 if (dir != NULL)
259 dir->Set(wzDir, (COUNT_T)cchDir);
260
261 if (fname != NULL)
262 fname->Set(wzFname, (COUNT_T)cchFname);
263
264 if (ext != NULL)
265 ext->Set(wzExt, (COUNT_T)cchExt);
266}
267
268