파일 탐색기의 작은 출발점을 독립 예제로 만들었다. 호스트가 정한 실제 폴더 아래에서 이름과 종류, 크기 같은 정보만 읽는다. 파일 내용을 열거나 코드를 실행하는 기능은 없다. 같은 경계 안의 목록과 직접 파일 정보 요청을 함께 살펴볼 수 있게 했으며 원래 프로그램의 서버나 주피터를 복제한 모듈은 아니다.

루트는 신뢰할 호스트가 정한다

list_metadata와 file_metadata는 설정한 절대 루트와 상대 경로를 받는다. 상대 경로에는 상위 이동, 절대 주소, 드라이브·공유 경로 형태, 역슬래시와 제어 문자, 빈 구간과 점 구간을 허용하지 않는다. 파일 이름을 URL처럼 이어 붙이는 예제가 아니라 해석할 경로를 좁히는 계약이다. 오류에는 원래 절대 경로를 넣지 않는다.

호출자가 임의의 루트를 골라도 된다는 뜻은 아니다. 인증된 서버가 등록된 프로젝트에서 허용한 루트를 가져와야 한다. 이 모듈에는 로그인과 프로젝트 권한 저장소가 없다. 숨김 파일 이름도 목록에 나올 수 있으므로 내용이 없다는 이유로 메타데이터 공개 권한을 생략하면 안 된다. 무엇을 볼 수 있는지 결정하는 책임은 사용하는 호스트에 남는다.

링크를 표시하는 것과 따라가는 것을 나눈다

목록에서 심볼릭 링크나 Windows 재분석 지점은 이름을 보여 주되 blocked로 표시한다. 그 항목을 통해 하위 경로를 요청하면 거절한다. 루트 자체가 링크인 경우도 허용하지 않는다. 새 예제는 루트 안으로 돌아오는 링크까지 모두 거절하는 더 좁은 정책이다. 내부 링크를 해석해 허용할 수 있는 원래 탐색기의 동작과 같은 구현이라고 쓰지 않는다.

각 경로 구간을 확인하고 최종 해석 위치가 설정한 루트 안인지 비교한다. 그래도 검사 사이에 다른 프로세스가 파일 시스템을 적대적으로 바꾸는 모든 경쟁 조건을 막는 도구는 아니다. 신뢰할 수 있는 작업 공간과 관련 변경의 직렬화가 필요하다. OS_sandbox_proven은 계속 False이며 OS 전체 접근 격리를 검증했다는 이름으로 사용하지 않는다.

페이지 크기는 탐색 비용의 상한이 아니다

목록은 폴더를 먼저, 그다음 파일 이름 순서로 정렬한다. 한 번에 바로 아래 항목만 읽고 자식 폴더의 내용으로 재귀 진입하지 않는다. limit과 offset은 반환할 페이지를 고르며 최대 반환 수는 100개다. 전체 구현은 아래에 넣었다. 직접 파일 요청도 같은 경로 확인을 거친 뒤 일반 파일의 크기만 반환한다.

정렬 전에 해당 폴더의 전체 즉시 하위 항목을 읽으므로 limit을 1로 줄여도 디스크 목록 작업이 한 항목으로 줄어들지는 않는다. directory_entries_scanned와 total을 함께 돌려 이 차이를 드러냈다. 여러 페이지 사이의 파일 생성·삭제도 고정하지 않는다. snapshot_stable은 False이며 페이지 번호를 같은 시점의 완전한 목록 증거로 사용하지 않는다.

코드와 구현 살펴보기

독립 합성 전체 구현

"""Single-directory metadata for a trusted configured root. No content or execution."""
from pathlib import Path
import os
import stat


class ScopeError(ValueError):
    pass


def _linked(info):
    return stat.S_ISLNK(info.st_mode) or bool(
        getattr(info, 'st_file_attributes', 0) & getattr(stat, 'FILE_ATTRIBUTE_REPARSE_POINT', 0x400))


def _parts(relative):
    if type(relative) is not str or len(relative) > 1000:
        raise ScopeError('RELATIVE_PATH_REQUIRED')
    if relative == '':
        return []
    parts = relative.split('/')
    if (any(ord(c) < 32 for c in relative) or '\\' in relative or ':' in relative
            or any(p in {'', '.', '..'} for p in parts)):
        raise ScopeError('RELATIVE_PATH_REQUIRED')
    return parts


def _root(root):
    p = Path(root)
    if not p.is_absolute():
        raise ScopeError('ABSOLUTE_CONFIGURED_ROOT_REQUIRED')
    try:
        if _linked(p.lstat()) or not p.is_dir():
            raise ScopeError('NORMAL_ROOT_REQUIRED')
        return p.resolve(strict=True)
    except OSError:
        raise ScopeError('ROOT_UNAVAILABLE') from None


def _target(root, relative):
    current = root
    for part in _parts(relative):
        current = current / part
        try:
            if _linked(current.lstat()):
                raise ScopeError('LINK_NOT_ALLOWED')
        except OSError:
            raise ScopeError('TARGET_UNAVAILABLE') from None
    try:
        resolved = current.resolve(strict=True)
    except (OSError, RuntimeError):
        raise ScopeError('TARGET_UNAVAILABLE') from None
    if not resolved.is_relative_to(root):
        raise ScopeError('OUTSIDE_CONFIGURED_ROOT')
    return resolved


