mirror of https://github.com/python/cpython
gh-87868: Sort and remove duplicates in getenvironment() (GH-102731)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com> Co-authored-by: Pieter Eendebak <pieter.eendebak@gmail.com> Co-authored-by: Erlend E. Aasland <erlend.aasland@protonmail.com>
This commit is contained in:
parent
fda901a1ff
commit
c31be58da8
|
@ -791,6 +791,19 @@ class ProcessTestCase(BaseTestCase):
|
|||
stdout, stderr = p.communicate()
|
||||
self.assertEqual(stdout, b"orange")
|
||||
|
||||
@unittest.skipUnless(sys.platform == "win32", "Windows only issue")
|
||||
def test_win32_duplicate_envs(self):
|
||||
newenv = os.environ.copy()
|
||||
newenv["fRUit"] = "cherry"
|
||||
newenv["fruit"] = "lemon"
|
||||
newenv["FRUIT"] = "orange"
|
||||
newenv["frUit"] = "banana"
|
||||
with subprocess.Popen(["CMD", "/c", "SET", "fruit"],
|
||||
stdout=subprocess.PIPE,
|
||||
env=newenv) as p:
|
||||
stdout, _ = p.communicate()
|
||||
self.assertEqual(stdout.strip(), b"frUit=banana")
|
||||
|
||||
# Windows requires at least the SYSTEMROOT environment variable to start
|
||||
# Python
|
||||
@unittest.skipIf(sys.platform == 'win32',
|
||||
|
@ -822,6 +835,17 @@ class ProcessTestCase(BaseTestCase):
|
|||
if not is_env_var_to_ignore(k)]
|
||||
self.assertEqual(child_env_names, [])
|
||||
|
||||
def test_one_environment_variable(self):
|
||||
newenv = {'fruit': 'orange'}
|
||||
cmd = [sys.executable, '-c',
|
||||
'import sys,os;'
|
||||
'sys.stdout.write("fruit="+os.getenv("fruit"))']
|
||||
if sys.platform == "win32":
|
||||
cmd = ["CMD", "/c", "SET", "fruit"]
|
||||
with subprocess.Popen(cmd, stdout=subprocess.PIPE, env=newenv) as p:
|
||||
stdout, _ = p.communicate()
|
||||
self.assertTrue(stdout.startswith(b"fruit=orange"))
|
||||
|
||||
def test_invalid_cmd(self):
|
||||
# null character in the command name
|
||||
cmd = sys.executable + '\0'
|
||||
|
@ -862,6 +886,19 @@ class ProcessTestCase(BaseTestCase):
|
|||
stdout, stderr = p.communicate()
|
||||
self.assertEqual(stdout, b"orange=lemon")
|
||||
|
||||
@unittest.skipUnless(sys.platform == "win32", "Windows only issue")
|
||||
def test_win32_invalid_env(self):
|
||||
# '=' in the environment variable name
|
||||
newenv = os.environ.copy()
|
||||
newenv["FRUIT=VEGETABLE"] = "cabbage"
|
||||
with self.assertRaises(ValueError):
|
||||
subprocess.Popen(ZERO_RETURN_CMD, env=newenv)
|
||||
|
||||
newenv = os.environ.copy()
|
||||
newenv["==FRUIT"] = "cabbage"
|
||||
with self.assertRaises(ValueError):
|
||||
subprocess.Popen(ZERO_RETURN_CMD, env=newenv)
|
||||
|
||||
def test_communicate_stdin(self):
|
||||
p = subprocess.Popen([sys.executable, "-c",
|
||||
'import sys;'
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Correctly sort and remove duplicate environment variables in
|
||||
:py:func:`!_winapi.CreateProcess`.
|
|
@ -774,12 +774,157 @@ gethandle(PyObject* obj, const char* name)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
sortenvironmentkey(PyObject *module, PyObject *item)
|
||||
{
|
||||
return _winapi_LCMapStringEx_impl(NULL, LOCALE_NAME_INVARIANT,
|
||||
LCMAP_UPPERCASE, item);
|
||||
}
|
||||
|
||||
static PyMethodDef sortenvironmentkey_def = {
|
||||
"sortenvironmentkey", _PyCFunction_CAST(sortenvironmentkey), METH_O, "",
|
||||
};
|
||||
|
||||
static int
|
||||
sort_environment_keys(PyObject *keys)
|
||||
{
|
||||
PyObject *keyfunc = PyCFunction_New(&sortenvironmentkey_def, NULL);
|
||||
if (keyfunc == NULL) {
|
||||
return -1;
|
||||
}
|
||||
PyObject *kwnames = Py_BuildValue("(s)", "key");
|
||||
if (kwnames == NULL) {
|
||||
Py_DECREF(keyfunc);
|
||||
return -1;
|
||||
}
|
||||
PyObject *args[] = { keys, keyfunc };
|
||||
PyObject *ret = PyObject_VectorcallMethod(&_Py_ID(sort), args, 1, kwnames);
|
||||
Py_DECREF(keyfunc);
|
||||
Py_DECREF(kwnames);
|
||||
if (ret == NULL) {
|
||||
return -1;
|
||||
}
|
||||
Py_DECREF(ret);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
compare_string_ordinal(PyObject *str1, PyObject *str2, int *result)
|
||||
{
|
||||
wchar_t *s1 = PyUnicode_AsWideCharString(str1, NULL);
|
||||
if (s1 == NULL) {
|
||||
return -1;
|
||||
}
|
||||
wchar_t *s2 = PyUnicode_AsWideCharString(str2, NULL);
|
||||
if (s2 == NULL) {
|
||||
PyMem_Free(s1);
|
||||
return -1;
|
||||
}
|
||||
*result = CompareStringOrdinal(s1, -1, s2, -1, TRUE);
|
||||
PyMem_Free(s1);
|
||||
PyMem_Free(s2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
dedup_environment_keys(PyObject *keys)
|
||||
{
|
||||
PyObject *result = PyList_New(0);
|
||||
if (result == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Iterate over the pre-ordered keys, check whether the current key is equal
|
||||
// to the next key (ignoring case), if different, insert the current value
|
||||
// into the result list. If they are equal, do nothing because we always
|
||||
// want to keep the last inserted one.
|
||||
for (Py_ssize_t i = 0; i < PyList_GET_SIZE(keys); i++) {
|
||||
PyObject *key = PyList_GET_ITEM(keys, i);
|
||||
|
||||
// The last key will always be kept.
|
||||
if (i + 1 == PyList_GET_SIZE(keys)) {
|
||||
if (PyList_Append(result, key) < 0) {
|
||||
Py_DECREF(result);
|
||||
return NULL;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
PyObject *next_key = PyList_GET_ITEM(keys, i + 1);
|
||||
int compare_result;
|
||||
if (compare_string_ordinal(key, next_key, &compare_result) < 0) {
|
||||
Py_DECREF(result);
|
||||
return NULL;
|
||||
}
|
||||
if (compare_result == CSTR_EQUAL) {
|
||||
continue;
|
||||
}
|
||||
if (PyList_Append(result, key) < 0) {
|
||||
Py_DECREF(result);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
normalize_environment(PyObject *environment)
|
||||
{
|
||||
PyObject *keys = PyMapping_Keys(environment);
|
||||
if (keys == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (sort_environment_keys(keys) < 0) {
|
||||
Py_DECREF(keys);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PyObject *normalized_keys = dedup_environment_keys(keys);
|
||||
Py_DECREF(keys);
|
||||
if (normalized_keys == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PyObject *result = PyDict_New();
|
||||
if (result == NULL) {
|
||||
Py_DECREF(normalized_keys);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (int i = 0; i < PyList_GET_SIZE(normalized_keys); i++) {
|
||||
PyObject *key = PyList_GET_ITEM(normalized_keys, i);
|
||||
PyObject *value = PyObject_GetItem(environment, key);
|
||||
if (value == NULL) {
|
||||
Py_DECREF(normalized_keys);
|
||||
Py_DECREF(result);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int ret = PyObject_SetItem(result, key, value);
|
||||
Py_DECREF(value);
|
||||
if (ret < 0) {
|
||||
Py_DECREF(normalized_keys);
|
||||
Py_DECREF(result);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
Py_DECREF(normalized_keys);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static wchar_t *
|
||||
getenvironment(PyObject* environment)
|
||||
{
|
||||
Py_ssize_t i, envsize, totalsize;
|
||||
wchar_t *buffer = NULL, *p, *end;
|
||||
PyObject *keys, *values;
|
||||
PyObject *normalized_environment = NULL;
|
||||
PyObject *keys = NULL;
|
||||
PyObject *values = NULL;
|
||||
|
||||
/* convert environment dictionary to windows environment string */
|
||||
if (! PyMapping_Check(environment)) {
|
||||
|
@ -788,11 +933,16 @@ getenvironment(PyObject* environment)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
keys = PyMapping_Keys(environment);
|
||||
if (!keys) {
|
||||
normalized_environment = normalize_environment(environment);
|
||||
if (normalize_environment == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
values = PyMapping_Values(environment);
|
||||
|
||||
keys = PyMapping_Keys(normalized_environment);
|
||||
if (!keys) {
|
||||
goto error;
|
||||
}
|
||||
values = PyMapping_Values(normalized_environment);
|
||||
if (!values) {
|
||||
goto error;
|
||||
}
|
||||
|
@ -884,6 +1034,7 @@ getenvironment(PyObject* environment)
|
|||
|
||||
cleanup:
|
||||
error:
|
||||
Py_XDECREF(normalized_environment);
|
||||
Py_XDECREF(keys);
|
||||
Py_XDECREF(values);
|
||||
return buffer;
|
||||
|
|
Loading…
Reference in New Issue