Skip to content

Commit 0590d04

Browse files
committed
Support custom robots.txt
Check for a custom `robots.txt` on the default version and if it does exist serve it. Otherwise, return 404.
1 parent dde80f8 commit 0590d04

File tree

2 files changed

+40
-1
lines changed

2 files changed

+40
-1
lines changed

readthedocs/core/urls/subdomain.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
from readthedocs.core.views.serve import (
1212
redirect_page_with_filename,
13-
redirect_project_slug, serve_docs
13+
redirect_project_slug, serve_docs, robots_txt,
1414
)
1515
from readthedocs.core.views import (
1616
server_error_500,
@@ -22,6 +22,10 @@
2222
handler404 = server_error_404
2323

2424
subdomain_urls = [
25+
url((r'robots.txt$'.format(**pattern_opts)),
26+
robots_txt,
27+
name='robots_txt'),
28+
2529
url(r'^(?:|projects/(?P<subproject_slug>{project_slug})/)'
2630
r'page/(?P<filename>.*)$'.format(**pattern_opts),
2731
redirect_page_with_filename,

readthedocs/core/views/serve.py

+35
Original file line numberDiff line numberDiff line change
@@ -215,3 +215,38 @@ def _serve_symlink_docs(request, project, privacy_level, filename=''):
215215

216216
raise Http404(
217217
'File not found. Tried these files: %s' % ','.join(files_tried))
218+
219+
220+
@map_project_slug
221+
def robots_txt(request, project):
222+
"""
223+
Serve custom user's defined ``/robots.txt``.
224+
225+
If the user added a ``robots.txt`` in the "default version" of the project,
226+
we serve it directly.
227+
"""
228+
if project.privacy_level == constants.PRIVATE:
229+
# If project is private, there is nothing to communicate to the bots.
230+
raise Http404()
231+
232+
# Use the ``robots.txt`` file from the default version configured
233+
version_slug = project.get_default_version()
234+
235+
filename = resolve_path(
236+
project,
237+
version_slug=version_slug,
238+
filename='robots.txt',
239+
subdomain=True, # subdomain will make it a "full" path without a URL prefix
240+
)
241+
242+
# This breaks path joining, by ignoring the root when given an "absolute" path
243+
if filename[0] == '/':
244+
filename = filename[1:]
245+
246+
basepath = PublicSymlink(project).project_root
247+
fullpath = os.path.join(basepath, filename)
248+
249+
if os.path.exists(fullpath):
250+
return HttpResponse(open(fullpath).read(), content_type='text/plain')
251+
252+
raise Http404()

0 commit comments

Comments
 (0)