authcaptureproxy 1.3.8__tar.gz → 1.3.9__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: authcaptureproxy
3
- Version: 1.3.8
3
+ Version: 1.3.9
4
4
  Summary: A Python project to create a proxy to capture authentication information from a webpage. This is useful to capture oauth login details without access to a third-party oauth.
5
5
  License-Expression: Apache-2.0
6
6
  License-File: LICENSE.txt
@@ -3,9 +3,9 @@
3
3
 
4
4
  import asyncio
5
5
  import logging
6
- import posixpath
7
6
  import re
8
7
  from functools import partial
8
+ from html import escape, unescape
9
9
  from json import JSONDecodeError
10
10
  from ssl import SSLContext
11
11
  from typing import Any, Callable, Dict, List, Optional, Set, Text, Tuple, Union
@@ -684,7 +684,6 @@ class AuthCaptureProxy:
684
684
  # Resolve relative form actions BEFORE modifiers run.
685
685
  if text and content_type == "text/html" and resp and resp.url:
686
686
  _resp_url = URL(str(resp.url))
687
- _resp_dir = _resp_url.path.rsplit("/", 1)[0] + "/" if "/" in _resp_url.path else "/"
688
687
 
689
688
  def _resolve_form_action(form_match):
690
689
  """Resolve relative action URLs only inside <form> tags."""
@@ -696,10 +695,13 @@ class AuthCaptureProxy:
696
695
  if action and not action.startswith(
697
696
  ("http://", "https://", "//", "#", "javascript:", "/")
698
697
  ):
699
- resolved_path = posixpath.normpath(_resp_dir + action)
698
+ resolved_url = _resp_url.join(URL(unescape(action)))
700
699
  _proxy_base = self.access_url().path.rstrip("/")
701
700
  abs_url = str(
702
- self.access_url().with_path(_proxy_base + resolved_path).with_query({})
701
+ self.access_url()
702
+ .with_path(_proxy_base + resolved_url.path)
703
+ .with_query(resolved_url.query)
704
+ .with_fragment(resolved_url.fragment)
703
705
  )
704
706
  _LOGGER.debug(
705
707
  "Resolved relative form action '%s' -> '%s' (page: %s)",
@@ -707,7 +709,11 @@ class AuthCaptureProxy:
707
709
  abs_url,
708
710
  _resp_url.path,
709
711
  )
710
- return form_tag[: action_m.start(2)] + abs_url + form_tag[action_m.end(2) :]
712
+ return (
713
+ form_tag[: action_m.start(2)]
714
+ + escape(abs_url, quote=True)
715
+ + form_tag[action_m.end(2) :]
716
+ )
711
717
  return form_tag
712
718
 
713
719
  text = re.sub(
@@ -893,18 +899,25 @@ class AuthCaptureProxy:
893
899
  result["Referer"] = self._swap_proxy_and_host(
894
900
  result.get("Referer", ""), domain_only=True
895
901
  )
896
- for item in [
897
- "Content-Length",
898
- "X-Forwarded-For",
899
- "X-Forwarded-Host",
900
- "X-Forwarded-Port",
901
- "X-Forwarded-Proto",
902
- "X-Forwarded-Scheme",
903
- "X-Forwarded-Server",
904
- "X-Real-IP",
905
- ]:
906
- # remove proxy headers
907
- if result.get(item):
902
+ # Remove hop-by-hop and reverse-proxy/CDN headers that must not be forwarded
903
+ # upstream. Besides leaking client info, these can carry non-ASCII values (e.g.
904
+ # Cloudflare's "cf-ipcity: Mérida") that break httpx header encoding. Matching is
905
+ # case-insensitive because HTTP/2 front-ends (e.g. Cloudflare) lowercase header
906
+ # names, so exact-case matching would silently miss them.
907
+ proxy_headers = {
908
+ "content-length",
909
+ "x-forwarded-for",
910
+ "x-forwarded-host",
911
+ "x-forwarded-port",
912
+ "x-forwarded-proto",
913
+ "x-forwarded-scheme",
914
+ "x-forwarded-server",
915
+ "x-real-ip",
916
+ "cdn-loop",
917
+ }
918
+ for item in list(result):
919
+ lowered = item.lower()
920
+ if lowered in proxy_headers or lowered.startswith("cf-"):
908
921
  result.pop(item)
909
922
  result.update(self.headers if self.headers else {})
910
923
  _LOGGER.debug("Final headers %s", result)
@@ -5,7 +5,6 @@ Python Package for auth capture proxy.
5
5
  Helper files.
6
6
  """
7
7
 
8
- import ast
9
8
  import json
10
9
  import logging
11
10
  import re
@@ -39,11 +38,11 @@ def print_resp(resp: Union[httpx.Response, ClientResponse]) -> None:
39
38
  method = resp.request.method
40
39
  status = resp.status_code
41
40
  reason = resp.reason_phrase
42
- headers = ast.literal_eval(str(resp.request.headers)[8:-1])
41
+ headers: Dict[str, Any] = dict(resp.request.headers)
43
42
  cookies = {}
44
43
  if headers.get("cookie"):
45
44
  cookie: SimpleCookie = SimpleCookie()
46
- cookie.load(headers.get("cookie"))
45
+ cookie.load(headers["cookie"])
47
46
  for key, morsel in cookie.items():
48
47
  cookies[key] = morsel.value
49
48
  headers["cookie"] = cookies
@@ -74,13 +73,11 @@ def print_resp_aiohttp(resp: ClientResponse) -> None:
74
73
  method = resp.request_info.method
75
74
  status = resp.status
76
75
  reason = resp.reason
77
- headers = ast.literal_eval(
78
- str(resp.request_info.headers).replace("<CIMultiDictProxy(", "{").replace(")>", "}")
79
- )
76
+ headers: Dict[str, Any] = dict(resp.request_info.headers)
80
77
  cookies = {}
81
78
  if headers.get("Cookie"):
82
79
  cookie: SimpleCookie = SimpleCookie()
83
- cookie.load(headers.get("Cookie"))
80
+ cookie.load(headers["Cookie"])
84
81
  for key, morsel in cookie.items():
85
82
  cookies[key] = morsel.value
86
83
  headers["Cookie"] = cookies
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [project]
6
6
  name = "authcaptureproxy"
7
- version = "1.3.8"
7
+ version = "1.3.9"
8
8
  description = "A Python project to create a proxy to capture authentication information from a webpage. This is useful to capture oauth login details without access to a third-party oauth."
9
9
  readme = "README.md"
10
10
  authors = [