win32-xpath 1.0.2 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ }