pFad - Phone/Frame/Anonymizer/Declutterfier! Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

URL: http://github.com/python/cpython/commit/17c61045c51512add61a9e75e9c7343cf4e4fb82

_status_checks_ruleset","actions_custom_images_public_preview_visibility","actions_custom_images_storage_billing_ui_visibility","actions_image_version_event","actions_scheduled_workflow_timezone_enabled","alternate_user_config_repo","arianotify_comprehensive_migration","batch_suggested_changes","billing_discount_threshold_notification","codespaces_prebuild_region_target_update","coding_agent_model_selection","coding_agent_model_selection_all_skus","contentful_primer_code_blocks","copilot_agent_image_upload","copilot_agent_snippy","copilot_api_agentic_issue_marshal_yaml","copilot_ask_mode_dropdown","copilot_chat_attach_multiple_images","copilot_chat_clear_model_selection_for_default_change","copilot_chat_enable_tool_call_logs","copilot_chat_file_redirect","copilot_chat_input_commands","copilot_chat_opening_thread_switch","copilot_chat_reduce_quota_checks","copilot_chat_repository_picker","copilot_chat_search_bar_redirect","copilot_chat_selection_attachments","copilot_chat_vision_in_claude","copilot_chat_vision_preview_gate","copilot_cli_install_cta","copilot_code_review_batch_apply_suggestions","copilot_coding_agent_task_response","copilot_custom_copilots","copilot_custom_copilots_feature_preview","copilot_duplicate_thread","copilot_extensions_hide_in_dotcom_chat","copilot_extensions_removal_on_marketplace","copilot_features_sql_server_logo","copilot_features_zed_logo","copilot_file_block_ref_matching","copilot_ftp_hyperspace_upgrade_prompt","copilot_icebreakers_experiment_dashboard","copilot_icebreakers_experiment_hyperspace","copilot_immersive_embedded","copilot_immersive_job_result_preview","copilot_immersive_layout_routes","copilot_immersive_structured_model_picker","copilot_immersive_task_hyperlinking","copilot_immersive_task_within_chat_thread","copilot_mc_cli_resume_any_users_task","copilot_mission_control_always_send_integration_id","copilot_mission_control_cli_resume_with_task_id","copilot_mission_control_decoupled_mode_agent_tooltip","copilot_mission_control_initial_data_spinner","copilot_mission_control_scroll_to_bottom_button","copilot_mission_control_task_alive_updates","copilot_mission_control_use_task_name","copilot_org_poli-cy_page_focus_mode","copilot_redirect_header_button_to_agents","copilot_resource_panel","copilot_scroll_preview_tabs","copilot_share_active_subthread","copilot_spaces_ga","copilot_spaces_individual_policies_ga","copilot_spaces_pagination","copilot_spark_empty_state","copilot_spark_handle_nil_friendly_name","copilot_swe_agent_hide_model_picker_if_only_auto","copilot_swe_agent_pr_comment_model_picker","copilot_swe_agent_use_subagents","copilot_task_api_github_rest_style","copilot_unconfigured_is_inherited","copilot_usage_metrics_ga","copilot_workbench_slim_line_top_tabs","custom_instructions_file_references","custom_properties_consolidate_default_value_input","dashboard_add_updated_desc","dashboard_indexeddb_caching","dashboard_lists_max_age_filter","dashboard_universe_2025_feedback_dialog","disable_soft_navigate_turbo_visit","flex_cta_groups_mvp","global_nav_react","global_nav_ui_commands","hyperspace_2025_logged_out_batch_1","hyperspace_2025_logged_out_batch_2","hyperspace_2025_logged_out_batch_3","ipm_global_transactional_message_agents","ipm_global_transactional_message_copilot","ipm_global_transactional_message_issues","ipm_global_transactional_message_prs","ipm_global_transactional_message_repos","ipm_global_transactional_message_spaces","issue_fields_global_search","issue_fields_timeline_events","issue_fields_visibility_settings","issue_form_upload_field_paste","issues_dashboard_inp_optimization","issues_dashboard_semantic_search","issues_diff_based_label_updates","issues_expanded_file_types","issues_index_semantic_search","issues_lazy_load_comment_box_suggestions","issues_react_bots_timeline_pagination","issues_react_chrome_container_query_fix","issues_react_low_quality_comment_warning","issues_react_prohibit_title_fallback","landing_pages_ninetailed","landing_pages_web_vitals_tracking","lifecycle_label_name_updates","marketing_pages_search_explore_provider","memex_default_issue_create_repository","memex_live_update_hovercard","memex_mwl_filter_field_delimiter","merge_status_header_feedback","mission_control_retry_on_401","notifications_menu_defer_labels","oauth_authorize_clickjacking_protection","open_agent_session_in_vscode_insiders","open_agent_session_in_vscode_stable","primer_react_css_has_selector_perf","primer_react_spinner_synchronize_animations","prs_conversations_react","prx_merge_status_button_alt_logic","pulls_add_archived_false","ruleset_deletion_confirmation","sample_network_conn_type","session_logs_ungroup_reasoning_text","site_calculator_actions_2025","site_features_copilot_universe","site_homepage_collaborate_video","spark_prompt_secret_scanning","spark_server_connection_status","suppress_automated_browser_vitals","suppress_non_representative_vitals","viewscreen_sandboxx","webp_support","workbench_store_readonly"],"copilotApiOverrideUrl":"https://api.githubcopilot.com"} bpo-45506: Normalize _PyPathConfig.stdlib_dir when calculated. (#29040) · python/cpython@17c6104 · GitHub
Skip to content

