Skip to content

Commit ee7a8b0

Browse files
committed
Major refactor of MIMETypes
1 parent 33fecc9 commit ee7a8b0

File tree

3 files changed

+211
-104
lines changed

3 files changed

+211
-104
lines changed

adafruit_httpserver/mime_type.py

Lines changed: 0 additions & 100 deletions
This file was deleted.

adafruit_httpserver/mime_types.py

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2022 Dan Halbert for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: MIT
4+
"""
5+
`adafruit_httpserver.mime_types`
6+
====================================================
7+
* Author(s): Michał Pokusa
8+
"""
9+
10+
try:
11+
from typing import List, Dict
12+
except ImportError:
13+
pass
14+
15+
16+
class MIMETypes:
17+
"""
18+
Contains MIME types for common file extensions.
19+
Allows to set default type for unknown files, unregister unused types and register new ones
20+
using the ``MIMETypes.configure()``.
21+
"""
22+
23+
DEFAULT = "text/plain"
24+
25+
REGISTERED = {
26+
".7z": "application/x-7z-compressed",
27+
".aac": "audio/aac",
28+
".abw": "application/x-abiword",
29+
".arc": "application/x-freearc",
30+
".avi": "video/x-msvideo",
31+
".azw": "application/vnd.amazon.ebook",
32+
".bin": "application/octet-stream",
33+
".bmp": "image/bmp",
34+
".bz": "application/x-bzip",
35+
".bz2": "application/x-bzip2",
36+
".cda": "application/x-cdf",
37+
".csh": "application/x-csh",
38+
".css": "text/css",
39+
".csv": "text/csv",
40+
".doc": "application/msword",
41+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
42+
".eot": "application/vnd.ms-fontobject",
43+
".epub": "application/epub+zip",
44+
".gif": "image/gif",
45+
".gz": "application/gzip",
46+
".htm": "text/html",
47+
".html": "text/html",
48+
".ico": "image/vnd.microsoft.icon",
49+
".ics": "text/calendar",
50+
".jar": "application/java-archive",
51+
".jpeg": "image/jpeg",
52+
".jpg": "image/jpeg",
53+
".js": "text/javascript",
54+
".json": "application/json",
55+
".jsonld": "application/ld+json",
56+
".mid": "audio/midi",
57+
".midi": "audio/midi",
58+
".mjs": "text/javascript",
59+
".mp3": "audio/mpeg",
60+
".mp4": "video/mp4",
61+
".mpeg": "video/mpeg",
62+
".mpkg": "application/vnd.apple.installer+xml",
63+
".odp": "application/vnd.oasis.opendocument.presentation",
64+
".ods": "application/vnd.oasis.opendocument.spreadsheet",
65+
".odt": "application/vnd.oasis.opendocument.text",
66+
".oga": "audio/ogg",
67+
".ogv": "video/ogg",
68+
".ogx": "application/ogg",
69+
".opus": "audio/opus",
70+
".otf": "font/otf",
71+
".pdf": "application/pdf",
72+
".php": "application/x-httpd-php",
73+
".png": "image/png",
74+
".ppt": "application/vnd.ms-powerpoint",
75+
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
76+
".rar": "application/vnd.rar",
77+
".rtf": "application/rtf",
78+
".sh": "application/x-sh",
79+
".svg": "image/svg+xml",
80+
".swf": "application/x-shockwave-flash",
81+
".tar": "application/x-tar",
82+
".tif": "image/tiff",
83+
".tiff": "image/tiff",
84+
".ts": "video/mp2t",
85+
".ttf": "font/ttf",
86+
".txt": "text/plain",
87+
".vsd": "application/vnd.visio",
88+
".wav": "audio/wav",
89+
".weba": "audio/webm",
90+
".webm": "video/webm",
91+
".webp": "image/webp",
92+
".woff": "font/woff",
93+
".woff2": "font/woff2",
94+
".xhtml": "application/xhtml+xml",
95+
".xls": "application/vnd.ms-excel",
96+
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
97+
".xml": "application/xml",
98+
".xul": "application/vnd.mozilla.xul+xml",
99+
".zip": "application/zip",
100+
}
101+
102+
@staticmethod
103+
def __check_all_start_with_dot(extensions: List[str]) -> None:
104+
for extension in extensions:
105+
if not extension.startswith("."):
106+
raise ValueError(
107+
f'Invalid extension: "{extension}". All extensions must start with a dot.'
108+
)
109+
110+
@classmethod
111+
def __check_all_are_registered(cls, extensions: List[str]) -> None:
112+
registered_extensions = cls.REGISTERED.keys()
113+
114+
for extension in extensions:
115+
if not extension in registered_extensions:
116+
raise ValueError(f'Extension "{extension}" is not registered. ')
117+
118+
@classmethod
119+
def _default_to(cls, mime_type: str) -> None:
120+
"""
121+
Set the default MIME type for unknown files.
122+
123+
:param str mime_type: The MIME type to use for unknown files.
124+
"""
125+
cls.DEFAULT = mime_type
126+
127+
@classmethod
128+
def _keep_for(cls, extensions: List[str]) -> None:
129+
"""
130+
Unregisters all MIME types except the ones for the given extensions,\
131+
**decreasing overall memory usage**.
132+
133+
It is recommended to **always** call this function before starting the server.
134+
135+
Example::
136+
137+
keep_for([".jpg", ".mp4", ".txt"])
138+
"""
139+
140+
cls.__check_all_start_with_dot(extensions)
141+
cls.__check_all_are_registered(extensions)
142+
143+
current_extensions = iter(cls.REGISTERED.keys())
144+
145+
cls.REGISTERED = {
146+
extension: cls.REGISTERED[extension]
147+
for extension in current_extensions
148+
if extension in extensions
149+
}
150+
151+
@classmethod
152+
def _register(cls, mime_types: dict) -> None:
153+
"""
154+
Register multiple MIME types.
155+
156+
Example::
157+
158+
register({
159+
".foo": "application/foo",
160+
".bar": "application/bar",
161+
})
162+
163+
:param dict mime_types: A dictionary mapping file extensions to MIME types.
164+
"""
165+
cls.__check_all_start_with_dot(mime_types.keys())
166+
cls.REGISTERED.update(mime_types)
167+
168+
@classmethod
169+
def configure(
170+
cls,
171+
default_to: str = None,
172+
keep_for: List[str] = None,
173+
register: Dict[str, str] = None,
174+
) -> None:
175+
"""
176+
Allows to globally configure the MIME types.
177+
178+
Example::
179+
180+
MIMETypes.configure(
181+
default_to="text/plain",
182+
keep_for=[".jpg", ".mp4", ".txt"],
183+
register={".foo": "text/foo", ".bar": "text/bar", ".baz": "text/baz"},
184+
)
185+
"""
186+
if default_to is not None:
187+
cls._default_to(default_to)
188+
if keep_for is not None:
189+
cls._keep_for(keep_for)
190+
if register is not None:
191+
cls._register(register)
192+
193+
@classmethod
194+
def get_for_filename(cls, filename: str, default: str = None) -> str:
195+
"""
196+
Return the MIME type for the given file name.
197+
198+
:param str filename: The file name to look up.
199+
"""
200+
if default is None:
201+
default = cls.DEFAULT
202+
203+
try:
204+
extension = filename.rsplit(".", 1)[-1].lower()
205+
return cls.REGISTERED.get(f".{extension}", default)
206+
except IndexError:
207+
return default

