source: trunk/src/allmydata/scripts/tahoe_put.py

Last change on this file was 1a807a0, checked in by Jean-Paul Calderone <exarkun@…>, at 2023-01-12T21:32:32Z

mollify the type checker

  • Property mode set to 100644
File size: 4.3 KB
Line 
1"""
2Implement the ``tahoe put`` command.
3"""
4from __future__ import annotations
5
6from io import BytesIO
7from urllib.parse import quote as url_quote
8from base64 import urlsafe_b64encode
9
10from cryptography.hazmat.primitives.serialization import load_pem_private_key
11
12from twisted.python.filepath import FilePath
13
14from allmydata.crypto.rsa import PrivateKey, der_string_from_signing_key
15from allmydata.scripts.common_http import do_http, format_http_success, format_http_error
16from allmydata.scripts.common import get_alias, DEFAULT_ALIAS, escape_path, \
17                                     UnknownAliasError
18from allmydata.util.encodingutil import quote_output
19
20def load_private_key(path: str) -> str:
21    """
22    Load a private key from a file and return it in a format appropriate
23    to include in the HTTP request.
24    """
25    privkey = load_pem_private_key(FilePath(path).getContent(), password=None)
26    assert isinstance(privkey, PrivateKey)
27    derbytes = der_string_from_signing_key(privkey)
28    return urlsafe_b64encode(derbytes).decode("ascii")
29
30def put(options):
31    """
32    @param verbosity: 0, 1, or 2, meaning quiet, verbose, or very verbose
33
34    @return: a Deferred which eventually fires with the exit code
35    """
36    nodeurl = options['node-url']
37    aliases = options.aliases
38    from_file = options.from_file
39    to_file = options.to_file
40    mutable = options['mutable']
41    if options["private-key-path"] is None:
42        private_key = None
43    else:
44        private_key = load_private_key(options["private-key-path"])
45    format = options['format']
46    if options['quiet']:
47        verbosity = 0
48    else:
49        verbosity = 2
50    stdin = options.stdin
51    stdout = options.stdout
52    stderr = options.stderr
53
54    if nodeurl[-1] != "/":
55        nodeurl += "/"
56    if to_file:
57        # several possibilities for the TO_FILE argument.
58        #  <none> : unlinked upload
59        #  foo : TAHOE_ALIAS/foo
60        #  subdir/foo : TAHOE_ALIAS/subdir/foo
61        #  /oops/subdir/foo : DISALLOWED
62        #  ALIAS:foo  : aliases[ALIAS]/foo
63        #  ALIAS:subdir/foo  : aliases[ALIAS]/subdir/foo
64
65        #  ALIAS:/oops/subdir/foo : DISALLOWED
66        #  DIRCAP:./foo        : DIRCAP/foo
67        #  DIRCAP:./subdir/foo : DIRCAP/subdir/foo
68        #  MUTABLE-FILE-WRITECAP : filecap
69
70        # FIXME: don't hardcode cap format.
71        if to_file.startswith("URI:MDMF:") or to_file.startswith("URI:SSK:"):
72            url = nodeurl + "uri/%s" % url_quote(to_file)
73        else:
74            try:
75                rootcap, path = get_alias(aliases, to_file, DEFAULT_ALIAS)
76            except UnknownAliasError as e:
77                e.display(stderr)
78                return 1
79            path = str(path, "utf-8")
80            if path.startswith("/"):
81                suggestion = to_file.replace(u"/", u"", 1)
82                print("Error: The remote filename must not start with a slash", file=stderr)
83                print("Please try again, perhaps with %s" % quote_output(suggestion), file=stderr)
84                return 1
85            url = nodeurl + "uri/%s/" % url_quote(rootcap)
86            if path:
87                url += escape_path(path)
88    else:
89        # unlinked upload
90        url = nodeurl + "uri"
91
92    queryargs = []
93    if mutable:
94        queryargs.append("mutable=true")
95        if private_key is not None:
96            queryargs.append(f"private-key={private_key}")
97    else:
98        if private_key is not None:
99            raise Exception("Can only supply a private key for mutables.")
100
101    if format:
102        queryargs.append("format=%s" % format)
103    if queryargs:
104        url += "?" + "&".join(queryargs)
105
106    if from_file:
107        infileobj = open(from_file, "rb")
108    else:
109        # do_http() can't use stdin directly: for one thing, we need a
110        # Content-Length field. So we currently must copy it.
111        if verbosity > 0:
112            print("waiting for file data on stdin..", file=stderr)
113        # We're uploading arbitrary files, so this had better be bytes:
114        stdinb = stdin.buffer
115        data = stdinb.read()
116        infileobj = BytesIO(data)
117
118    resp = do_http("PUT", url, infileobj)
119
120    if resp.status in (200, 201,):
121        print(format_http_success(resp), file=stderr)
122        print(quote_output(resp.read(), quotemarks=False), file=stdout)
123        return 0
124
125    print(format_http_error("Error", resp), file=stderr)
126    return 1
Note: See TracBrowser for help on using the repository browser.