8000 gh-87868: Sort and remove duplicates in getenvironment() (GH-102731) · python/cpython@255872a · GitHub
[go: up one dir, main page]

Skip to content

Commit 255872a

Browse files
aiskAlexWaygoodeendebakpterlend-aasland
authored andcommitted
gh-87868: Sort and remove duplicates in getenvironment() (GH-102731)
(cherry picked from commit c31be58) Co-authored-by: AN Long <aisk@users.noreply.github.com> 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>
1 parent 50efd7d commit 255872a

File tree

3 files changed

+194
-4
lines changed

3 files changed

+194
-4
lines changed

Lib/test/test_subprocess.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,19 @@ def test_env(self):
789789
stdout, stderr = p.communicate()
790790
self.assertEqual(stdout, b"orange")
791791

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

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

887+
@unittest.skipUnless(sys.platform == "win32", "Windows only issue")
888+
def test_win32_invalid_env(self):
889+
# '=' in the environment variable name
890+
newenv = os.environ.copy()
891+
newenv["FRUIT=VEGETABLE"] = "cabbage"
892+
with self.assertRaises(ValueError):
893+
subprocess.Popen(ZERO_RETURN_CMD, env=newenv)
894+
895+
newenv = os.environ.copy()
896+
newenv["==FRUIT"] = "cabbage"
897+
with self.assertRaises(ValueError):
898+
subprocess.Popen(ZERO_RETURN_CMD, env=newenv)
899+
863900
def test_communicate_stdin(self):
864901
p = subprocess.Popen([sys.executable, "-c",
865902
'import sys;'
Lines changed: 2 additions & 0 deletions
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

Lines changed: 155 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -781,12 +781,157 @@ gethandle(PyObject* obj, const char* name)
781781
return ret;
782782
}
783783

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

791936
/* convert environment dictionary to windows environment string */
792937
if (! PyMapping_Check(environment)) {
@@ -795,11 +940,16 @@ getenvironment(PyObject* environment)
795940
return NULL;
796941
}
797942

798-
keys = PyMapping_Keys(environment);
799-
if (!keys) {
943+
normalized_environment = normalize_environment(environment);
944+
if (normalize_environment == NULL) {
800945
return NULL;
801946
}
802-
values = PyMapping_Values(environment);
947+
948+
keys = PyMapping_Keys(normalized_environment);
949+
if (!keys) {
950+
goto error;
951+
}
952+
values = PyMapping_Values(normalized_environment);
803953
if (!values) {
804954
goto error;
805955
}
@@ -891,6 +1041,7 @@ getenvironment(PyObject* environment)
8911041

8921042
cleanup:
8931043
error:
1044+
Py_XDECREF(normalized_environment);
8941045
Py_XDECREF(keys);
8951046
Py_XDECREF(values);
8961047
return buffer;

0 commit comments

Comments
 (0)
0