hash.c: env encoding fallback on Windows

* hash.c (env_str_new, env_path_str_new): make default string
  UTF-8 for the case conversion is not possible.  [Bug #8822]
* hash.c (get_env_cstr): convert non-ASCII string to UTF-8 string.
* hash.c (ruby_setenv): use wide char version to put environment
  variable to deal with non-ASCII value.

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@52896 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
nobu 2015-12-05 08:26:26 +00:00
parent 613737eee9
commit 5e3467c441
3 changed files with 84 additions and 17 deletions

View File

@ -1,3 +1,13 @@
Sat Dec 5 17:26:24 2015 Nobuyoshi Nakada <nobu@ruby-lang.org>
* hash.c (env_str_new, env_path_str_new): make default string
UTF-8 for the case conversion is not possible. [Bug #8822]
* hash.c (get_env_cstr): convert non-ASCII string to UTF-8 string.
* hash.c (ruby_setenv): use wide char version to put environment
variable to deal with non-ASCII value.
Sat Dec 5 09:56:50 2015 Nobuyoshi Nakada <nobu@ruby-lang.org> Sat Dec 5 09:56:50 2015 Nobuyoshi Nakada <nobu@ruby-lang.org>
* ruby_atomic.h (ATOMIC_CAS): old value to be swapped should be * ruby_atomic.h (ATOMIC_CAS): old value to be swapped should be

80
hash.c
View File

@ -2875,7 +2875,7 @@ extern char **environ;
static VALUE static VALUE
env_str_transcode(VALUE str, rb_encoding *enc) env_str_transcode(VALUE str, rb_encoding *enc)
{ {
return rb_str_conv_enc_opts(str, rb_utf8_encoding(), enc, return rb_str_conv_enc_opts(str, NULL, enc,
ECONV_INVALID_REPLACE | ECONV_UNDEF_REPLACE, Qnil); ECONV_INVALID_REPLACE | ECONV_UNDEF_REPLACE, Qnil);
} }
#endif #endif
@ -2884,7 +2884,7 @@ static VALUE
env_str_new(const char *ptr, long len) env_str_new(const char *ptr, long len)
{ {
#ifdef _WIN32 #ifdef _WIN32
VALUE str = env_str_transcode(rb_str_new(ptr, len), rb_locale_encoding()); VALUE str = env_str_transcode(rb_utf8_str_new(ptr, len), rb_locale_encoding());
#else #else
VALUE str = rb_locale_str_new(ptr, len); VALUE str = rb_locale_str_new(ptr, len);
#endif #endif
@ -2897,7 +2897,7 @@ static VALUE
env_path_str_new(const char *ptr) env_path_str_new(const char *ptr)
{ {
#ifdef _WIN32 #ifdef _WIN32
VALUE str = env_str_transcode(rb_str_new_cstr(ptr), rb_filesystem_encoding()); VALUE str = env_str_transcode(rb_utf8_str_new_cstr(ptr), rb_filesystem_encoding());
#else #else
VALUE str = rb_filesystem_str_new_cstr(ptr); VALUE str = rb_filesystem_str_new_cstr(ptr);
#endif #endif
@ -2914,14 +2914,28 @@ env_str_new2(const char *ptr)
} }
static void * static void *
get_env_cstr(VALUE str, const char *name) get_env_cstr(
#ifdef _WIN32
volatile VALUE *pstr,
#else
VALUE str,
#endif
const char *name)
{ {
#ifdef _WIN32
VALUE str = *pstr;
#endif
char *var; char *var;
rb_encoding *enc = rb_enc_get(str); rb_encoding *enc = rb_enc_get(str);
if (!rb_enc_asciicompat(enc)) { if (!rb_enc_asciicompat(enc)) {
rb_raise(rb_eArgError, "bad environment variable %s: ASCII incompatible encoding: %s", rb_raise(rb_eArgError, "bad environment variable %s: ASCII incompatible encoding: %s",
name, rb_enc_name(enc)); name, rb_enc_name(enc));
} }
#ifdef _WIN32
if (!rb_enc_str_asciionly_p(str)) {
*pstr = str = rb_str_conv_enc(str, NULL, rb_utf8_encoding());
}
#endif
var = RSTRING_PTR(str); var = RSTRING_PTR(str);
if (memchr(var, '\0', RSTRING_LEN(str))) { if (memchr(var, '\0', RSTRING_LEN(str))) {
rb_raise(rb_eArgError, "bad environment variable %s: contains null byte", name); rb_raise(rb_eArgError, "bad environment variable %s: contains null byte", name);
@ -2929,8 +2943,13 @@ get_env_cstr(VALUE str, const char *name)
return var; return var;
} }
#ifdef _WIN32
#define get_env_ptr(var, val) \
(var = get_env_cstr(&(val), #var))
#else
#define get_env_ptr(var, val) \ #define get_env_ptr(var, val) \
(var = get_env_cstr(val, #var)) (var = get_env_cstr(val, #var))
#endif
static inline const char * static inline const char *
env_name(volatile VALUE *s) env_name(volatile VALUE *s)
@ -3024,7 +3043,7 @@ rb_f_getenv(VALUE obj, VALUE name)
static VALUE static VALUE
env_fetch(int argc, VALUE *argv) env_fetch(int argc, VALUE *argv)
{ {
VALUE key; VALUE key, name;
long block_given; long block_given;
const char *nam, *env; const char *nam, *env;
@ -3034,7 +3053,8 @@ env_fetch(int argc, VALUE *argv)
if (block_given && argc == 2) { if (block_given && argc == 2) {
rb_warn("block supersedes default value argument"); rb_warn("block supersedes default value argument");
} }
nam = env_name(key); name = key;
nam = env_name(name);
env = getenv(nam); env = getenv(nam);
if (!env) { if (!env) {
if (block_given) return rb_yield(key); if (block_given) return rb_yield(key);
@ -3102,10 +3122,10 @@ envix(const char *nam)
#if defined(_WIN32) #if defined(_WIN32)
static size_t static size_t
getenvsize(const char* p) getenvsize(const WCHAR* p)
{ {
const char* porg = p; const WCHAR* porg = p;
while (*p++) p += strlen(p) + 1; while (*p++) p += lstrlenW(p) + 1;
return p - porg + 1; return p - porg + 1;
} }
static size_t static size_t
@ -3140,27 +3160,52 @@ void
ruby_setenv(const char *name, const char *value) ruby_setenv(const char *name, const char *value)
{ {
#if defined(_WIN32) #if defined(_WIN32)
# if defined(MINGW_HAS_SECURE_API) || RUBY_MSVCRT_VERSION >= 80
# define HAVE__WPUTENV_S 1
# endif
VALUE buf; VALUE buf;
WCHAR *wname;
WCHAR *wvalue = 0;
int failed = 0; int failed = 0;
int len;
check_envname(name); check_envname(name);
len = MultiByteToWideChar(CP_UTF8, 0, name, -1, NULL, 0);
if (value) { if (value) {
char* p = GetEnvironmentStringsA(); WCHAR* p = GetEnvironmentStringsW();
size_t n; size_t n;
int len2;
if (!p) goto fail; /* never happen */ if (!p) goto fail; /* never happen */
n = strlen(name) + 2 + strlen(value) + getenvsize(p); n = lstrlen(name) + 2 + strlen(value) + getenvsize(p);
FreeEnvironmentStringsA(p); FreeEnvironmentStringsW(p);
if (n >= getenvblocksize()) { if (n >= getenvblocksize()) {
goto fail; /* 2 for '=' & '\0' */ goto fail; /* 2 for '=' & '\0' */
} }
buf = rb_sprintf("%s=%s", name, value); len2 = MultiByteToWideChar(CP_UTF8, 0, value, -1, NULL, 0);
wname = ALLOCV_N(WCHAR, buf, len + len2);
wvalue = wname + len;
MultiByteToWideChar(CP_UTF8, 0, name, -1, wname, len);
MultiByteToWideChar(CP_UTF8, 0, value, -1, wvalue, len2);
#ifndef HAVE__WPUTENV_S
wname[len-1] = L'=';
#endif
} }
else { else {
buf = rb_sprintf("%s=", name); wname = ALLOCV_N(WCHAR, buf, len + 1);
MultiByteToWideChar(CP_UTF8, 0, name, -1, wname, len);
wvalue = wname + len;
*wvalue = L'\0';
#ifndef HAVE__WPUTENV_S
wname[len-1] = L'=';
#endif
} }
failed = putenv(RSTRING_PTR(buf)); #ifndef HAVE__WPUTENV_S
failed = _wputenv(wname);
#else
failed = _wputenv_s(wname, wvalue);
#endif
ALLOCV_END(buf);
/* even if putenv() failed, clean up and try to delete the /* even if putenv() failed, clean up and try to delete the
* variable from the system area. */ * variable from the system area. */
rb_str_resize(buf, 0);
if (!value || !*value) { if (!value || !*value) {
/* putenv() doesn't handle empty value */ /* putenv() doesn't handle empty value */
if (!SetEnvironmentVariable(name, value) && if (!SetEnvironmentVariable(name, value) &&
@ -3797,8 +3842,9 @@ static VALUE
env_assoc(VALUE env, VALUE key) env_assoc(VALUE env, VALUE key)
{ {
const char *s, *e; const char *s, *e;
VALUE name = key;
s = env_name(key); s = env_name(name);
e = getenv(s); e = getenv(s);
if (e) return rb_assoc_new(key, rb_tainted_str_new2(e)); if (e) return rb_assoc_new(key, rb_tainted_str_new2(e));
return Qnil; return Qnil;

View File

@ -482,5 +482,16 @@ class TestEnv < Test::Unit::TestCase
500.times(&doit) 500.times(&doit)
end; end;
end end
if Encoding.find("locale") == Encoding::UTF_8
def test_utf8
text = "testing åáâäãāあ"
test = ENV["test"]
ENV["test"] = text
assert_equal text, ENV["test"]
ensure
ENV["test"] = test
end
end
end end
end end