screengraft 0.56.0 → 0.56.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/scripts/ui.py +17 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "screengraft",
3
- "version": "0.56.0",
3
+ "version": "0.56.1",
4
4
  "description": "Put a UI screenshot or screen recording onto a photographed device screen with the perspective exactly right \u2014 a homography you confirm by hand, not a generative guess.",
5
5
  "keywords": [
6
6
  "mockup",
package/scripts/ui.py CHANGED
@@ -680,9 +680,10 @@ def _grain_gain(b) -> float:
680
680
  """How much of the surround's noise floor the screen carries.
681
681
 
682
682
  The page does not set this; fresh renders get grade.SCREEN_GRAIN_GAIN. It
683
- is read from the body so a sidecar replayed through the API keeps its own
684
- value -- a sidecar from before the gain existed carries none and passes
685
- 1.0 explicitly at the replay site, never here.
683
+ is read from the body so a caller that does send one (a future control, a
684
+ test) is honoured. Absent means the shipped gain, NOT 1.0: the API is for
685
+ new renders. Replaying an old sidecar is done through compose() directly,
686
+ where the parameter's own default (1.0) reproduces the old bytes.
686
687
  """
687
688
  try:
688
689
  g = float(b.get("grain_gain")) if b.get("grain_gain") is not None \
@@ -845,7 +846,11 @@ class Handler(BaseHTTPRequestHandler):
845
846
  if site and site not in ("same-origin", "none"):
846
847
  return False
847
848
  tok = self.headers.get("X-Screengraft-Token") or (q.get("t") or [""])[0]
848
- return bool(tok) and bool(TOKEN) and secrets.compare_digest(tok, TOKEN)
849
+ # compare_digest wants ASCII str on both sides; a token is url-safe
850
+ # base64, so anything else is wrong before it is compared.
851
+ if not (tok and TOKEN and tok.isascii()):
852
+ return False
853
+ return secrets.compare_digest(tok, TOKEN)
849
854
 
850
855
  def _refuse(self, u):
851
856
  if u.path == "/":
@@ -1003,9 +1008,15 @@ class Handler(BaseHTTPRequestHandler):
1003
1008
  # Read the body off the wire first (bounded): refusing while the
1004
1009
  # client is still sending turns a clean 403 into a broken pipe on
1005
1010
  # its side, which reads as "the server died", not "you were refused".
1006
- n = min(int(self.headers.get("Content-Length") or 0), 64 << 20)
1011
+ try:
1012
+ n = min(int(self.headers.get("Content-Length") or 0), 64 << 20)
1013
+ except ValueError:
1014
+ n = 0
1007
1015
  while n > 0:
1008
- n -= len(self.rfile.read(min(n, 1 << 20)) or b"\0")
1016
+ chunk = self.rfile.read(min(n, 1 << 20))
1017
+ if not chunk: # client gone; nothing left to drain
1018
+ break
1019
+ n -= len(chunk)
1009
1020
  return self._refuse(u)
1010
1021
  try:
1011
1022
  if u.path == "/api/upload":