summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAN Long <aisk@users.noreply.github.com>2024-01-09 15:58:26 (GMT)
committerGitHub <noreply@github.com>2024-01-09 15:58:26 (GMT)
commitc31be58da8577ef140e83d4e46502c7bb1eb9abf (patch)
tree6b51808f95800ae57c782d31b1338ad37eb739e4
parentfda901a1ff94ea6cc338b74928acdbc5ee165ed7 (diff)
downloadcpython-c31be58da8577ef140e83d4e46502c7bb1eb9abf.zip
cpython-c31be58da8577ef140e83d4e46502c7bb1eb9abf.tar.gz
cpython-c31be58da8577ef140e83d4e46502c7bb1eb9abf.tar.bz2
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>
-rw-r--r--Lib/test/test_subprocess.py37
-rw-r--r--Misc/NEWS.d/next/Windows/2023-03-15-23-53-45.gh-issue-87868.4C36oQ.rst2
-rw-r--r--Modules/_winapi.c159
3 files changed, 194 insertions, 4 deletions
diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py
index 6d3228b..102e697 100644
--- a/Lib/test/test_subprocess.py
+++ b/Lib/test/test_subprocess.py
@@ -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;'
diff --git a/Misc/NEWS.d/next/Windows/2023-03-15-23-53-45.gh-issue-87868.4C36oQ.rst b/Misc/NEWS.d/next/Windows/2023-03-15-23-53-45.gh-issue-87868.4C36oQ.rst
new file mode 100644
index 0000000..37e8103
--- /dev/null
+++ b/Misc/NEWS.d/next/Windows/2023-03-15-23-53-45.gh-issue-87868.4C36oQ.rst
@@ -0,0 +1,2 @@
+Correctly sort and remove duplicate environment variables in
+:py:func:`!_winapi.CreateProcess`.
diff --git a/Modules/_winapi.c b/Modules/_winapi.c
index 8c48b6f..a26850e 100644
--- a/Modules/_winapi.c
+++ b/Modules/_winapi.c
@@ -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;