tl; dr
Gọi is_path_exists_or_creatable()
hàm được xác định bên dưới.
Python 3. Đó chỉ là cách chúng tôi cuộn.
Câu chuyện về hai câu hỏi
Câu hỏi "Làm cách nào để kiểm tra tính hợp lệ của tên đường dẫn và đối với tên đường dẫn hợp lệ, sự tồn tại hoặc khả năng ghi của các đường dẫn đó?" rõ ràng là hai câu hỏi riêng biệt. Cả hai đều thú vị, và cả hai đều không nhận được câu trả lời thực sự thỏa đáng ở đây ... hoặc, tốt, bất cứ nơi nào mà tôi có thể grep.
Câu trả lời của vikki có lẽ gần nhất, nhưng có những nhược điểm đáng chú ý là:
- Mở ( ... và sau đó không thể đóng ) các tệp xử lý một cách không cần thiết .
- Không cần thiết phải ghi ( ... và sau đó không thể đóng hoặc xóa ) các tệp 0 byte đáng tin cậy .
- Bỏ qua các lỗi dành riêng cho hệ điều hành, phân biệt giữa tên đường dẫn không hợp lệ không thể bỏ qua và các sự cố hệ thống tệp có thể bỏ qua. Không có gì đáng ngạc nhiên, điều này rất quan trọng trong Windows. ( Xem bên dưới. )
- Bỏ qua các điều kiện chủng tộc do các quy trình bên ngoài đồng thời (lại) di chuyển các thư mục mẹ của tên đường dẫn cần kiểm tra. ( Xem bên dưới. )
- Bỏ qua thời gian chờ kết nối do tên đường dẫn này nằm trên hệ thống tệp cũ, chậm hoặc tạm thời không thể truy cập được. Điều này có thể khiến các dịch vụ công khai trước các cuộc tấn công DoS -driven tiềm ẩn . ( Xem bên dưới. )
Chúng tôi sẽ sửa chữa tất cả những điều đó.
Câu hỏi # 0: Tính hợp lệ của Pathname lại là gì?
Trước khi ném bộ quần áo mỏng manh của chúng ta vào đống phân của con trăn vì đau đớn, có lẽ chúng ta nên định nghĩa "tính hợp lệ của tên đường dẫn" là gì. Điều gì xác định tính hợp lệ, chính xác?
Bởi "tính hợp lệ của tên đường dẫn", chúng tôi muốn nói đến tính đúng cú pháp của tên đường dẫn đối với hệ thống tệp gốc của hệ thống hiện tại - bất kể đường dẫn đó hay các thư mục mẹ của chúng có tồn tại thực tế hay không. Tên đường dẫn là chính xác về mặt cú pháp theo định nghĩa này nếu nó tuân thủ tất cả các yêu cầu cú pháp của hệ thống tệp gốc.
Theo "hệ thống tệp gốc", chúng tôi muốn nói:
- Trên các hệ thống tương thích với POSIX, hệ thống tệp được gắn vào thư mục gốc (
/
).
- Trên Windows, hệ thống tệp được gắn vào
%HOMEDRIVE%
, ký tự ổ đĩa có hậu tố là dấu hai chấm chứa cài đặt Windows hiện tại (thường nhưng không nhất thiết C:
).
Đến lượt mình, ý nghĩa của "tính đúng cú pháp" phụ thuộc vào loại hệ thống tệp gốc. Đối với ext4
(và hầu hết nhưng không phải tất cả các hệ thống tệp tương thích với POSIX), tên đường dẫn là chính xác về mặt cú pháp nếu và chỉ khi tên đường dẫn đó:
- Không chứa byte rỗng (tức là
\x00
trong Python). Đây là một yêu cầu khó đối với tất cả các hệ thống tệp tương thích với POSIX.
- Không chứa các thành phần đường dẫn dài hơn 255 byte (ví dụ:
'a'*256
trong Python). Một thành phần con đường là một chuỗi dài nhất của một tên đường dẫn không chứa /
ký tự (ví dụ, bergtatt
, ind
, i
, và fjeldkamrene
trong tên đường dẫn /bergtatt/ind/i/fjeldkamrene
).
Tính đúng đắn về mặt cú pháp. Hệ thống tập tin gốc. Đó là nó.
Câu hỏi # 1: Bây giờ chúng ta sẽ thực hiện tính hợp lệ của tên đường dẫn như thế nào?
Xác thực tên đường dẫn trong Python đáng ngạc nhiên là không trực quan. Tôi đồng ý chắc chắn với Fake Name ở đây: os.path
gói chính thức nên cung cấp giải pháp ngoại vi cho việc này. Vì những lý do không xác định (và có thể là không thuyết phục), nó không. May mắn thay, việc gỡ bỏ giải pháp đặc biệt của riêng bạn không phải là điều khiến bạn khó chịu ...
OK, nó thực sự là như vậy. Nó có lông; thật khó chịu; nó có thể khó thở khi nó nhào lộn và cười khúc khích khi nó phát sáng. Nhưng bạn sẽ làm gì? Nuthin '.
Chúng ta sẽ sớm rơi xuống vực thẳm phóng xạ của mã cấp thấp. Nhưng trước hết hãy nói đến shop cao cấp. Tiêu chuẩn os.stat()
và các os.lstat()
hàm nêu ra các ngoại lệ sau khi chuyển tên đường dẫn không hợp lệ:
- Đối với tên đường dẫn nằm trong các thư mục không tồn tại, các trường hợp của
FileNotFoundError
.
- Đối với tên đường dẫn nằm trong các thư mục hiện có:
- Trong Windows, các trường hợp
WindowsError
có winerror
thuộc tính là 123
(tức là ERROR_INVALID_NAME
).
- Trong tất cả các hệ điều hành khác:
- Đối với tên đường dẫn chứa byte rỗng (tức là,
'\x00'
), các trường hợp của TypeError
.
- Đối với tên đường dẫn chứa các thành phần đường dẫn dài hơn 255 byte, các trường hợp
OSError
có errcode
thuộc tính là:
- Dưới SunOS và họ hệ điều hành * BSD ,
errno.ERANGE
. (Đây dường như là một lỗi cấp hệ điều hành, hay còn được gọi là "diễn giải có chọn lọc" của tiêu chuẩn POSIX.)
- Trong tất cả các hệ điều hành khác
errno.ENAMETOOLONG
,.
Điều quan trọng, điều này ngụ ý rằng chỉ những tên đường dẫn nằm trong các thư mục hiện có mới có thể xác thực được. Các hàm os.stat()
và os.lstat()
nâng cao các FileNotFoundError
ngoại lệ chung khi các tên đường dẫn được chuyển nằm trong các thư mục không tồn tại, bất kể các tên đường dẫn đó có hợp lệ hay không. Sự tồn tại của thư mục được ưu tiên hơn tính không hợp lệ của tên đường dẫn.
Điều này có nghĩa là các tên đường dẫn nằm trong các thư mục không tồn tại sẽ không hợp lệ? Có - trừ khi chúng tôi sửa đổi các tên đường dẫn đó để nằm trong các thư mục hiện có. Tuy nhiên, điều đó có khả thi một cách an toàn không? Việc sửa đổi tên đường dẫn có ngăn chúng ta xác thực tên đường dẫn ban đầu không?
Để trả lời câu hỏi này, hãy nhớ lại từ phía trên rằng các tên đường dẫn chính xác về mặt cú pháp trên ext4
hệ thống tệp không chứa thành phần đường dẫn (A) chứa byte rỗng hoặc (B) có độ dài trên 255 byte. Do đó, ext4
tên đường dẫn hợp lệ nếu và chỉ khi tất cả các thành phần đường dẫn trong tên đường dẫn đó hợp lệ. Điều này đúng với hầu hết các hệ thống tệp tin trong thế giới thực .
Cái nhìn sâu sắc đó có thực sự giúp chúng ta không? Đúng. Nó làm giảm vấn đề lớn hơn là xác thực tên đường dẫn đầy đủ trong một đường dẫn đã chuyển sang vấn đề nhỏ hơn là chỉ xác thực tất cả các thành phần đường dẫn trong tên đường dẫn đó. Mọi tên đường dẫn tùy ý đều có thể xác thực (bất kể tên đường dẫn đó có nằm trong thư mục hiện có hay không) theo cách đa nền tảng bằng cách thực hiện theo thuật toán sau:
- Chia tên đường dẫn đó thành các thành phần đường dẫn (ví dụ: tên đường dẫn
/troldskog/faren/vild
vào danh sách ['', 'troldskog', 'faren', 'vild']
).
- Đối với mỗi thành phần như vậy:
- Nối tên đường dẫn của một thư mục được đảm bảo tồn tại với thành phần đó thành một tên đường dẫn tạm thời mới (ví dụ
/troldskog
:).
- Chuyển tên đường dẫn đó đến
os.stat()
hoặc os.lstat()
. Nếu tên đường dẫn đó và do đó thành phần đó không hợp lệ, lệnh gọi này được đảm bảo đưa ra một ngoại lệ thể hiện kiểu không hợp lệ thay vì một FileNotFoundError
ngoại lệ chung chung . Tại sao? Bởi vì tên đường dẫn đó nằm trong một thư mục hiện có. (Logic tròn là vòng tròn.)
Có một thư mục được đảm bảo tồn tại? Có, nhưng thường chỉ có một: thư mục trên cùng của hệ thống tệp gốc (như đã định nghĩa ở trên).
Chuyển tên đường dẫn nằm trong bất kỳ thư mục nào khác (và do đó không được đảm bảo tồn tại) đến os.stat()
hoặc os.lstat()
mời các điều kiện cuộc đua, ngay cả khi thư mục đó đã được kiểm tra trước đó để tồn tại. Tại sao? Bởi vì các tiến trình bên ngoài không thể bị ngăn chặn đồng thời xóa thư mục đó sau khi kiểm tra đó đã được thực hiện nhưng trước khi tên đường dẫn đó được chuyển đến os.stat()
hoặc os.lstat()
. Giải phóng những con chó điên loạn trí óc!
Cũng có một lợi ích phụ đáng kể đối với cách tiếp cận trên: bảo mật. (Không phải là mà đẹp?) Cụ thể là:
Các ứng dụng trực diện xác nhận các tên đường dẫn tùy ý từ các nguồn không đáng tin cậy bằng cách chỉ cần chuyển các tên đường dẫn đó đến os.stat()
hoặc os.lstat()
dễ bị tấn công Từ chối Dịch vụ (DoS) và các trò tai quái khác. Người dùng độc hại có thể cố gắng xác thực nhiều lần các tên đường dẫn nằm trên các hệ thống tệp được biết là cũ hoặc chậm (ví dụ: chia sẻ NFS Samba); trong trường hợp đó, việc khai báo tên đường dẫn đến một cách mù quáng có thể cuối cùng sẽ không thành công với thời gian chờ kết nối hoặc tiêu tốn nhiều thời gian và tài nguyên hơn khả năng yếu ớt của bạn để chịu thất nghiệp.
Cách tiếp cận trên loại bỏ điều này bằng cách chỉ xác thực các thành phần đường dẫn của tên đường dẫn so với thư mục gốc của hệ thống tệp gốc. (Nếu thậm chí nó đã cũ, chậm hoặc không thể truy cập được, bạn đã gặp phải vấn đề lớn hơn xác thực tên đường dẫn.)
Mất đi? Tuyệt quá. Hãy bắt đầu nào. (Giả sử trong Python 3. Hãy xem "Mong manh mong manh cho 300, leycec là gì?")
import errno, os
ERROR_INVALID_NAME = 123
'''
Windows-specific error code indicating an invalid pathname.
See Also
----------
https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
Official listing of all such codes.
'''
def is_pathname_valid(pathname: str) -> bool:
'''
`True` if the passed pathname is a valid pathname for the current OS;
`False` otherwise.
'''
try:
if not isinstance(pathname, str) or not pathname:
return False
_, pathname = os.path.splitdrive(pathname)
root_dirname = os.environ.get('HOMEDRIVE', 'C:') \
if sys.platform == 'win32' else os.path.sep
assert os.path.isdir(root_dirname)
root_dirname = root_dirname.rstrip(os.path.sep) + os.path.sep
for pathname_part in pathname.split(os.path.sep):
try:
os.lstat(root_dirname + pathname_part)
except OSError as exc:
if hasattr(exc, 'winerror'):
if exc.winerror == ERROR_INVALID_NAME:
return False
elif exc.errno in {errno.ENAMETOOLONG, errno.ERANGE}:
return False
except TypeError as exc:
return False
else:
return True
Làm xong. Đừng nheo mắt trước mã đó. ( Nó cắn. )
Câu hỏi # 2: Sự tồn tại hoặc khả năng tạo ra tên đường dẫn có thể không hợp lệ, Eh?
Việc kiểm tra sự tồn tại hoặc khả năng tạo ra các tên đường dẫn có thể không hợp lệ, với giải pháp trên, hầu hết là việc vặt. Chìa khóa nhỏ ở đây là gọi hàm được xác định trước đó trước khi kiểm tra đường dẫn đã truyền:
def is_path_creatable(pathname: str) -> bool:
'''
`True` if the current user has sufficient permissions to create the passed
pathname; `False` otherwise.
'''
dirname = os.path.dirname(pathname) or os.getcwd()
return os.access(dirname, os.W_OK)
def is_path_exists_or_creatable(pathname: str) -> bool:
'''
`True` if the passed pathname is a valid pathname for the current OS _and_
either currently exists or is hypothetically creatable; `False` otherwise.
This function is guaranteed to _never_ raise exceptions.
'''
try:
return is_pathname_valid(pathname) and (
os.path.exists(pathname) or is_path_creatable(pathname))
except OSError:
return False
Xong và xong. Ngoại trừ không hoàn toàn.
Câu hỏi # 3: Sự tồn tại hoặc khả năng ghi tên đường dẫn có thể không hợp lệ trên Windows
Có một cảnh báo trước. Tất nhiên là có.
Như os.access()
tài liệu chính thức thừa nhận:
Lưu ý: Các hoạt động I / O có thể không thành công ngay cả khi os.access()
chỉ ra rằng chúng sẽ thành công, đặc biệt đối với các hoạt động trên hệ thống tệp mạng có thể có ngữ nghĩa quyền ngoài mô hình bit quyền POSIX thông thường.
Không ai ngạc nhiên, Windows là nghi phạm thường thấy ở đây. Nhờ sử dụng rộng rãi Danh sách kiểm soát truy cập (ACL) trên hệ thống tệp NTFS, mô hình bit cho phép POSIX đơn giản ánh xạ kém với thực tế cơ bản của Windows. Mặc dù đây (được cho là) không phải là lỗi của Python, nhưng nó vẫn có thể gây lo ngại cho các ứng dụng tương thích với Windows.
Nếu đây là bạn, một sự thay thế mạnh mẽ hơn là điều cần thiết. Nếu đường dẫn đã qua không tồn tại, thay vào đó, chúng tôi cố gắng tạo một tệp tạm thời được đảm bảo sẽ bị xóa ngay lập tức trong thư mục mẹ của đường dẫn đó - một bài kiểm tra khả năng tạo di động hơn (nếu tốn kém):
import os, tempfile
def is_path_sibling_creatable(pathname: str) -> bool:
'''
`True` if the current user has sufficient permissions to create **siblings**
(i.e., arbitrary files in the parent directory) of the passed pathname;
`False` otherwise.
'''
dirname = os.path.dirname(pathname) or os.getcwd()
try:
with tempfile.TemporaryFile(dir=dirname): pass
return True
except EnvironmentError:
return False
def is_path_exists_or_creatable_portable(pathname: str) -> bool:
'''
`True` if the passed pathname is a valid pathname on the current OS _and_
either currently exists or is hypothetically creatable in a cross-platform
manner optimized for POSIX-unfriendly filesystems; `False` otherwise.
This function is guaranteed to _never_ raise exceptions.
'''
try:
return is_pathname_valid(pathname) and (
os.path.exists(pathname) or is_path_sibling_creatable(pathname))
except OSError:
return False
Lưu ý, tuy nhiên, ngay cả điều này có thể là không đủ.
Nhờ Kiểm soát truy cập người dùng (UAC), Windows Vista luôn hoạt động tốt và tất cả các lần lặp lại tiếp theo của nó đều nói dối trắng trợn về các quyền liên quan đến thư mục hệ thống. Khi người dùng không phải Quản trị viên cố gắng tạo tệp trong thư mục chuẩn C:\Windows
hoặc C:\Windows\system32
thư mục, UAC sẽ cho phép người dùng làm như vậy một cách hời hợt trong khi thực sự cô lập tất cả các tệp đã tạo thành "Cửa hàng ảo" trong hồ sơ của người dùng đó. (Ai có thể ngờ rằng việc lừa dối người dùng sẽ gây ra hậu quả lâu dài có hại?)
Điều này là điên. Đây là Windows.
Chứng minh điều đó
Chúng ta có dám không? Đã đến lúc lái thử các bài kiểm tra trên.
Vì NULL là ký tự duy nhất bị cấm trong tên đường dẫn trên hệ thống tệp hướng UNIX, chúng ta hãy tận dụng điều đó để chứng minh sự thật lạnh lùng, khó hiểu - bỏ qua những trò tai quái không thể bỏ qua của Windows, điều này thực sự khiến tôi bực bội và tức giận ở mức độ tương đương:
>>> print('"foo.bar" valid? ' + str(is_pathname_valid('foo.bar')))
"foo.bar" valid? True
>>> print('Null byte valid? ' + str(is_pathname_valid('\x00')))
Null byte valid? False
>>> print('Long path valid? ' + str(is_pathname_valid('a' * 256)))
Long path valid? False
>>> print('"/dev" exists or creatable? ' + str(is_path_exists_or_creatable('/dev')))
"/dev" exists or creatable? True
>>> print('"/dev/foo.bar" exists or creatable? ' + str(is_path_exists_or_creatable('/dev/foo.bar')))
"/dev/foo.bar" exists or creatable? False
>>> print('Null byte exists or creatable? ' + str(is_path_exists_or_creatable('\x00')))
Null byte exists or creatable? False
Ngoài sự tỉnh táo. Vượt lên trên nỗi đau. Bạn sẽ thấy những lo ngại về tính di động của Python.