|
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