Cat Feeder  1.0.0
The Cat feeder project
Loading...
Searching...
No Matches
server_header.py
Go to the documentation of this file.
1r"""
2# +==== BEGIN CatFeeder =================+
3# LOGO:
4# ..............(..../\
5# ...............)..(.')
6# ..............(../..)
7# ...............\‍(__)|
8# Inspired by Joan Stark
9# source https://www.asciiart.eu/
10# animals/cats
11# /STOP
12# PROJECT: CatFeeder
13# FILE: server_header.py
14# CREATION DATE: 24-11-2025
15# LAST Modified: 14:51:49 19-12-2025
16# DESCRIPTION:
17# This is the backend server in charge of making the actual website work.
18# /STOP
19# COPYRIGHT: (c) Cat Feeder
20# PURPOSE: The class in charge of building the header for the server (This allows a certain flexibility with regards to the response structure)
21# // AR
22# +==== END CatFeeder =================+
23"""
24
25from typing import Dict
26
27from display_tty import Disp, initialise_logger
28
29from . import server_header_constants as HEADER_CONST
30
31from ..core import FinalClass
32
33
34class ServerHeaders(metaclass=FinalClass):
35 """HTTP response header builder for FastAPI responses.
36
37 Provides standardized header generation methods for different content types
38 with appropriate security policies, caching strategies, and content disposition.
39
40 Security Features:
41 - X-Content-Type-Options: nosniff (prevents MIME sniffing)
42 - X-Frame-Options: DENY/SAMEORIGIN (clickjacking protection)
43 - X-XSS-Protection: 1; mode=block (XSS protection)
44 - Referrer-Policy: strict-origin-when-cross-origin (privacy control)
45 - Content-Security-Policy: Applied to HTML responses
46
47 Caching Strategies:
48 - Dynamic content (JSON/text/HTML/forms): no-cache
49 - Versioned static assets (JS/CSS): 1 year immutable
50 - Media files (images/video/audio): 24 hours
51 - Downloadable files (PDF/CSV): 1 hour
52 - Streaming content: no-cache
53
54 Usage:
55 >>> headers = ServerHeaders(app_name="MyAPI")
56 >>> json_headers = headers.for_json()
57 >>> file_headers = headers.for_file(filename="report.pdf")
58 >>> return Response(content=data, headers=json_headers)
59
60 Attributes:
61 host (str): Server host address.
62 port (int): Server port number.
63 app_name (str): Application name included in X-App-Name header.
64 error (int): Error return code.
65 success (int): Success return code.
66 debug (bool): Debug mode flag.
67 """
68 disp: Disp = initialise_logger(__qualname__, False)
69
70 def __init__(self, host: str = "0.0.0.0", port: int = 5000, app_name: str = "Asperguide", error: int = 84, success: int = 0, debug: bool = False) -> None:
71 """Initialize ServerHeaders instance.
72
73 Args:
74 host: Server host address. Defaults to "0.0.0.0".
75 port: Server port number. Defaults to 5000.
76 app_name: Application name for headers. Defaults to "Asperguide".
77 error: Error return code. Defaults to 84.
78 success: Success return code. Defaults to 0.
79 debug: Enable debug logging. Defaults to False.
80 """
81 # ------------------------ The logging function ------------------------
82 self.disp.update_disp_debug(debug)
83 self.disp.log_debug("Initialising...")
84 # -------------------------- Inherited values --------------------------
85 self.host: str = host
86 self.port: int = port
87 self.app_name: str = app_name
88 self.error: int = error
89 self.success: int = success
90 self.debug: bool = debug
91 self.disp.log_debug("Initialised")
92
93 def _get_app_name_str(self) -> str:
94 """Convert app_name to string format.
95
96 Returns:
97 str: Application name as string.
98 """
99 if not isinstance(self.app_name, str):
100 return f"{self.app_name}"
101 return self.app_name
102
103 def _base_security_headers(self) -> Dict[str, str]:
104 """Common security headers for all responses.
105
106 Includes standard security headers to protect against common web vulnerabilities:
107 - MIME sniffing attacks
108 - Clickjacking
109 - Cross-site scripting (XSS)
110 - Referrer leakage
111
112 Returns:
113 Dict[str, str]: Base security headers dictionary.
114 """
115 return {
116 HEADER_CONST.HEADER_APP_NAME: self._get_app_name_str(),
117 HEADER_CONST.CONTENT_TYPE: "nosniff",
118 HEADER_CONST.FRAME_OPTIONS: "DENY",
119 HEADER_CONST.XSS_PROTECTION: "1; mode=block",
120 HEADER_CONST.REFERRER_POLICY: "strict-origin-when-cross-origin",
121 }
122
123 def for_json(self) -> Dict[str, str]:
124 """Headers for JSON responses.
125
126 Includes aggressive no-cache policy for dynamic API data.
127
128 Returns:
129 Dict[str, str]: Headers optimized for JSON responses.
130 """
131 headers = self._base_security_headers()
132 headers[HEADER_CONST.CACHE_CONTROL] = "no-cache, no-store, must-revalidate"
133 headers[HEADER_CONST.PRAGMA] = "no-cache"
134 headers[HEADER_CONST.EXPIRES] = "0"
135 return headers
136
137 def for_text(self) -> Dict[str, str]:
138 """Headers for plain text responses.
139
140 Returns:
141 Dict[str, str]: Headers optimized for plain text responses.
142 """
143 headers = self._base_security_headers()
144 headers[HEADER_CONST.CACHE_CONTROL] = "no-cache, no-store, must-revalidate"
145 return headers
146
147 def for_html(self) -> Dict[str, str]:
148 """Headers for HTML responses.
149
150 Includes Content-Security-Policy to control script/style sources and prevent XSS.
151
152 Returns:
153 Dict[str, str]: Headers optimized for HTML responses with CSP.
154 """
155 headers = self._base_security_headers()
156 headers[HEADER_CONST.CACHE_CONTROL] = "no-cache, no-store, must-revalidate"
157 headers[HEADER_CONST.CONTENT_SECURITY_POLICY] = "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:;"
158 return headers
159
160 def for_xml(self) -> Dict[str, str]:
161 """Headers for XML responses.
162
163 Returns:
164 Dict[str, str]: Headers optimized for XML responses.
165 """
166 headers = self._base_security_headers()
167 headers[HEADER_CONST.CACHE_CONTROL] = "no-cache, no-store, must-revalidate"
168 return headers
169
170 def for_css(self) -> Dict[str, str]:
171 """Headers for CSS responses.
172
173 Uses aggressive caching (1 year) for versioned static CSS files.
174
175 Returns:
176 Dict[str, str]: Headers optimized for CSS with long-term caching.
177 """
178 headers = self._base_security_headers()
179 headers[HEADER_CONST.CACHE_CONTROL] = "public, max-age=31536000, immutable"
180 return headers
181
182 def for_javascript(self) -> Dict[str, str]:
183 """Headers for JavaScript responses.
184
185 Uses aggressive caching (1 year) for versioned static JavaScript files.
186
187 Returns:
188 Dict[str, str]: Headers optimized for JavaScript with long-term caching.
189 """
190 headers = self._base_security_headers()
191 headers[HEADER_CONST.CACHE_CONTROL] = "public, max-age=31536000, immutable"
192 return headers
193
194 def for_image(self) -> Dict[str, str]:
195 """Headers for image responses.
196
197 Uses 24-hour caching and allows iframe embedding from same origin.
198
199 Returns:
200 Dict[str, str]: Headers optimized for image responses.
201 """
202 headers = self._base_security_headers()
203 headers[HEADER_CONST.CACHE_CONTROL] = "public, max-age=86400"
204 headers[HEADER_CONST.FRAME_OPTIONS] = "SAMEORIGIN"
205 return headers
206
207 def for_file(self, filename: str = "") -> Dict[str, str]:
208 """Headers for file download responses.
209
210 Args:
211 filename: Optional filename for Content-Disposition header.
212
213 Returns:
214 Dict[str, str]: Headers optimized for file downloads with optional filename.
215 """
216 headers = self._base_security_headers()
217 headers[HEADER_CONST.CACHE_CONTROL] = "public, max-age=3600"
218 if filename:
219 headers[HEADER_CONST.CONTENT_DISPOSITION] = f'attachment; filename="{filename}"'
220 return headers
221
222 def for_pdf(self, filename: str = "document.pdf", inline: bool = False) -> Dict[str, str]:
223 """Headers for PDF responses.
224
225 Args:
226 filename: PDF filename for Content-Disposition. Defaults to "document.pdf".
227 inline: If True, display inline in browser; if False, force download. Defaults to False.
228
229 Returns:
230 Dict[str, str]: Headers optimized for PDF with configurable display mode.
231 """
232 headers = self._base_security_headers()
233 headers[HEADER_CONST.CACHE_CONTROL] = "public, max-age=3600"
234 if inline:
235 disposition = "inline"
236 else:
237 disposition = "attachment"
238 headers[HEADER_CONST.CONTENT_DISPOSITION] = f'{disposition}; filename="{filename}"'
239 return headers
240
241 def for_stream(self) -> Dict[str, str]:
242 """Headers for streaming responses.
243
244 Supports byte-range requests for efficient streaming.
245
246 Returns:
247 Dict[str, str]: Headers optimized for streaming with range support.
248 """
249 headers = self._base_security_headers()
250 headers[HEADER_CONST.CACHE_CONTROL] = "no-cache"
251 headers[HEADER_CONST.ACCEPT_RANGES] = "bytes"
252 return headers
253
254 def for_video(self) -> Dict[str, str]:
255 """Headers for video streaming responses.
256
257 Supports byte-range requests for seeking and allows iframe embedding from same origin.
258
259 Returns:
260 Dict[str, str]: Headers optimized for video streaming.
261 """
262 headers = self._base_security_headers()
263 headers[HEADER_CONST.CACHE_CONTROL] = "public, max-age=86400"
264 headers[HEADER_CONST.ACCEPT_RANGES] = "bytes"
265 headers[HEADER_CONST.FRAME_OPTIONS] = "SAMEORIGIN"
266 return headers
267
268 def for_audio(self) -> Dict[str, str]:
269 """Headers for audio streaming responses.
270
271 Supports byte-range requests for seeking.
272
273 Returns:
274 Dict[str, str]: Headers optimized for audio streaming.
275 """
276 headers = self._base_security_headers()
277 headers[HEADER_CONST.CACHE_CONTROL] = "public, max-age=86400"
278 headers[HEADER_CONST.ACCEPT_RANGES] = "bytes"
279 return headers
280
281 def for_csv(self, filename: str = "export.csv") -> Dict[str, str]:
282 """Headers for CSV export responses.
283
284 Forces download with specified filename and no caching for fresh exports.
285
286 Args:
287 filename: CSV filename for Content-Disposition. Defaults to "export.csv".
288
289 Returns:
290 Dict[str, str]: Headers optimized for CSV exports with download enforcement.
291 """
292 headers = self._base_security_headers()
293 headers[HEADER_CONST.CACHE_CONTROL] = "no-cache, no-store, must-revalidate"
294 headers[HEADER_CONST.CONTENT_DISPOSITION] = f'attachment; filename="{filename}"'
295 return headers
296
297 def for_redirect(self) -> Dict[str, str]:
298 """Headers for redirect responses.
299
300 Minimal headers for HTTP redirects.
301
302 Returns:
303 Dict[str, str]: Headers optimized for redirect responses.
304 """
305 return {
306 HEADER_CONST.HEADER_APP_NAME: self._get_app_name_str(),
307 HEADER_CONST.CACHE_CONTROL: "no-cache, no-store, must-revalidate",
308 }
309
310 def for_form(self) -> Dict[str, str]:
311 """Headers for form data responses.
312
313 Returns:
314 Dict[str, str]: Headers optimized for form data responses.
315 """
316 headers = self._base_security_headers()
317 headers[HEADER_CONST.CACHE_CONTROL] = "no-cache, no-store, must-revalidate"
318 return headers
None __init__(self, str host="0.0.0.0", int port=5000, str app_name="Asperguide", int error=84, int success=0, bool debug=False)
Dict[str, str] for_pdf(self, str filename="document.pdf", bool inline=False)
Dict[str, str] for_csv(self, str filename="export.csv")