|
21 | 21 | import datetime
|
22 | 22 | import json
|
23 | 23 | from typing import TYPE_CHECKING, Any, cast
|
24 |
| -from urllib.parse import unquote |
| 24 | +from urllib.parse import unquote, urlsplit |
25 | 25 |
|
26 | 26 | import bleach
|
27 | 27 | import humanize
|
|
37 | 37 | from core import Application
|
38 | 38 |
|
39 | 39 |
|
| 40 | +with open("web/paste.html") as fp: |
| 41 | + PASTE_HTML: str = fp.read() |
| 42 | + |
| 43 | + |
40 | 44 | class HTMXView(starlette_plus.View, prefix="htmx"):
|
41 | 45 | def __init__(self, app: Application) -> None:
|
42 | 46 | self.app: Application = app
|
43 | 47 |
|
44 | 48 | def highlight_code(self, filename: str, content: str, *, index: int, raw_url: str, annotation: str) -> str:
|
45 | 49 | filename = bleach.clean(filename, attributes=[], tags=[])
|
46 |
| - content = bleach.clean(content, attributes=[], tags=[]) |
| 50 | + filename = "_".join(filename.splitlines()) |
47 | 51 |
|
| 52 | + content = bleach.clean(content.replace("<!", "<!"), attributes=[], tags=[], strip_comments=False) |
48 | 53 | annotations: str = f'<small class="annotations">❌ {annotation}</small>' if annotation else ""
|
49 | 54 |
|
50 | 55 | return f"""
|
@@ -73,17 +78,94 @@ async def home(self, request: starlette_plus.Request) -> starlette_plus.Response
|
73 | 78 |
|
74 | 79 | return starlette_plus.FileResponse("web/index.html")
|
75 | 80 |
|
| 81 | + @starlette_plus.route("/protected/{id}", prefix=False) |
| 82 | + @starlette_plus.limit(**CONFIG["LIMITS"]["paste_get"]) |
| 83 | + @starlette_plus.limit(**CONFIG["LIMITS"]["paste_get_day"]) |
| 84 | + async def protected_paste(self, request: starlette_plus.Request) -> starlette_plus.Response: |
| 85 | + return starlette_plus.FileResponse("web/password.html") |
| 86 | + |
76 | 87 | @starlette_plus.route("/{id}", prefix=False)
|
77 | 88 | @starlette_plus.limit(**CONFIG["LIMITS"]["paste_get"])
|
78 | 89 | @starlette_plus.limit(**CONFIG["LIMITS"]["paste_get_day"])
|
79 | 90 | async def paste(self, request: starlette_plus.Request) -> starlette_plus.Response:
|
80 | 91 | if resp := self.check_discord(request=request):
|
81 | 92 | return resp
|
82 | 93 |
|
83 |
| - identifier: str = request.path_params.get("id", "") |
84 |
| - headers = {"Link": f'</htmx/fetch?id=%2F{identifier}>; rel="preload"; as="fetch" crossorigin="anonymous"'} |
| 94 | + identifier: str = request.path_params.get("id", "pass") |
| 95 | + htmx_url: str | None = request.headers.get("HX-Current-URL", None) |
| 96 | + |
| 97 | + if htmx_url and identifier == "pass": |
| 98 | + identifier = urlsplit(htmx_url).path.removeprefix("/protected/") |
| 99 | + |
| 100 | + not_found: str = """ |
| 101 | + <div class="notFound"> |
| 102 | + <h2>404 - This page or paste could not be found</h2> |
| 103 | + <a href="/">Return Home...</a> |
| 104 | + </div> |
| 105 | + """ |
| 106 | + |
| 107 | + password: str = unquote(request.query_params.get("pastePassword", "")) |
| 108 | + paste = await self.app.database.fetch_paste(identifier, password=password) |
| 109 | + |
| 110 | + if not paste: |
| 111 | + return starlette_plus.HTMLResponse(PASTE_HTML.format(__PASTES__=not_found)) |
| 112 | + |
| 113 | + if paste.has_password and not paste.password_ok: |
| 114 | + if not password: |
| 115 | + return starlette_plus.RedirectResponse(f"/protected/{identifier}") |
| 116 | + |
| 117 | + error_headers: dict[str, str] = {"HX-Retarget": "#errorResponse", "HX-Reswap": "outerHTML"} |
| 118 | + return starlette_plus.HTMLResponse( |
| 119 | + """<span id="errorResponse">Incorrect Password.</span>""", |
| 120 | + headers=error_headers, |
| 121 | + ) |
| 122 | + |
| 123 | + data: dict[str, Any] = paste.serialize(exclude=["password", "password_ok"]) |
| 124 | + files: list[dict[str, Any]] = data["files"] |
| 125 | + created_delta: datetime.timedelta = datetime.datetime.now(tz=datetime.timezone.utc) - paste.created_at.replace( |
| 126 | + tzinfo=datetime.timezone.utc |
| 127 | + ) |
| 128 | + |
| 129 | + url: str = f"/{identifier}" |
| 130 | + raw_url: str = f"/raw/{identifier}" |
| 131 | + security_html: str = "" |
| 132 | + |
| 133 | + stored: list[str] = request.session.get("pastes", []) |
| 134 | + if identifier in stored: |
| 135 | + security_url: str = f"/api/security/info/{data['safety']}" |
| 136 | + |
| 137 | + security_html = f""" |
| 138 | + <div class="identifierHeaderSection"> |
| 139 | + <a class="security" href="{security_url}">Security Info</a> |
| 140 | + </div>""" |
| 141 | + |
| 142 | + html: str = f""" |
| 143 | + <div class="identifierHeader"> |
| 144 | + <div class="identifierHeaderLeft"> |
| 145 | + <a href="{url}">/{identifier}</a> |
| 146 | + <span>Created {humanize.naturaltime(created_delta)}...</span> |
| 147 | + </div> |
| 148 | + {security_html} |
| 149 | + <div class="identifierHeaderSection"> |
| 150 | + <a href="{raw_url}">Raw</a> |
| 151 | + <!-- <a href="#">Download</a> --> |
| 152 | + </div> |
| 153 | + </div> |
| 154 | + """ |
| 155 | + |
| 156 | + for i, file in enumerate(files): |
| 157 | + html += self.highlight_code( |
| 158 | + file["filename"], |
| 159 | + file["content"], |
| 160 | + index=i, |
| 161 | + raw_url=raw_url, |
| 162 | + annotation=file["annotation"], |
| 163 | + ) |
| 164 | + |
| 165 | + if htmx_url and password: |
| 166 | + return starlette_plus.HTMLResponse(html, headers={"HX-Replace-Url": f"{url}?pastePassword={password}"}) |
85 | 167 |
|
86 |
| - return starlette_plus.FileResponse("web/paste.html", headers=headers) |
| 168 | + return starlette_plus.HTMLResponse(PASTE_HTML.format(__PASTES__=html), media_type="text/html") |
87 | 169 |
|
88 | 170 | @starlette_plus.route("/raw/{id}", prefix=False)
|
89 | 171 | @starlette_plus.limit(**CONFIG["LIMITS"]["paste_get"])
|
@@ -146,88 +228,6 @@ async def paste_raw_page(self, request: starlette_plus.Request) -> starlette_plu
|
146 | 228 |
|
147 | 229 | return starlette_plus.PlainTextResponse(text)
|
148 | 230 |
|
149 |
| - @starlette_plus.route("/fetch") |
150 |
| - @starlette_plus.limit(**CONFIG["LIMITS"]["paste_get"]) |
151 |
| - @starlette_plus.limit(**CONFIG["LIMITS"]["paste_get_day"]) |
152 |
| - async def fetch_paste(self, request: starlette_plus.Request) -> starlette_plus.Response: |
153 |
| - if resp := self.check_discord(request=request): |
154 |
| - return resp |
155 |
| - |
156 |
| - not_found: str = """ |
157 |
| - <div class="notFound"> |
158 |
| - <h2>404 - This page or paste could not be found</h2> |
159 |
| - <a href="/">Return Home...</a> |
160 |
| - </div> |
161 |
| - """ |
162 |
| - |
163 |
| - no_reload: bool = request.query_params.get("noReload", False) == "true" |
164 |
| - password: str = unquote(request.query_params.get("pastePassword", "")) |
165 |
| - |
166 |
| - identifier_quoted: str | None = request.query_params.get("id", None) |
167 |
| - if not identifier_quoted: |
168 |
| - return starlette_plus.HTMLResponse(not_found) |
169 |
| - |
170 |
| - identifier: str = unquote(identifier_quoted).replace("/", "") |
171 |
| - paste = await self.app.database.fetch_paste(identifier, password=password) |
172 |
| - |
173 |
| - if not paste: |
174 |
| - return starlette_plus.HTMLResponse(not_found) |
175 |
| - |
176 |
| - if paste.has_password and not paste.password_ok: |
177 |
| - error_headers: dict[str, str] = {"HX-Retarget": "#errorResponse", "HX-Reswap": "outerHTML"} |
178 |
| - |
179 |
| - if no_reload: |
180 |
| - return starlette_plus.HTMLResponse( |
181 |
| - """<span id="errorResponse">Incorrect Password.</span>""", |
182 |
| - headers=error_headers, |
183 |
| - ) |
184 |
| - |
185 |
| - return starlette_plus.FileResponse("web/password.html") |
186 |
| - |
187 |
| - data: dict[str, Any] = paste.serialize(exclude=["password", "password_ok"]) |
188 |
| - files: list[dict[str, Any]] = data["files"] |
189 |
| - created_delta: datetime.timedelta = datetime.datetime.now(tz=datetime.timezone.utc) - paste.created_at.replace( |
190 |
| - tzinfo=datetime.timezone.utc |
191 |
| - ) |
192 |
| - |
193 |
| - url: str = f"/{identifier}" |
194 |
| - raw_url: str = f"/raw/{identifier}" |
195 |
| - security_html: str = "" |
196 |
| - |
197 |
| - stored: list[str] = request.session.get("pastes", []) |
198 |
| - if identifier in stored: |
199 |
| - security_url: str = f"/api/security/info/{data['safety']}" |
200 |
| - |
201 |
| - security_html = f""" |
202 |
| - <div class="identifierHeaderSection"> |
203 |
| - <a class="security" href="{security_url}">Security Info</a> |
204 |
| - </div>""" |
205 |
| - |
206 |
| - html: str = f""" |
207 |
| - <div class="identifierHeader"> |
208 |
| - <div class="identifierHeaderLeft"> |
209 |
| - <a href="{url}">/{identifier}</a> |
210 |
| - <span>Created {humanize.naturaltime(created_delta)}...</span> |
211 |
| - </div> |
212 |
| - {security_html} |
213 |
| - <div class="identifierHeaderSection"> |
214 |
| - <a href="{raw_url}">Raw</a> |
215 |
| - <!-- <a href="#">Download</a> --> |
216 |
| - </div> |
217 |
| - </div> |
218 |
| - """ |
219 |
| - |
220 |
| - for i, file in enumerate(files): |
221 |
| - html += self.highlight_code( |
222 |
| - file["filename"], |
223 |
| - file["content"], |
224 |
| - index=i, |
225 |
| - raw_url=raw_url, |
226 |
| - annotation=file["annotation"], |
227 |
| - ) |
228 |
| - |
229 |
| - return starlette_plus.HTMLResponse(html) |
230 |
| - |
231 | 231 | @starlette_plus.route("/save", methods=["POST"])
|
232 | 232 | @starlette_plus.limit(**CONFIG["LIMITS"]["paste_post"])
|
233 | 233 | @starlette_plus.limit(**CONFIG["LIMITS"]["paste_post_day"])
|
|
0 commit comments