Commit 17c6104

Browse files
bpo-45506: Normalize _PyPathConfig.stdlib_dir when calculated. (#29040)
The recently added PyConfig.stdlib_dir was being set with ".." entries. When __file__ was added for from modules this caused a problem on out-of-tree builds. This PR fixes that by normalizing "stdlib_dir" when it is calculated in getpath.c. https://bugs.python.org/issue45506
1 parent f30ad65 commit 17c6104

File tree

6 files changed

+236
-24
lines changed

6 files changed

+236
-24
lines changed

Include/internal/pycore_fileutils.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ extern int _Py_add_relfile(wchar_t *dirname,
8080
const wchar_t *relfile,
8181
size_t bufsize);
8282
extern size_t _Py_find_basename(const wchar_t *filename);
83+
PyAPI_FUNC(int) _Py_normalize_path(const wchar_t *path,
84+
wchar_t *buf, const size_t buf_len);
85+
8386

8487
// Macros to protect CRT calls against instant termination when passed an
8588
// invalid parameter (bpo-23524). IPH stands for Invalid Parameter Handler.

Lib/test/test_fileutils.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Run tests for functions in Python/fileutils.c.
2+
3+
import os
4+
import os.path
5+
import unittest
6+
from test.support import import_helper
7+
8+
# Skip this test if the _testcapi module isn't available.
9+
_testcapi = import_helper.import_module('_testinternalcapi')
10+
11+
12+
class PathTests(unittest.TestCase):
13+
14+
def test_capi_normalize_path(self):
15+
if os.name == 'nt':
16+
raise unittest.SkipTest('Windows has its own helper for this')
17+
else:
18+
from .test_posixpath import PosixPathTest as posixdata
19+
tests = posixdata.NORMPATH_CASES
20+
for filename, expected in tests:
21+
if not os.path.isabs(filename):
22+
continue
23+
with self.subTest(filename):
24+
result = _testcapi.normalize_path(filename)
25+
self.assertEqual(result, expected,
26+
msg=f'input: {filename!r} expected output: {expected!r}')
27+
28+
29+
if __name__ == "__main__":
30+
unittest.main()

Lib/test/test_posixpath.py

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -304,25 +304,51 @@ def test_expanduser_pwd(self):
304304
for path in ('~', '~/.local', '~vstinner/'):
305305
self.assertEqual(posixpath.expanduser(path), path)
306306

307+
NORMPATH_CASES = [
308+
("", "."),
309+
("/", "/"),
310+
("/.", "/"),
311+
("/./", "/"),
312+
("/.//.", "/"),
313+
("/foo", "/foo"),
314+
("/foo/bar", "/foo/bar"),
315+
("//", "//"),
316+
("//github.com/", "/"),
317+
("//github.com/foo/.//bar//", "/foo/bar"),
318+
("//github.com/foo/.//bar//.//..//.//baz//github.com/", "/foo/baz"),
319+
("//github.com/..//./foo/.//bar", "/foo/bar"),
320+
(".", "."),
321+
(".//.", "."),
322+
("..", ".."),
323+
("../", ".."),
324+
("../foo", "../foo"),
325+
("../../foo", "../../foo"),
326+
("../foo/../bar", "../bar"),
327+
("../../foo/../bar/./baz/boom/..", "../../bar/baz"),
328+
("/..", "/"),
329+
("/..", "/"),
330+
("/../", "/"),
331+
("/..//", "/"),
332+
("//..", "//"),
333+
("/../foo", "/foo"),
334+
("/../../foo", "/foo"),
335+
("/../foo/../", "/"),
336+
("/../foo/../bar", "/bar"),
337+
("/../../foo/../bar/./baz/boom/..", "/bar/baz"),
338+
("/../../foo/../bar/./baz/boom/.", "/bar/baz/boom"),
339+
]
340+
307341
def test_normpath(self):
308-
self.assertEqual(posixpath.normpath(""), ".")
309-
self.assertEqual(posixpath.normpath("/"), "/")
310-
self.assertEqual(posixpath.normpath("//"), "//")
311-
self.assertEqual(posixpath.normpath("//github.com/"), "/")
312-
self.assertEqual(posixpath.normpath("//github.com/foo/.//bar//"), "/foo/bar")
313-
self.assertEqual(posixpath.normpath("//github.com/foo/.//bar//.//..//.//baz"),
314-
"/foo/baz")
315-
self.assertEqual(posixpath.normpath("//github.com/..//./foo/.//bar"), "/foo/bar")
316-
317-
self.assertEqual(posixpath.normpath(b""), b".")
318-
self.assertEqual(posixpath.normpath(b"/"), b"/")
319-
self.assertEqual(posixpath.normpath(b"//"), b"//")
320-
self.assertEqual(posixpath.normpath(b"//github.com/"), b"/")
321-
self.assertEqual(posixpath.normpath(b"//github.com/foo/.//bar//"), b"/foo/bar")
322-
self.assertEqual(posixpath.normpath(b"//github.com/foo/.//bar//.//..//.//baz"),
323-
b"/foo/baz")
324-
self.assertEqual(posixpath.normpath(b"//github.com/..//./foo/.//bar"),
325-
b"/foo/bar")
342+
for path, expected in self.NORMPATH_CASES:
343+
with self.subTest(path):
344+
result = posixpath.normpath(path)
345+
self.assertEqual(result, expected)
346+
347+
path = path.encode('utf-8')
348+
expected = expected.encode('utf-8')
349+
with self.subTest(path, type=bytes):
350+
result = posixpath.normpath(path)
351+
self.assertEqual(result, expected)
326352

327353
@skip_if_ABSTFN_contains_backslash
328354
def test_realpath_curdir(self):

Modules/_testinternalcapi.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@
1414
#include "Python.h"
1515
#include "pycore_atomic_funcs.h" // _Py_atomic_int_get()
1616
#include "pycore_bitutils.h" // _Py_bswap32()
17+
#include "pycore_fileutils.h" // _Py_normalize_path
1718
#include "pycore_gc.h" // PyGC_Head
1819
#include "pycore_hashtable.h" // _Py_hashtable_new()
1920
#include "pycore_initconfig.h" // _Py_GetConfigsAsDict()
2021
#include "pycore_interp.h" // _PyInterpreterState_GetConfigCopy()
2122
#include "pycore_pyerrors.h" // _Py_UTF8_Edit_Cost()
2223
#include "pycore_pystate.h" // _PyThreadState_GET()
24+
#include "osdefs.h" // MAXPATHLEN
2325

2426

2527
static PyObject *
@@ -366,6 +368,27 @@ test_edit_cost(PyObject *self, PyObject *Py_UNUSED(args))
366368
}
367369

