comparison src/server/server.py @ 1037:dc6c8c4d8ff6

server: redirection rework: - redirection parsing is now launched after LiberviaPages are imported, this way the pages are accessible and their methods are usable - inv_redirections attribute in LiberviaRootResource keep a map from new URL to old URL, allowing to check if a redirected path can lead to the same page - LiberviaPage methods are used to retrieve their URL - fixed remaining args when going to a redirected page - new checkRedirection method to see if a redirection can lead to the same URL
author Goffi <goffi@goffi.org>
date Wed, 24 Jan 2018 09:57:38 +0100
parents 34240d08f682
children 6b906b1f419a
comparison
equal deleted inserted replaced
1036:f5661761b1b9 1037:dc6c8c4d8ff6
105 """Specialized resource for Libervia root 105 """Specialized resource for Libervia root
106 106
107 handle redirections declared in sat.conf 107 handle redirections declared in sat.conf
108 """ 108 """
109 109
110 def __init__(self, options, *args, **kwargs): 110 def _initRedirections(self, options):
111 """
112 @param options(dict): configuration options, same as Libervia.options
113 """
114 super(LiberviaRootResource, self).__init__(*args, **kwargs)
115
116 ## redirections 111 ## redirections
117 self.redirections = {} 112 self.redirections = {}
113 self.inv_redirections = {} # new URL to old URL map
114
118 if options['url_redirections_dict'] and not options['url_redirections_profile']: 115 if options['url_redirections_dict'] and not options['url_redirections_profile']:
119 # FIXME: url_redirections_profile should not be needed. It is currently used to 116 # FIXME: url_redirections_profile should not be needed. It is currently used to
120 # redirect to an URL which associate the profile with the service, but this 117 # redirect to an URL which associate the profile with the service, but this
121 # is not clean, and service should be explicitly specified 118 # is not clean, and service should be explicitly specified
122 raise ValueError(u"url_redirections_profile need to be filled if you want to use url_redirections_dict") 119 raise ValueError(u"url_redirections_profile need to be filled if you want to use url_redirections_dict")
155 for k,v in new['query_args'].iteritems(): 152 for k,v in new['query_args'].iteritems():
156 if isinstance(v, basestring): 153 if isinstance(v, basestring):
157 new['query_args'][k] = [v] 154 new['query_args'][k] = [v]
158 elif 'path' in new_data: 155 elif 'path' in new_data:
159 new = 'file:{}'.format(urllib.quote(new_data['path'])) 156 new = 'file:{}'.format(urllib.quote(new_data['path']))
160 else: 157 elif isinstance(new_data, basestring):
161 new = new_data 158 new = new_data
162 new_data = {} 159 new_data = {}
160 else:
161 log.error(_(u"ignoring invalid redirection value: {new_data}").format(new_data=new_data))
162 continue
163 163
164 # some normalization 164 # some normalization
165 if not old.strip(): 165 if not old.strip():
166 # root URL special case 166 # root URL special case
167 old = '' 167 old = ''
168 elif not old.startswith('/'): 168 elif not old.startswith('/'):
169 raise ValueError(u"redirected url must start with '/', got {}".format(old)) 169 log.error(_(u"redirected url must start with '/', got {value}. Ignoring").format(value=old))
170 continue
170 else: 171 else:
171 old = self._normalizeURL(old) 172 old = self._normalizeURL(old)
172 173
173 if isinstance(new, dict): 174 if isinstance(new, dict):
174 # dict are handled differently, they contain data 175 # dict are handled differently, they contain data
175 # which are use dynamically when the request is done 176 # which ared use dynamically when the request is done
176 self.redirections[old] = new 177 self.redirections[old] = new
177 if not old: 178 if not old:
178 if new['type'] == 'page': 179 if new[u'type'] == u'page':
179 log.info(_(u"Root URL redirected to page {name}").format(name=new['page'])) 180 log.info(_(u"Root URL redirected to page {name}").format(name=new[u'page']))
181 else:
182 if new[u'type'] == u'page':
183 page = LiberviaPage.getPageByName(new[u'page'])
184 url = page.getURL(*new.get(u'path_args', []))
185 self.inv_redirections[url] = old
180 continue 186 continue
181 187
182 # at this point we have a rediction URL in new, we can parse it 188 # at this point we have a redirection URL in new, we can parse it
183 new_url = urlparse.urlsplit(new.encode('utf-8')) 189 new_url = urlparse.urlsplit(new.encode('utf-8'))
184 190
185 # we handle the known URL schemes 191 # we handle the known URL schemes
186 if new_url.scheme == 'xmpp': 192 if new_url.scheme == 'xmpp':
187 # XMPP URI 193 # XMPP URI
195 location = "/blog/{profile}/{item}".format( 201 location = "/blog/{profile}/{item}".format(
196 profile=quote(options['url_redirections_profile']), 202 profile=quote(options['url_redirections_profile']),
197 item = urllib.quote_plus(item), 203 item = urllib.quote_plus(item),
198 ).decode('utf-8') 204 ).decode('utf-8')
199 request_data = self._getRequestData(location) 205 request_data = self._getRequestData(location)
206 if old:
207 self.inv_redirections[location] = old
200 208
201 elif new_url.scheme in ('', 'http', 'https'): 209 elif new_url.scheme in ('', 'http', 'https'):
202 # direct redirection 210 # direct redirection
203 if new_url.netloc: 211 if new_url.netloc:
204 raise NotImplementedError(u"netloc ({netloc}) is not implemented yet for url_redirections_dict, it is not possible to redirect to an external website".format( 212 raise NotImplementedError(u"netloc ({netloc}) is not implemented yet for url_redirections_dict, it is not possible to redirect to an external website".format(
205 netloc = new_url.netloc)) 213 netloc = new_url.netloc))
206 location = urlparse.urlunsplit(('', '', new_url.path, new_url.query, new_url.fragment)).decode('utf-8') 214 location = urlparse.urlunsplit(('', '', new_url.path, new_url.query, new_url.fragment)).decode('utf-8')
207 request_data = self._getRequestData(location) 215 request_data = self._getRequestData(location)
216 if old:
217 self.inv_redirections[location] = old
208 218
209 elif new_url.scheme in ('file'): 219 elif new_url.scheme in ('file'):
210 # file or directory 220 # file or directory
211 if new_url.netloc: 221 if new_url.netloc:
212 raise NotImplementedError(u"netloc ({netloc}) is not implemented for url redirection to file system, it is not possible to redirect to an external host".format( 222 raise NotImplementedError(u"netloc ({netloc}) is not implemented for url redirection to file system, it is not possible to redirect to an external host".format(
222 resource = web_resource.NoResource() 232 resource = web_resource.NoResource()
223 current.putChild(segment, resource) 233 current.putChild(segment, resource)
224 current = resource 234 current = resource
225 resource_class = ProtectedFile if new_data.get('protected',True) else static.File 235 resource_class = ProtectedFile if new_data.get('protected',True) else static.File
226 current.putChild(last_segment, resource_class(path)) 236 current.putChild(last_segment, resource_class(path))
227 log.debug(u"Added redirection from /{old} to file system path {path}".format(old=old.decode('utf-8'), path=path.decode('utf-8'))) 237 log.info(u"Added redirection from /{old} to file system path {path}".format(old=old.decode('utf-8'), path=path.decode('utf-8')))
228 continue # we don't want to use redirection system, so we continue here 238 continue # we don't want to use redirection system, so we continue here
229 239
230 else: 240 else:
231 raise NotImplementedError(u"{scheme}: scheme is not managed for url_redirections_dict".format(scheme=new_url.scheme)) 241 raise NotImplementedError(u"{scheme}: scheme is not managed for url_redirections_dict".format(scheme=new_url.scheme))
232 242
295 else: 305 else:
296 try: 306 try:
297 dummy, uri, dummy, dummy = request_data 307 dummy, uri, dummy, dummy = request_data
298 except ValueError: 308 except ValueError:
299 uri = u'' 309 uri = u''
300 log.warning(D_(u"recursive redirection, please fix this URL:\n{old} ==> {new}").format( 310 log.error(D_(u"recursive redirection, please fix this URL:\n{old} ==> {new}").format(
301 old=request.uri.decode('utf-8'), 311 old=request.uri.decode('utf-8'),
302 new=uri.decode('utf-8'), 312 new=uri.decode('utf-8'),
303 )) 313 ))
304 return web_resource.NoResource() 314 return web_resource.NoResource()
305 315
329 log.debug(u"Redirecting URL {old} to {new}".format( 339 log.debug(u"Redirecting URL {old} to {new}".format(
330 old=request.uri.decode('utf-8'), 340 old=request.uri.decode('utf-8'),
331 new=uri.decode('utf-8'), 341 new=uri.decode('utf-8'),
332 )) 342 ))
333 # we change the request to reflect the new url 343 # we change the request to reflect the new url
334 request.postpath = path_list[1:] 344 request.postpath = path_list[1:] + request.postpath
335 request.uri = uri 345 request.uri = '/'.join([uri] + request.postpath)
336 request.path = path 346 request.path = '/'.join([path] + request.postpath)
337 request.args = args 347 request.args = args
338 348
339 # we start again to look for a child with the new url 349 # we start again to look for a child with the new url
340 return self.getChildWithDefault(path_list[0], request) 350 return self.getChildWithDefault(path_list[0], request)
341 351
1431 1441
1432 def _namespacesGetEb(self, failure_): 1442 def _namespacesGetEb(self, failure_):
1433 log.error(_(u"Can't get namespaces map: {msg}").format(msg=failure_)) 1443 log.error(_(u"Can't get namespaces map: {msg}").format(msg=failure_))
1434 1444
1435 def backendReady(self, dummy): 1445 def backendReady(self, dummy):
1436 self.root = root = LiberviaRootResource(self.options, self.html_dir) 1446 self.root = root = LiberviaRootResource(self.html_dir)
1437 _register = Register(self) 1447 _register = Register(self)
1438 _upload_radiocol = UploadManagerRadioCol(self) 1448 _upload_radiocol = UploadManagerRadioCol(self)
1439 _upload_avatar = UploadManagerAvatar(self) 1449 _upload_avatar = UploadManagerAvatar(self)
1440 d = self.bridgeCall('namespacesGet') 1450 d = self.bridgeCall('namespacesGet')
1441 d.addCallback(self._namespacesGetCb) 1451 d.addCallback(self._namespacesGetCb)
1499 self.putChild('radiocol', ProtectedFile(_upload_radiocol.getTmpDir(), defaultType="audio/ogg")) # FIXME: We cheat for PoC because we know we are on the same host, so we use directly upload dir 1509 self.putChild('radiocol', ProtectedFile(_upload_radiocol.getTmpDir(), defaultType="audio/ogg")) # FIXME: We cheat for PoC because we know we are on the same host, so we use directly upload dir
1500 # pyjamas tests, redirected only for dev versions 1510 # pyjamas tests, redirected only for dev versions
1501 if self.version[-1] == 'D': 1511 if self.version[-1] == 'D':
1502 self.putChild('test', web_util.Redirect('/libervia_test.html')) 1512 self.putChild('test', web_util.Redirect('/libervia_test.html'))
1503 1513
1514 # redirections
1515 root._initRedirections(self.options)
1504 1516
1505 server.Request.defaultContentType = 'text/html; charset=utf-8' 1517 server.Request.defaultContentType = 'text/html; charset=utf-8'
1506 wrapped = web_resource.EncodingResourceWrapper(root, [server.GzipEncoderFactory()]) 1518 wrapped = web_resource.EncodingResourceWrapper(root, [server.GzipEncoderFactory()])
1507 self.site = server.Site(wrapped) 1519 self.site = server.Site(wrapped)
1508 self.site.sessionFactory = LiberviaSession 1520 self.site.sessionFactory = LiberviaSession
1867 split_result.scheme.decode('utf-8') if scheme is None else scheme, 1879 split_result.scheme.decode('utf-8') if scheme is None else scheme,
1868 split_result.netloc.decode('utf-8'), 1880 split_result.netloc.decode('utf-8'),
1869 os.path.join(split_result.path, path), 1881 os.path.join(split_result.path, path),
1870 query, fragment)) 1882 query, fragment))
1871 1883
1884 def checkRedirection(self, url):
1885 """check is a part of the URL prefix is redirected then replace it
1886
1887 @param url(unicode): url to check
1888 @return (unicode): possibly redirected URL which should link to the same location
1889 """
1890 inv_redirections = self.root.inv_redirections
1891 url_parts = url.strip(u'/').split(u'/')
1892 for idx in xrange(len(url), 0, -1):
1893 test_url = u'/' + u'/'.join(url_parts[:idx])
1894 if test_url in inv_redirections:
1895 rem_url = url_parts[idx:]
1896 return u'/'.join([inv_redirections[test_url]] + rem_url)
1897 return url
1872 1898
1873 ## Sessions ## 1899 ## Sessions ##
1874 1900
1875 def purgeSession(self, request): 1901 def purgeSession(self, request):
1876 """helper method to purge a session during request handling""" 1902 """helper method to purge a session during request handling"""