adafruit_httpserver/response.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
ParentDirectoryReferenceError,
2424
ResponseAlreadySentError,
2525
)
26-
from .mime_type import MIMEType
26+
from .mime_types import MIMETypes
2727
from .request import Request
2828
from .status import Status, OK_200
2929
from .headers import Headers
@@ -97,7 +97,7 @@ def route_func(request):
9797
Can be explicitly provided in the constructor, in ``send()`` or
9898
implicitly determined from filename in ``send_file()``.
9999
100-
Common MIME types are defined in `adafruit_httpserver.mime_type.MIMEType`.
100+
Common MIME types are defined in `adafruit_httpserver.mime_types`.
101101
"""
102102

103103
def __init__( # pylint: disable=too-many-arguments
@@ -146,7 +146,7 @@ def _send_headers(
146146
)
147147

148148
headers.setdefault(
149-
"Content-Type", content_type or self.content_type or MIMEType.TYPE_TXT
149+
"Content-Type", content_type or self.content_type or MIMETypes.DEFAULT
150150
)
151151
headers.setdefault("Connection", "close")
152152
if self.chunked:
@@ -244,7 +244,7 @@ def send_file( # pylint: disable=too-many-arguments
244244
file_length = self._get_file_length(full_file_path)
245245

246246
self._send_headers(
247-
content_type=MIMEType.from_file_name(filename),
247+
content_type=MIMETypes.get_for_filename(filename),
248248
content_length=file_length,
249249
)
250250

0 commit comments

Comments
 (0)