|
5 | 5 |
|
6 | 6 | import hashlib |
7 | 7 | import os |
| 8 | +import tempfile |
8 | 9 | from textwrap import dedent |
9 | 10 | from typing import IO, TYPE_CHECKING |
10 | 11 | from pathlib import Path |
|
18 | 19 | from filelock import BaseFileLock |
19 | 20 |
|
20 | 21 |
|
21 | | -def _secure_open_write(filename: str, fmode: int) -> IO[bytes]: |
22 | | - # We only want to write to this file, so open it in write only mode |
23 | | - flags = os.O_WRONLY |
24 | | - |
25 | | - # os.O_CREAT | os.O_EXCL will fail if the file already exists, so we only |
26 | | - # will open *new* files. |
27 | | - # We specify this because we want to ensure that the mode we pass is the |
28 | | - # mode of the file. |
29 | | - flags |= os.O_CREAT | os.O_EXCL |
30 | | - |
31 | | - # Do not follow symlinks to prevent someone from making a symlink that |
32 | | - # we follow and insecurely open a cache file. |
33 | | - if hasattr(os, "O_NOFOLLOW"): |
34 | | - flags |= os.O_NOFOLLOW |
35 | | - |
36 | | - # On Windows we'll mark this file as binary |
37 | | - if hasattr(os, "O_BINARY"): |
38 | | - flags |= os.O_BINARY |
39 | | - |
40 | | - # Before we open our file, we want to delete any existing file that is |
41 | | - # there |
42 | | - try: |
43 | | - os.remove(filename) |
44 | | - except OSError: |
45 | | - # The file must not exist already, so we can just skip ahead to opening |
46 | | - pass |
47 | | - |
48 | | - # Open our file, the use of os.O_CREAT | os.O_EXCL will ensure that if a |
49 | | - # race condition happens between the os.remove and this line, that an |
50 | | - # error will be raised. Because we utilize a lockfile this should only |
51 | | - # happen if someone is attempting to attack us. |
52 | | - fd = os.open(filename, flags, fmode) |
53 | | - try: |
54 | | - return os.fdopen(fd, "wb") |
55 | | - |
56 | | - except: |
57 | | - # An error occurred wrapping our FD in a file object |
58 | | - os.close(fd) |
59 | | - raise |
60 | | - |
61 | | - |
62 | 22 | class _FileCacheMixin: |
63 | 23 | """Shared implementation for both FileCache variants.""" |
64 | 24 |
|
@@ -122,15 +82,18 @@ def _write(self, path: str, data: bytes) -> None: |
122 | 82 | Safely write the data to the given path. |
123 | 83 | """ |
124 | 84 | # Make sure the directory exists |
125 | | - try: |
126 | | - os.makedirs(os.path.dirname(path), self.dirmode) |
127 | | - except OSError: |
128 | | - pass |
| 85 | + dirname = os.path.dirname(path) |
| 86 | + os.makedirs(dirname, self.dirmode, exist_ok=True) |
129 | 87 |
|
130 | 88 | with self.lock_class(path + ".lock"): |
131 | 89 | # Write our actual file |
132 | | - with _secure_open_write(path, self.filemode) as fh: |
133 | | - fh.write(data) |
| 90 | + (fd, name) = tempfile.mkstemp(dir=dirname) |
| 91 | + try: |
| 92 | + os.write(fd, data) |
| 93 | + finally: |
| 94 | + os.close(fd) |
| 95 | + os.chmod(name, self.filemode) |
| 96 | + os.replace(name, path) |
134 | 97 |
|
135 | 98 | def _delete(self, key: str, suffix: str) -> None: |
136 | 99 | name = self._fn(key) + suffix |
|
0 commit comments