]>
Commit | Line | Data |
---|---|---|
940227cb MT |
1 | #!/usr/bin/python |
2 | ||
cc3b928d | 3 | import datetime |
66862195 | 4 | import dateutil.parser |
cfe7d74c | 5 | import functools |
11347e46 | 6 | import http.client |
a69e87a1 | 7 | import ipaddress |
47ed77ed | 8 | import logging |
940227cb MT |
9 | import time |
10 | import tornado.locale | |
11 | import tornado.web | |
12 | ||
f110a9ff | 13 | from ..decorators import * |
a95c2f97 | 14 | from .. import util |
60024cc8 | 15 | |
372ef119 MT |
16 | class ratelimit(object): |
17 | def __init__(self, minutes=15, requests=180): | |
18 | self.minutes = minutes | |
19 | self.requests = requests | |
20 | ||
21 | def __call__(self, method): | |
22 | @functools.wraps(method) | |
23 | def wrapper(handler, *args, **kwargs): | |
24 | # Pass the request to the rate limiter and get a request object | |
25 | req = handler.backend.ratelimiter.handle_request(handler.request, | |
26 | handler, minutes=self.minutes, limit=self.requests) | |
27 | ||
28 | # If the rate limit has been reached, we won't allow | |
29 | # processing the request and therefore send HTTP error code 429. | |
30 | if req.is_ratelimited(): | |
31 | raise tornado.web.HTTPError(429, "Rate limit exceeded") | |
32 | ||
33 | return method(handler, *args, **kwargs) | |
34 | ||
35 | return wrapper | |
36 | ||
cfe7d74c | 37 | |
940227cb | 38 | class BaseHandler(tornado.web.RequestHandler): |
90199689 MT |
39 | def prepare(self): |
40 | # Mark this as private when someone is logged in | |
41 | if self.current_user: | |
42 | self.set_header("Cache-Control", "private") | |
43 | ||
44 | # Always send Vary: Cookie | |
45 | self.set_header("Vary", "Cookie") | |
46 | ||
f6ed3d4d MT |
47 | def set_expires(self, seconds): |
48 | # For HTTP/1.1 | |
b592d7c8 | 49 | self.add_header("Cache-Control", "max-age=%s, must-revalidate" % seconds) |
f6ed3d4d MT |
50 | |
51 | # For HTTP/1.0 | |
52 | expires = datetime.datetime.utcnow() + datetime.timedelta(seconds=seconds) | |
90199689 | 53 | self.set_header("Expires", expires) |
f6ed3d4d | 54 | |
37ed7c3c MT |
55 | def write_error(self, status_code, **kwargs): |
56 | # Translate code into message | |
57 | try: | |
11347e46 | 58 | message = http.client.responses[status_code] |
37ed7c3c MT |
59 | except KeyError: |
60 | message = None | |
61 | ||
62 | self.render("error.html", status_code=status_code, message=message, **kwargs) | |
63 | ||
4ca1a601 MT |
64 | @property |
65 | def hostname(self): | |
242d11d5 MT |
66 | # Return hostname in production |
67 | if self.request.host.endswith("ipfire.org"): | |
68 | return self.request.host | |
69 | ||
4ca1a601 | 70 | # Remove the development prefix |
242d11d5 MT |
71 | subdomain, delimier, domain = self.request.host.partition(".") |
72 | if subdomain: | |
73 | return "%s.ipfire.org" % subdomain | |
74 | ||
75 | # Return whatever it is | |
76 | return self.request.host | |
4ca1a601 | 77 | |
bff08cb0 MT |
78 | def get_template_namespace(self): |
79 | ns = tornado.web.RequestHandler.get_template_namespace(self) | |
80 | ||
911064cf | 81 | now = datetime.date.today() |
cc3b928d | 82 | |
bff08cb0 | 83 | ns.update({ |
6c13ca2d | 84 | "backend" : self.backend, |
5613b94b | 85 | "debug" : self.application.settings.get("debug", False), |
a95c2f97 MT |
86 | "format_size" : util.format_size, |
87 | "format_time" : util.format_time, | |
4ca1a601 | 88 | "hostname" : self.hostname, |
911064cf MT |
89 | "now" : now, |
90 | "year" : now.year, | |
bff08cb0 | 91 | }) |
940227cb | 92 | |
bff08cb0 | 93 | return ns |
940227cb | 94 | |
9068dba1 MT |
95 | def get_remote_ip(self): |
96 | # Fix for clients behind a proxy that sends "X-Forwarded-For". | |
66862195 | 97 | remote_ips = self.request.remote_ip.split(", ") |
494d80e6 | 98 | |
66862195 MT |
99 | for remote_ip in remote_ips: |
100 | try: | |
a69e87a1 | 101 | addr = ipaddress.ip_address(remote_ip) |
66862195 MT |
102 | except ValueError: |
103 | # Skip invalid IP addresses. | |
104 | continue | |
9068dba1 | 105 | |
66862195 MT |
106 | # Check if the given IP address is from a |
107 | # private network. | |
108 | if addr.is_private: | |
109 | continue | |
9068dba1 | 110 | |
66862195 | 111 | return remote_ip |
9068dba1 | 112 | |
494d80e6 MT |
113 | # Return the last IP if nothing else worked |
114 | return remote_ips.pop() | |
115 | ||
cfe7d74c | 116 | @lazy_property |
440aba92 | 117 | def current_address(self): |
cfe7d74c MT |
118 | address = self.get_remote_ip() |
119 | ||
120 | if address: | |
440aba92 | 121 | return util.Address(self.backend, address) |
cfe7d74c | 122 | |
f110a9ff MT |
123 | @lazy_property |
124 | def current_country_code(self): | |
440aba92 MT |
125 | if self.current_address: |
126 | return self.current_address.country_code | |
9068dba1 | 127 | |
bd2723d4 MT |
128 | def get_argument_int(self, *args, **kwargs): |
129 | arg = self.get_argument(*args, **kwargs) | |
130 | ||
131 | if arg is None or arg == "": | |
132 | return | |
133 | ||
134 | try: | |
135 | return int(arg) | |
136 | except ValueError: | |
a5f94966 MT |
137 | raise tornado.web.HTTPError(400, "Could not convert integer: %s" % arg) |
138 | ||
139 | def get_argument_float(self, *args, **kwargs): | |
140 | arg = self.get_argument(*args, **kwargs) | |
141 | ||
142 | if arg is None or arg == "": | |
143 | return | |
144 | ||
145 | try: | |
146 | return float(arg) | |
147 | except ValueError: | |
148 | raise tornado.web.HTTPError(400, "Could not convert float: %s" % arg) | |
bd2723d4 | 149 | |
66862195 MT |
150 | def get_argument_date(self, arg, *args, **kwargs): |
151 | value = self.get_argument(arg, *args, **kwargs) | |
152 | if value is None: | |
153 | return | |
154 | ||
155 | try: | |
156 | return dateutil.parser.parse(value) | |
157 | except ValueError: | |
158 | raise tornado.web.HTTPError(400) | |
159 | ||
5cc10421 MT |
160 | def get_file(self, name): |
161 | try: | |
162 | file = self.request.files[name][0] | |
163 | ||
164 | return file["filename"], file["body"], file["content_type"] | |
165 | except KeyError: | |
166 | return None | |
167 | ||
66862195 MT |
168 | # Login stuff |
169 | ||
170 | def get_current_user(self): | |
171 | session_id = self.get_cookie("session_id") | |
172 | if not session_id: | |
173 | return | |
174 | ||
175 | # Get account from the session object | |
176 | account = self.backend.accounts.get_by_session(session_id, self.request.host) | |
177 | ||
178 | # If the account was not found or the session was not valid | |
179 | # any more, we will remove the cookie. | |
180 | if not account: | |
181 | self.clear_cookie("session_id") | |
182 | ||
183 | return account | |
184 | ||
a6dc0bad MT |
185 | @property |
186 | def backend(self): | |
187 | return self.application.backend | |
188 | ||
9068dba1 MT |
189 | @property |
190 | def db(self): | |
191 | return self.backend.db | |
192 | ||
940227cb MT |
193 | @property |
194 | def accounts(self): | |
a6dc0bad | 195 | return self.backend.accounts |
940227cb MT |
196 | |
197 | @property | |
9068dba1 MT |
198 | def downloads(self): |
199 | return self.backend.downloads | |
200 | ||
66862195 MT |
201 | @property |
202 | def fireinfo(self): | |
203 | return self.backend.fireinfo | |
204 | ||
9068dba1 MT |
205 | @property |
206 | def iuse(self): | |
207 | return self.backend.iuse | |
940227cb | 208 | |
e28b082e MT |
209 | @property |
210 | def memcached(self): | |
211 | return self.backend.memcache | |
212 | ||
940227cb MT |
213 | @property |
214 | def mirrors(self): | |
9068dba1 MT |
215 | return self.backend.mirrors |
216 | ||
217 | @property | |
218 | def netboot(self): | |
219 | return self.backend.netboot | |
940227cb | 220 | |
940227cb MT |
221 | @property |
222 | def releases(self): | |
9068dba1 | 223 | return self.backend.releases |
940227cb | 224 | |
66862195 | 225 | |
689effd0 MT |
226 | class APIHandler(BaseHandler): |
227 | def check_xsrf_cookie(self): | |
228 | """ | |
229 | Do nothing here, because we cannot verify the XSRF token | |
230 | """ | |
231 | pass | |
232 | ||
233 | ||
3403dc5e MT |
234 | class NotFoundHandler(BaseHandler): |
235 | def prepare(self): | |
236 | # Raises 404 as soon as it is called | |
237 | raise tornado.web.HTTPError(404) | |
238 | ||
3403dc5e | 239 | |
37ed7c3c MT |
240 | class ErrorHandler(BaseHandler): |
241 | """ | |
242 | Raises any error we want | |
243 | """ | |
244 | def get(self, code): | |
245 | try: | |
246 | code = int(code) | |
247 | except: | |
248 | raise tornado.web.HTTPError(400) | |
249 | ||
250 | raise tornado.web.HTTPError(code) |