]>
git.ipfire.org Git - ipfire.org.git/blob - src/web/base.py
16 from ..decorators
import *
19 class ratelimit(object):
21 A decorator class which limits how often a function can be called
23 def __init__(self
, *, minutes
, requests
):
24 self
.minutes
= minutes
25 self
.requests
= requests
27 def __call__(self
, method
):
28 @functools.wraps(method
)
29 async def wrapper(handler
, *args
, **kwargs
):
30 # Pass the request to the rate limiter and get a request object
31 req
= handler
.backend
.ratelimiter
.handle_request(handler
.request
,
32 handler
, minutes
=self
.minutes
, limit
=self
.requests
)
34 # If the rate limit has been reached, we won't allow
35 # processing the request and therefore send HTTP error code 429.
36 if await req
.is_ratelimited():
37 raise tornado
.web
.HTTPError(429, "Rate limit exceeded")
39 # Call the wrapped method
40 result
= method(handler
, *args
, **kwargs
)
42 # Await it if it is a coroutine
43 if asyncio
.iscoroutine(result
):
52 class BaseHandler(tornado
.web
.RequestHandler
):
54 # Mark this as private when someone is logged in
56 self
.set_header("Cache-Control", "private")
58 # Always send Vary: Cookie
59 self
.set_header("Vary", "Cookie")
61 def set_expires(self
, seconds
):
63 self
.add_header("Cache-Control", "max-age=%s, must-revalidate" % seconds
)
66 expires
= datetime
.datetime
.utcnow() + datetime
.timedelta(seconds
=seconds
)
67 self
.set_header("Expires", expires
)
69 def write_error(self
, status_code
, **kwargs
):
70 # Translate code into message
72 message
= http
.client
.responses
[status_code
]
76 self
.render("error.html", status_code
=status_code
, message
=message
, **kwargs
)
78 def browser_accepts(self
, t
):
80 Checks if type is in Accept: header
84 for elem
in self
.request
.headers
.get("Accept", "").split(","):
86 type, delim
, q
= elem
.partition(";")
90 # Check if the filetype is in the list of accepted ones
95 # Return hostname in production
96 if self
.request
.host
.endswith("ipfire.org"):
97 return self
.request
.host
99 # Remove the development prefix
100 subdomain
, delimier
, domain
= self
.request
.host
.partition(".")
102 return "%s.ipfire.org" % subdomain
104 # Return whatever it is
105 return self
.request
.host
107 def get_template_namespace(self
):
108 ns
= tornado
.web
.RequestHandler
.get_template_namespace(self
)
110 now
= datetime
.date
.today()
113 "backend" : self
.backend
,
114 "debug" : self
.application
.settings
.get("debug", False),
115 "format_size" : util
.format_size
,
116 "format_time" : util
.format_time
,
117 "hostname" : self
.hostname
,
125 def get_remote_ip(self
):
126 # Fix for clients behind a proxy that sends "X-Forwarded-For".
127 remote_ips
= self
.request
.remote_ip
.split(", ")
129 for remote_ip
in remote_ips
:
131 addr
= ipaddress
.ip_address(remote_ip
)
133 # Skip invalid IP addresses.
136 # Check if the given IP address is from a
143 # Return the last IP if nothing else worked
144 return remote_ips
.pop()
147 def current_address(self
):
148 address
= self
.get_remote_ip()
151 return util
.Address(self
.backend
, address
)
154 def current_country_code(self
):
155 if self
.current_address
:
156 return self
.current_address
.country_code
158 def get_argument_int(self
, *args
, **kwargs
):
159 arg
= self
.get_argument(*args
, **kwargs
)
161 if arg
is None or arg
== "":
167 raise tornado
.web
.HTTPError(400, "Could not convert integer: %s" % arg
)
169 def get_argument_float(self
, *args
, **kwargs
):
170 arg
= self
.get_argument(*args
, **kwargs
)
172 if arg
is None or arg
== "":
178 raise tornado
.web
.HTTPError(400, "Could not convert float: %s" % arg
)
180 def get_argument_date(self
, arg
, *args
, **kwargs
):
181 value
= self
.get_argument(arg
, *args
, **kwargs
)
186 return dateutil
.parser
.parse(value
)
188 raise tornado
.web
.HTTPError(400)
190 def get_file(self
, name
):
192 file = self
.request
.files
[name
][0]
194 return file["filename"], file["body"], file["content_type"]
198 # Initialize libmagic
199 magic
= magic
.Magic(mime
=True, uncompress
=True)
203 def _deliver_file(self
, data
, filename
=None, prefix
=None):
205 mimetype
= self
.magic
.from_buffer(data
)
208 self
.set_header("Content-Type", mimetype
or "application/octet-stream")
210 # Fetch the file extension
211 if not filename
and prefix
:
212 ext
= mimetypes
.guess_extension(mimetype
)
214 # Compose a new filename
215 filename
= "%s%s" % (prefix
, ext
)
219 self
.set_header("Content-Disposition", "inline; filename=\"%s\"" % filename
)
223 self
.set_header("Content-Length", len(data
))
230 def get_current_user(self
):
231 session_id
= self
.get_cookie("session_id")
235 # Get account from the session object
236 account
= self
.backend
.accounts
.get_by_session(session_id
, self
.request
.host
)
238 # If the account was not found or the session was not valid
239 # any more, we will remove the cookie.
241 self
.clear_cookie("session_id")
247 return self
.application
.backend
251 return self
.backend
.db
255 return self
.backend
.accounts
259 return self
.backend
.downloads
263 return self
.backend
.fireinfo
267 return self
.backend
.iuse
271 return self
.backend
.mirrors
275 return self
.backend
.netboot
279 return self
.backend
.releases
282 class APIHandler(BaseHandler
):
283 def check_xsrf_cookie(self
):
285 Do nothing here, because we cannot verify the XSRF token
290 # Do not cache any API communication
291 self
.set_header("Cache-Control", "no-cache")
294 class NotFoundHandler(BaseHandler
):
296 # Raises 404 as soon as it is called
297 raise tornado
.web
.HTTPError(404)
300 class ErrorHandler(BaseHandler
):
302 Raises any error we want
308 raise tornado
.web
.HTTPError(400)
310 raise tornado
.web
.HTTPError(code
)