Coverage for /Users/davegaeddert/Developer/dropseed/plain/plain/plain/internal/files/utils.py: 49%

1 import os

2 import pathlib

3

4 from plain.exceptions import SuspiciousFileOperation

5

6

7 def validate_file_name(name, allow_relative_path=False):

8 # Remove potentially dangerous names

9 if os.path.basename(name) in {"", ".", ".."}:

10 raise SuspiciousFileOperation(f"Could not derive file name from '{name}'")

11

12 if allow_relative_path:

13 # Use PurePosixPath() because this branch is checked only in

14 # FileField.generate_filename() where all file paths are expected to be

15 # Unix style (with forward slashes).

16 path = pathlib.PurePosixPath(name)

17 if path.is_absolute() or ".." in path.parts:

18 raise SuspiciousFileOperation(

19 f"Detected path traversal attempt in '{name}'"

20 )

21 elif name != os.path.basename(name):

22 raise SuspiciousFileOperation(f"File name '{name}' includes path elements")

23

24 return name

25

26

27 class FileProxyMixin:

28 """

29 A mixin class used to forward file methods to an underlying file

30 object. The internal file object has to be called "file"::

31

32 class FileProxy(FileProxyMixin):

33 def __init__(self, file):

34 self.file = file

35 """

36

37 encoding = property(lambda self: self.file.encoding)

38 fileno = property(lambda self: self.file.fileno)

39 flush = property(lambda self: self.file.flush)

40 isatty = property(lambda self: self.file.isatty)

41 newlines = property(lambda self: self.file.newlines)

42 read = property(lambda self: self.file.read)

43 readinto = property(lambda self: self.file.readinto)

44 readline = property(lambda self: self.file.readline)

45 readlines = property(lambda self: self.file.readlines)

46 seek = property(lambda self: self.file.seek)

47 tell = property(lambda self: self.file.tell)

48 truncate = property(lambda self: self.file.truncate)

49 write = property(lambda self: self.file.write)

50 writelines = property(lambda self: self.file.writelines)

51

52 @property

53 def closed(self):

54 return not self.file or self.file.closed

55

56 def readable(self):

57 if self.closed:

58 return False

59 if hasattr(self.file, "readable"):

60 return self.file.readable()

61 return True

62

63 def writable(self):

64 if self.closed:

65 return False

66 if hasattr(self.file, "writable"):

67 return self.file.writable()

68 return "w" in getattr(self.file, "mode", "")

69

70 def seekable(self):

71 if self.closed:

72 return False

73 if hasattr(self.file, "seekable"):

74 return self.file.seekable()

75 return True

76

77 def __iter__(self):

78 return iter(self.file)