privacy pass: degoogle, CSP, referrer
- Replace Google Fonts with system font stacks across all themes - Add Referrer-Policy, X-Content-Type-Options, X-Frame-Options, CSP headers - Add rel="noreferrer noopener" on all outbound links - Add no-referrer and dns-prefetch-control meta tags to all themes - Clean tracking params on outbound links from trusted/remote sources - Remove Google domains from CSP whitelists
This commit is contained in:
parent
9738d28b60
commit
a9f426132e
6 changed files with 285 additions and 275 deletions
|
|
@ -123,6 +123,14 @@ class GatewayHandler(BaseHTTPRequestHandler):
|
|||
|
||||
self.send_response(resp["status"])
|
||||
self.send_header("Content-Type", resp.get("content_type", "text/html; charset=utf-8"))
|
||||
self.send_header("Referrer-Policy", "no-referrer")
|
||||
self.send_header("X-Content-Type-Options", "nosniff")
|
||||
self.send_header("X-Frame-Options", "DENY")
|
||||
self.send_header("Content-Security-Policy",
|
||||
"default-src 'self'; "
|
||||
"style-src 'self' 'unsafe-inline'; "
|
||||
"script-src 'self' 'unsafe-inline'; "
|
||||
"img-src 'self' data:")
|
||||
for k, v in resp.get("headers", {}).items():
|
||||
self.send_header(k, v)
|
||||
self.end_headers()
|
||||
|
|
|
|||
19
handlers.py
19
handlers.py
|
|
@ -245,7 +245,7 @@ def handle_search(query):
|
|||
snip_html = f'<br>{esc(r["summary"])}' if r["summary"] else ""
|
||||
result_html += (
|
||||
f'<div class="result">'
|
||||
f'<a href="{esc(r["url"])}">{esc(r["title"])}</a><br>'
|
||||
f'<a href="{esc(r["url"])}" rel="noreferrer noopener">{esc(r["title"])}</a><br>'
|
||||
f'<small>{esc(r["url"])}</small>'
|
||||
f'{snip_html}'
|
||||
f'{note_html}{tags_html}'
|
||||
|
|
@ -276,7 +276,7 @@ def handle_search(query):
|
|||
items = ""
|
||||
for l in trusted:
|
||||
items += (
|
||||
f'<li><a href="{esc(l["url"])}">{esc(l["label"])}</a> '
|
||||
f'<li><a href="{esc(clean_url(l["url"]))}" rel="noreferrer noopener">{esc(l["label"])}</a> '
|
||||
f'<small>— from {esc(l["source_title"])}</small></li>'
|
||||
)
|
||||
trusted_html = (
|
||||
|
|
@ -311,8 +311,8 @@ def handle_search(query):
|
|||
for r in items:
|
||||
note_html = f' — <em>{esc(r["note"])}</em>' if r["note"] else ""
|
||||
source_items += (
|
||||
f'<li><a href="{esc(r["url"])}">{esc(r["title"])}</a>'
|
||||
f'{note_html} <small>({esc(r["url"])})</small></li>'
|
||||
f'<li><a href="{esc(clean_url(r["url"]))}" rel="noreferrer noopener">{esc(r["title"])}</a>'
|
||||
f'{note_html} <small>({esc(clean_url(r["url"]))})</small></li>'
|
||||
)
|
||||
remote_html += (
|
||||
f'<details class="remote" open>'
|
||||
|
|
@ -473,7 +473,7 @@ def handle_add_manual_submit(body):
|
|||
# Log error but don't fail the whole operation
|
||||
print(f"Error generating embeddings: {e}")
|
||||
|
||||
return handle_add_form(f'Added manually: <a href="{esc(url)}">{esc(manual_title)}</a>')
|
||||
return handle_add_form(f'Added manually: <a href="{esc(url)}" rel="noreferrer noopener">{esc(manual_title)}</a>')
|
||||
finally:
|
||||
return_db(db)
|
||||
|
||||
|
|
@ -500,7 +500,7 @@ def handle_pages(query=None):
|
|||
tags_html = f' {tag_links}'
|
||||
items += (
|
||||
f'<li>{esc(r["title"])}{note_html}{tags_html} '
|
||||
f'<small>(<a href="{esc(r["url"])}">{esc(r["url"])}</a>)</small> '
|
||||
f'<small>(<a href="{esc(r["url"])}" rel="noreferrer noopener">{esc(r["url"])}</a>)</small> '
|
||||
f'<a href="/edit/{r["id"]}">edit</a> '
|
||||
f'<a href="/delete/{r["id"]}">remove</a></li>'
|
||||
)
|
||||
|
|
@ -700,7 +700,7 @@ def handle_style_form(msg=""):
|
|||
f"<small>Default: reticulum.derickphan.com:4242</small><br>"
|
||||
f'<input name="transport_host" value="{esc(transport_host)}" placeholder="hostname" size="30">'
|
||||
f' <input name="transport_port" value="{esc(transport_port)}" placeholder="port" size="6"><br>'
|
||||
f'<p><a href="https://rmap.world/" target="_blank">discover more nodes</a></p><br>'
|
||||
f'<p><a href="https://rmap.world/" target="_blank" rel="noreferrer noopener">discover more nodes</a></p><br>'
|
||||
f"<h2>search</h2>"
|
||||
f"<h3>ai</h3>"
|
||||
f'<label><input type="checkbox" name="semantic_search" value="1"{semantic_checked} '
|
||||
|
|
@ -851,7 +851,7 @@ def handle_tag_browse(tag_name, query=None):
|
|||
tag_links = " ".join(f'<a href="/tags/{esc(t)}">[{esc(t)}]</a>' for t in tags)
|
||||
items += (
|
||||
f'<li>{esc(r["title"])}{note_html} {tag_links} '
|
||||
f'<small>(<a href="{esc(r["url"])}">{esc(r["url"])}</a>)</small></li>'
|
||||
f'<small>(<a href="{esc(r["url"])}" rel="noreferrer noopener">{esc(r["url"])}</a>)</small></li>'
|
||||
)
|
||||
finally:
|
||||
return_db(db)
|
||||
|
|
@ -1397,8 +1397,7 @@ def dispatch_request(data):
|
|||
resp["headers"]["Content-Security-Policy"] = (
|
||||
"default-src 'self'; "
|
||||
"script-src 'self' 'unsafe-inline'; "
|
||||
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; "
|
||||
"font-src 'self' https://fonts.gstatic.com; "
|
||||
"style-src 'self' 'unsafe-inline'; "
|
||||
"img-src * data:; "
|
||||
"frame-ancestors 'none'; "
|
||||
"form-action 'self'; "
|
||||
|
|
|
|||
|
|
@ -7,13 +7,13 @@ def esc(s):
|
|||
|
||||
|
||||
|
||||
DEFAULT_TEMPLATE = "<html>\n<head>\n</head>\n<body>\n{{content}}\n</body>\n</html>"
|
||||
DEFAULT_TEMPLATE = "<html>\n<head>\n<meta name=\"referrer\" content=\"no-referrer\">\n<meta http-equiv=\"x-dns-prefetch-control\" content=\"off\">\n</head>\n<body>\n{{content}}\n</body>\n</html>"
|
||||
|
||||
|
||||
def _default_template():
|
||||
name = esc(get_setting("site_name", "tinyweb"))
|
||||
return (
|
||||
"<html>\n<head>\n</head>\n<body>\n"
|
||||
'<html>\n<head>\n<meta name="referrer" content="no-referrer">\n<meta http-equiv="x-dns-prefetch-control" content="off">\n</head>\n<body>\n'
|
||||
f'<p><b><a href="/">{name}</a></b>'
|
||||
' | <a href="/">search</a> | <a href="/pages">browse</a>'
|
||||
' | <a href="/tags">tags</a> | <a href="/subscriptions">subscriptions</a>'
|
||||
|
|
|
|||
|
|
@ -3,15 +3,16 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="referrer" content="no-referrer">
|
||||
<meta http-equiv="x-dns-prefetch-control" content="off">
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Nunito:ital,wght@0,400;0,600;0,700;1,400&family=Fira+Code:wght@400;500&display=swap');
|
||||
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
html, body { min-height: 100vh; }
|
||||
|
||||
body {
|
||||
font-family: 'Nunito', -apple-system, sans-serif;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.65;
|
||||
color: #e0d8c8;
|
||||
|
|
@ -81,7 +82,7 @@
|
|||
right: 10px;
|
||||
z-index: 900;
|
||||
pointer-events: none;
|
||||
font-family: 'Fira Code', monospace;
|
||||
font-family: ui-monospace, 'SF Mono', 'Cascadia Code', 'Segoe UI Mono', Menlo, Consolas, monospace;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
|
|
@ -289,7 +290,7 @@
|
|||
}
|
||||
|
||||
nav .site {
|
||||
font-family: 'Fira Code', monospace;
|
||||
font-family: ui-monospace, 'SF Mono', 'Cascadia Code', 'Segoe UI Mono', Menlo, Consolas, monospace;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
color: #f0d878;
|
||||
|
|
@ -383,7 +384,7 @@
|
|||
border-radius: 4px;
|
||||
padding: 0.6rem 0.85rem;
|
||||
color: #d0c8b8;
|
||||
font-family: 'Nunito', sans-serif;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif;
|
||||
font-size: 0.95rem;
|
||||
transition: border-color 0.2s, box-shadow 0.3s;
|
||||
}
|
||||
|
|
@ -400,7 +401,7 @@
|
|||
border-radius: 4px;
|
||||
padding: 0.6rem 1.1rem;
|
||||
color: #a098b0;
|
||||
font-family: 'Nunito', sans-serif;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif;
|
||||
font-size: 0.88rem;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
|
@ -443,7 +444,7 @@
|
|||
.tags { margin-top: 0.3rem; }
|
||||
|
||||
.tag, .tags a {
|
||||
font-family: 'Fira Code', monospace;
|
||||
font-family: ui-monospace, 'SF Mono', 'Cascadia Code', 'Segoe UI Mono', Menlo, Consolas, monospace;
|
||||
font-size: 0.7rem;
|
||||
color: #8878a0;
|
||||
border: 1px solid rgba(255,255,255,0.08);
|
||||
|
|
@ -486,7 +487,7 @@
|
|||
|
||||
/* code */
|
||||
pre {
|
||||
font-family: 'Fira Code', monospace;
|
||||
font-family: ui-monospace, 'SF Mono', 'Cascadia Code', 'Segoe UI Mono', Menlo, Consolas, monospace;
|
||||
font-size: 0.8rem;
|
||||
background: rgba(15, 10, 40, 0.5);
|
||||
border: 1px solid rgba(255,255,255,0.08);
|
||||
|
|
@ -498,7 +499,7 @@
|
|||
}
|
||||
|
||||
code {
|
||||
font-family: 'Fira Code', monospace;
|
||||
font-family: ui-monospace, 'SF Mono', 'Cascadia Code', 'Segoe UI Mono', Menlo, Consolas, monospace;
|
||||
font-size: 0.82rem;
|
||||
background: rgba(15, 10, 40, 0.4);
|
||||
border-radius: 3px;
|
||||
|
|
@ -513,7 +514,7 @@
|
|||
border-radius: 4px;
|
||||
padding: 0.7rem 0.9rem;
|
||||
color: #d0c8b8;
|
||||
font-family: 'Fira Code', monospace;
|
||||
font-family: ui-monospace, 'SF Mono', 'Cascadia Code', 'Segoe UI Mono', Menlo, Consolas, monospace;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.6;
|
||||
resize: vertical;
|
||||
|
|
@ -547,7 +548,7 @@
|
|||
hr { border: none; border-top: 1px solid rgba(255,255,255,0.06); margin: 1rem 0; }
|
||||
|
||||
small {
|
||||
font-family: 'Fira Code', monospace;
|
||||
font-family: ui-monospace, 'SF Mono', 'Cascadia Code', 'Segoe UI Mono', Menlo, Consolas, monospace;
|
||||
font-size: 0.7rem;
|
||||
color: #5a5070;
|
||||
}
|
||||
|
|
@ -562,7 +563,7 @@
|
|||
}
|
||||
|
||||
footer .clock {
|
||||
font-family: 'Fira Code', monospace;
|
||||
font-family: ui-monospace, 'SF Mono', 'Cascadia Code', 'Segoe UI Mono', Menlo, Consolas, monospace;
|
||||
font-size: 0.72rem;
|
||||
color: #3a3050;
|
||||
margin-top: 0.25rem;
|
||||
|
|
|
|||
|
|
@ -3,13 +3,14 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="referrer" content="no-referrer">
|
||||
<meta http-equiv="x-dns-prefetch-control" content="off">
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500&family=IBM+Plex+Sans:ital,wght@0,400;0,500;0,600;1,400&display=swap');
|
||||
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
font-family: 'IBM Plex Sans', -apple-system, sans-serif;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.65;
|
||||
color: #c8c8c8;
|
||||
|
|
@ -99,7 +100,7 @@
|
|||
}
|
||||
|
||||
nav .site {
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
font-family: ui-monospace, 'SF Mono', 'Cascadia Code', 'Segoe UI Mono', Menlo, Consolas, monospace;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
color: #e8e8e8;
|
||||
|
|
@ -193,7 +194,7 @@
|
|||
border-radius: 4px;
|
||||
padding: 0.6rem 0.85rem;
|
||||
color: #d0d0d0;
|
||||
font-family: 'IBM Plex Sans', sans-serif;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif;
|
||||
font-size: 0.95rem;
|
||||
transition: border-color 0.2s, box-shadow 0.3s;
|
||||
}
|
||||
|
|
@ -210,7 +211,7 @@
|
|||
border-radius: 4px;
|
||||
padding: 0.6rem 1.1rem;
|
||||
color: #999;
|
||||
font-family: 'IBM Plex Sans', sans-serif;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif;
|
||||
font-size: 0.88rem;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
|
@ -253,7 +254,7 @@
|
|||
.tags { margin-top: 0.3rem; }
|
||||
|
||||
.tag, .tags a {
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
font-family: ui-monospace, 'SF Mono', 'Cascadia Code', 'Segoe UI Mono', Menlo, Consolas, monospace;
|
||||
font-size: 0.7rem;
|
||||
color: #555;
|
||||
border: 1px solid #252525;
|
||||
|
|
@ -296,7 +297,7 @@
|
|||
|
||||
/* code */
|
||||
pre {
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
font-family: ui-monospace, 'SF Mono', 'Cascadia Code', 'Segoe UI Mono', Menlo, Consolas, monospace;
|
||||
font-size: 0.8rem;
|
||||
background: #151515;
|
||||
border: 1px solid #232323;
|
||||
|
|
@ -308,7 +309,7 @@
|
|||
}
|
||||
|
||||
code {
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
font-family: ui-monospace, 'SF Mono', 'Cascadia Code', 'Segoe UI Mono', Menlo, Consolas, monospace;
|
||||
font-size: 0.82rem;
|
||||
background: #1a1a1a;
|
||||
border-radius: 3px;
|
||||
|
|
@ -323,7 +324,7 @@
|
|||
border-radius: 4px;
|
||||
padding: 0.7rem 0.9rem;
|
||||
color: #c8c8c8;
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
font-family: ui-monospace, 'SF Mono', 'Cascadia Code', 'Segoe UI Mono', Menlo, Consolas, monospace;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.6;
|
||||
resize: vertical;
|
||||
|
|
@ -357,7 +358,7 @@
|
|||
hr { border: none; border-top: 1px solid #1e1e1e; margin: 1rem 0; }
|
||||
|
||||
small {
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
font-family: ui-monospace, 'SF Mono', 'Cascadia Code', 'Segoe UI Mono', Menlo, Consolas, monospace;
|
||||
font-size: 0.7rem;
|
||||
color: #484848;
|
||||
}
|
||||
|
|
@ -372,7 +373,7 @@
|
|||
}
|
||||
|
||||
footer .clock {
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
font-family: ui-monospace, 'SF Mono', 'Cascadia Code', 'Segoe UI Mono', Menlo, Consolas, monospace;
|
||||
font-size: 0.72rem;
|
||||
color: #282828;
|
||||
margin-top: 0.25rem;
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="referrer" content="no-referrer">
|
||||
<meta http-equiv="x-dns-prefetch-control" content="off">
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500&family=IBM+Plex+Sans:ital,wght@0,400;0,500;0,600;1,400&display=swap');
|
||||
|
||||
* { margin: 0; padding: 0; box-sizing: border-box;
|
||||
cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Ccircle cx='10' cy='10' r='3' fill='none' stroke='%23557766' stroke-width='1.5'/%3E%3Ccircle cx='10' cy='10' r='1' fill='%2377aa88'/%3E%3C/svg%3E") 10 10, default;
|
||||
|
|
@ -13,7 +14,7 @@
|
|||
html, body { min-height: 100vh; }
|
||||
|
||||
body {
|
||||
font-family: 'IBM Plex Sans', -apple-system, sans-serif;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.65;
|
||||
color: #9ab4b8;
|
||||
|
|
@ -65,7 +66,7 @@
|
|||
}
|
||||
|
||||
nav .site {
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
font-family: ui-monospace, 'SF Mono', 'Cascadia Code', 'Segoe UI Mono', Menlo, Consolas, monospace;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
color: #b0ccc4;
|
||||
|
|
@ -158,7 +159,7 @@
|
|||
border-radius: 4px;
|
||||
padding: 0.6rem 0.85rem;
|
||||
color: #90b4ac;
|
||||
font-family: 'IBM Plex Sans', sans-serif;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif;
|
||||
font-size: 0.95rem;
|
||||
transition: border-color 0.2s, box-shadow 0.3s;
|
||||
}
|
||||
|
|
@ -175,7 +176,7 @@
|
|||
border-radius: 4px;
|
||||
padding: 0.6rem 1.1rem;
|
||||
color: #5a7880;
|
||||
font-family: 'IBM Plex Sans', sans-serif;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif;
|
||||
font-size: 0.88rem;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
|
@ -240,7 +241,7 @@
|
|||
.tags { margin-top: 0.3rem; }
|
||||
|
||||
.tag, .tags a {
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
font-family: ui-monospace, 'SF Mono', 'Cascadia Code', 'Segoe UI Mono', Menlo, Consolas, monospace;
|
||||
font-size: 0.7rem;
|
||||
color: #3a5e55;
|
||||
border: 1px solid rgba(40, 70, 60, 0.35);
|
||||
|
|
@ -273,7 +274,7 @@
|
|||
li a:hover { border-bottom: 1px solid rgba(80, 130, 110, 0.4); }
|
||||
|
||||
pre {
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
font-family: ui-monospace, 'SF Mono', 'Cascadia Code', 'Segoe UI Mono', Menlo, Consolas, monospace;
|
||||
font-size: 0.8rem;
|
||||
background: rgba(6, 14, 16, 0.6);
|
||||
border: 1px solid rgba(30, 55, 50, 0.3);
|
||||
|
|
@ -285,7 +286,7 @@
|
|||
}
|
||||
|
||||
code {
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
font-family: ui-monospace, 'SF Mono', 'Cascadia Code', 'Segoe UI Mono', Menlo, Consolas, monospace;
|
||||
font-size: 0.82rem;
|
||||
background: rgba(8, 18, 22, 0.6);
|
||||
border-radius: 3px;
|
||||
|
|
@ -299,7 +300,7 @@
|
|||
border-radius: 4px;
|
||||
padding: 0.7rem 0.9rem;
|
||||
color: #9ab4b8;
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
font-family: ui-monospace, 'SF Mono', 'Cascadia Code', 'Segoe UI Mono', Menlo, Consolas, monospace;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.6;
|
||||
resize: vertical;
|
||||
|
|
@ -330,7 +331,7 @@
|
|||
hr { border: none; border-top: 1px solid rgba(30, 55, 50, 0.3); margin: 1rem 0; }
|
||||
|
||||
small {
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
font-family: ui-monospace, 'SF Mono', 'Cascadia Code', 'Segoe UI Mono', Menlo, Consolas, monospace;
|
||||
font-size: 0.7rem;
|
||||
color: #28454e;
|
||||
}
|
||||
|
|
@ -344,7 +345,7 @@
|
|||
}
|
||||
|
||||
footer .clock {
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
font-family: ui-monospace, 'SF Mono', 'Cascadia Code', 'Segoe UI Mono', Menlo, Consolas, monospace;
|
||||
font-size: 0.72rem;
|
||||
color: #162a30;
|
||||
margin-top: 0.25rem;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue