10
10
from __future__ import division
11
11
import os
12
12
import logging
13
+ from urllib .parse import urlparse
13
14
14
15
from django .conf import settings
15
16
from django .http import HttpResponseRedirect , Http404 , JsonResponse
16
17
from django .shortcuts import render , get_object_or_404 , redirect
17
18
from django .views .generic import TemplateView
18
19
20
+
19
21
from readthedocs .builds .models import Version
22
+ from readthedocs .core .resolver import resolve_path
23
+ from readthedocs .core .symlink import PrivateSymlink , PublicSymlink
20
24
from readthedocs .core .utils import broadcast
25
+ from readthedocs .core .views .serve import _serve_file
26
+ from readthedocs .projects .constants import PRIVATE
21
27
from readthedocs .projects .models import Project , ImportedFile
22
28
from readthedocs .projects .tasks import remove_dirs
23
- from readthedocs .redirects .utils import get_redirect_response
29
+ from readthedocs .redirects .utils import get_redirect_response , project_and_path_from_request , language_and_version_from_path
24
30
25
31
log = logging .getLogger (__name__ )
26
32
@@ -115,6 +121,7 @@ def server_error_404(request, exception=None, template_name='404.html'): # pyli
115
121
"""
116
122
response = get_redirect_response (request , full_path = request .get_full_path ())
117
123
124
+ # Return a redirect response if there is one
118
125
if response :
119
126
if response .url == request .build_absolute_uri ():
120
127
# check that we do have a response and avoid infinite redirect
@@ -124,6 +131,88 @@ def server_error_404(request, exception=None, template_name='404.html'): # pyli
124
131
)
125
132
else :
126
133
return response
134
+
135
+ # Try to serve custom 404 pages if it's a subdomain/cname
136
+ if getattr (request , 'subdomain' , False ) or getattr (request , 'cname' , False ):
137
+ return server_error_404_subdomain (request , template_name )
138
+
139
+ # Return the default 404 page generated by Read the Docs
140
+ r = render (request , template_name )
141
+ r .status_code = 404
142
+ return r
143
+
144
+
145
+ def server_error_404_subdomain (request , template_name = '404.html' ):
146
+ """
147
+ Handler for 404 pages on subdomains.
148
+
149
+ Check if the project associated has a custom ``404.html`` and serve this
150
+ page. First search for a 404 page in the current version, then continues
151
+ with the default version and finally, if none of them are found, the Read
152
+ the Docs default page (Maze Found) is rendered by Django and served.
153
+ """
154
+
155
+ def resolve_404_path (project , version_slug = None , language = None ):
156
+ """
157
+ Helper to resolve the path of ``404.html`` for project.
158
+
159
+ The resolution is based on ``project`` object, version slug and
160
+ language.
161
+
162
+ :returns: tuple containing the (basepath, filename)
163
+ :rtype: tuple
164
+ """
165
+ filename = resolve_path (
166
+ project ,
167
+ version_slug = version_slug ,
168
+ language = language ,
169
+ filename = '404.html' ,
170
+ subdomain = True , # subdomain will make it a "full" path without a URL prefix
171
+ )
172
+
173
+ # This breaks path joining, by ignoring the root when given an "absolute" path
174
+ if filename [0 ] == '/' :
175
+ filename = filename [1 :]
176
+
177
+ version = None
178
+ if version_slug :
179
+ version = project .versions .get (slug = version_slug )
180
+
181
+ private = any ([
182
+ version and version .privacy_level == PRIVATE ,
183
+ not version and project .privacy_level == PRIVATE ,
184
+ ])
185
+ if private :
186
+ symlink = PrivateSymlink (project )
187
+ else :
188
+ symlink = PublicSymlink (project )
189
+ basepath = symlink .project_root
190
+ fullpath = os .path .join (basepath , filename )
191
+ return (basepath , filename , fullpath )
192
+
193
+ project , full_path = project_and_path_from_request (request , request .get_full_path ())
194
+
195
+ language = None
196
+ version_slug = None
197
+ schema , netloc , path , params , query , fragments = urlparse (full_path )
198
+ if not project .single_version :
199
+ language , version_slug , path = language_and_version_from_path (path )
200
+
201
+ # Firstly, attempt to serve the 404 of the current version (version_slug)
202
+ # Secondly, try to serve the 404 page for the default version (project.get_default_version())
203
+ for slug in (version_slug , project .get_default_version ()):
204
+ basepath , filename , fullpath = resolve_404_path (project , slug , language )
205
+ if os .path .exists (fullpath ):
206
+ log .debug (
207
+ 'serving 404.html page current version: [project: %s] [version: %s]' ,
208
+ project .slug ,
209
+ slug ,
210
+ )
211
+ r = _serve_file (request , filename , basepath )
212
+ r .status_code = 404
213
+ return r
214
+
215
+ # Finally, return the default 404 page generated by Read the Docs
127
216
r = render (request , template_name )
128
217
r .status_code = 404
129
218
return r
0 commit comments