win32-xpath 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,13 @@
1
- require 'mkmf'
2
- have_library('shlwapi')
3
- have_library('advapi32')
4
- create_makefile('win32/xpath', 'win32')
1
+ require 'mkmf'
2
+
3
+ have_library('shlwapi')
4
+ have_library('advapi32')
5
+
6
+ # Windows 8 or later
7
+ if have_library('pathcch')
8
+ if have_header('pathcch.h')
9
+ have_func('PathCchAppendEx', 'pathcch.h')
10
+ end
11
+ end
12
+
13
+ create_makefile('win32/xpath', 'win32')
@@ -1,370 +1,400 @@
1
- #include <ruby.h>
2
- #include <ruby/encoding.h>
3
- #include <windows.h>
4
- #include <shlwapi.h>
5
- #include <sddl.h>
6
-
1
+ #include <ruby.h>
2
+ #include <ruby/encoding.h>
3
+ #include <windows.h>
4
+ #include <shlwapi.h>
5
+ #include <sddl.h>
6
+
7
7
  #ifdef __MINGW32__
8
- #define swprintf _snwprintf
9
- #endif
10
-
11
- #define MAX_WPATH MAX_PATH * sizeof(wchar_t)
12
-
13
- // Equivalent to raise SystemCallError.new(string, errnum)
14
- void rb_raise_syserr(const char* msg, int errnum){
15
- VALUE v_sys = rb_funcall(rb_eSystemCallError, rb_intern("new"), 2, rb_str_new2(msg), INT2FIX(errnum));
16
- rb_funcall(rb_mKernel, rb_intern("raise"), 1, v_sys);
17
- }
18
-
19
- // Helper function to find user's home directory
20
- wchar_t* find_user(wchar_t* str){
21
- SID* sid;
22
- DWORD cbSid, cbDom, cbData, lpType;
23
- SID_NAME_USE peUse;
24
- LPWSTR str_sid;
25
- LONG rv;
26
- HKEY phkResult;
27
- wchar_t subkey[MAX_WPATH];
28
- wchar_t* lpData;
29
- wchar_t* dom;
30
- wchar_t* ptr;
31
- const wchar_t* key_base = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\";
32
-
33
- // Read up until first backslash, and preserve the rest for later
34
- if (ptr = wcschr(str, L'\\')){
35
- ptr++;
36
- str[wcscspn(str, L"\\")] = 0;
37
- }
38
-
39
- sid = (SID*)ruby_xmalloc(MAX_WPATH);
40
- dom = (wchar_t*)ruby_xmalloc(MAX_WPATH);
41
-
42
- cbSid = MAX_PATH;
43
- cbDom = MAX_PATH;
44
-
45
- // Get the user's SID
46
- if (!LookupAccountNameW(NULL, str, sid, &cbSid, dom, &cbDom, &peUse)){
47
- ruby_xfree(sid);
48
- ruby_xfree(dom);
49
- rb_raise(rb_eArgError, "can't find user %ls", str);
50
- }
51
-
52
- ruby_xfree(dom); // Don't need this any more
53
-
54
- // Get the stringy version of the SID
55
- if (!ConvertSidToStringSidW(sid, &str_sid)){
56
- ruby_xfree(sid);
57
- rb_raise_syserr("ConvertSidToStringSid", GetLastError());
58
- }
59
-
60
- ruby_xfree(sid); // Don't need this any more
61
-
62
- // Mash the stringified SID onto our base key
63
- if(swprintf(subkey, MAX_WPATH, L"%s%s", key_base, str_sid) < 0)
64
- rb_raise_syserr("swprintf", GetLastError());
65
-
66
- // Get the key handle we need
67
- rv = RegOpenKeyExW(HKEY_LOCAL_MACHINE, subkey, 0, KEY_QUERY_VALUE, &phkResult);
68
-
69
- if (rv != ERROR_SUCCESS)
70
- rb_raise_syserr("RegOpenKeyEx", GetLastError());
71
-
72
- lpData = (wchar_t*)malloc(MAX_WPATH);
73
- cbData = MAX_WPATH;
74
- lpType = REG_EXPAND_SZ;
75
-
76
- // Finally, get the user's home directory
77
- rv = RegQueryValueExW(phkResult, L"ProfileImagePath", NULL, &lpType, (LPBYTE)lpData, &cbData);
78
-
79
- if (rv != ERROR_SUCCESS){
80
- ruby_xfree(lpData);
81
- rb_raise(rb_eArgError, "can't find home directory for user %ls", str);
82
- }
83
-
84
- // Append any remaining path data that was originally present
85
- if (ptr){
86
- if (swprintf(lpData, MAX_WPATH, L"%s/%s", lpData, ptr) < 0)
87
- rb_raise_syserr("swprintf", GetLastError());
88
- }
89
-
90
- return lpData;
91
- }
92
-
93
- // Helper function to expand tilde into full path
94
- wchar_t* expand_tilde(){
95
- DWORD size = 0;
96
- wchar_t* home = NULL;
97
- const wchar_t* env = L"HOME";
98
-
99
- // First, try to get HOME environment variable
100
- size = GetEnvironmentVariableW(env, NULL, 0);
101
-
102
- // If that isn't found then try USERPROFILE
103
- if(!size){
104
- env = L"USERPROFILE";
105
- size = GetEnvironmentVariableW(env, home, size);
106
- }
107
-
108
- // If that isn't found the try HOMEDRIVE + HOMEPATH
109
- if(!size){
110
- DWORD size2;
111
- wchar_t* temp;
112
- const wchar_t* env2 = L"HOMEPATH";
113
- env = L"HOMEDRIVE";
114
-
115
- // If neither are found then raise an error
116
- size = GetEnvironmentVariableW(env, NULL, 0);
117
- size2 = GetEnvironmentVariableW(env2, NULL, 0);
118
-
119
- if(!size || !size2){
120
- if (GetLastError() != ERROR_ENVVAR_NOT_FOUND)
121
- rb_raise_syserr("GetEnvironmentVariable", GetLastError());
122
- else
123
- rb_raise(rb_eArgError, "couldn't find HOME environment -- expanding '~'");
124
- }
125
-
126
- home = (wchar_t*)ruby_xmalloc(MAX_WPATH);
127
- temp = (wchar_t*)ruby_xmalloc(MAX_WPATH);
128
-
129
- if(!GetEnvironmentVariableW(env, home, size) || !GetEnvironmentVariableW(env2, temp, size2)){
130
- ruby_xfree(home);
131
- ruby_xfree(temp);
132
- rb_raise_syserr("GetEnvironmentVariable", GetLastError());
133
- }
134
-
135
- if(!PathAppendW(home, temp))
136
- rb_raise_syserr("PathAppend", GetLastError());
137
- }
138
- else{
139
- home = (wchar_t*)ruby_xmalloc(MAX_WPATH);
140
- size = GetEnvironmentVariableW(env, home, size);
141
-
142
- if(!size){
143
- ruby_xfree(home);
144
- rb_raise_syserr("GetEnvironmentVariable", GetLastError());
145
- }
146
- }
147
-
148
- while(wcsstr(home, L"/"))
149
- home[wcscspn(home, L"/")] = L'\\';
150
-
151
- if (PathIsRelativeW(home))
152
- rb_raise(rb_eArgError, "non-absolute home");
153
-
154
- return home;
155
- }
156
-
157
- // My version of File.expand_path
158
- static VALUE rb_xpath(int argc, VALUE* argv, VALUE self){
159
- VALUE v_path, v_path_orig, v_dir_orig;
160
- wchar_t* buffer = NULL;
161
- wchar_t* ptr = NULL;
162
- wchar_t* path = NULL;
163
- char* final_path;
164
- int length;
165
- rb_encoding* path_encoding;
166
- rb_econv_t* ec;
167
- const int replaceflags = ECONV_UNDEF_REPLACE|ECONV_INVALID_REPLACE;
168
-
169
- rb_scan_args(argc, argv, "11", &v_path_orig, &v_dir_orig);
170
-
171
- if (rb_respond_to(v_path_orig, rb_intern("to_path")))
172
- v_path_orig = rb_funcall(v_path_orig, rb_intern("to_path"), 0, NULL);
173
-
174
- SafeStringValue(v_path_orig);
175
-
176
- if (!NIL_P(v_dir_orig))
177
- SafeStringValue(v_dir_orig);
178
-
179
- // Dup and prep string for modification
180
- path_encoding = rb_enc_get(v_path_orig);
181
-
182
- if (rb_enc_to_index(path_encoding) == rb_utf8_encindex()){
183
- v_path = rb_str_dup(v_path_orig);
184
- }
185
- else{
186
- ec = rb_econv_open(rb_enc_name(path_encoding), "UTF-8", replaceflags);
187
- v_path = rb_econv_str_convert(ec, v_path_orig, ECONV_PARTIAL_INPUT);
188
- rb_econv_close(ec);
189
- }
190
-
191
- rb_str_modify_expand(v_path, MAX_PATH);
192
-
193
- // Make our path a wide string for later functions
194
- length = MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(v_path), -1, NULL, 0);
195
- path = (wchar_t*)ruby_xmalloc(MAX_PATH * sizeof(wchar_t));
196
-
197
- if(!MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(v_path), -1, path, length)){
198
- ruby_xfree(path);
199
- rb_raise_syserr("MultibyteToWideChar", GetLastError());
200
- }
201
-
202
- // Convert all forward slashes to backslashes to Windows API functions work properly
203
- while(wcsstr(path, L"/"))
204
- path[wcscspn(path, L"/")] = L'\\';
205
-
206
- // Handle ~ expansion.
207
- if (ptr = wcschr(path, L'~')){
208
- wchar_t* home;
209
-
210
- // Handle both ~/user and ~user syntax
211
- if (ptr[1] && ptr[1] != L'\\'){
212
- home = find_user(++ptr);
213
- }
214
- else{
215
- home = expand_tilde(path);
216
-
217
- if (!PathAppendW(home, ++ptr)){
218
- ruby_xfree(home);
219
- rb_raise_syserr("PathAppend", GetLastError());
220
- }
221
- }
222
-
223
- path = home;
224
- }
225
-
226
- // Directory argument is present
227
- if (!NIL_P(v_dir_orig)){
228
- wchar_t* dir;
229
- VALUE v_dir;
230
- rb_encoding* dir_encoding;
231
-
232
- dir_encoding = rb_enc_get(v_dir_orig);
233
-
234
- // Hooray for encodings
235
- if (rb_enc_to_index(dir_encoding) == rb_utf8_encindex()){
236
- v_dir = rb_str_dup(v_dir_orig);
237
- }
238
- else{
239
- ec = rb_econv_open(rb_enc_name(dir_encoding), "UTF-8", replaceflags);
240
- v_dir = rb_econv_str_convert(ec, v_dir_orig, ECONV_PARTIAL_INPUT);
241
- rb_econv_close(ec);
242
- }
243
-
244
- // Prep string for modification
245
- rb_str_modify_expand(v_dir, MAX_PATH);
246
-
247
- length = MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(v_dir), -1, NULL, 0);
248
- dir = (wchar_t*)ruby_xmalloc(MAX_PATH * sizeof(wchar_t));
249
-
250
- if (!MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(v_dir), -1, dir, length)){
251
- ruby_xfree(dir);
252
- rb_raise_syserr("MultibyteToWideChar", GetLastError());
253
- }
254
-
255
- while (wcsstr(dir, L"/"))
256
- dir[wcscspn(dir, L"/")] = L'\\';
257
-
258
- if (ptr = wcschr(dir, L'~')){
259
- if (ptr[1] && ptr[1] != L'\\'){
260
- dir = find_user(++ptr);
261
- }
262
- else{
263
- dir = expand_tilde();
264
-
265
- if (!PathAppendW(dir, ++ptr)){
266
- ruby_xfree(dir);
267
- rb_raise_syserr("PathAppend", GetLastError());
268
- }
269
- }
270
- }
271
-
272
- if (!wcslen(path))
273
- path = dir;
274
-
275
- if (PathIsRelativeW(path)){
276
- if(!PathAppendW(dir, path)){
277
- ruby_xfree(dir);
278
- rb_raise_syserr("PathAppend", GetLastError());
279
- }
280
-
281
- // Remove leading slashes from relative paths
282
- if (dir[0] == L'\\')
283
- ++dir;
284
-
285
- path = dir;
286
- }
287
- }
288
- else{
289
- if (!wcslen(path)){
290
- char* pwd;
291
- wchar_t* wpwd;
292
-
293
- // First call, get the length
294
- length = GetCurrentDirectoryW(0, NULL);
295
- wpwd = (wchar_t*)ruby_xmalloc(length * sizeof(wchar_t));
296
-
297
- length = GetCurrentDirectoryW(length, wpwd);
298
-
299
- if(!length){
300
- ruby_xfree(wpwd);
301
- rb_raise_syserr("GetCurrentDirectory", GetLastError());
302
- }
303
-
304
- // Convert backslashes into forward slashes
305
- while(wcsstr(wpwd, L"\\"))
306
- wpwd[wcscspn(wpwd, L"\\")] = L'/';
307
-
308
- // Convert string back to multibyte string before returning Ruby object
309
- length = WideCharToMultiByte(CP_UTF8, 0, wpwd, -1, NULL, 0, NULL, NULL);
310
- pwd = (char*)ruby_xmalloc(length);
311
- length = WideCharToMultiByte(CP_UTF8, 0, wpwd, -1, pwd, length, NULL, NULL);
312
-
313
- if (!length){
314
- ruby_xfree(pwd);
315
- rb_raise_syserr("WideCharToMultiByte", GetLastError());
316
- }
317
-
318
- return rb_str_new2(pwd);
319
- }
320
- }
321
-
322
- // Strip all trailing backslashes
323
- while (!*PathRemoveBackslashW(path));
324
-
325
- // First call, get the length
326
- length = GetFullPathNameW(path, 0, buffer, NULL);
327
- buffer = (wchar_t*)ruby_xmalloc(length * sizeof(wchar_t));
328
-
329
- // Now get the path
330
- length = GetFullPathNameW(path, length, buffer, NULL);
331
-
332
- if (!length){
333
- ruby_xfree(buffer);
334
- rb_raise_syserr("GetFullPathName", GetLastError());
335
- }
336
-
337
- // Convert backslashes into forward slashes
338
- while(wcsstr(buffer, L"\\"))
339
- buffer[wcscspn(buffer, L"\\")] = L'/';
340
-
341
- length = WideCharToMultiByte(CP_UTF8, 0, buffer, -1, NULL, 0, NULL, NULL);
342
- final_path = (char*)ruby_xmalloc(length);
343
- length = WideCharToMultiByte(CP_UTF8, 0, buffer, -1, final_path, length, NULL, NULL);
344
-
345
- ruby_xfree(buffer);
346
-
347
- if (!length){
348
- ruby_xfree(final_path);
349
- rb_raise_syserr("WideCharToMultiByte", GetLastError());
350
- }
351
-
352
- v_path = rb_str_new(final_path, length - 1); // Don't count null terminator
353
-
354
- if (rb_enc_to_index(path_encoding) != rb_utf8_encindex()){
355
- ec = rb_econv_open("UTF-8", rb_enc_name(path_encoding), replaceflags);
356
- v_path = rb_econv_str_convert(ec, v_path, ECONV_PARTIAL_INPUT);
357
- rb_econv_close(ec);
358
- }
359
-
360
- rb_enc_associate(v_path, path_encoding);
361
-
362
- if (OBJ_TAINTED(v_path_orig) || rb_equal(v_path, v_path_orig) == Qfalse)
363
- OBJ_TAINT(v_path);
364
-
365
- return v_path;
366
- }
367
-
368
- void Init_xpath(){
369
- rb_define_singleton_method(rb_cFile, "expand_path", rb_xpath, -1);
370
- }
8
+ #define swprintf _snwprintf
9
+ #endif
10
+
11
+ #ifdef HAVE_PATHCCH_H
12
+ #include <pathcch.h>
13
+ #endif
14
+
15
+ #define MAX_WPATH MAX_PATH * sizeof(wchar_t)
16
+
17
+ // Equivalent to raise SystemCallError.new(string, errnum)
18
+ void rb_raise_syserr(const char* msg, DWORD errnum){
19
+ VALUE v_sys = rb_funcall(rb_eSystemCallError, rb_intern("new"), 2, rb_str_new2(msg), LONG2FIX(errnum));
20
+ rb_funcall(rb_mKernel, rb_intern("raise"), 1, v_sys);
21
+ }
22
+
23
+ // Helper function to find user's home directory
24
+ wchar_t* find_user(wchar_t* str){
25
+ SID* sid;
26
+ DWORD cbSid, cbDom, cbData, lpType;
27
+ SID_NAME_USE peUse;
28
+ LPWSTR str_sid;
29
+ LONG rv;
30
+ HKEY phkResult;
31
+ wchar_t subkey[MAX_WPATH];
32
+ wchar_t* lpData;
33
+ wchar_t* dom;
34
+ wchar_t* ptr;
35
+ const wchar_t* key_base = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\";
36
+
37
+ // Read up until first backslash, and preserve the rest for later
38
+ if (ptr = wcschr(str, L'\\')){
39
+ ptr++;
40
+ str[wcscspn(str, L"\\")] = 0;
41
+ }
42
+
43
+ sid = (SID*)ruby_xmalloc(MAX_WPATH);
44
+ dom = (wchar_t*)ruby_xmalloc(MAX_WPATH);
45
+
46
+ cbSid = MAX_PATH;
47
+ cbDom = MAX_PATH;
48
+
49
+ // Get the user's SID
50
+ if (!LookupAccountNameW(NULL, str, sid, &cbSid, dom, &cbDom, &peUse)){
51
+ ruby_xfree(sid);
52
+ ruby_xfree(dom);
53
+ rb_raise(rb_eArgError, "can't find user %ls", str);
54
+ }
55
+
56
+ ruby_xfree(dom); // Don't need this any more
57
+
58
+ // Get the stringy version of the SID
59
+ if (!ConvertSidToStringSidW(sid, &str_sid)){
60
+ ruby_xfree(sid);
61
+ rb_raise_syserr("ConvertSidToStringSid", GetLastError());
62
+ }
63
+
64
+ ruby_xfree(sid); // Don't need this any more
65
+
66
+ // Mash the stringified SID onto our base key
67
+ if(swprintf(subkey, MAX_WPATH, L"%s%s", key_base, str_sid) < 0)
68
+ rb_raise_syserr("swprintf", GetLastError());
69
+
70
+ // Get the key handle we need
71
+ rv = RegOpenKeyExW(HKEY_LOCAL_MACHINE, subkey, 0, KEY_QUERY_VALUE, &phkResult);
72
+
73
+ if (rv != ERROR_SUCCESS)
74
+ rb_raise_syserr("RegOpenKeyEx", GetLastError());
75
+
76
+ lpData = (wchar_t*)malloc(MAX_WPATH);
77
+ cbData = MAX_WPATH;
78
+ lpType = REG_EXPAND_SZ;
79
+
80
+ // Finally, get the user's home directory
81
+ rv = RegQueryValueExW(phkResult, L"ProfileImagePath", NULL, &lpType, (LPBYTE)lpData, &cbData);
82
+
83
+ if (rv != ERROR_SUCCESS){
84
+ ruby_xfree(lpData);
85
+ rb_raise(rb_eArgError, "can't find home directory for user %ls", str);
86
+ }
87
+
88
+ // Append any remaining path data that was originally present
89
+ if (ptr){
90
+ if (swprintf(lpData, MAX_WPATH, L"%s/%s", lpData, ptr) < 0)
91
+ rb_raise_syserr("swprintf", GetLastError());
92
+ }
93
+
94
+ return lpData;
95
+ }
96
+
97
+ /* Helper function to expand tilde into full path. Note that I don't use the
98
+ * PathCchXXX functions here because it's extremely unlikely that a person's
99
+ * home directory exceeds MAX_PATH. In the unlikely even that it does exceed
100
+ * MAX_PATH, an error will be raised.
101
+ */
102
+ wchar_t* expand_tilde(){
103
+ DWORD size = 0;
104
+ wchar_t* home = NULL;
105
+ const wchar_t* env = L"HOME";
106
+
107
+ // First, try to get HOME environment variable
108
+ size = GetEnvironmentVariableW(env, NULL, 0);
109
+
110
+ // If that isn't found then try USERPROFILE
111
+ if(!size){
112
+ env = L"USERPROFILE";
113
+ size = GetEnvironmentVariableW(env, home, size);
114
+ }
115
+
116
+ // If that isn't found the try HOMEDRIVE + HOMEPATH
117
+ if(!size){
118
+ DWORD size2;
119
+ wchar_t* temp;
120
+ const wchar_t* env2 = L"HOMEPATH";
121
+ env = L"HOMEDRIVE";
122
+
123
+ // If neither are found then raise an error
124
+ size = GetEnvironmentVariableW(env, NULL, 0);
125
+ size2 = GetEnvironmentVariableW(env2, NULL, 0);
126
+
127
+ if(!size || !size2){
128
+ if (GetLastError() != ERROR_ENVVAR_NOT_FOUND)
129
+ rb_raise_syserr("GetEnvironmentVariable", GetLastError());
130
+ else
131
+ rb_raise(rb_eArgError, "couldn't find HOME environment -- expanding '~'");
132
+ }
133
+
134
+ home = (wchar_t*)ruby_xmalloc(MAX_WPATH);
135
+ temp = (wchar_t*)ruby_xmalloc(MAX_WPATH);
136
+
137
+ if(!GetEnvironmentVariableW(env, home, size) || !GetEnvironmentVariableW(env2, temp, size2)){
138
+ ruby_xfree(home);
139
+ ruby_xfree(temp);
140
+ rb_raise_syserr("GetEnvironmentVariable", GetLastError());
141
+ }
142
+
143
+ if(!PathAppendW(home, temp))
144
+ rb_raise_syserr("PathAppend", GetLastError());
145
+ }
146
+ else{
147
+ home = (wchar_t*)ruby_xmalloc(MAX_WPATH);
148
+ size = GetEnvironmentVariableW(env, home, size);
149
+
150
+ if(!size){
151
+ ruby_xfree(home);
152
+ rb_raise_syserr("GetEnvironmentVariable", GetLastError());
153
+ }
154
+ }
155
+
156
+ while(wcsstr(home, L"/"))
157
+ home[wcscspn(home, L"/")] = L'\\';
158
+
159
+ if (PathIsRelativeW(home))
160
+ rb_raise(rb_eArgError, "non-absolute home");
161
+
162
+ return home;
163
+ }
164
+
165
+ /*
166
+ * File.expand_path(path, dir = nil)
167
+ *
168
+ * Converts +path+ to an absolute path.
169
+ *
170
+ * This is a custom version of the File.expand_path method for Windows
171
+ * that is much faster than the MRI core method. It also supports "~user"
172
+ * expansion on Windows while the MRI core method does not.
173
+ */
174
+ static VALUE rb_xpath(int argc, VALUE* argv, VALUE self){
175
+ VALUE v_path, v_path_orig, v_dir_orig;
176
+ wchar_t* buffer = NULL;
177
+ wchar_t* ptr = NULL;
178
+ wchar_t* path = NULL;
179
+ char* final_path;
180
+ int length;
181
+ rb_encoding* path_encoding;
182
+ rb_econv_t* ec;
183
+ const int replaceflags = ECONV_UNDEF_REPLACE|ECONV_INVALID_REPLACE;
184
+
185
+ rb_scan_args(argc, argv, "11", &v_path_orig, &v_dir_orig);
186
+
187
+ if (rb_respond_to(v_path_orig, rb_intern("to_path")))
188
+ v_path_orig = rb_funcall(v_path_orig, rb_intern("to_path"), 0, NULL);
189
+
190
+ SafeStringValue(v_path_orig);
191
+
192
+ if (!NIL_P(v_dir_orig))
193
+ SafeStringValue(v_dir_orig);
194
+
195
+ // Dup and prep string for modification
196
+ path_encoding = rb_enc_get(v_path_orig);
197
+
198
+ if (rb_enc_to_index(path_encoding) == rb_utf8_encindex()){
199
+ v_path = rb_str_dup(v_path_orig);
200
+ }
201
+ else{
202
+ ec = rb_econv_open(rb_enc_name(path_encoding), "UTF-8", replaceflags);
203
+ v_path = rb_econv_str_convert(ec, v_path_orig, ECONV_PARTIAL_INPUT);
204
+ rb_econv_close(ec);
205
+ }
206
+
207
+ rb_str_modify_expand(v_path, MAX_PATH);
208
+
209
+ // Make our path a wide string for later functions
210
+ length = MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(v_path), -1, NULL, 0);
211
+ path = (wchar_t*)ruby_xmalloc(length * sizeof(wchar_t));
212
+
213
+ if(!MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(v_path), -1, path, length)){
214
+ ruby_xfree(path);
215
+ rb_raise_syserr("MultibyteToWideChar", GetLastError());
216
+ }
217
+
218
+ // Convert all forward slashes to backslashes to Windows API functions work properly
219
+ while(wcsstr(path, L"/"))
220
+ path[wcscspn(path, L"/")] = L'\\';
221
+
222
+ // Handle ~ expansion.
223
+ if (ptr = wcschr(path, L'~')){
224
+ wchar_t* home;
225
+
226
+ // Handle both ~/user and ~user syntax
227
+ if (ptr[1] && ptr[1] != L'\\'){
228
+ home = find_user(++ptr);
229
+ }
230
+ else{
231
+ #ifdef HAVE_PATHCCHAPPENDEX
232
+ HRESULT result;
233
+ wchar_t buffer[PATHCCH_MAX_CCH];
234
+
235
+ wcscpy_s(buffer, PATHCCH_MAX_CCH, expand_tilde(path));
236
+
237
+ result = PathCchAppendEx(buffer, MAX_PATH, ++ptr, PATHCCH_ALLOW_LONG_PATHS);
238
+
239
+ if (result != S_OK)
240
+ rb_raise_syserr("PathCchAppend", result);
241
+
242
+ home = &buffer[0];
243
+ #else
244
+ home = expand_tilde(path);
245
+
246
+ if (!PathAppendW(home, ++ptr)){
247
+ ruby_xfree(home);
248
+ rb_raise_syserr("PathAppend", GetLastError());
249
+ }
250
+ #endif
251
+ }
252
+
253
+ path = home;
254
+ }
255
+
256
+ // Directory argument is present
257
+ if (!NIL_P(v_dir_orig)){
258
+ wchar_t* dir;
259
+ VALUE v_dir;
260
+ rb_encoding* dir_encoding;
261
+
262
+ dir_encoding = rb_enc_get(v_dir_orig);
263
+
264
+ // Hooray for encodings
265
+ if (rb_enc_to_index(dir_encoding) == rb_utf8_encindex()){
266
+ v_dir = rb_str_dup(v_dir_orig);
267
+ }
268
+ else{
269
+ ec = rb_econv_open(rb_enc_name(dir_encoding), "UTF-8", replaceflags);
270
+ v_dir = rb_econv_str_convert(ec, v_dir_orig, ECONV_PARTIAL_INPUT);
271
+ rb_econv_close(ec);
272
+ }
273
+
274
+ // Prep string for modification
275
+ rb_str_modify_expand(v_dir, MAX_PATH);
276
+
277
+ length = MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(v_dir), -1, NULL, 0);
278
+ dir = (wchar_t*)ruby_xmalloc(MAX_PATH * sizeof(wchar_t));
279
+
280
+ if (!MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(v_dir), -1, dir, length)){
281
+ ruby_xfree(dir);
282
+ rb_raise_syserr("MultibyteToWideChar", GetLastError());
283
+ }
284
+
285
+ while (wcsstr(dir, L"/"))
286
+ dir[wcscspn(dir, L"/")] = L'\\';
287
+
288
+ if (ptr = wcschr(dir, L'~')){
289
+ if (ptr[1] && ptr[1] != L'\\'){
290
+ dir = find_user(++ptr);
291
+ }
292
+ else{
293
+ dir = expand_tilde();
294
+
295
+ if (!PathAppendW(dir, ++ptr)){
296
+ ruby_xfree(dir);
297
+ rb_raise_syserr("PathAppend", GetLastError());
298
+ }
299
+ }
300
+ }
301
+
302
+ if (!wcslen(path))
303
+ path = dir;
304
+
305
+ if (PathIsRelativeW(path)){
306
+ if(!PathAppendW(dir, path)){
307
+ ruby_xfree(dir);
308
+ rb_raise_syserr("PathAppend", GetLastError());
309
+ }
310
+
311
+ // Remove leading slashes from relative paths
312
+ if (dir[0] == L'\\')
313
+ ++dir;
314
+
315
+ path = dir;
316
+ }
317
+ }
318
+ else{
319
+ if (!wcslen(path)){
320
+ char* pwd;
321
+ wchar_t* wpwd;
322
+
323
+ // First call, get the length
324
+ length = GetCurrentDirectoryW(0, NULL);
325
+ wpwd = (wchar_t*)ruby_xmalloc(length * sizeof(wchar_t));
326
+
327
+ length = GetCurrentDirectoryW(length, wpwd);
328
+
329
+ if(!length){
330
+ ruby_xfree(wpwd);
331
+ rb_raise_syserr("GetCurrentDirectory", GetLastError());
332
+ }
333
+
334
+ // Convert backslashes into forward slashes
335
+ while(wcsstr(wpwd, L"\\"))
336
+ wpwd[wcscspn(wpwd, L"\\")] = L'/';
337
+
338
+ // Convert string back to multibyte string before returning Ruby object
339
+ length = WideCharToMultiByte(CP_UTF8, 0, wpwd, -1, NULL, 0, NULL, NULL);
340
+ pwd = (char*)ruby_xmalloc(length);
341
+ length = WideCharToMultiByte(CP_UTF8, 0, wpwd, -1, pwd, length, NULL, NULL);
342
+
343
+ if (!length){
344
+ ruby_xfree(pwd);
345
+ rb_raise_syserr("WideCharToMultiByte", GetLastError());
346
+ }
347
+
348
+ return rb_str_new2(pwd);
349
+ }
350
+ }
351
+
352
+ // Strip all trailing backslashes
353
+ while (!*PathRemoveBackslashW(path));
354
+
355
+ // First call, get the length
356
+ length = GetFullPathNameW(path, 0, buffer, NULL);
357
+ buffer = (wchar_t*)ruby_xmalloc(length * sizeof(wchar_t));
358
+
359
+ // Now get the path
360
+ length = GetFullPathNameW(path, length, buffer, NULL);
361
+
362
+ if (!length){
363
+ ruby_xfree(buffer);
364
+ rb_raise_syserr("GetFullPathName", GetLastError());
365
+ }
366
+
367
+ // Convert backslashes into forward slashes
368
+ while(wcsstr(buffer, L"\\"))
369
+ buffer[wcscspn(buffer, L"\\")] = L'/';
370
+
371
+ length = WideCharToMultiByte(CP_UTF8, 0, buffer, -1, NULL, 0, NULL, NULL);
372
+ final_path = (char*)ruby_xmalloc(length);
373
+ length = WideCharToMultiByte(CP_UTF8, 0, buffer, -1, final_path, length, NULL, NULL);
374
+
375
+ ruby_xfree(buffer);
376
+
377
+ if (!length){
378
+ ruby_xfree(final_path);
379
+ rb_raise_syserr("WideCharToMultiByte", GetLastError());
380
+ }
381
+
382
+ v_path = rb_str_new(final_path, length - 1); // Don't count null terminator
383
+
384
+ if (rb_enc_to_index(path_encoding) != rb_utf8_encindex()){
385
+ ec = rb_econv_open("UTF-8", rb_enc_name(path_encoding), replaceflags);
386
+ v_path = rb_econv_str_convert(ec, v_path, ECONV_PARTIAL_INPUT);
387
+ rb_econv_close(ec);
388
+ }
389
+
390
+ rb_enc_associate(v_path, path_encoding);
391
+
392
+ if (OBJ_TAINTED(v_path_orig) || rb_equal(v_path, v_path_orig) == Qfalse)
393
+ OBJ_TAINT(v_path);
394
+
395
+ return v_path;
396
+ }
397
+
398
+ void Init_xpath(){
399
+ rb_define_singleton_method(rb_cFile, "expand_path", rb_xpath, -1);
400
+ }