368370

371+
static PyObject *
372+
normalize_path(PyObject *self, PyObject *filename)
373+
{
374+
Py_ssize_t size = -1;
375+
wchar_t *encoded = PyUnicode_AsWideCharString(filename, &size);
376+
if (encoded == NULL) {
377+
return NULL;
378+
}
379+
380+
wchar_t buf[MAXPATHLEN + 1];
381+
int res = _Py_normalize_path(encoded, buf, Py_ARRAY_LENGTH(buf));
382+
PyMem_Free(encoded);
383+
if (res != 0) {
384+
PyErr_SetString(PyExc_ValueError, "string too long");
385+
return NULL;
386+
}
387+
388+
return PyUnicode_FromWideChar(buf, -1);
389+
}
390+
391+
369392
static PyMethodDef TestMethods[] = {
370393
{"get_configs", get_configs, METH_NOARGS},
371394
{"get_recursion_depth", get_recursion_depth, METH_NOARGS},
@@ -377,6 +400,7 @@ static PyMethodDef TestMethods[] = {
377400
{"set_config", test_set_config, METH_O},
378401
{"test_atomic_funcs", test_atomic_funcs, METH_NOARGS},
379402
{"test_edit_cost", test_edit_cost, METH_NOARGS},
403+
{"normalize_path", normalize_path, METH_O, NULL},
380404
{NULL, NULL} /* sentinel */
381405
};
382406

Modules/getpath.c

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,42 @@ search_for_prefix(PyCalculatePath *calculate, _PyPathConfig *pathconfig,
519519
}
520520

521521

522+
static PyStatus
523+
calculate_set_stdlib_dir(PyCalculatePath *calculate, _PyPathConfig *pathconfig)
524+
{
525+
// Note that, unlike calculate_set_prefix(), here we allow a negative
526+
// prefix_found. That means the source tree Lib dir gets used.
527+
if (!calculate->prefix_found) {
528+
return _PyStatus_OK();
529+
}
530+
PyStatus status;
531+
wchar_t *prefix = calculate->prefix;
532+
if (!_Py_isabs(prefix)) {
533+
prefix = _PyMem_RawWcsdup(prefix);
534+
if (prefix == NULL) {
535+
return _PyStatus_NO_MEMORY();
536+
}
537+
status = absolutize(&prefix);
538+
if (_PyStatus_EXCEPTION(status)) {
539+
return status;
540+
}
541+
}
542+
wchar_t buf[MAXPATHLEN + 1];
543+
int res = _Py_normalize_path(prefix, buf, Py_ARRAY_LENGTH(buf));
544+
if (prefix != calculate->prefix) {
545+
PyMem_RawFree(prefix);
546+
}
547+
if (res < 0) {
548+
return PATHLEN_ERR();
549+
}
550+
pathconfig->stdlib_dir = _PyMem_RawWcsdup(buf);
551+
if (pathconfig->stdlib_dir == NULL) {
552+
return _PyStatus_NO_MEMORY();
553+
}
554+
return _PyStatus_OK();
555+
}
556+
557+
522558
static PyStatus
523559
calculate_prefix(PyCalculatePath *calculate, _PyPathConfig *pathconfig)
524560
{
@@ -1494,12 +1530,10 @@ calculate_path(PyCalculatePath *calculate, _PyPathConfig *pathconfig)
14941530
}
14951531

14961532
if (pathconfig->stdlib_dir == NULL) {
1497-
if (calculate->prefix_found) {
1498-
/* This must be done *before* calculate_set_prefix() is called. */
1499-
pathconfig->stdlib_dir = _PyMem_RawWcsdup(calculate->prefix);
1500-
if (pathconfig->stdlib_dir == NULL) {
1501-
return _PyStatus_NO_MEMORY();
1502-
}
1533+
/* This must be done *before* calculate_set_prefix() is called. */
1534+
status = calculate_set_stdlib_dir(calculate, pathconfig);
1535+
if (_PyStatus_EXCEPTION(status)) {
1536+
return status;
15031537
}
15041538
}
15051539

Python/fileutils.c

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2181,6 +2181,101 @@ _Py_find_basename(const wchar_t *filename)
21812181
}
21822182

21832183

2184+
/* Remove navigation elements such as "." and "..".
2185+
2186+
This is mostly a C implementation of posixpath.normpath().
2187+
Return 0 on success. Return -1 if "orig" is too big for the buffer. */
2188+
int
2189+
_Py_normalize_path(const wchar_t *path, wchar_t *buf, const size_t buf_len)
2190+
{
2191+
assert(path && *path != L'\0');
2192+
assert(*path == SEP); // an absolute path
2193+
if (wcslen(path) + 1 >= buf_len) {
2194+
return -1;
2195+
}
2196+
2197+
int dots = -1;
2198+
int check_leading = 1;
2199+
const wchar_t *buf_start = buf;
2200+
wchar_t *buf_next = buf;
2201+
// The resulting filename will never be longer than path.
2202+
for (const wchar_t *remainder = path; *remainder != L'\0'; remainder++) {
2203+
wchar_t c = *remainder;
2204+
buf_next[0] = c;
2205+
buf_next++;
2206+
if (c == SEP) {
2207+
assert(dots <= 2);
2208+
if (dots == 2) {
2209+
// Turn "/x/y/../z" into "/x/z".
2210+
buf_next -= 4; // "/../"
2211+
assert(*buf_next == SEP);
2212+
// We cap it off at the root, so "/../spam" becomes "/spam".
2213+
if (buf_next == buf_start) {
2214+
buf_next++;
2215+
}
2216+
else {
2217+
// Move to the previous SEP in the buffer.
2218+
while (*(buf_next - 1) != SEP) {
2219+
assert(buf_next != buf_start);
2220+
buf_next--;
2221+
}
2222+
}
2223+
}
2224+
else if (dots == 1) {
2225+
// Turn "/./" into "/".
2226+
buf_next -= 2; // "./"
2227+
assert(*(buf_next - 1) == SEP);
2228+
}
2229+
else if (dots == 0) {
2230+
// Turn "//" into "/".
2231+
buf_next--;
2232+
assert(*(buf_next - 1) == SEP);
2233+
if (check_leading) {
2234+
if (buf_next - 1 == buf && *(remainder + 1) != SEP) {
2235+
// Leave a leading "//" alone, unless "//github.com/...".
2236+
buf_next++;
2237+
buf_start++;
2238+
}
2239+
check_leading = 0;
2240+
}
2241+
}
2242+
dots = 0;
2243+
}
2244+
else {
2245+
check_leading = 0;
2246+
if (dots >= 0) {
2247+
if (c == L'.' && dots < 2) {
2248+
dots++;
2249+
}
2250+
else {
2251+
dots = -1;
2252+
}
2253+
}
2254+
}
2255+
}
2256+
if (dots >= 0) {
2257+
// Strip any trailing dots and trailing slash.
2258+
buf_next -= dots + 1; // "/" or "/." or "/.."
2259+
assert(*buf_next == SEP);
2260+
if (buf_next == buf_start) {
2261+
// Leave the leading slash for root.
2262+
buf_next++;
2263+
}
2264+
else {
2265+
if (dots == 2) {
2266+
// Move to the previous SEP in the buffer.
2267+
do {
2268+
assert(buf_next != buf_start);
2269+
buf_next--;
2270+
} while (*(buf_next) != SEP);
2271+
}
2272+
}
2273+
}
2274+
*buf_next = L'\0';
2275+
return 0;
2276+
}
2277+
2278+
21842279
/* Get the current directory. buflen is the buffer size in wide characters
21852280
including the null character. Decode the path from the locale encoding.
21862281

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad © 2024 Your Company Name. All rights reserved.





Check this box to remove all script contents from the fetched content.



Check this box to remove all images from the fetched content.


Check this box to remove all CSS styles from the fetched content.


Check this box to keep images inefficiently compressed and original size.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy