File size: 3,818 Bytes
be93389
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
"""资料持久媒体地址准入;不解析DNS、不下载内容。"""
import ipaddress
import re
from urllib.parse import unquote, urlsplit
from deployment_config import PUBLIC_API_BASE_URL

MAX_PROFILE_MEDIA_URL_BYTES = 16384
LOCAL_IMAGE_PREFIX = "/community_hub/image?url="
COMPLETE_IMAGE_SUFFIX = "&require_complete=1"
INVALID_MEDIA = "invalid_profile_media_url"


def _host(parts):
    host = parts.hostname
    if not host or "%" in host:
        raise ValueError(INVALID_MEDIA)
    host = host.removesuffix(".").lower()
    if not host:
        raise ValueError(INVALID_MEDIA)
    try:
        address = ipaddress.ip_address(host)
    except ValueError:
        ascii_host = host.encode("idna").decode("ascii")
        labels = ascii_host.split(".")
        if (len(ascii_host) > 253 or not all(re.fullmatch(r"[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?", label) for label in labels)):
            raise ValueError(INVALID_MEDIA)
        return ascii_host, None
    return address.compressed, address


def normalize_profile_media(value):
    if value is None:
        return None
    if not isinstance(value, str):
        raise ValueError(INVALID_MEDIA)
    if value == "":
        return ""
    if value.strip() != value or any(ord(char) < 32 or ord(char) == 127 for char in value):
        raise ValueError(INVALID_MEDIA)
    if value[:5].lower() in {"data:", "blob:"}:
        return None
    if len(value.encode("utf-8")) > MAX_PROFILE_MEDIA_URL_BYTES or "\\" in value:
        raise ValueError(INVALID_MEDIA)
    source = value
    layers = 0
    while source.startswith(LOCAL_IMAGE_PREFIX):
        layers += 1
        if layers > 10:
            raise ValueError(INVALID_MEDIA)
        encoded = source[len(LOCAL_IMAGE_PREFIX):]
        if encoded.endswith(COMPLETE_IMAGE_SUFFIX):
            encoded = encoded[:-len(COMPLETE_IMAGE_SUFFIX)]
        if re.search(r"%(?![a-fA-F0-9]{2})", encoded):
            raise ValueError(INVALID_MEDIA)
        source = unquote(encoded, errors="strict")
    if source.strip() != source or "\\" in source or any(ord(char) < 32 or ord(char) == 127 for char in source):
        raise ValueError(INVALID_MEDIA)
    if not re.match(r"^https?://", source, re.I):
        raise ValueError(INVALID_MEDIA)
    try:
        parsed = urlsplit(source)
        if parsed.username is not None or parsed.password is not None or parsed.netloc.endswith(":"):
            raise ValueError(INVALID_MEDIA)
        if parsed.port is not None and not 1 <= parsed.port <= 65535:
            raise ValueError(INVALID_MEDIA)
        port = parsed.port or (443 if parsed.scheme == "https" else 80)
        host, address = _host(parsed)
        base = urlsplit(PUBLIC_API_BASE_URL)
        base_host, _ = _host(base)
        if host == base_host:
            base_port = base.port or (443 if base.scheme == "https" else 80)
            if parsed.scheme != base.scheme or port != base_port or parsed.path != base.path.rstrip("/") + "/api/image_proxy":
                raise ValueError(INVALID_MEDIA)
            return source
        if address:
            if (not address.is_global or address.is_multicast or (address.version == 6
                    and (address.ipv4_mapped is not None or int(address) >> 125 != 1))):
                raise ValueError(INVALID_MEDIA)
        elif (host == "localhost" or host.endswith((".localhost", ".local")) or "." not in host
                or re.fullmatch(r"(?:0x[0-9a-f]+|[0-9]+)(?:\.(?:0x[0-9a-f]+|[0-9]+))*", host, re.I)
                or re.fullmatch(r"(?:0x[0-9a-f]+|[0-9]+)", host.rsplit(".", 1)[-1], re.I)):
            raise ValueError(INVALID_MEDIA)
        return source
    except (ValueError, UnicodeError):
        raise ValueError(INVALID_MEDIA) from None