Zeth - Zerocash on Ethereum  0.8
Reference implementation of the Zeth protocol by Clearmatics
upload_utils.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
2 
3 # Copyright (c) 2015-2022 Clearmatics Technologies Ltd
4 #
5 # SPDX-License-Identifier: LGPL-3.0+
6 
7 from Crypto.Hash import SHA512 # pylint: disable=import-error,no-name-in-module
8 from typing import Optional
9 import io
10 
11 READ_CHUNK_SIZE = 4096
12 
13 
14 def _read_part_headers(stream: io.IOBase) -> int:
15  total_bytes = 0
16  while True:
17  line = stream.readline()
18  bytes_read = len(line)
19  total_bytes = total_bytes + bytes_read
20 
21  l_str = line.decode()
22  # print(f"read_part_headers: line({len(line)} bytes): '{l_str}'")
23  if bytes_read < 3:
24  if l_str in ["\r\n", "\n"]:
25  break
26  if bytes_read == 0:
27  raise Exception("unexpected 0-length line")
28 
29  return total_bytes
30 
31 
32 def _read_to_file(
33  stream: io.BufferedIOBase,
34  file_name: str,
35  bytes_to_read: int) -> Optional[bytes]:
36  """
37  Stream bytes to a file, while computing the digest. Return the digest or
38  None if there is an error.
39  """
40 
41  h = SHA512.new()
42  with open(file_name, "wb") as out_f:
43  while bytes_to_read > 0:
44  read_size = min(READ_CHUNK_SIZE, bytes_to_read)
45  chunk = stream.read(read_size)
46  if len(chunk) == 0:
47  return None
48 
49  h.update(chunk)
50  out_f.write(chunk)
51  bytes_to_read = bytes_to_read - len(chunk)
52 
53  # print(f"_read_to_file: digest={h.hexdigest()}")
54  return h.digest()
55 
56 
57 def _read_to_memory(
58  stream: io.BufferedIOBase,
59  bytes_to_read: int) -> Optional[bytes]:
60  data = io.BytesIO()
61  while bytes_to_read > 0:
62  chunk = stream.read(bytes_to_read)
63  if len(chunk) == 0:
64  return None
65 
66  data.write(chunk)
67  bytes_to_read = bytes_to_read - len(chunk)
68 
69  return data.getvalue()
70 
71 
73  content_length: int,
74  content_boundary: str,
75  expect_digest: bytes,
76  stream: io.BufferedIOBase,
77  file_name: str) -> None:
78  """
79  Given sufficient header data and an input stream, stream raw content to a
80  file, hashing it at the same time to verify the given signature.
81  """
82 
83  final_boundary = f"\r\n--{content_boundary}--\r\n"
84  final_boundary_size = len(final_boundary)
85 
86  # Expect the stream to be formatted:
87  # --------------------------985b875979d96dfa <boundary>
88  # Content-Disposition: form-data; .... <part-header>
89  # Content-Type: application/octet-stream <part-header>
90  # ... <part-header>
91  # <blank-line>
92  # <raw-content> <part-data>
93  # --------------------------985b875979d96dfa-- <final-boundary>
94  # Note, for simplicity we assume content is single-part
95 
96  remaining_bytes = content_length - final_boundary_size
97 
98  # Skip the headers
99  header_bytes = _read_part_headers(stream)
100  remaining_bytes = remaining_bytes - header_bytes
101 
102  # Read up to the final boundary,
103  # print(f"expecting {remaining_bytes} file bytes")
104  digest = _read_to_file(stream, file_name, remaining_bytes)
105  if digest is None:
106  raise Exception("invalid part format")
107  if digest != expect_digest:
108  raise Exception("digest mismatch")
109 
110  # Read final boundary and sanity check
111  tail = _read_to_memory(stream, final_boundary_size)
112  if tail is None or tail.decode() != final_boundary:
113  raise Exception("invalid part tail")
coordinator.upload_utils.handle_upload_request
None handle_upload_request(int content_length, str content_boundary, bytes expect_digest, io.BufferedIOBase stream, str file_name)
Definition: upload_utils.py:72