Skip to content

Commit c31be58

Browse files
aiskAlexWaygoodeendebakpterlend-aasland
authored
gh-87868: Sort and remove duplicates in getenvironment() (GH-102731)
Co-authored-by: Alex Waygood <[email protected]> Co-authored-by: Pieter Eendebak <[email protected]> Co-authored-by: Erlend E. Aasland <[email protected]>
1 parent fda901a commit c31be58

File tree

3 files changed

+194
-4
lines changed

3 files changed

+194
-4
lines changed

Lib/test/test_subprocess.py

+37
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,17 @@ def is_env_var_to_ignore(n):
822835
if not is_env_var_to_ignore(k)]
823836
self.assertEqual(child_env_names, [])
824837

838+
def test_one_environment_variable(self):
839+
newenv = {'fruit': 'orange'}
840+
cmd = [sys.executable, '-c',
841+
'import sys,os;'
842+
'sys.stdout.write("fruit="+os.getenv("fruit"))']
843+
if sys.platform == "win32":
844+
cmd = ["CMD", "/c", "SET", "fruit"]
845+
with subprocess.Popen(cmd, stdout=subprocess.PIPE, env=newenv) as p:
846+
stdout, _ = p.communicate()
847+
self.assertTrue(stdout.startswith(b"fruit=orange"))
848+
825849
def test_invalid_cmd(self):
826850
# null character in the command name
827851
cmd = sys.executable + '\0'
@@ -862,6 +886,19 @@ def test_invalid_env(self):
862886
stdout, stderr = p.communicate()
863887
self.assertEqual(stdout, b"orange=lemon")
864888

889+
@unittest.skipUnless(sys.platform == "win32", "Windows only issue")
890+
def test_win32_invalid_env(self):
891+
# '=' in the environment variable name
892+
newenv = os.environ.copy()
893+
newenv["FRUIT=VEGETABLE"] = "cabbage"
894+
with self.assertRaises(ValueError):
895+
subprocess.Popen(ZERO_RETURN_CMD, env=newenv)
896+
897+
newenv = os.environ.copy()
898+
newenv["==FRUIT"] = "cabbage"
899+
with self.assertRaises(ValueError):
900+
subprocess.Popen(ZERO_RETURN_CMD, env=newenv)
901+
865902
def test_communicate_stdin(self):
866903
p = subprocess.Popen([sys.executable, "-c",
867904
'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
@@ -774,12 +774,157 @@ gethandle(PyObject* obj, const char* name)
774774
return ret;
775775
}
776776

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

784929
/* convert environment dictionary to windows environment string */
785930
if (! PyMapping_Check(environment)) {
@@ -788,11 +933,16 @@ getenvironment(PyObject* environment)
788933
return NULL;
789934
}
790935

791-
keys = PyMapping_Keys(environment);
792-
if (!keys) {
936+
normalized_environment = normalize_environment(environment);
937+
if (normalize_environment == NULL) {
793938
return NULL;
794939
}
795-
values = PyMapping_Values(environment);
940+
941+
keys = PyMapping_Keys(normalized_environment);
942+
if (!keys) {
943+
goto error;
944+
}
945+
values = PyMapping_Values(normalized_environment);
796946
if (!values) {
797947
goto error;
798948
}
@@ -884,6 +1034,7 @@ getenvironment(PyObject* environment)
8841034

8851035
cleanup:
8861036
error:
1037+
Py_XDECREF(normalized_environment);
8871038
Py_XDECREF(keys);
8881039
Py_XDECREF(values);
8891040
return buffer;

0 commit comments

Comments
 (0)