Skip to content

Commit d15e1ac

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 a956e51 commit d15e1ac

File tree

3 files changed

+210
-5
lines changed

3 files changed

+210
-5
lines changed

Lib/test/test_subprocess.py

+46
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,26 @@ def is_env_var_to_ignore(n):
820833
if not is_env_var_to_ignore(k)]
821834
self.assertEqual(child_env_names, [])
822835

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

896+
@unittest.skipUnless(sys.platform == "win32", "Windows only issue")
897+
def test_win32_invalid_env(self):
898+
# '=' in the environment variable name
899+
newenv = os.environ.copy()
900+
newenv["FRUIT=VEGETABLE"] = "cabbage"
901+
with self.assertRaises(ValueError):
902+
subprocess.Popen(ZERO_RETURN_CMD, env=newenv)
903+
904+
newenv = os.environ.copy()
905+
newenv["==FRUIT"] = "cabbage"
906+
with self.assertRaises(ValueError):
907+
subprocess.Popen(ZERO_RETURN_CMD, env=newenv)
908+
863909
def test_communicate_stdin(self):
864910
p = subprocess.Popen([sys.executable, "-c",
865911
'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

+162-5
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,12 @@
3939
#include "structmember.h" // PyMemberDef
4040

4141

42-
#define WINDOWS_LEAN_AND_MEAN
4342
#include "windows.h"
4443
#include <crtdbg.h>
4544
#include "winreparse.h"
4645

46+
#include "pycore_runtime.h" // _Py_ID
47+
4748
#if defined(MS_WIN32) && !defined(MS_WIN64)
4849
#define HANDLE_TO_PYNUM(handle) \
4950
PyLong_FromUnsignedLong((unsigned long) handle)
@@ -781,12 +782,162 @@ gethandle(PyObject* obj, const char* name)
781782
return ret;
782783
}
783784

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

791942
/* convert environment dictionary to windows environment string */
792943
if (! PyMapping_Check(environment)) {
@@ -795,11 +946,16 @@ getenvironment(PyObject* environment)
795946
return NULL;
796947
}
797948

798-
keys = PyMapping_Keys(environment);
799-
if (!keys) {
949+
normalized_environment = normalize_environment(environment);
950+
if (normalize_environment == NULL) {
800951
return NULL;
801952
}
802-
values = PyMapping_Values(environment);
953+
954+
keys = PyMapping_Keys(normalized_environment);
955+
if (!keys) {
956+
goto error;
957+
}
958+
values = PyMapping_Values(normalized_environment);
803959
if (!values) {
804960
goto error;
805961
}
@@ -891,6 +1047,7 @@ getenvironment(PyObject* environment)
8911047

8921048
cleanup:
8931049
error:
1050+
Py_XDECREF(normalized_environment);
8941051
Py_XDECREF(keys);
8951052
Py_XDECREF(values);
8961053
return buffer;

0 commit comments

Comments
 (0)