def _entry(path, relative):
    result = {'name': path.name, 'path': relative, 'kind': 'unavailable',
              'size': None, 'blocked': True}
    try:
        _parts(relative)
        info = path.lstat()
        if _linked(info):
            result['kind'] = 'link'
        elif stat.S_ISDIR(info.st_mode):
            result.update(kind='directory', blocked=False)
        elif stat.S_ISREG(info.st_mode):
            result.update(kind='file', size=info.st_size, blocked=False)
    except (OSError, ScopeError):
        pass
    return result


def list_metadata(root, relative='', *, offset=0, limit=50):
    """Page a sorted one-level listing; pagination is not an I/O or snapshot bound."""
    if type(offset) is not int or not 0 <= offset <= 1000000:
        raise ScopeError('INVALID_OFFSET')
    if type(limit) is not int or not 1 <= limit <= 100:
        raise ScopeError('INVALID_LIMIT')
    configured = _root(root)
    directory = _target(configured, relative)
    if not directory.is_dir():
        raise ScopeError('DIRECTORY_REQUIRED')
    try:
        rows = [_entry(p, '/'.join(filter(None, (relative, p.name)))) for p in directory.iterdir()]
    except OSError:
        raise ScopeError('DIRECTORY_UNAVAILABLE') from None
    rows.sort(key=lambda x: (x['kind'] != 'directory', x['name'].casefold(), x['name']))
    page = rows[offset:offset + limit]
    end = offset + len(page)
    return {'path': relative, 'entries': page, 'directory_entries_scanned': len(rows),
            'total': len(rows), 'offset': offset, 'next_offset': end if end < len(rows) else None,
            'content_read': False, 'code_executed': False, 'snapshot_stable': False,
            'OS_sandbox_proven': False}


def file_metadata(root, relative):
    configured = _root(root)
    target = _target(configured, relative)
    item = _entry(target, relative)
    if item['blocked'] or item['kind'] != 'file':
        raise ScopeError('REGULAR_FILE_REQUIRED')
    return {**item, 'content_read': False, 'code_executed': False, 'OS_sandbox_proven': False}

파일 정보는 내용 열기와 실행을 만들지 않는다

반환값에는 이름, 상대 경로, 종류, 크기와 차단 상태가 있다. 실제 루트의 절대 위치나 파일 본문은 넣지 않는다. Python 파일의 크기를 읽었다고 그 파일을 import하거나 실행하지 않는다. content_read와 code_executed는 거짓이며 문서를 읽고 저장하고 실행하는 기능도 이 패키지에는 없다.

파일이 목록을 만든 뒤 바뀌거나 사라질 수 있으므로 사용자는 이후 작업에 맞는 새 확인을 해야 한다. 메타데이터 조회 결과를 파일 내용의 고정 해시나 실행 허가로 쓰지 않는다. 서버의 열린 파일 처리와 커널 권한은 별도 계층이다. 작은 함수의 반환 형식을 다른 프로그램 전체의 권한 보장으로 넓히지 않아야 재사용 범위를 설명할 수 있다.

실제 합성 폴더의 결과와 시험

20개 시험은 상대 경로와 입력 형식, 페이지와 전체 스캔 수, 숨김 이름, 본문 미노출, 바이트 보존과 링크 거절을 확인한다. Windows에서는 이 패키지 아래 만든 소유 시험 폴더의 정션을 실제로 사용했다. 정션을 먼저 제거하고 임시 폴더가 소유 경계 안인지 확인한 뒤 정리한다. 고객 폴더나 원래 프로그램의 자료를 사용하지 않았다.

첫 페이지, 다음 페이지의 차단 링크, 하위 폴더, 파일 정보, 거절된 요청을 기록으로 만들었다. 모두 이 코드의 실제 로컬 합성 결과다. 아래 선택기는 저장된 JSON을 읽으며 방문자의 파일 시스템을 탐색하지 않는다. ZIP 추출 후에도 같은 20개 시험을 실행하고 공개 파일의 바이트·해시를 확인한다. 실제 서버 인증·Jupyter 화면·커널의 검수는 아니다.

합성 로컬 기록의 읽기 뷰어

다른 관리자 화면에 연결하는 방법

다운로드에는 구현, 시험, 기록 생성기와 결과, README, 재사용 허가가 들어 있다. 서버는 사용자 입력의 루트 문자열을 그대로 넘기기보다 현재 인증 문맥에 등록한 루트를 선택해야 한다. 파일을 여는 별도 API도 같은 범위를 다시 확인해야 하며 이 모듈을 브라우저에 넣어 서버 접근 통제를 대신할 수는 없다.

기존 완료 폴더 반복기는 원본·작업 사본·완료 사본을 다루는 다른 부품이다. 그 복사·이동 기능을 탐색기에 불필요하게 실행하지 않으려고 이번에는 읽기 전용 메타데이터를 독립시켰다. 원래 컨트롤러에는 설치하거나 연결하지 않았다. 이 코드로 확인한 범위와 호스트가 더 구현할 인증·열기·저장·실행 책임을 함께 남겨 둔다.

코드·시험·실행 기록 ZIP 받기
Python 표준 라이브러리 · 합성 자료 · 수정과 재사용 가능