eaf_base_api 3.3.3__tar.gz → 3.3.5__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: eaf_base_api
3
- Version: 3.3.3
3
+ Version: 3.3.5
4
4
  Summary: A base API for EchterAlsFake's Porn APIs
5
5
  Author: Johannes Habel
6
6
  Author-email: Johannes Habel <EchterAlsFake@proton.me>
@@ -488,9 +488,9 @@ class BaseCore:
488
488
  if self.configuration.dns_over_https:
489
489
  curl_options[CurlOpt.DOH_URL] = str(self.configuration.dns_over_https).encode("utf-8")
490
490
 
491
- proxies = None
492
- if self.configuration.proxies:
493
- proxies = self.configuration.proxies
491
+ proxy = None
492
+ if self.configuration.proxy:
493
+ proxy = self.configuration.proxy
494
494
 
495
495
  if self.configuration.max_bandwidth_mb is not None and self.configuration.max_bandwidth_mb > 0:
496
496
  global_limit_bytes = int(self.configuration.max_bandwidth_mb * 1024 * 1024)
@@ -511,7 +511,8 @@ class BaseCore:
511
511
  p_auth = (u, p)
512
512
 
513
513
  self.session = cast(Any, AsyncSession)(
514
- proxies=proxies,
514
+ interface=self.configuration.interface,
515
+ proxy=proxy,
515
516
  timeout=self.configuration.timeout,
516
517
  verify=verify,
517
518
  impersonate=impersonation,
@@ -18,7 +18,7 @@ class RuntimeConfig:
18
18
  self.request_delay: int = 0
19
19
  self.timeout: int = 20
20
20
  self.max_bandwidth_mb: float| None = None # Set speed limit in megabytes per second e.g, 2.0, 3.5 etc...
21
- self.proxies: Dict[str, str] | None = None
21
+ self.proxy = None
22
22
  self.http_version: str = "v2" # "v3 = HTTP/3.0, v2 = HTTP/2.0, v1 = HTTP/1.1
23
23
  self.dns_over_https: str | None = None
24
24
  self.impersonation: str = "chrome"
@@ -31,6 +31,7 @@ class RuntimeConfig:
31
31
  self.max_workers_download: int = 20
32
32
  self.videos_concurrency: int = 5
33
33
  self.pages_concurrency: int = 2
34
+ self.interface: str | None = None # IP Address of the network interface you want to bind to
34
35
 
35
36
 
36
37
  @dataclass
@@ -2,6 +2,8 @@ import os
2
2
  import re
3
3
  import math
4
4
  import json
5
+ import unicodedata
6
+ from pathlib import PurePath
5
7
  from .type_hints import DownloadState
6
8
  from datetime import timezone, datetime
7
9
  from curl_cffi.requests import Response
@@ -432,31 +434,49 @@ def log_precondition_failed(logger, response: Response, attempt: int) -> None:
432
434
  )
433
435
 
434
436
 
435
- def strip_title(title: str, max_length: int = 255) -> str:
436
- """
437
- Sanitize a filename to be safe across Windows, macOS, Linux, and Android.
438
- Replaces or strips illegal characters and trims to a safe length.
437
+ def strip_title(
438
+ title: str, max_length: int = 255, default_name: str = "untitled"
439
+ ) -> str:
440
+ """Sanitize a filename to be safe across Windows, macOS, Linux, and Android.
441
+
442
+ Prevents path traversal, replaces illegal characters, handles Windows reserved
443
+ names, and trims to a safe length.
439
444
  """
445
+ if not title:
446
+ return default_name
447
+
448
+ # 1. Normalize Unicode (converts full-width slashes/characters to standard ASCII where applicable)
449
+ sanitized = unicodedata.normalize("NFKC", title)
440
450
 
441
- # Reserved characters on Windows + `/` for Unix/macOS
451
+ # 2. Extract only the filename component (strips drive letters & path prefixes)
452
+ sanitized = PurePath(sanitized).name
453
+
454
+ # 3. Replace illegal filename characters & explicit path separators with underscores
442
455
  illegal_chars = r'[<>:"/\\|?*\x00-\x1F]'
443
- sanitized = re.sub(illegal_chars, "_", title)
456
+ sanitized = re.sub(illegal_chars, "_", sanitized)
444
457
 
445
- # Strip invisible zero-width characters
446
- sanitized = re.sub(r'[\u200B-\u200D\uFEFF]', '', sanitized)
458
+ # 4. Strip invisible zero-width & non-printable Unicode control characters
459
+ sanitized = re.sub(r"[\u200B-\u200D\uFEFF]", "", sanitized)
447
460
 
448
- # Strip trailing periods or spaces (Windows)
449
- sanitized = sanitized.rstrip(" .")
461
+ # 5. Strip LEADING and TRAILING spaces and dots
462
+ # (Prevents '.' and '..' traversal tokens and hidden files)
463
+ sanitized = sanitized.strip(" .")
450
464
 
451
- # Prevent reserved Windows filenames
465
+ # 6. Prevent Windows reserved filenames (CON, PRN, AUX, NUL, COM1-9, LPT1-9)
452
466
  reserved_names = {
453
- "CON", "PRN", "AUX", "NUL",
467
+ "CON",
468
+ "PRN",
469
+ "AUX",
470
+ "NUL",
454
471
  *(f"COM{i}" for i in range(1, 10)),
455
472
  *(f"LPT{i}" for i in range(1, 10)),
456
473
  }
457
- name_only = sanitized.split('.')[0].upper()
474
+ name_only = sanitized.split(".")[0].upper()
458
475
  if name_only in reserved_names:
459
476
  sanitized = f"_{sanitized}"
460
477
 
461
- # Trim to max length
462
- return sanitized[:max_length]
478
+ # 7. Trim to max length, then re-strip trailing dots/spaces in case the slice cut mid-string
479
+ sanitized = sanitized[:max_length].rstrip(" .")
480
+
481
+ # 8. Return default fallback if sanitization leaves an empty string
482
+ return sanitized if sanitized else default_name
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "eaf_base_api"
7
- version = "3.3.3"
7
+ version = "3.3.5"
8
8
  description = "A base API for EchterAlsFake's Porn APIs"
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  requires-python = ">=3.12"
File without changes
File without changes