From 6f4eb432fabaf91118fbb13e8a8959929644dfbe Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Tue, 8 Jan 2019 16:57:45 -0500 Subject: [PATCH] canonicalize Windows environment variables to uppercase (#30593) * canonicalize Windows environment variables to uppercase * use windows uppercase function * fix test * use systemerror in unlikely event of an error * documentation for case-insensitivity of ENV on Windows. * use symbol rather than string for `systemerror` --- NEWS.md | 5 ++++- base/env.jl | 16 +++++++++++++++- test/env.jl | 23 ++++++++++++++++++++++- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/NEWS.md b/NEWS.md index 9092fc909f25b..685572a3fa480 100644 --- a/NEWS.md +++ b/NEWS.md @@ -29,7 +29,6 @@ New library functions Standard library changes ------------------------ - #### LinearAlgebra * Added keyword arguments `rtol`, `atol` to `pinv` and `nullspace` ([#29998]). @@ -42,6 +41,10 @@ Standard library changes * Fixed `repr` such that it displays `DateTime` as it would be entered in Julia ([#30200]). +#### Miscellaneous + +* Since environment variables on Windows are case-insensitive, `ENV` now converts its keys + to uppercase for display, iteration, and copying ([#30593]). External dependencies --------------------- diff --git a/base/env.jl b/base/env.jl index ffeb9d409c110..166609378cebf 100644 --- a/base/env.jl +++ b/base/env.jl @@ -70,6 +70,10 @@ struct EnvDict <: AbstractDict{String,String}; end Reference to the singleton `EnvDict`, providing a dictionary interface to system environment variables. + +(On Windows, system environment variables are case-insensitive, and `ENV` correspondingly converts +all keys to uppercase for display, iteration, and copying. Portable code should not rely on the +existence of lower-case environment variables or on the ability to distinguish variables by case.) """ const ENV = EnvDict() @@ -85,6 +89,16 @@ push!(::EnvDict, kv::Pair{<:AbstractString}) = setindex!(ENV, kv.second, kv.firs if Sys.iswindows() GESW() = (pos = ccall(:GetEnvironmentStringsW,stdcall,Ptr{UInt16},()); (pos,pos)) + function winuppercase(s::AbstractString) + isempty(s) && return s + LOCALE_INVARIANT = 0x0000007f + LCMAP_UPPERCASE = 0x00000200 + ws = transcode(UInt16, String(s)) + result = ccall(:LCMapStringW, stdcall, Cint, (UInt32, UInt32, Ptr{UInt16}, Cint, Ptr{UInt16}, Cint), + LOCALE_INVARIANT, LCMAP_UPPERCASE, ws, length(ws), ws, length(ws)) + systemerror(:LCMapStringW, iszero(result)) + return transcode(String, ws) + end function iterate(hash::EnvDict, block::Tuple{Ptr{UInt16},Ptr{UInt16}} = GESW()) if unsafe_load(block[1]) == 0 ccall(:FreeEnvironmentStringsW, stdcall, Int32, (Ptr{UInt16},), block[2]) @@ -100,7 +114,7 @@ if Sys.iswindows() if m === nothing error("malformed environment entry: $env") end - return (Pair{String,String}(m.captures[1], m.captures[2]), (pos+(len+1)*2, blk)) + return (Pair{String,String}(winuppercase(m.captures[1]), m.captures[2]), (pos+(len+1)*2, blk)) end else # !windows function iterate(::EnvDict, i=0) diff --git a/test/env.jl b/test/env.jl index 48cbbdd20c324..6d429ab5040d8 100644 --- a/test/env.jl +++ b/test/env.jl @@ -36,7 +36,7 @@ end end @testset "#17956" begin @test length(ENV) > 1 - k1, k2 = "__test__", "__test1__" + k1, k2 = "__TEST__", "__TEST1__" withenv(k1=>k1, k2=>k2) do b_k1, b_k2 = false, false for (k, v) in ENV @@ -81,3 +81,24 @@ end @test ENV["testing_envdict"] == "tested" delete!(ENV, "testing_envdict") end + +if Sys.iswindows() + @testset "windows case-insensitivity" begin + for k in ("testing_envdict", "testing_envdict_\u00ee") + K = uppercase(k) + v = "tested $k" + ENV[k] = v + @test haskey(ENV, K) + @test ENV[K] == v + @test K in keys(ENV) + @test K in collect(keys(ENV)) + @test k ∉ collect(keys(ENV)) + env = copy(ENV) + @test haskey(env, K) + @test env[K] == v + @test !haskey(env, k) + delete!(ENV, k) + @test !haskey(ENV, K) + end + end +end