Skip to content

Commit 4050a15

Browse files
miss-islingtonaiskAlexWaygoodeendebakpterlend-aasland
authored
gh-87868: Sort and remove duplicates in getenvironment() (GH-102731)
(cherry picked from commit c31be58) Co-authored-by: AN Long <[email protected]> Co-authored-by: Alex Waygood <[email protected]> Co-authored-by: Pieter Eendebak <[email protected]> Co-authored-by: Erlend E. Aasland <[email protected]>
1 parent 3fc45e6 commit 4050a15

File tree

3 files changed

+203
-4
lines changed

3 files changed

+203
-4
lines changed

Lib/test/test_subprocess.py

+46
Original file line numberDiff line numberDiff line change
@@ -791,6 +791,19 @@ def test_env(self):
791791
stdout, stderr = p.communicate()
792792
self.assertEqual(stdout, b"orange")
793793

794+
@unittest.skipUnless(sys.platform == "win32", "Windows only issue")
795+
def test_win32_duplicate_envs(self):
796+
newenv = os.environ.copy()
797+
newenv["fRUit"] = "cherry"
798+
newenv["fruit"] = "lemon"
799+
newenv["FRUIT"] = "orange"
800+
newenv["frUit"] = "banana"
801+
with subprocess.Popen(["CMD", "/c", "SET", "fruit"],
802+
stdout=subprocess.PIPE,
803+
env=newenv) as p:
804+
stdout, _ = p.communicate()
805+
self.assertEqual(stdout.strip(), b"frUit=banana")
806+
794807
# Windows requires at least the SYSTEMROOT environment variable to start
795808
# Python
796809
@unittest.skipIf(sys.platform == 'win32',
@@ -822,6 +835,26 @@ def is_env_var_to_ignore(n):
822835
if not is_env_var_to_ignore(k)]
823836
self.assertEqual(child_env_names, [])
824837

838+
@unittest.skipIf(sysconfig.get_config_var('Py_ENABLE_SHARED') == 1,
839+
'The Python shared library cannot be loaded '
840+
'without some system environments.')
841+
@unittest.skipIf(check_sanitizer(address=True),
842+
'AddressSanitizer adds to the environment.')
843+
def test_one_environment_variable(self):
844+
newenv = {'fruit': 'orange'}
845+
cmd = [sys.executable, '-c',
846+
'import sys,os;'
847+
'sys.stdout.write("fruit="+os.getenv("fruit"))']
848+
if sys.platform == "win32":
849+
cmd = ["CMD", "/c", "SET", "fruit"]
850+
with subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=newenv) as p:
851+
stdout, stderr = p.communicate()
852+
if p.returncode and support.verbose:
853+
print("STDOUT:", stdout.decode("ascii", "replace"))
854+
print("STDERR:", stderr.decode("ascii", "replace"))
855+
self.assertEqual(p.returncode, 0)
856+
self.assertEqual(stdout.strip(), b"fruit=orange")
857+
825858
def test_invalid_cmd(self):
826859
# null character in the command name
827860
cmd = sys.executable + '\0'
@@ -862,6 +895,19 @@ def test_invalid_env(self):
862895
stdout, stderr = p.communicate()
863896
self.assertEqual(stdout, b"orange=lemon")
864897

