tftp.backends
tftp.backends
Request handlers beyond the local filesystem.
- :class:
FilesystemBackend-- a directory (whatTFTPServer("/srv/tftp")serves). - :class:
MemoryBackend-- a dictionary of files. - :class:
HTTPBackend-- a TFTP-to-HTTP(S) gateway (urllib, no extra dependency);PUTfor uploads. - :class:
UpstreamBackend-- a terminating proxy to another TFTP server, each side negotiating its own options. - :class:
Remap-- rewrite the requested filenames, with regular expressions, before another handler sees them. - :class:
PerClient-- serve each client from a directory named for its address. - :class:
CaseInsensitive-- a :class:FilesystemBackendthat finds files whatever the case of the requested name. - :class:
Pipe-- the bounded, backpressured byte pipe they use to feed a transfer from a worker thread; usable for your own handlers.
CaseInsensitive(root, *, writable=False, create=True, overwrite=False, backslash=True, max_upload=None)
Bases: FilesystemBackend
A :class:FilesystemBackend that matches names regardless of case.
The exact name wins when it exists; otherwise each component is looked
up case-insensitively (firmware asking for \Boot\BCD finds
boot/bcd). An upload's final component keeps the requested case. The
corrected name goes through the normal containment checks, so nothing a
client names leaves root. Takes the arguments of
:class:FilesystemBackend.
Looking a name up lists each directory on the way, which costs a request more than the exact lookup does.
Source code in src/tftp/backends/_filesystem.py
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | |
FilesystemBackend(root, *, writable=False, create=True, overwrite=False, backslash=True, max_upload=None)
Serves the files under root.
:param root: the directory served. Requests can never reach outside it:
.. components are refused, and the resolved path (symlinks
included) must still be inside root.
:param writable: accept WRQ at all.
:param create: a WRQ may create a file that does not exist.
:param overwrite: a WRQ may replace a file that exists.
:param backslash: treat \ in filenames as a separator, as Windows
boot loaders send them (\boot\bcd).
:param max_upload: refuse uploads announced (tsize) or grown larger
than this many bytes, with ERROR 3.
Uploads go through :class:AtomicWriter, so a failed or partial upload
leaves no trace, and directories are never created.
Source code in src/tftp/backends/_filesystem.py
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | |
resolve(filename)
The local path for filename, or :class:TFTPError (2) if it escapes.
Source code in src/tftp/backends/_filesystem.py
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | |
HTTPBackend(base_url=None, *, url_for=None, writable=False, headers=None, timeout=10.0, buffer=1 << 20, opener=None)
Serve (and optionally accept, as PUT) files from an HTTP server.
:param base_url: prefix; the normalised filename is appended,
percent-encoded (http://images/pxe/ + boot/x). Its scheme must
be http or https.
:param url_for: alternative to base_url: url_for(context) -> str,
for routing by client, filename or anything else. A request whose URL
is not http or https is refused with ERROR 2.
:param writable: map WRQ to an HTTP PUT of the uploaded body.
:param headers: sent with every request (Authorization...); they win
over the default User-Agent.
:param timeout: seconds for connecting and for each read.
:param buffer: bytes buffered between HTTP and TFTP per transfer.
:param opener: a urllib.request.OpenerDirector (proxies, TLS context).
The default opener speaks http and https only and follows
redirects between them; with an opener of your own the scheme of
base_url and of url_for's URLs is yours to choose.
HTTP 404/410 become ERROR 1, 401/403 ERROR 2, 409 ERROR 6, 413/507
ERROR 3, anything else ERROR 0; a Content-Length answers tsize.
The request is made in a server worker thread, never on the event loop.
A download is read ahead of the transfer by a few KiB until the peer has
acknowledged data, and by up to buffer octets after. An upload's
PUT carries the announced tsize as its Content-Length (chunked
without one, and for netascii) and sends the last octets only when the
transfer is complete: more octets than announced fail the transfer with
ERROR 3, fewer with ERROR 0, and the origin stores nothing.
Source code in src/tftp/backends/_http.py
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 | |
MemoryBackend(files=None, *, writable=False, overwrite=True, max_upload=DEFAULT_MAX_UPLOAD, max_entries=DEFAULT_MAX_ENTRIES)
Serves files (name -> bytes); uploads land in it when writable.
Names are normalised (:func:normalize_name), so /boot/x and
boot\x find the same entry. An upload replaces the entry only once
it is complete. Safe to read and update files from other threads.
Whatever an anonymous peer may upload is bounded. max_upload is the
octets one upload may hold (default 16 MiB): an announced tsize above
it is refused with ERROR 3 at the request, and an upload that grows past it
fails with ERROR 3. max_entries is the names the backend may hold
(default 1024, those in files included): an upload to a new name beyond
it fails with ERROR 3, while a name that exists can still be replaced. The
memory a writable backend can hold is at most the product of the two;
None removes a bound.
Source code in src/tftp/backends/_memory.py
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | |
PerClient(root, make, *, fallback=True)
Serves root/<client address>/ to a client whose directory exists.
A client with no directory of its own is served root itself by
default, so it can read every other client's directory: that is a
convenience for a fleet that mostly shares files, not isolation. With
fallback=False such a client is answered ERROR 1 (file not found) to
every request, and a client reaches only its own directory (what it names
stays inside that directory, as :class:FilesystemBackend keeps it).
The directory name is the client's address with each : written -
(fe80--1, valid on Windows), and a v4-mapped IPv6 address is named by
its IPv4 address (see :meth:directory).
:param root: the directory holding one directory per client.
:param make: make(directory) returns the handler for a directory, for
example lambda d: FilesystemBackend(d, writable=True); it is called
once for each directory a client is served from. It should return a
handler that opens quickly (a local directory): this one opens inline
on the server's thread.
:param fallback: serve root itself to a client with no directory.
Source code in src/tftp/backends/_per_client.py
40 41 42 43 44 45 46 47 48 49 50 | |
directory(peer)
staticmethod
The directory name for the client at peer ((host, port, ...)).
Source code in src/tftp/backends/_per_client.py
52 53 54 55 56 57 | |
handler_for(context)
The handler of the directory context's client is served from.
:raises TFTPError: ERROR 1, for a client with no directory when
fallback is false.
Source code in src/tftp/backends/_per_client.py
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | |
Pipe(capacity=1 << 20, size=None)
A bounded FIFO of bytes with one producer and one consumer.
:param capacity: bytes buffered at most; a producer blocks (or gets
WouldBlock) beyond it. This is the backpressure that keeps a slow
consumer from making a fast producer buffer a whole file.
:param size: the total size, when the producer knows it in advance; a
server answers tsize from it.
Source code in src/tftp/backends/_pipe.py
34 35 36 37 38 39 40 41 42 43 44 45 46 47 | |
abort(error=None)
Give up from the transfer side; the worker's next call raises.
Bytes still buffered are dropped: nothing will read them.
Source code in src/tftp/backends/_pipe.py
170 171 172 173 174 175 176 177 178 179 180 181 | |
close()
The transfer side is done.
Download pipe (the transfer reads): the producer's next put
raises, which stops a worker that is still fetching.
Upload pipe (:meth:for_upload, the transfer writes): marks the end
of the data, then raises :class:WouldBlock until the worker calls
:meth:set_result -- so the client's final ACK waits until the data
really reached its destination -- and re-raises the worker's error if
it had one.
Source code in src/tftp/backends/_pipe.py
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | |
finish(error=None)
The producer is done (or failed with error).
Source code in src/tftp/backends/_pipe.py
92 93 94 95 96 97 98 99 | |
for_upload()
Make this an upload pipe, whose close() waits for :meth:set_result.
Source code in src/tftp/backends/_pipe.py
187 188 189 190 | |
get(n=65536, timeout=None)
Blocking read for a worker thread; b"" at the end.
Source code in src/tftp/backends/_pipe.py
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | |
put(data, timeout=None)
Blocking write for a worker thread; raises if the consumer is gone.
Source code in src/tftp/backends/_pipe.py
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | |
read(n=65536)
Blocking read so a worker thread can treat the pipe as a file.
Source code in src/tftp/backends/_pipe.py
139 140 141 | |
readinto(view)
Non-blocking read for a transfer: some bytes, 0 at the end, or WouldBlock.
Source code in src/tftp/backends/_pipe.py
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | |
set_result(error=None)
Worker side of an upload: finished, successfully or with error.
Source code in src/tftp/backends/_pipe.py
192 193 194 195 196 | |
set_wakeup(callback)
Called (from any thread) when the non-blocking side can proceed.
Source code in src/tftp/backends/_pipe.py
51 52 53 | |
write(data)
Non-blocking write for a transfer: all of data or WouldBlock.
Source code in src/tftp/backends/_pipe.py
79 80 81 82 83 84 85 86 87 88 89 90 | |
Remap(inner, rules)
Rewrites each requested filename with the first rule that matches it, then asks inner.
Only the first rule whose pattern is found in the name applies, and it
replaces every match in it (:meth:re.Pattern.sub, so \1 names a
group). inner sees the rewritten name and decides what it may reach: a
rewrite cannot take a request out of the directory a
:class:FilesystemBackend serves. Everything else about the request
(the listing flag, the arrival interface) is carried over.
:param inner: the handler that serves the rewritten names.
:param rules: "REGEX=REPLACEMENT" text (split at the first =) or
(pattern, replacement) pairs, tried in order.
:raises TFTPValueError: a rule without = or a regular expression that
does not compile.
:raises TypeError: a rule of another shape.
Source code in src/tftp/backends/_remap.py
53 54 55 56 | |
rewrite(filename)
filename as the first matching rule rewrites it, or unchanged.
Source code in src/tftp/backends/_remap.py
58 59 60 61 62 63 | |
UpstreamBackend(upstream, *, client_options=None, buffer=1 << 20, stall_timeout=30.0, writable=False)
Serve every request from an upstream TFTP server.
:param upstream: "host", "host:port", ("host", port), or a
callable upstream(context) returning one of those -- route by
client address, filename, the interface the request arrived on.
:param client_options: keyword arguments for the upstream
:class:tftp.TFTPClient (blksize, windowsize, timeout...).
:param buffer: bytes buffered between the two transfers.
:param stall_timeout: seconds one side may wait on the other before the
transfer is abandoned.
:param writable: forward WRQ upstream (otherwise ERROR 2).
The upstream request is made in a server worker thread. An RRQ is
answered only once the upstream has answered: its errors (file not
found...) reach the client with the same code and text, and its tsize
is passed through. A WRQ's final ACK is sent only once the upstream has
acknowledged the whole file.
Source code in src/tftp/backends/_proxy.py
91 92 93 94 95 96 97 98 99 100 101 102 103 104 | |
normalize_name(filename, backslash=True)
/a\b//c -> a/b/c: the key a request maps to.
Source code in src/tftp/backends/_memory.py
31 32 33 34 | |