|
17 | 17 | from readthedocs.builds.models import Version
|
18 | 18 | from readthedocs.core.mixins import CDNCacheControlMixin
|
19 | 19 | from readthedocs.core.resolver import resolve_path
|
| 20 | +from readthedocs.core.unresolver import ( |
| 21 | + InvalidExternalVersionError, |
| 22 | + TranslationNotFoundError, |
| 23 | + UnresolvedPathError, |
| 24 | + VersionNotFoundError, |
| 25 | + unresolver, |
| 26 | +) |
20 | 27 | from readthedocs.core.utils.extend import SettingsOverrideObject
|
21 | 28 | from readthedocs.projects import constants
|
22 | 29 | from readthedocs.projects.models import Domain, Feature
|
@@ -82,13 +89,17 @@ def get(self,
|
82 | 89 | lang_slug=None,
|
83 | 90 | version_slug=None,
|
84 | 91 | filename='',
|
85 |
| - ): # noqa |
| 92 | + ): |
86 | 93 | """
|
87 | 94 | Take the incoming parsed URL's and figure out what file to serve.
|
88 | 95 |
|
89 | 96 | ``subproject_slash`` is used to determine if the subproject URL has a slash,
|
90 | 97 | so that we can decide if we need to serve docs or add a /.
|
91 | 98 | """
|
| 99 | + unresolved_domain = request.unresolved_domain |
| 100 | + if unresolved_domain.project.has_feature(Feature.USE_UNRESOLVER_WITH_PROXITO): |
| 101 | + return self.get_using_unresolver(request) |
| 102 | + |
92 | 103 | version_slug = self.get_version_from_host(request, version_slug)
|
93 | 104 | final_project, lang_slug, version_slug, filename = _get_project_data_from_request( # noqa
|
94 | 105 | request,
|
@@ -274,6 +285,125 @@ def _get_canonical_redirect_type(self, request):
|
274 | 285 |
|
275 | 286 | return None
|
276 | 287 |
|
| 288 | + def get_using_unresolver(self, request): |
| 289 | + unresolved_domain = request.unresolved_domain |
| 290 | + # TODO: capture this path in the URL. |
| 291 | + path = request.path_info |
| 292 | + try: |
| 293 | + unresolved = unresolver.unresolve_path( |
| 294 | + unresolved_domain=unresolved_domain, |
| 295 | + path=path, |
| 296 | + append_indexhtml=False, |
| 297 | + ) |
| 298 | + except (VersionNotFoundError, TranslationNotFoundError): |
| 299 | + raise Http404 |
| 300 | + except InvalidExternalVersionError: |
| 301 | + raise Http404 |
| 302 | + except UnresolvedPathError as exc: |
| 303 | + # This exception happends if the project didn't have an explict version, |
| 304 | + # this is a ``/ -> /en/latest`` or |
| 305 | + # ``/projects/subproject/ -> /projects/subproject/en/latest/`` redirect. |
| 306 | + project = exc.project |
| 307 | + # A system redirect can be cached if we don't have information |
| 308 | + # about the version, since the final URL will check for authz. |
| 309 | + if self._is_cache_enabled(project): |
| 310 | + self.cache_request = True |
| 311 | + |
| 312 | + if unresolved_domain.is_from_external_domain: |
| 313 | + version_slug = unresolved_domain.external_version_slug |
| 314 | + else: |
| 315 | + version_slug = None |
| 316 | + |
| 317 | + return self.system_redirect( |
| 318 | + request=request, |
| 319 | + final_project=project, |
| 320 | + version_slug=version_slug, |
| 321 | + filename=exc.path, |
| 322 | + is_external_version=unresolved_domain.is_from_external_domain, |
| 323 | + ) |
| 324 | + |
| 325 | + project = unresolved.project |
| 326 | + version = unresolved.version |
| 327 | + filename = unresolved.filename |
| 328 | + |
| 329 | + if not version.active: |
| 330 | + log.warning("Version is not active.") |
| 331 | + raise Http404("Version is not active.") |
| 332 | + |
| 333 | + if self._is_cache_enabled(project) and version and not version.is_private: |
| 334 | + # All public versions can be cached. |
| 335 | + self.cache_request = True |
| 336 | + |
| 337 | + log.bind(cache_request=self.cache_request) |
| 338 | + log.debug("Serving docs.") |
| 339 | + |
| 340 | + # Verify if the project is marked as spam and return a 401 in that case |
| 341 | + spam_response = self._spam_response(request, project) |
| 342 | + if spam_response: |
| 343 | + return spam_response |
| 344 | + |
| 345 | + # Handle requests that need canonicalizing (eg. HTTP -> HTTPS, redirect to canonical domain) |
| 346 | + redirect_type = self._get_canonical_redirect_type(request) |
| 347 | + if redirect_type: |
| 348 | + # A canonical redirect can be cached, if we don't have information |
| 349 | + # about the version, since the final URL will check for authz. |
| 350 | + if not version and self._is_cache_enabled(project): |
| 351 | + self.cache_request = True |
| 352 | + try: |
| 353 | + return self.canonical_redirect( |
| 354 | + request=request, |
| 355 | + final_project=project, |
| 356 | + version_slug=version.slug, |
| 357 | + filename=filename, |
| 358 | + redirect_type=redirect_type, |
| 359 | + is_external_version=unresolved.external, |
| 360 | + ) |
| 361 | + except InfiniteRedirectException: |
| 362 | + # Don't redirect in this case, since it would break things |
| 363 | + pass |
| 364 | + |
| 365 | + # Trailing slash redirect. |
| 366 | + if filename == "/" and not path.endswith("/"): |
| 367 | + return self.system_redirect( |
| 368 | + request=request, |
| 369 | + final_project=project, |
| 370 | + version_slug=version.slug, |
| 371 | + filename=filename, |
| 372 | + is_external_version=unresolved_domain.is_from_external_domain, |
| 373 | + ) |
| 374 | + |
| 375 | + redirect_path, http_status = self.get_redirect( |
| 376 | + project=project, |
| 377 | + lang_slug=project.language, |
| 378 | + version_slug=version.slug, |
| 379 | + filename=unresolved.filename, |
| 380 | + full_path=request.path, |
| 381 | + forced_only=True, |
| 382 | + ) |
| 383 | + if redirect_path and http_status: |
| 384 | + log.bind(forced_redirect=True) |
| 385 | + try: |
| 386 | + return self.get_redirect_response( |
| 387 | + request=request, |
| 388 | + redirect_path=redirect_path, |
| 389 | + proxito_path=request.path, |
| 390 | + http_status=http_status, |
| 391 | + ) |
| 392 | + except InfiniteRedirectException: |
| 393 | + # Continue with our normal serve. |
| 394 | + pass |
| 395 | + |
| 396 | + # Check user permissions and return an unauthed response if needed |
| 397 | + if not self.allowed_user(request, project, version.slug): |
| 398 | + return self.get_unauthed_response(request, project) |
| 399 | + |
| 400 | + return self._serve_docs( |
| 401 | + request=request, |
| 402 | + project=project, |
| 403 | + version=version, |
| 404 | + filename=unresolved.filename, |
| 405 | + ) |
| 406 | + |
277 | 407 |
|
278 | 408 | class ServeDocs(SettingsOverrideObject):
|
279 | 409 | _default_class = ServeDocsBase
|
|
0 commit comments