]> git.ipfire.org Git - pbs.git/blob - src/web/mirrors.py
builds: Load all builds with the group
[pbs.git] / src / web / mirrors.py
1 #!/usr/bin/python
2
3 import datetime
4 import ipaddress
5 import logging
6 import stat
7 import tornado.web
8
9 from . import base
10
11 # Setup logging
12 log = logging.getLogger("pbs.web.mirrors")
13
14 class IndexHandler(base.BaseHandler):
15 async def get(self):
16 await self.render("mirrors/index.html", mirrors=self.backend.mirrors)
17
18
19 class ShowHandler(base.BaseHandler):
20 async def get(self, hostname):
21 mirror = await self.backend.mirrors.get_by_hostname(hostname)
22 if not mirror:
23 raise tornado.web.HTTPError(404, "Could not find mirror %s" % hostname)
24
25 await self.render("mirrors/show.html", mirror=mirror)
26
27
28 class CheckHandler(base.BaseHandler):
29 @base.authenticated
30 async def post(self, hostname):
31 mirror = await self.backend.mirrors.get_by_hostname(hostname)
32 if not mirror:
33 raise tornado.web.HTTPError(404, "Could not find mirror %s" % hostname)
34
35 # Check permissions
36 if not mirror.has_perm(self.current_user):
37 raise tornado.web.HTTPError(403, "%s has no permission for %s" \
38 % (self.current_user, mirror))
39
40 # Run the check
41 await mirror.check(force=True)
42
43 # Redirect back to the mirror
44 self.redirect("/mirrors/%s" % mirror.hostname)
45
46
47 class CreateHandler(base.AdminHandler):
48 @base.authenticated
49 async def get(self):
50 await self.render("mirrors/edit.html", mirror=None)
51
52 @base.authenticated
53 async def post(self):
54 # Create the mirror
55 mirror = await self.backend.mirrors.create(
56 hostname = self.get_argument("hostname"),
57 path = self.get_argument("path"),
58 owner = self.get_argument("owner"),
59 contact = self.get_argument("contact"),
60 notes = self.get_argument("notes", ""),
61 user = self.current_user,
62 )
63
64 # Redirect the user back
65 self.redirect("/mirrors/%s" % mirror.hostname)
66
67
68 class EditHandler(base.BaseHandler):
69 @base.authenticated
70 async def get(self, hostname):
71 mirror = await self.backend.mirrors.get_by_hostname(hostname)
72 if not mirror:
73 raise tornado.web.HTTPError(404, "Could not find mirror %s" % hostname)
74
75 # Check permissions
76 if not mirror.has_perm(self.current_user):
77 raise tornado.web.HTTPError(403)
78
79 await self.render("mirrors/edit.html", mirror=mirror)
80
81 @base.authenticated
82 async def post(self, hostname):
83 mirror = await self.backend.mirrors.get_by_hostname(hostname)
84 if not mirror:
85 raise tornado.web.HTTPError(404, "Could not find mirror %s" % hostname)
86
87 # Check permissions
88 if not mirror.has_perm(self.current_user):
89 raise tornado.web.HTTPError(403)
90
91 # Store new values
92 mirror.owner = self.get_argument("owner")
93 mirror.contact = self.get_argument("contact")
94 mirror.notes = self.get_argument("notes", None)
95
96 self.redirect("/mirrors/%s" % mirror.hostname)
97
98
99 class DeleteHandler(base.BaseHandler):
100 @base.authenticated
101 async def get(self, hostname):
102 mirror = await self.backend.mirrors.get_by_hostname(hostname)
103 if not mirror:
104 raise tornado.web.HTTPError(404, "Could not find mirror %s" % hostname)
105
106 # Check permissions
107 if not mirror.has_perm(self.current_user):
108 raise tornado.web.HTTPError(403)
109
110 await self.render("mirrors/delete.html", mirror=mirror)
111
112 @base.authenticated
113 async def post(self, hostname):
114 mirror = await self.backend.mirrors.get_by_hostname(hostname)
115 if not mirror:
116 raise tornado.web.HTTPError(404, "Could not find mirror %s" % hostname)
117
118 # Check permissions
119 if not mirror.has_perm(self.current_user):
120 raise tornado.web.HTTPError(403)
121
122 # Delete the mirror
123 await mirror.delete(deleted_by=self.current_user)
124
125 # Redirect back to all mirrors
126 self.redirect("/mirrors")
127
128
129 class DownloadsHandler(base.BaseHandler):
130 """
131 A universal download redirector which does not need to keep any state.
132
133 It will check if the mirror serves the file and redirect the client. We are
134 starting with the closest mirror and walk through all of the until we have
135 found the right file.
136
137 As a last resort, we will try to serve the file locally.
138 """
139 def prepare(self):
140 # Don't send any Content-Type header
141 self.clear_header("Content-Type")
142
143 @base.ratelimit(limit=100, minutes=60, key="downloads")
144 async def get(self, path):
145 # Check if the file exists
146 if not await self.backend.stat(path, stat.S_IFREG):
147 raise tornado.web.HTTPError(404)
148
149 # Tell the clients to never cache the redirect
150 self.set_header("Cache-Control", "no-store")
151
152 # Fetch all mirrors for this client
153 mirrors = await self.backend.mirrors.get_mirrors_for_address(self.current_address)
154
155 # Walk through all mirrors
156 for mirror in mirrors:
157 # Don't send clients to a mirror they don't support
158 if isinstance(self.current_address, ipaddress.IPv6Address):
159 if not mirror.supports_ipv6():
160 continue
161 elif isinstance(self.current_address, ipaddress.IPv4Address):
162 if not mirror.supports_ipv4():
163 continue
164
165 # Skip the mirror if it does not serve the file we are looking for
166 if not await mirror.serves_file(path):
167 continue
168
169 # Log action
170 log.info("Sending %s to download %s from %s", self.current_address, path, mirror)
171
172 # Make the URL
173 url = mirror.make_url(path)
174
175 # Redirect the user
176 return self.redirect(url)
177
178 # If we go here, we did not find any working mirror.
179 # We will send the user to our local file store and hopefully the right
180 # file will be there. If not, the client will receive 404 from there.
181 url = self.backend.path_to_url(path, mirrored=False)
182
183 self.redirect(url)
184
185 @base.ratelimit(limit=100, minutes=60, key="downloads")
186 async def head(self, path):
187 # Stat the file
188 s = await self.backend.stat(path, stat.S_IFREG)
189
190 # Send 404 if the file does not exist
191 if not s:
192 raise tornado.web.HTTPError(404)
193
194 # Fetch the MIME type
195 mimetype = await self.backend.mimetype(path)
196
197 # Send a couple of headers
198 self.set_header("Content-Type", mimetype or "application/octet-stream")
199 self.set_header("Content-Length", s.st_size)
200 self.set_header("Last-Modified", datetime.datetime.fromtimestamp(s.st_mtime))
201 self.set_header("Etag", "%x-%x" % (int(s.st_mtime), s.st_size))
202
203 async def write_error(self, *args, **kwargs):
204 """
205 Don't send any body in error responses
206 """
207 pass