Project

General

Profile

Bug #6836 ยป improve-require-and-file-expand_path-windows.v2.diff

luislavena (Luis Lavena), 08/23/2012 01:45 PM

View differences:

configure.in
1186 1186
		AC_CHECK_FUNCS(cygwin_conv_path)
1187 1187
		AC_LIBOBJ([langinfo])
1188 1188
		],
1189
[mingw*], [	LIBS="-lshell32 -lws2_32 -limagehlp $LIBS"
1189
[mingw*], [	LIBS="-lshell32 -lws2_32 -limagehlp -lshlwapi $LIBS"
1190 1190
		ac_cv_header_a_out_h=no
1191 1191
		ac_cv_header_pwd_h=no
1192 1192
		ac_cv_header_utime_h=no
file.c
2882 2882
    return buf + dirlen;
2883 2883
}
2884 2884

  
2885
static VALUE
2886
file_expand_path(VALUE fname, VALUE dname, int abs_mode, VALUE result)
2885
#ifndef _WIN32
2886
VALUE
2887
rb_file_expand_path_internal(VALUE fname, VALUE dname, int abs_mode, int long_name, VALUE result)
2887 2888
{
2888 2889
    const char *s, *b, *fend;
2889 2890
    char *buf, *p, *pend, *root;
......
2945 2946
	    /* specified drive, but not full path */
2946 2947
	    int same = 0;
2947 2948
	    if (!NIL_P(dname) && !not_same_drive(dname, s[0])) {
2948
		file_expand_path(dname, Qnil, abs_mode, result);
2949
		rb_file_expand_path_internal(dname, Qnil, abs_mode, long_name, result);
2949 2950
		BUFINIT();
2950 2951
		if (has_drive_letter(p) && TOLOWER(p[0]) == TOLOWER(s[0])) {
2951 2952
		    /* ok, same drive */
......
2969 2970
#endif
2970 2971
    else if (!rb_is_absolute_path(s)) {
2971 2972
	if (!NIL_P(dname)) {
2972
	    file_expand_path(dname, Qnil, abs_mode, result);
2973
	    rb_file_expand_path_internal(dname, Qnil, abs_mode, long_name, result);
2973 2974
	    rb_enc_associate(result, rb_enc_check(result, fname));
2974 2975
	    BUFINIT();
2975 2976
	    p = pend;
......
3222 3223
    ENC_CODERANGE_CLEAR(result);
3223 3224
    return result;
3224 3225
}
3226
#endif /* _WIN32 */
3225 3227

  
3226 3228
#define EXPAND_PATH_BUFFER() rb_usascii_str_new(0, MAXPATHLEN + 2)
3227 3229

  
......
3232 3234
static VALUE
3233 3235
file_expand_path_1(VALUE fname)
3234 3236
{
3235
    return file_expand_path(fname, Qnil, 0, EXPAND_PATH_BUFFER());
3237
    return rb_file_expand_path_internal(fname, Qnil, 0, 0, EXPAND_PATH_BUFFER());
3236 3238
}
3237 3239

  
3238 3240
VALUE
3239 3241
rb_file_expand_path(VALUE fname, VALUE dname)
3240 3242
{
3241 3243
    check_expand_path_args(fname, dname);
3242
    return file_expand_path(fname, dname, 0, EXPAND_PATH_BUFFER());
3244
    return rb_file_expand_path_internal(fname, dname, 0, 1, EXPAND_PATH_BUFFER());
3245
}
3246

  
3247
VALUE
3248
rb_file_expand_path_fast(VALUE fname, VALUE dname)
3249
{
3250
    check_expand_path_args(fname, dname);
3251
    return rb_file_expand_path_internal(fname, dname, 0, 0, EXPAND_PATH_BUFFER());
3243 3252
}
3244 3253

  
3245 3254
/*
......
3276 3285
rb_file_absolute_path(VALUE fname, VALUE dname)
3277 3286
{
3278 3287
    check_expand_path_args(fname, dname);
3279
    return file_expand_path(fname, dname, 1, EXPAND_PATH_BUFFER());
3288
    return rb_file_expand_path_internal(fname, dname, 1, 1, EXPAND_PATH_BUFFER());
3280 3289
}
3281 3290

  
3282 3291
/*
......
5250 5259

  
5251 5260
	    RB_GC_GUARD(str) = rb_get_path_check(str, safe_level);
5252 5261
	    if (RSTRING_LEN(str) == 0) continue;
5253
	    file_expand_path(fname, str, 0, tmp);
5262
	    rb_file_expand_path_internal(fname, str, 0, 0, tmp);
5254 5263
	    if (rb_file_load_ok(RSTRING_PTR(tmp))) {
5255 5264
		*filep = copy_path_class(tmp, *filep);
5256 5265
		return (int)(j+1);
......
5309 5318
	    VALUE str = RARRAY_PTR(load_path)[i];
5310 5319
	    RB_GC_GUARD(str) = rb_get_path_check(str, safe_level);
5311 5320
	    if (RSTRING_LEN(str) > 0) {
5312
		file_expand_path(path, str, 0, tmp);
5321
		rb_file_expand_path_internal(path, str, 0, 0, tmp);
5313 5322
		f = RSTRING_PTR(tmp);
5314 5323
		if (rb_file_load_ok(f)) goto found;
5315 5324
	    }
......
5544 5553
    rb_define_method(rb_cStat, "setuid?",  rb_stat_suid, 0);
5545 5554
    rb_define_method(rb_cStat, "setgid?",  rb_stat_sgid, 0);
5546 5555
    rb_define_method(rb_cStat, "sticky?",  rb_stat_sticky, 0);
5556

  
5557
#ifdef _WIN32
5558
    rb_w32_init_file();
5559
#endif
5547 5560
}
internal.h
105 105
VALUE rb_realpath_internal(VALUE basedir, VALUE path, int strict);
106 106
void rb_file_const(const char*, VALUE);
107 107
int rb_file_load_ok(const char *);
108
VALUE rb_file_expand_path_fast(VALUE, VALUE);
109
VALUE rb_file_expand_path_internal(VALUE, VALUE, int, int, VALUE);
108 110
void Init_File(void);
109 111

  
112
#ifdef _WIN32
113
/* file.c, win32/file.c */
114
void rb_w32_init_file(void);
115
#endif
116

  
110 117
/* gc.c */
111 118
void Init_heap(void);
112 119
void *ruby_mimmalloc(size_t size);
load.c
43 43

  
44 44
    ary = rb_ary_new2(RARRAY_LEN(load_path));
45 45
    for (i = 0; i < RARRAY_LEN(load_path); ++i) {
46
	VALUE path = rb_file_expand_path(RARRAY_PTR(load_path)[i], Qnil);
46
	VALUE path = rb_file_expand_path_fast(RARRAY_PTR(load_path)[i], Qnil);
47 47
	rb_str_freeze(path);
48 48
	rb_ary_push(ary, path);
49 49
    }
......
233 233

  
234 234
    if (*feature == '.' &&
235 235
	(feature[1] == '/' || strncmp(feature+1, "./", 2) == 0)) {
236
	fullpath = rb_file_expand_path(rb_str_new2(feature), Qnil);
236
	fullpath = rb_file_expand_path_fast(rb_str_new2(feature), Qnil);
237 237
	feature = RSTRING_PTR(fullpath);
238 238
    }
239 239
    if (ext && !strchr(ext, '/')) {
test/ruby/test_file_exhaustive.rb
14 14

  
15 15
  def setup
16 16
    @dir = Dir.mktmpdir("rubytest-file")
17
    @rootdir = "#{DRIVE}/"
17 18
    File.chown(-1, Process.gid, @dir)
18 19
    @file = make_tmp_filename("file")
19 20
    @zerofile = make_tmp_filename("zerofile")
......
450 451
      assert_equal(expected.force_encoding(cp), File.expand_path(a.dup.force_encoding(cp)), cp)
451 452
    end
452 453

  
454
    path = "\u3042\u3044\u3046\u3048\u304a".encode("EUC-JP")
455
    assert_equal("#{Dir.pwd}/#{path}".encode("CP932"), File.expand_path(path).encode("CP932"))
456

  
457
    path = "\u3042\u3044\u3046\u3048\u304a".encode("CP51932")
458
    assert_equal("#{Dir.pwd}/#{path}", File.expand_path(path))
459

  
453 460
    assert_incompatible_encoding {|d| File.expand_path(d)}
454 461
  end
455 462

  
......
460 467
    begin
461 468
      bug3630 = '[ruby-core:31537]'
462 469
      home = ENV["HOME"]
470
      home_drive = ENV["HOMEDRIVE"]
471
      home_path = ENV["HOMEPATH"]
472
      user_profile = ENV["USERPROFILE"]
463 473
      ENV["HOME"] = nil
474
      ENV["HOMEDRIVE"] = nil
475
      ENV["HOMEPATH"] = nil
476
      ENV["USERPROFILE"] = nil
464 477
      assert_raise(ArgumentError) { File.expand_path("~") }
465 478
      ENV["HOME"] = "~"
466 479
      assert_raise(ArgumentError, bug3630) { File.expand_path("~") }
......
468 481
      assert_raise(ArgumentError, bug3630) { File.expand_path("~") }
469 482
    ensure
470 483
      ENV["HOME"] = home
484
      ENV["HOMEDRIVE"] = home_drive
485
      ENV["HOMEPATH"] = home_path
486
      ENV["USERPROFILE"] = user_profile
487
    end
488
  end
489

  
490
  def test_expand_path_resolve_empty_string_current_directory
491
    assert_equal(Dir.pwd, File.expand_path(""))
492
  end
493

  
494
  def test_expand_path_resolve_dot_current_directory
495
    assert_equal(Dir.pwd, File.expand_path("."))
496
  end
497

  
498
  def test_expand_path_resolve_file_name_relative_current_directory
499
    assert_equal(File.join(Dir.pwd, "foo"), File.expand_path("foo"))
500
  end
501

  
502
  def test_ignore_nil_dir_string
503
    assert_equal(File.join(Dir.pwd, "foo"), File.expand_path("foo", nil))
504
  end
505

  
506
  def test_expand_path_resolve_file_name_and_dir_string_relative
507
    assert_equal(File.join(Dir.pwd, "bar", "foo"),
508
      File.expand_path("foo", "bar"))
509
  end
510

  
511
  def test_expand_path_cleanup_dots_file_name
512
    bug = "[ruby-talk:18512]"
513

  
514
    assert_equal(File.join(Dir.pwd, ".a"), File.expand_path(".a"), bug)
515
    assert_equal(File.join(Dir.pwd, "..a"), File.expand_path("..a"), bug)
516

  
517
    if DRIVE
518
      # cleanup dots only on Windows
519
      assert_equal(File.join(Dir.pwd, "a"), File.expand_path("a."), bug)
520
      skip "FIXME"
521
      assert_equal(File.join(Dir.pwd, "a"), File.expand_path("a.."), bug)
522
    else
523
      assert_equal(File.join(Dir.pwd, "a."), File.expand_path("a."), bug)
524
      assert_equal(File.join(Dir.pwd, "a.."), File.expand_path("a.."), bug)
471 525
    end
472 526
  end
473 527

  
528
  def test_expand_path_converts_a_pathname_to_an_absolute_pathname_using_a_complete_path
529
    assert_equal(@dir, File.expand_path("", "#{@dir}"))
530
    assert_equal(File.join(@dir, "a"), File.expand_path("a", "#{@dir}"))
531
    assert_equal(File.join(@dir, "a"), File.expand_path("../a", "#{@dir}/xxx"))
532
    assert_equal(@rootdir, File.expand_path(".", "#{@rootdir}"))
533
  end
534

  
535
  def test_expand_path_ignores_supplied_dir_if_path_contains_a_drive_letter
536
    assert_equal(@rootdir, File.expand_path(@rootdir, "D:/"))
537
  end if DRIVE
538

  
539
  def test_expand_path_removes_trailing_slashes_from_absolute_path
540
    assert_equal(File.join(@rootdir, "foo"), File.expand_path("#{@rootdir}foo/"))
541
    assert_equal(File.join(@rootdir, "foo.rb"), File.expand_path("#{@rootdir}foo.rb/"))
542
  end
543

  
544
  def test_expand_path_removes_trailing_spaces_from_absolute_path
545
    assert_equal(File.join(@rootdir, "a"), File.expand_path("#{@rootdir}a "))
546
  end if DRIVE
547

  
548
  def test_expand_path_converts_a_pathname_which_starts_with_a_slash_using_dir_s_drive
549
    assert_match(%r"\Az:/foo\z"i, File.expand_path('/foo', "z:/bar"))
550
  end if DRIVE
551

  
552
  def test_expand_path_converts_a_pathname_which_starts_with_a_slash_and_unc_pathname
553
    assert_equal("//foo", File.expand_path('//foo', "//bar"))
554
    assert_equal("//bar/foo", File.expand_path('/foo', "//bar"))
555
    assert_equal("//foo", File.expand_path('//foo', "/bar"))
556
  end if DRIVE
557

  
558
  def test_expand_path_converts_a_dot_with_unc_dir
559
    assert_equal("//", File.expand_path('.', "//"))
560
  end
561

  
562
  def test_expand_path_preserves_unc_path_root
563
    assert_equal("//", File.expand_path("//"))
564
    assert_equal("//", File.expand_path("//."))
565
    assert_equal("//", File.expand_path("//.."))
566
  end
567

  
568
  def test_expand_path_converts_a_pathname_which_starts_with_a_slash_using_host_share
569
    assert_match(%r"\A//host/share/foo\z"i, File.expand_path('/foo', "//host/share/bar"))
570
  end if DRIVE
571

  
572
  def test_expand_path_converts_a_pathname_which_starts_with_a_slash_using_a_current_drive
573
    assert_match(%r"\A#{DRIVE}/foo\z"i, File.expand_path('/foo'))
574
  end
575

  
576
  def test_expand_path_returns_tainted_strings_or_not
577
    assert_equal(true, File.expand_path('foo').tainted?)
578
    assert_equal(true, File.expand_path('foo'.taint).tainted?)
579
    assert_equal(true, File.expand_path('/foo'.taint).tainted?)
580
    assert_equal(true, File.expand_path('foo', 'bar').tainted?)
581
    assert_equal(true, File.expand_path('foo', '/bar'.taint).tainted?)
582
    assert_equal(true, File.expand_path('foo'.taint, '/bar').tainted?)
583
    assert_equal(true, File.expand_path('~').tainted?)
584

  
585
    if DRIVE
586
      assert_equal(true, File.expand_path('/foo').tainted?)
587
      assert_equal(false, File.expand_path('//foo').tainted?)
588
      assert_equal(true, File.expand_path('C:/foo'.taint).tainted?)
589
      assert_equal(false, File.expand_path('C:/foo').tainted?)
590
      assert_equal(true, File.expand_path('foo', '/bar').tainted?)
591
      assert_equal(true, File.expand_path('foo', 'C:/bar'.taint).tainted?)
592
      assert_equal(true, File.expand_path('foo'.taint, 'C:/bar').tainted?)
593
      assert_equal(false, File.expand_path('foo', 'C:/bar').tainted?)
594
      assert_equal(false, File.expand_path('C:/foo/../bar').tainted?)
595
      assert_equal(false, File.expand_path('foo', '//bar').tainted?)
596
    else
597
      assert_equal(false, File.expand_path('/foo').tainted?)
598
      assert_equal(false, File.expand_path('foo', '/bar').tainted?)
599
    end
600
  end
601

  
602
  def test_expand_path_converts_a_pathname_to_an_absolute_pathname_using_home_as_base
603
    old_home = ENV["HOME"]
604
    home = ENV["HOME"] = "#{DRIVE}/UserHome"
605
    assert_equal(home, File.expand_path("~"))
606
    assert_equal(home, File.expand_path("~", "C:/FooBar"))
607
    assert_equal(File.join(home, "a"), File.expand_path("~/a", "C:/FooBar"))
608
  ensure
609
    ENV["HOME"] = old_home
610
  end
611

  
612
  def test_expand_path_converts_a_pathname_to_an_absolute_pathname_using_unc_home
613
    old_home = ENV["HOME"]
614
    unc_home = ENV["HOME"] = "//UserHome"
615
    assert_equal(unc_home, File.expand_path("~"))
616
  ensure
617
    ENV["HOME"] = old_home
618
  end if DRIVE
619

  
620
  def test_expand_path_does_not_modify_a_home_string_argument
621
    old_home = ENV["HOME"]
622
    home = ENV["HOME"] = "#{DRIVE}/UserHome"
623
    str = "~/a"
624
    assert_equal("#{home}/a", File.expand_path(str))
625
    assert_equal("~/a", str)
626
  ensure
627
    ENV["HOME"] = old_home
628
  end
629

  
630
  def test_expand_path_raises_argument_error_for_any_supplied_username
631
    bug = '[ruby-core:39597]'
632
    assert_raise(ArgumentError, bug) { File.expand_path("~anything") }
633
  end if DRIVE
634

  
635
  def test_expand_path_raises_a_type_error_if_not_passed_a_string_type
636
    assert_raise(TypeError) { File.expand_path(1) }
637
    assert_raise(TypeError) { File.expand_path(nil) }
638
    assert_raise(TypeError) { File.expand_path(true) }
639
  end
640

  
641
  def test_expand_path_expands_dot_dir
642
    assert_equal("#{DRIVE}/dir", File.expand_path("#{DRIVE}/./dir"))
643
  end
644

  
645
  def test_expand_path_does_not_modify_the_string_argument
646
    str = "./a/b/../c"
647
    assert_equal("#{Dir.pwd}/a/c", File.expand_path(str, Dir.pwd))
648
    assert_equal("./a/b/../c", str)
649
  end
650

  
651
  def test_expand_path_returns_a_string_when_passed_a_string_subclass
652
    sub = Class.new(String)
653
    str = sub.new "./a/b/../c"
654
    path = File.expand_path(str, Dir.pwd)
655
    assert_equal("#{Dir.pwd}/a/c", path)
656
    assert_instance_of(String, path)
657
  end
658

  
659
  def test_expand_path_accepts_objects_that_have_a_to_path_method
660
    klass = Class.new { def to_path; "a/b/c"; end }
661
    obj = klass.new
662
    assert_equal("#{Dir.pwd}/a/b/c", File.expand_path(obj))
663
  end
664

  
474 665
  def test_basename
475 666
    assert_equal(File.basename(@file).sub(/\.test$/, ""), File.basename(@file, ".test"))
476 667
    assert_equal("", s = File.basename(""))
win32/Makefile.sub
226 226
EXTSOLIBS =
227 227
!endif
228 228
!if !defined(LIBS)
229
LIBS = oldnames.lib user32.lib advapi32.lib shell32.lib ws2_32.lib imagehlp.lib $(EXTLIBS)
229
LIBS = oldnames.lib user32.lib advapi32.lib shell32.lib ws2_32.lib imagehlp.lib shlwapi.lib $(EXTLIBS)
230 230
!endif
231 231
!if !defined(MISSING)
232 232
MISSING = acosh.obj cbrt.obj crypt.obj erf.obj ffs.obj langinfo.obj lgamma_r.obj strlcat.obj strlcpy.obj tgamma.obj win32/win32.obj win32/file.obj setproctitle.obj
win32/file.c
1 1
#include "ruby/ruby.h"
2
#include "ruby/encoding.h"
2 3
#include <winbase.h>
4
#include <wchar.h>
5
#include <shlwapi.h>
3 6

  
4 7
#ifndef INVALID_FILE_ATTRIBUTES
5 8
# define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
6 9
#endif
7 10

  
11
/* cache 'encoding name' => 'code page' into a hash */
12
static VALUE rb_code_page;
13

  
14
#define IS_DIR_SEPARATOR_P(c) (c == L'\\' || c == L'/')
15
#define IS_DIR_UNC_P(c) (IS_DIR_SEPARATOR_P(c[0]) && IS_DIR_SEPARATOR_P(c[1]))
16

  
17
/* MultiByteToWideChar() doesn't work with code page 51932 */
18
#define INVALID_CODE_PAGE 51932
19
#define PATH_BUFFER_SIZE MAX_PATH * 2
20

  
21
#define insecure_obj_p(obj, level) ((level) >= 4 || ((level) > 0 && OBJ_TAINTED(obj)))
22

  
23
static inline void
24
replace_wchar(wchar_t *s, int find, int replace)
25
{
26
    while (*s != 0) {
27
	if (*s == find)
28
	    *s = replace;
29
	s++;
30
    }
31
}
32

  
33
/* Convert str from multibyte char to wchar with specified code page */
34
static inline void
35
convert_mb_to_wchar(VALUE str, wchar_t **wstr, wchar_t **wstr_pos, size_t *wstr_len, UINT code_page)
36
{
37
    size_t len;
38

  
39
    if (NIL_P(str))
40
	return;
41

  
42
    len = MultiByteToWideChar(code_page, 0, RSTRING_PTR(str), -1, NULL, 0) + 1;
43
    *wstr = (wchar_t *)xmalloc(len * sizeof(wchar_t));
44
    if (wstr_pos)
45
	*wstr_pos = *wstr;
46

  
47
    MultiByteToWideChar(code_page, 0, RSTRING_PTR(str), -1, *wstr, len);
48
    *wstr_len = len - 2;
49
}
50

  
51
static inline void
52
convert_wchar_to_mb(const wchar_t *wstr, char **str, size_t *str_len, UINT code_page)
53
{
54
    size_t len;
55

  
56
    len = WideCharToMultiByte(code_page, 0, wstr, -1, NULL, 0, NULL, NULL);
57
    *str = (char *)xmalloc(len * sizeof(char));
58
    WideCharToMultiByte(code_page, 0, wstr, -1, *str, len, NULL, NULL);
59

  
60
    /* do not count terminator as part of the string length */
61
    *str_len = len - 1;
62
}
63

  
64
/*
65
  Return user's home directory using environment variables combinations.
66
  Memory allocated by this function should be manually freeded afterwards.
67

  
68
  Try:
69
  HOME, HOMEDRIVE + HOMEPATH and USERPROFILE environment variables
70
  TODO: Special Folders - Profile and Personal
71
*/
72
static wchar_t *
73
home_dir()
74
{
75
    wchar_t *buffer = NULL;
76
    size_t buffer_len = 0, len = 0;
77
    size_t home_env = 0;
78

  
79
    /*
80
      GetEnvironmentVariableW when used with NULL will return the required
81
      buffer size and its terminating character.
82
      http://msdn.microsoft.com/en-us/library/windows/desktop/ms683188(v=vs.85).aspx
83
    */
84

  
85
    if (len = GetEnvironmentVariableW(L"HOME", NULL, 0)) {
86
	buffer_len = len;
87
	home_env = 1;
88
    } else if (len = GetEnvironmentVariableW(L"HOMEDRIVE", NULL, 0)) {
89
	buffer_len = len;
90
	if (len = GetEnvironmentVariableW(L"HOMEPATH", NULL, 0)) {
91
	    buffer_len += len;
92
	    home_env = 2;
93
	} else {
94
	    buffer_len = 0;
95
	}
96
    } else if (len = GetEnvironmentVariableW(L"USERPROFILE", NULL, 0)) {
97
	buffer_len = len;
98
	home_env = 3;
99
    }
100

  
101
    /* allocate buffer */
102
    if (home_env)
103
	buffer = (wchar_t *)xmalloc(buffer_len * sizeof(wchar_t));
104

  
105
    switch (home_env) {
106
	case 1:
107
	    /* HOME */
108
	    GetEnvironmentVariableW(L"HOME", buffer, buffer_len);
109
	    break;
110
	case 2:
111
	    /* HOMEDRIVE + HOMEPATH */
112
	    len = GetEnvironmentVariableW(L"HOMEDRIVE", buffer, buffer_len);
113
	    GetEnvironmentVariableW(L"HOMEPATH", buffer + len, buffer_len - len);
114
	    break;
115
	case 3:
116
	    /* USERPROFILE */
117
	    GetEnvironmentVariableW(L"USERPROFILE", buffer, buffer_len);
118
	    break;
119
	default:
120
	    break;
121
	}
122

  
123
    if (home_env) {
124
	/* sanitize backslashes with forwardslashes */
125
	replace_wchar(buffer, L'\\', L'/');
126

  
127
	return buffer;
128
    }
129

  
130
    return NULL;
131
}
132

  
133
/* Remove trailing invalid ':$DATA' of the path. */
134
static inline size_t
135
remove_invalid_alternative_data(wchar_t *wfullpath, size_t size) {
136
    static const wchar_t prime[] = L":$DATA";
137
    enum { prime_len = (sizeof(prime) / sizeof(wchar_t)) -1 };
138

  
139
    if (size <= prime_len || _wcsnicmp(wfullpath + size - prime_len, prime, prime_len) != 0)
140
	return size;
141

  
142
    /* alias of stream */
143
    /* get rid of a bug of x64 VC++ */
144
    if (wfullpath[size - (prime_len + 1)] == ':') {
145
	/* remove trailing '::$DATA' */
146
	size -= prime_len + 1; /* prime */
147
	wfullpath[size] = L'\0';
148
    } else {
149
	/* remove trailing ':$DATA' of paths like '/aa:a:$DATA' */
150
	wchar_t *pos = wfullpath + size - (prime_len + 1);
151
	while (!IS_DIR_SEPARATOR_P(*pos) && pos != wfullpath) {
152
	    if (*pos == L':') {
153
		size -= prime_len; /* alternative */
154
		wfullpath[size] = L'\0';
155
		break;
156
	    }
157
	    pos--;
158
	}
159
    }
160
    return size;
161
}
162

  
163
/* Return system code page. */
164
static inline UINT
165
system_code_page() {
166
    return AreFileApisANSI() ? CP_ACP : CP_OEMCP;
167
}
168

  
169
/*
170
  Return code page number of the encoding.
171
  Cache code page into a hash for performance since finding the code page in
172
  Encoding#names is slow.
173
*/
174
static UINT
175
code_page(rb_encoding *enc)
176
{
177
    VALUE code_page_value, name_key;
178
    VALUE encoding, names_ary = Qundef, name;
179
    char *enc_name;
180
    struct RString fake_str;
181
    ID names;
182
    long i;
183

  
184
    if (!enc)
185
	return system_code_page();
186

  
187
    enc_name = (char *)rb_enc_name(enc);
188

  
189
    fake_str.basic.flags = T_STRING|RSTRING_NOEMBED;
190
    fake_str.basic.klass = rb_cString;
191
    fake_str.as.heap.len = strlen(enc_name);
192
    fake_str.as.heap.ptr = enc_name;
193
    fake_str.as.heap.aux.capa = fake_str.as.heap.len;
194
    name_key = (VALUE)&fake_str;
195
    ENCODING_CODERANGE_SET(name_key, rb_usascii_encindex(), ENC_CODERANGE_7BIT);
196

  
197
    code_page_value = rb_hash_lookup(rb_code_page, name_key);
198
    if (code_page_value != Qnil)
199
	return (UINT)FIX2INT(code_page_value);
200

  
201
    name_key = rb_usascii_str_new2(enc_name);
202

  
203
    encoding = rb_enc_from_encoding(enc);
204
    if (!NIL_P(encoding)) {
205
	CONST_ID(names, "names");
206
	names_ary = rb_funcall(encoding, names, 0);
207
    }
208

  
209
    if (enc == rb_usascii_encoding()) {
210
	UINT code_page = 20127;
211
	rb_hash_aset(rb_code_page, name_key, INT2FIX(code_page));
212
	return code_page;
213
    }
214
    else if (enc == rb_ascii8bit_encoding()) {
215
	UINT code_page = 437;
216
	rb_hash_aset(rb_code_page, name_key, INT2FIX(code_page));
217
	return code_page;
218
    }
219

  
220
    if (names_ary != Qundef) {
221
	for (i = 0; i < RARRAY_LEN(names_ary); i++) {
222
	    name = RARRAY_PTR(names_ary)[i];
223
	    if (strncmp("CP", RSTRING_PTR(name), 2) == 0) {
224
		int code_page = atoi(RSTRING_PTR(name) + 2);
225
		if (code_page != 0) {
226
		    rb_hash_aset(rb_code_page, name_key, INT2FIX(code_page));
227
		    return (UINT)code_page;
228
		}
229
	    }
230
	}
231
    }
232

  
233
    rb_hash_aset(rb_code_page, name_key, INT2FIX(INVALID_CODE_PAGE));
234
    return INVALID_CODE_PAGE;
235
}
236

  
237
static inline VALUE
238
fix_string_encoding(VALUE str, rb_encoding *encoding)
239
{
240
    VALUE result, tmp;
241

  
242
    tmp = rb_enc_str_new(RSTRING_PTR(str), RSTRING_LEN(str), encoding);
243
    result = rb_str_encode(tmp, rb_enc_from_encoding(rb_utf8_encoding()), 0, Qnil);
244

  
245
    return result;
246
}
247

  
248
/*
249
  Replace the last part of the path to long name.
250
  We try to avoid to call FindFirstFileW() since it takes long time.
251
*/
252
static inline size_t
253
replace_to_long_name(wchar_t **wfullpath, size_t size, int heap) {
254
    WIN32_FIND_DATAW find_data;
255
    HANDLE find_handle;
256

  
257
    /*
258
      Skip long name conversion if the path is already long name.
259
      Short name is 8.3 format.
260
      http://en.wikipedia.org/wiki/8.3_filename
261
      This check can be skipped for directory components that have file
262
      extensions longer than 3 characters, or total lengths longer than
263
      12 characters.
264
      http://msdn.microsoft.com/en-us/library/windows/desktop/aa364980(v=vs.85).aspx
265
    */
266
    size_t const max_short_name_size = 8 + 1 + 3;
267
    size_t const max_extension_size = 3;
268
    size_t path_len = 1, extension_len = 0;
269
    wchar_t *pos = *wfullpath;
270

  
271
    if (size == 3 && pos[1] == L':' && pos[2] == L'\\' && pos[3] == L'\0') {
272
	/* root path doesn't need short name expansion */
273
	return size;
274
    }
275

  
276
    pos = *wfullpath + size - 1;
277
    while (!IS_DIR_SEPARATOR_P(*pos) && pos != *wfullpath) {
278
	if (!extension_len && *pos == L'.') {
279
	    extension_len = path_len - 1;
280
	}
281
	if (path_len > max_short_name_size || extension_len > max_extension_size) {
282
	    return size;
283
	}
284
	path_len++;
285
	pos--;
286
    }
287

  
288
    find_handle = FindFirstFileW(*wfullpath, &find_data);
289
    if (find_handle != INVALID_HANDLE_VALUE) {
290
	size_t trail_pos = wcslen(*wfullpath);
291
	size_t file_len = wcslen(find_data.cFileName);
292

  
293
	FindClose(find_handle);
294
	while (trail_pos > 0) {
295
	    if (IS_DIR_SEPARATOR_P((*wfullpath)[trail_pos]))
296
		break;
297
	    trail_pos--;
298
	}
299
	size = trail_pos + 1 + file_len;
300
	if ((size + 1) > sizeof(*wfullpath) / sizeof((*wfullpath)[0])) {
301
	    wchar_t *buf = (wchar_t *)xmalloc((size + 1) * sizeof(wchar_t));
302
	    wcsncpy(buf, *wfullpath, trail_pos + 1);
303
	    if (heap)
304
		xfree(*wfullpath);
305
	    *wfullpath = buf;
306
	}
307
	wcsncpy(*wfullpath + trail_pos + 1, find_data.cFileName, file_len + 1);
308
    }
309
    return size;
310
}
311

  
312
VALUE
313
rb_file_expand_path_internal(VALUE fname, VALUE dname, int abs_mode, int long_name, VALUE result)
314
{
315
    size_t size = 0, wpath_len = 0, wdir_len = 0, whome_len = 0;
316
    size_t buffer_len = 0;
317
    char *fullpath = NULL;
318
    wchar_t *wfullpath = NULL, *wpath = NULL, *wpath_pos = NULL, *wdir = NULL;
319
    wchar_t *whome = NULL, *buffer = NULL, *buffer_pos = NULL;
320
    UINT path_cp, cp;
321
    VALUE path = fname, dir = dname;
322
    wchar_t wfullpath_buffer[PATH_BUFFER_SIZE];
323
    wchar_t path_drive = L'\0', dir_drive = L'\0';
324
    int ignore_dir = 0;
325
    rb_encoding *path_encoding;
326
    int tainted = 0;
327

  
328
    /* tainted if path is tainted */
329
    tainted = OBJ_TAINTED(path);
330

  
331
    /* get path encoding */
332
    if (NIL_P(dir)) {
333
	path_encoding = rb_enc_get(path);
334
    } else {
335
	path_encoding = rb_enc_check(path, dir);
336
    }
337

  
338
    cp = path_cp = code_page(path_encoding);
339

  
340
    /* workaround invalid codepage */
341
    if (path_cp == INVALID_CODE_PAGE) {
342
	cp = CP_UTF8;
343
	if (!NIL_P(path)) {
344
	    path = fix_string_encoding(path, path_encoding);
345
	}
346
    }
347

  
348
    /* convert char * to wchar_t */
349
    convert_mb_to_wchar(path, &wpath, &wpath_pos, &wpath_len, cp);
350

  
351
    /* determine if we need the user's home directory */
352
    /* expand '~' only if NOT rb_file_absolute_path() where `abs_mode` is 1 */
353
    if (abs_mode == 0 && ((wpath_len == 1 && wpath_pos[0] == L'~') ||
354
	(wpath_len >= 2 && wpath_pos[0] == L'~' && IS_DIR_SEPARATOR_P(wpath_pos[1])))) {
355
	/* tainted if expanding '~' */
356
	tainted = 1;
357

  
358
	whome = home_dir();
359
	if (whome == NULL) {
360
	    xfree(wpath);
361
	    rb_raise(rb_eArgError, "couldn't find HOME environment -- expanding `~'");
362
	}
363
	whome_len = wcslen(whome);
364

  
365
	if (PathIsRelativeW(whome) && !(whome_len >= 2 && IS_DIR_UNC_P(whome))) {
366
	    xfree(wpath);
367
	    rb_raise(rb_eArgError, "non-absolute home");
368
	}
369

  
370
	/* use filesystem encoding if expanding home dir */
371
	path_encoding = rb_filesystem_encoding();
372
	cp = path_cp = system_code_page();
373

  
374
	/* ignores dir since we are expading home */
375
	ignore_dir = 1;
376

  
377
	/* exclude ~ from the result */
378
	wpath_pos++;
379
	wpath_len--;
380

  
381
	/* exclude separator if present */
382
	if (wpath_len && IS_DIR_SEPARATOR_P(wpath_pos[0])) {
383
	    wpath_pos++;
384
	    wpath_len--;
385
	}
386
    } else if (wpath_len >= 2 && wpath_pos[1] == L':') {
387
	if (wpath_len >= 3 && IS_DIR_SEPARATOR_P(wpath_pos[2])) {
388
	    /* ignore dir since path contains a drive letter and a root slash */
389
	    ignore_dir = 1;
390
	} else {
391
	    /* determine if we ignore dir or not later */
392
	    path_drive = wpath_pos[0];
393
	}
394
    } else if (abs_mode == 0 && wpath_len >= 2 && wpath_pos[0] == L'~') {
395
	wchar_t *wuser = wpath_pos + 1;
396
	wchar_t *pos = wuser;
397
	char *user;
398

  
399
	/* tainted if expanding '~' */
400
	tainted = 1;
401

  
402
	while (!IS_DIR_SEPARATOR_P(*pos) && *pos != '\0')
403
	    pos++;
404

  
405
	*pos = '\0';
406
	convert_wchar_to_mb(wuser, &user, &size, cp);
407

  
408
	/* convert to VALUE and set the path encoding */
409
	if (path_cp == INVALID_CODE_PAGE) {
410
	    VALUE tmp = rb_enc_str_new(user, size, rb_utf8_encoding());
411
	    result = rb_str_encode(tmp, rb_enc_from_encoding(path_encoding), 0, Qnil);
412
	    rb_str_resize(tmp, 0);
413
	} else {
414
	    result = rb_enc_str_new(user, size, path_encoding);
415
	}
416

  
417
	xfree(wpath);
418
	if (user)
419
	    xfree(user);
420

  
421
	rb_raise(rb_eArgError, "can't find user %s", StringValuePtr(result));
422
    }
423

  
424
    /* convert dir */
425
    if (!ignore_dir && !NIL_P(dir)) {
426
	/* fix string encoding */
427
	if (path_cp == INVALID_CODE_PAGE) {
428
	    dir = fix_string_encoding(dir, path_encoding);
429
	}
430

  
431
	/* convert char * to wchar_t */
432
	convert_mb_to_wchar(dir, &wdir, NULL, &wdir_len, cp);
433

  
434
	if (wdir_len >= 2 && wdir[1] == L':') {
435
	    dir_drive = wdir[0];
436
	    if (wpath_len && IS_DIR_SEPARATOR_P(wpath_pos[0])) {
437
		wdir_len = 2;
438
	    }
439
	} else if (wdir_len >= 2 && IS_DIR_UNC_P(wdir)) {
440
	    /* UNC path */
441
	    if (wpath_len && IS_DIR_SEPARATOR_P(wpath_pos[0])) {
442
		/* cut the UNC path tail to '//host/share' */
443
		size_t separators = 0;
444
		size_t pos = 2;
445
		while (pos < wdir_len && separators < 2) {
446
		    if (IS_DIR_SEPARATOR_P(wdir[pos])) {
447
			separators++;
448
		    }
449
		    pos++;
450
		}
451
		if (separators == 2)
452
		    wdir_len = pos - 1;
453
	    }
454
	}
455
    }
456

  
457
    /* determine if we ignore dir or not */
458
    if (!ignore_dir && path_drive && dir_drive) {
459
	if (towupper(path_drive) == towupper(dir_drive)) {
460
	    /* exclude path drive letter to use dir */
461
	    wpath_pos += 2;
462
	    wpath_len -= 2;
463
	} else {
464
	    /* ignore dir since path drive is different from dir drive */
465
	    ignore_dir = 1;
466
	    wdir_len = 0;
467
	}
468
    }
469

  
470
    if (!ignore_dir && wpath_len >= 2 && IS_DIR_UNC_P(wpath)) {
471
	/* ignore dir since path has UNC root */
472
	ignore_dir = 1;
473
	wdir_len = 0;
474
    } else if (!ignore_dir && wpath_len >= 1 && IS_DIR_SEPARATOR_P(wpath[0]) &&
475
	!dir_drive && !(wdir_len >= 2 && IS_DIR_UNC_P(wdir))) {
476
	/* ignore dir since path has root slash and dir doesn't have drive or UNC root */
477
	ignore_dir = 1;
478
	wdir_len = 0;
479
    }
480

  
481
    buffer_len = wpath_len + 1 + wdir_len + 1 + whome_len + 1;
482

  
483
    buffer = buffer_pos = (wchar_t *)xmalloc((buffer_len + 1) * sizeof(wchar_t));
484

  
485
    /* add home */
486
    if (whome_len) {
487
	wcsncpy(buffer_pos, whome, whome_len);
488
	buffer_pos += whome_len;
489
    }
490

  
491
    /* Add separator if required */
492
    if (whome_len && wcsrchr(L"\\/:", buffer_pos[-1]) == NULL) {
493
	buffer_pos[0] = L'\\';
494
	buffer_pos++;
495
    }
496

  
497
    if (wdir_len) {
498
	/* tainted if dir is used and dir is tainted */
499
	if (!tainted && OBJ_TAINTED(dir))
500
	    tainted = 1;
501

  
502
	wcsncpy(buffer_pos, wdir, wdir_len);
503
	buffer_pos += wdir_len;
504
    }
505

  
506
    /* add separator if required */
507
    if (wdir_len && wcsrchr(L"\\/:", buffer_pos[-1]) == NULL) {
508
	buffer_pos[0] = L'\\';
509
	buffer_pos++;
510
    }
511

  
512
    /* now deal with path */
513
    if (wpath_len) {
514
	wcsncpy(buffer_pos, wpath_pos, wpath_len);
515
	buffer_pos += wpath_len;
516
    }
517

  
518
    /* GetFullPathNameW requires at least "." to determine current directory */
519
    if (wpath_len == 0) {
520
	buffer_pos[0] = L'.';
521
	buffer_pos++;
522
    }
523

  
524
    /* Ensure buffer is NULL terminated */
525
    buffer_pos[0] = L'\0';
526

  
527
    /* tainted if path is relative */
528
    if (!tainted && PathIsRelativeW(buffer) && !(buffer_len >= 2 && IS_DIR_UNC_P(buffer)))
529
	tainted = 1;
530

  
531
    /* FIXME: Make this more robust */
532
    /* Determine require buffer size */
533
    size = GetFullPathNameW(buffer, PATH_BUFFER_SIZE, wfullpath_buffer, NULL);
534
    if (size > PATH_BUFFER_SIZE) {
535
	// allocate enough memory to contain the response
536
	wfullpath = (wchar_t *)xmalloc(size * sizeof(wchar_t));
537
	size = GetFullPathNameW(buffer, size, wfullpath, NULL);
538
    } else {
539
	wfullpath = wfullpath_buffer;
540
    }
541

  
542
    /* Remove any trailing slashes */
543
    if (IS_DIR_SEPARATOR_P(wfullpath[size - 1]) &&
544
	wfullpath[size - 2] != L':' &&
545
	!(size == 2 && IS_DIR_UNC_P(wfullpath))) {
546
	size -= 1;
547
	wfullpath[size] = L'\0';
548
    }
549

  
550
    /* Remove any trailing dot */
551
    if (wfullpath[size - 1] == L'.') {
552
	size -= 1;
553
	wfullpath[size] = L'\0';
554
    }
555

  
556
    /* removes trailing invalid ':$DATA' */
557
    size = remove_invalid_alternative_data(wfullpath, size);
558

  
559
    /* Replace the trailing path to long name */
560
    if (long_name)
561
	size = replace_to_long_name(&wfullpath, size, (wfullpath != wfullpath_buffer));
562

  
563
    /* sanitize backslashes with forwardslashes */
564
    replace_wchar(wfullpath, L'\\', L'/');
565

  
566
    /* convert to char * */
567
    size = WideCharToMultiByte(cp, 0, wfullpath, size, NULL, 0, NULL, NULL);
568
    if (size > (size_t)RSTRING_LEN(result)) {
569
	rb_str_modify(result);
570
	rb_str_resize(result, size);
571
    }
572

  
573
    WideCharToMultiByte(cp, 0, wfullpath, size, RSTRING_PTR(result), size, NULL, NULL);
574
    rb_str_set_len(result, size);
575

  
576
    /* convert to VALUE and set the path encoding */
577
    if (path_cp == INVALID_CODE_PAGE) {
578
	VALUE tmp;
579
	size_t len;
580

  
581
	rb_enc_associate(result, rb_utf8_encoding());
582
	ENC_CODERANGE_CLEAR(result);
583
	tmp = rb_str_encode(result, rb_enc_from_encoding(path_encoding), 0, Qnil);
584
	len = RSTRING_LEN(tmp);
585
	rb_str_modify(result);
586
	rb_str_resize(result, len);
587
	memcpy(RSTRING_PTR(result), RSTRING_PTR(tmp), len);
588
	rb_str_resize(tmp, 0);
589
    }
590
    rb_enc_associate(result, path_encoding);
591
    ENC_CODERANGE_CLEAR(result);
592

  
593
    /* makes the result object tainted if expanding tainted strings or returning modified path */
594
    if (tainted)
595
	OBJ_TAINT(result);
596

  
597
    /* TODO: better cleanup */
598
    if (buffer)
599
	xfree(buffer);
600

  
601
    if (wpath)
602
	xfree(wpath);
603

  
604
    if (wdir)
605
	xfree(wdir);
606

  
607
    if (whome)
608
	xfree(whome);
609

  
610
    if (wfullpath && wfullpath != wfullpath_buffer)
611
	xfree(wfullpath);
612

  
613
    if (fullpath)
614
	xfree(fullpath);
615

  
616
    return result;
617
}
618

  
8 619
int
9 620
rb_file_load_ok(const char *path)
10 621
{
......
27 638
    }
28 639
    return ret;
29 640
}
641

  
642
void
643
rb_w32_init_file(void)
644
{
645
    rb_code_page = rb_hash_new();
646

  
647
    /* prevent GC removing rb_code_page */
648
    rb_gc_register_mark_object(rb_code_page);
649
}