Ориентация главы

Модуль path ведает лексическим слоем путей — склейка, разложение, зачистка, валидация — всё без единого касания файловой системы (чистые строковые операции: ноль IO, ноль побочных эффектов, тесты без диска). Три ключевые операции: xrtPathJoin — многосегментная склейка с автоматической лексической зачисткой (свёртка ../, нормализация разделителей); четырёхстороннее разложение (Name/Stem/Ext/каталог за один заход); xrtPathIsSafeEntry — проверка безопасности (отказ обходу каталога и именам устройств — досмотровая рамка для внешнего входа, входящего в путь). Плюс посегментная итерация без распределений и запросы системных путей (home/cwd/real).

44.1引入

Файл, загруженный пользователем, нужно положить в data/{用户名}/icon.png (имя пользователя) — а имя пользователя это внешний вход. Склейка напрямую: имя ../../etc — и файл записан вне песочницы; имя CON.txt (имя устройства Windows) ведёт себя причудливо. Такой «обход пути» — завсегдатай рейтингов уязвимостей, и корень всегда один: внешний вход вошёл в путь без лексической проверки.

Вторая ежедневная боль — зачистка рукописной склейки путей: "project" + "src/../include/xrt.h" — склеенный путь несёт избыточный сегмент src/..; большинство файловых систем это терпят, но сравнение журналов, ключи кеша и утверждения тестов загрязняются «один путь — много написаний». xrtPathJoin встраивает зачистку в склейку: выход всегда свёрнутая каноническая форма — один путь — единственное написание есть ключевой обет лексического слоя.

44.2概念

Склейка и автоматическая зачистка

xrtPathJoin("project", "src/../include/xrt.h") (два сегмента cstr на входе, владеющий str на выходе) → project\include\xrt.h — src/.. свёрнут, разделители нормализованы по платформе. Зачистка лексического уровня (без разрешения симлинков и без проверки существования на диске): .. сохраняется, когда свернуть нельзя (../secret ошибочно не сворачивается в пустоту — это и есть семантика безопасности). Кроссплатформенные формы: XPATH_NATIVE — по текущей платформе (обратные слеши Windows/прямые Unix); явное указание формы — для кроссплатформенно согласованных продуктов (снимки тестов, файлы манифестов).

Четырёхстороннее разложение и пересборка

РазложениеРезультат (для project\include\xrt.h)
xrtPathNamexrt.h (последний сегмент)
xrtPathStemxrt (без расширения)
xrtPathExt.h (с точкой)
Часть каталогапересборка склейкой или получение итерацией

xrtPathWithName заменяет имя файла, сохраняя каталог (renamed=project\include\runtime.h) — стандартная поза смены расширения и добавления суффикса. Разложение и пересборка взаимно обратны: 目录 + Name == 原路径 (каталог + Name == исходный путь) — тесты утверждают именно этот закон обратимости.

Проверка безопасности: досмотровая рамка для внешнего входа

xrtPathIsSafeEntry — валидация с правом вето в один голос, отказывающая трём опасным формам: обход (последовательности ../ — побег из базового каталога), абсолютный путь (внешний вход не должен решать корень), имена устройств платформы (CON/NUL и другие зарезервированные имена Windows). Стандартный конвейер перед входом внешних данных в путь: IsSafeEntry 校验 → Join 拼接(自动清理)→ 使用 (проверка IsSafeEntry → склейка Join (с автозачисткой) → использование) — вывод образца safe (rejected) и есть протокол правоприменения этой рамки. Второй параметр управляет допустимостью подкаталоговых записей (распаковка архивов открывает по мере надобности).

Анатомическая схема одного пути

1Входproject/src/../include/xrt.h (пользователь пишет кто как)
2Зачистка Joinсвернуть src/.., нормализовать разделители - выдать каноническую форму
3Четырёхстороннее разложениеName=xrt.h / Stem=xrt / Ext=.h / каталог=project/include
4Обратная пересборкакаталог + Name == исходный путь; WithName меняет имя, сохраняя каталог

Посегментная итерация и системные пути