898+
@unittest.skipUnless(sys.platform == "win32", "Windows only issue")
899+
def test_win32_invalid_env(self):
900+
# '=' in the environment variable name
901+
newenv = os.environ.copy()
902+
newenv["FRUIT=VEGETABLE"] = "cabbage"
903+
with self.assertRaises(ValueError):
904+
subprocess.Popen(ZERO_RETURN_CMD, env=newenv)
905+
906+
newenv = os.environ.copy()
907+
newenv["==FRUIT"] = "cabbage"
908+
with self.assertRaises(ValueError):
909+
subprocess.Popen(ZERO_RETURN_CMD, env=newenv)
910+
865911
def test_communicate_stdin(self):
866912
p = subprocess.Popen([sys.executable, "-c",
867913
'import sys;'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Correctly sort and remove duplicate environment variables in
2+
:py:func:`!_winapi.CreateProcess`.

Modules/_winapi.c

+155-4
Original file line numberDiff line numberDiff line change
@@ -772,12 +772,157 @@ gethandle(PyObject* obj, const char* name)
772772
return ret;
773773
}
774774

775+
static PyObject *
776+
sortenvironmentkey(PyObject *module, PyObject *item)
777+
{
778+
return _winapi_LCMapStringEx_impl(NULL, LOCALE_NAME_INVARIANT,
779+
LCMAP_UPPERCASE, item);
780+
}
781+
782+
static PyMethodDef sortenvironmentkey_def = {
783+
"sortenvironmentkey", _PyCFunction_CAST(sortenvironmentkey), METH_O, "",
784+
};
785+
786+
static int
787+
sort_environment_keys(PyObject *keys)
788+
{
789+
PyObject *keyfunc = PyCFunction_New(&sortenvironmentkey_def, NULL);
790+
if (keyfunc == NULL) {
791+
return -1;
792+
}
793+
PyObject *kwnames = Py_BuildValue("(s)", "key");
794+
if (kwnames == NULL) {
795+
Py_DECREF(keyfunc);
796+
return -1;
797+
}
798+
PyObject *args[] = { keys, keyfunc };
799+
PyObject *ret = PyObject_VectorcallMethod(&_Py_ID(sort), args, 1, kwnames);
800+
Py_DECREF(keyfunc);
801+
Py_DECREF(kwnames);
802+
if (ret == NULL) {
803+
return -1;
804+
}
805+
Py_DECREF(ret);
806+
807+
return 0;
808+
}
809+
810+
static int
811+
compare_string_ordinal(PyObject *str1, PyObject *str2, int *result)
812+
{
813+
wchar_t *s1 = PyUnicode_AsWideCharString(str1, NULL);
814+
if (s1 == NULL) {
815+
return -1;
816+
}
817+
wchar_t *s2 = PyUnicode_AsWideCharString(str2, NULL);
818+
if (s2 == NULL) {
819+
PyMem_Free(s1);
820+
return -1;
821+
}
822+
*result = CompareStringOrdinal(s1, -1, s2, -1, TRUE);
823+
PyMem_Free(s1);
824+
PyMem_Free(s2);
825+
return 0;
826+
}
827+
828+
static PyObject *
829+
dedup_environment_keys(PyObject *keys)
830+
{
831+
PyObject *result = PyList_New(0);
832+
if (result == NULL) {
833+
return NULL;
834+
}
835+
836+
// Iterate over the pre-ordered keys, check whether the current key is equal
837+
// to the next key (ignoring case), if different, insert the current value
838+
// into the result list. If they are equal, do nothing because we always
839+
// want to keep the last inserted one.
840+
for (Py_ssize_t i = 0; i < PyList_GET_SIZE(keys); i++) {
841+
PyObject *key = PyList_GET_ITEM(keys, i);
842+
843+
// The last key will always be kept.
844+
if (i + 1 == PyList_GET_SIZE(keys)) {
845+
if (PyList_Append(result, key) < 0) {
846+
Py_DECREF(result);
847+
return NULL;
848+
}
849+
continue;
850+
}
851+
852+
PyObject *next_key = PyList_GET_ITEM(keys, i + 1);
853+
int compare_result;
854+
if (compare_string_ordinal(key, next_key, &compare_result) < 0) {
855+
Py_DECREF(result);
856+
return NULL;
857+
}
858+
if (compare_result == CSTR_EQUAL) {
859+
continue;
860+
}
861+
if (PyList_Append(result, key) < 0) {
862+
Py_DECREF(result);
863+
return NULL;
864+
}
865+
}
866+
867+
return result;
868+
}
869+
870+
static PyObject *
871+
normalize_environment(PyObject *environment)
872+
{
873+
PyObject *keys = PyMapping_Keys(environment);
874+
if (keys == NULL) {
875+
return NULL;
876+
}
877+
878+
if (sort_environment_keys(keys) < 0) {
879+
Py_DECREF(keys);
880+
return NULL;
881+
}
882+
883+
PyObject *normalized_keys = dedup_environment_keys(keys);
884+
Py_DECREF(keys);
885+
if (normalized_keys == NULL) {
886+
return NULL;
887+
}
888+
889+
PyObject *result = PyDict_New();
890+
if (result == NULL) {
891+
Py_DECREF(normalized_keys);
892+
return NULL;
893+
}
894+
895+
for (int i = 0; i < PyList_GET_SIZE(normalized_keys); i++) {
896+
PyObject *key = PyList_GET_ITEM(normalized_keys, i);
897+
PyObject *value = PyObject_GetItem(environment, key);
898+
if (value == NULL) {
899+
Py_DECREF(normalized_keys);
900+
Py_DECREF(result);
901+
return NULL;
902+
}
903+
904+
int ret = PyObject_SetItem(result, key, value);
905+
Py_DECREF(value);
906+
if (ret < 0) {
907+
Py_DECREF(normalized_keys);
908+
Py_DECREF(result);
909+
return NULL;
910+
}
911+
}
912+
913+
Py_DECREF(normalized_keys);
914+
915+
return result;
916+
}
917+
775918
static wchar_t *
776919
getenvironment(PyObject* environment)
777920
{
778921
Py_ssize_t i, envsize, totalsize;
779922
wchar_t *buffer = NULL, *p, *end;
780-
PyObject *keys, *values;
923+
PyObject *normalized_environment = NULL;
924+
PyObject *keys = NULL;
925+
PyObject *values = NULL;
781926

782927
/* convert environment dictionary to windows environment string */
783928
if (! PyMapping_Check(environment)) {
@@ -786,11 +931,16 @@ getenvironment(PyObject* environment)
786931
return NULL;
787932
}
788933

789-
keys = PyMapping_Keys(environment);
790-
if (!keys) {
934+
normalized_environment = normalize_environment(environment);
935+
if (normalize_environment == NULL) {
791936
return NULL;
792937
}
793-
values = PyMapping_Values(environment);
938+
939+
keys = PyMapping_Keys(normalized_environment);
940+
if (!keys) {
941+
goto error;
942+
}
943+
values = PyMapping_Values(normalized_environment);
794944
if (!values) {
795945
goto error;
796946
}
@@ -882,6 +1032,7 @@ getenvironment(PyObject* environment)
8821032

8831033
cleanup:
8841034
error:
1035+
Py_XDECREF(normalized_environment);
8851036
Py_XDECREF(keys);
8861037
Py_XDECREF(values);
8871038
return buffer;

0 commit comments

Comments
 (0)