xrtPathIterInit/Next обходит по сегментам без распределений (представления-заимствования: каждый сегмент — срез исходного пути, конвейерное мышление главы 25); подсчёт сегментов и создание каталогов уровень за уровнем (глава 47) держатся на нём. Запросы системных путей: home (каталог пользователя), cwd (текущий каталог), real (истинный путь после разрешения симлинков — вот это требует касания файловой системы и есть единственное исключение модуля).

44.3示例

Полная программа: склейка с зачисткой и четырёхстороннее разложение

Из образца репозитория examples/path/basic/main.c:

examples/path/basic/main.c源码 ↗
























 */

#include <stdio.h>

#include <xrt.h>



int main(void)
{
	str sJoined = NULL;
	str sName = NULL;
	str sStem = NULL;
	str sExt = NULL;
	str sRenamed = NULL;
	xpathiter Iterator;
	xpathcomponent Component;
	size_t iComponents = 0;
	int iResult = 1;

	/* 拼接 + 清理:src/.. 折叠,得到 project\include\xrt.h。 */
	sJoined = xrtPathJoin("project", "src/../include/xrt.h");
	if ( sJoined == NULL ) {
		goto cleanup;
	}

	/* 四路分解:文件名 / 主干 / 扩展名 / 换名拷贝。 */
	sName = xrtPathName(sJoined);
	sStem = xrtPathStem(sJoined);
	sExt = xrtPathExt(sJoined);
	sRenamed = xrtPathWithName(sJoined, "runtime.h");
	if ( (sName == NULL) || (sStem == NULL) ||
		 (sExt == NULL) || (sRenamed == NULL) ) {
		goto cleanup;
	}

	


	 */
	if ( !xrtPathIterInit(&Iterator, xrtStrView(sJoined), XPATH_NATIVE) ) {
		goto cleanup;
	}
	while ( xrtPathNext(&Iterator, &Component) ) {
		iComponents++;
	}

	printf("path=%s\nname=%s\nstem=%s\next=%s\nrenamed=%s\n"
		"components=%zu\nlocal=%d\n",
		sJoined, sName, sStem, sExt, sRenamed, iComponents,
		xrtPathIsLocal(xrtStrView(sJoined), XPATH_NATIVE) ? 1 : 0);
	iResult = 0;

cleanup:
	xrtFree(sJoined);
	xrtFree(sName);
	xrtFree(sStem);
	xrtFree(sExt);
	xrtFree(sRenamed);
	return iResult;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single impl.c examples/path/basic/main.c -lws2_32 -liphlpapipath=project\include\xrt.hname=xrt.hstem=xrtext=.hrenamed=project\include\runtime.hcomponents=3local=1

Что только что произошло. (1) Вывод Join("project", "src/../include/xrt.h") уже не содержит src/.. — склейка и есть зачистка, продукт канонический; разделитель — обратный слеш Windows (та же программа на Linux выведет прямые слеши — форма платформы выбирается сама). (2) Четырёхстороннее разложение за один заход: Name — xrt.h, Stem отбрасывает .h, Ext содержит точку — три представления получены независимо. (3) WithName заменяет имя файла, каталог не тронут — стандартная поза переименования. (4) components=3 получен итерацией без распределений — три сегмента (project/include/xrt.h) пересчитаны по одному, на всём обходе ни одного распределения в куче. (5) local=1 — вердикт об относительности: развилка абсолютный/относительный нужна при маршрутизации путей (конфигурация относительно базового каталога).

Полная программа: тройной отказ проверки безопасности

Из examples/path/safe/main.c:

examples/path/safe/main.c源码 ↗




















 */

#include <stdio.h>

#include <xrt.h>



int main(void)
{
	cstr arrEntries[] = {
		"assets/icon.png",     /* 合法相对条目 */
		"../secret.txt",       /* 穿越逃逸 */
		"CON.txt"              /* Windows 保留设备名 */
	};

	for ( size_t i = 0; i < sizeof(arrEntries) / sizeof(arrEntries[0]); i++ ) {
		printf("%s: %s\n", arrEntries[i],
			xrtPathIsSafeEntry(xrtStrView(arrEntries[i]), false) ?
			"safe" : "rejected");
	}
	return 0;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single impl.c examples/path/safe/main.c -lws2_32 -liphlpapiassets/icon.png: safe../secret.txt: rejectedCON.txt: rejected

Что только что произошло. (1) Обычный относительный путь assets/icon.png проходит — проверка не задерживает легальный вход. (2) ../secret.txt отвергнут — последовательность обхода не выйдет за досмотровую рамку; третий вход (форма абсолютного пути) отвергнут так же — внешний вход не должен нести с собой корень. (3) CON.txt отвергнут — кроссплатформенное interception имён устройств Windows (на Linux отказ такой же — согласованность прежде всего). Образец tour оставлен чтением всех интерфейсов: структура разбора (root/flags/stem/ext), build и clean, преобразование в относительный вид, вердикты относительности/абсолютности — по ok на группу, четыре группы покрывают полную интерфейсную поверхность модуля path.

44.4契约

  • Чистая лексика: всё, кроме real (разрешение симлинков), не касается файловой системы — тестируемость на максимум.
  • Склейка есть зачистка: продукт Join (два сегмента cstr) — свёрнутая нормализованная каноническая форма; многосегментные пути — вложенными Join или одним Join после сборки; один путь — одно написание.
  • Обратимость разложения: каталог + Name == исходный путь; Stem + Ext == Name (с точкой).
  • Досмотровая дисциплина: внешний вход перед входом в путь обязан пройти IsSafe; обход/абсолютный/имя устройства — тройной отказ.
  • Формы платформ: NATIVE по платформе; явное указание — для кроссплатформенно согласованных продуктов.
  • Итерация без распределений: представления сегментов заимствуют исходный путь, срок действия от него же.

От примеров к инженерии: три хозяина путей

Разбор конфигурации и ресурсов: на старте приложения три базовых пути — «каталог конфигурации/каталог данных/каталог временного» — нормализуются (Join + real), и дальше все относительные пути приклеиваются к базе — маршрутизация путей централизована, голые строковые пути по закоулкам не разбросаны. Отображение пользовательского ввода: имена загружаемых файлов, пути статических ресурсов URL — привратник IsSafeEntry + склейка Join + белый список расширений (зеркало конвейера главы 36 на файловом слое).Кроссплатформенные продукты: пути в файлах манифестов, ключах кеша, снимках тестов — явная форма (не NATIVE) гарантирует одинаковые продукты на Linux и Windows, и только тогда хеш-отпечатки главы 12 воспроизводимы. Три хозяина делят одну нижнюю черту: сначала нормализуй путь, потом употребляй — голый строковый путь есть корень всех зол на пути.

Отношение к главам 44~47

path — лексическая предпосылка файлового семейства (главы 45~48): прежде чем открыть файл, есть путь, а путь прежде проходит нормализацию и досмотр Join/IsSafeEntry. Главы файлового семейства будут оглядываться сюда — «откуда путь» рассказано один раз у источника (в этой главе), файловые главы сосредоточены на «что после открытия». Есть и обратное отношение: запрос real этой главы (единственный интерфейс, касающийся диска) внутри опирается на операции с метаданными файловой системы — лексический слой и слой IO пожимают руки в точке real, а в остальном расходятся как банка и берег.

Три точки кроссплатформенных различий

Три различия стоит выучить наизусть при кроссплатформенном коде путей. Разделители: Join нормализует сам; рукописный / на Windows пройдёт в большинство API — но каноническая форма, ключи кеша и сравнение журналов сломаются о смешанные разделители. Форма корня: единый корень Unix / против буквы диска Windows C: и UNC \server — вердикт абсолютности (обратный запрос IsLocal) идёт по таблице форм; /foo на Unix абсолютно, на Windows — «относительно без диска».Имена устройств и зарезервированные имена: CON/NUL/COM1 — специфические капканы Windows; IsSafeEntry проверяет единообразно по самой строгой платформе (отказывает и на Linux) — только тогда продукт распространяем кроссплатформенно. Все три различия API этой главы сглаживает — при лексическом коде можно «забыть о платформе»; в этом и смысл существования модуля.

Типичные ошибки

Ловушка 1: внешний вход прямо в путь:Ловушка 1: внешний вход прямо в путь

Симптом: аудит безопасности репортит уязвимость обхода пути — вход ../../ читает и пишет вне песочницы; или ввод имени устройства порождает причудливое поведение.

Причина: перед склейкой нет этапа проверки — «пользователь даёт имя файла» приняли за неявный контракт.

错误写法
str sPath = xrtPathJoin("data/", UserNameBuf);   /* UserNameBuf - внешний вход */
SaveTo(sPath);   /* при UserName="../etc/passwd": сегмент обхода убегает из песочницы data */
正确写法
xstrview Name = (xstrview){ UserName, strlen(UserName) };
if ( !xrtPathIsSafeEntry(Name, false) ) {
	RejectUpload();   /* обход/абсолютный/имя устройства отвергнуты в дверях */
	return false;
}
Ловушка 2: рукописная склейка путей строками:Ловушка 2: рукописная склейка путей строками

Симптом: "dir/" + name на Windows даёт смешанные разделители (dir/\file); избыточные сегменты a/b/../c загрязняют ключи кеша и сравнение журналов.

Причина: минуя зачистку и нормализацию Join — работой путей занялся строковый Concat (глава 25).

错误写法
str sPath = xrtStrConcat(XRT_STR_LITERAL("data/"), NameView);
sPath = xrtStrConcat((xstrview){ sPath, strlen(sPath) }, XRT_STR_LITERAL("/icon.png"));
/* каша разделителей + без зачистки + три распределения */
正确写法
str sPath = xrtPathJoin("data/", NameBuf);   /* один вызов: нормализация + зачистка + одно распределение */

Практические задания

Базовое: проверка закона обратимости (регрессионный тест лексического слоя)

Для пяти путей разных форм (относительный/абсолютный/многосегментный/с расширением/без) выполните «разложить и пересобрать»; утвердите совпадение пересобранного с канонической формой исходного — закон обратимости на практике.

Продвинутое: маршрутизатор путей (единый вход базовых каталогов)

Реализуйте resolve(基准目录, 输入路径) (базовый каталог, входной путь): вход относительный — приклеить базу Join-ом; абсолютный — отказ или пропуск по конфигурации; на выходе каноническая форма. Различие поведения переключателя зафиксируйте комментарием.

Вызов: песочница загрузок (досмотровая рамка в полной форме)

Комплексный валидатор: досмотр имени файла (тройной отказ IsSafe) + лимит длины + белый список расширений + уникализация итогового пути (при конфликте к Stem добавляется номер). Критерии приёмки: двадцать сконструированных входов (обход/устройство/сверхдлина/вне белого списка/конфликт) каждый получает ожидаемую обработку; на всём пути ноль вызовов файловой системы (чисто лексический слой — тестируемо).

Итоги

ТемаКратко
Чистая лексикавесь модуль без IO, кроме real (разрешение симлинков) - тесты без диска
Joinсклейка есть зачистка: ../ свёрнут, разделители нормализованы, каноническая форма единственна
Четырёхстороннее разложениеName (последний сегмент) / Stem (без расширения) / Ext (с точкой) / каталог пересборкой
ДосмотрIsSafeEntry(视图, 是否允许子目录) (представление, разрешать ли подкаталоги): обход/абсолютный путь/буква диска/имя устройства - отказы подряд; обязателен для внешнего входа
ИтерацияIterInit/Next посегментно без распределений; представления сегментов заимствованы
Системные путизапросы home/cwd; real разрешает симлинки (единственный интерфейс, касающийся диска)
Три хозяинанормализация базовых путей / отображение пользовательского ввода (привратник + белый список) / кроссплатформенные продукты
Три кроссплатформенных различиянормализация разделителей / вердикт формы корня / имена устройств единообразно на страже - всё сглажено API

📖 Справочники API в этой главе:path (41 записей)

Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки