Wednesday, April 28, 2021

Condition never met on process_response with common middleware

Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1import re 

2from urllib.parse import urlparse 

3 

4from django.conf import settings 

5from django.core.exceptions import PermissionDenied 

6from django.core.mail import mail_managers 

7from django.http import HttpResponsePermanentRedirect 

8from django.urls import is_valid_path 

9from django.utils.deprecation import MiddlewareMixin 

10from django.utils.http import escape_leading_slashes 

11 

12 

13class CommonMiddleware(MiddlewareMixin): 

14 """ 

15 "Common" middleware for taking care of some basic operations: 

16 

17 - Forbid access to User-Agents in settings.DISALLOWED_USER_AGENTS 

18 

19 - URL rewriting: Based on the APPEND_SLASH and PREPEND_WWW settings, 

20 append missing slashes and/or prepends missing "www."s. 

21 

22 - If APPEND_SLASH is set and the initial URL doesn't end with a 

23 slash, and it is not found in urlpatterns, form a new URL by 

24 appending a slash at the end. If this new URL is found in 

25 urlpatterns, return an HTTP redirect to this new URL; otherwise 

26 process the initial URL as usual. 

27 

28 This behavior can be customized by subclassing CommonMiddleware and 

29 overriding the response_redirect_class attribute. 

30 """ 

31 

32 response_redirect_class = HttpResponsePermanentRedirect 

33 

34 def process_request(self, request): 

35 """ 

36 Check for denied User-Agents and rewrite the URL based on 

37 settings.APPEND_SLASH and settings.PREPEND_WWW 

38 """ 

39 

40 # Check for denied User-Agents 

41 user_agent = request.META.get('HTTP_USER_AGENT') 

42 if user_agent is not None: 

43 for user_agent_regex in settings.DISALLOWED_USER_AGENTS: 

44 if user_agent_regex.search(user_agent): 44 ↛ 43line 44 didn't jump to line 43, because the condition on line 44 was never false

45 raise PermissionDenied('Forbidden user agent') 

46 

47 # Check for a redirect based on settings.PREPEND_WWW 

48 host = request.get_host() 

49 must_prepend = settings.PREPEND_WWW and host and not host.startswith('www.') 

50 redirect_url = ('%s://www.%s' % (request.scheme, host)) if must_prepend else '' 

51 

52 # Check if a slash should be appended 

53 if self.should_redirect_with_slash(request): 

54 path = self.get_full_path_with_slash(request) 

55 else: 

56 path = request.get_full_path() 

57 

58 # Return a redirect if necessary 

59 if redirect_url or path != request.get_full_path(): 

60 redirect_url += path 

61 return self.response_redirect_class(redirect_url) 

62 

63 def should_redirect_with_slash(self, request): 

64 """ 

65 Return True if settings.APPEND_SLASH is True and appending a slash to 

66 the request path turns an invalid path into a valid one. 

67 """ 

68 if settings.APPEND_SLASH and not request.path_info.endswith('/'): 

69 urlconf = getattr(request, 'urlconf', None) 

70 if not is_valid_path(request.path_info, urlconf): 

71 match = is_valid_path('%s/' % request.path_info, urlconf) 

72 if match: 

73 view = match.func 

74 return getattr(view, 'should_append_slash', True) 

75 return False 

76 

77 def get_full_path_with_slash(self, request): 

78 """ 

79 Return the full path of the request with a trailing slash appended. 

80 

81 Raise a RuntimeError if settings.DEBUG is True and request.method is 

82 POST, PUT, or PATCH. 

83 """ 

84 new_path = request.get_full_path(force_append_slash=True) 

85 # Prevent construction of scheme relative urls. 

86 new_path = escape_leading_slashes(new_path) 

87 if settings.DEBUG and request.method in ('POST', 'PUT', 'PATCH'): 

88 raise RuntimeError( 

89 "You called this URL via %(method)s, but the URL doesn't end " 

90 "in a slash and you have APPEND_SLASH set. Django can't " 

91 "redirect to the slash URL while maintaining %(method)s data. " 

92 "Change your form to point to %(url)s (note the trailing " 

93 "slash), or set APPEND_SLASH=False in your Django settings." % { 

94 'method': request.method, 

95 'url': request.get_host() + new_path, 

96 } 

97 ) 

98 return new_path 

99 

100 def process_response(self, request, response): 

101 """ 

102 When the status code of the response is 404, it may redirect to a path 

103 with an appended slash if should_redirect_with_slash() returns True. 

104 """ 

105 # If the given URL is "Not Found", then check if we should redirect to 

106 # a path with a slash appended. 

107 if response.status_code == 404 and self.should_redirect_with_slash(request): 107 ↛ 108line 107 didn't jump to line 108, because the condition on line 107 was never true

108 return self.response_redirect_class(self.get_full_path_with_slash(request)) 

109 

110 # Add the Content-Length header to non-streaming responses if not 

111 # already set. 

112 if not response.streaming and not response.has_header('Content-Length'): 

113 response.headers['Content-Length'] = str(len(response.content)) 

114 

115 return response 

116 

117 

118class BrokenLinkEmailsMiddleware(MiddlewareMixin): 

119 

120 def process_response(self, request, response): 

121 """Send broken link emails for relevant 404 NOT FOUND responses.""" 

122 if response.status_code == 404 and not settings.DEBUG: 122 ↛ 139line 122 didn't jump to line 139, because the condition on line 122 was never false

123 domain = request.get_host() 

124 path = request.get_full_path() 

125 referer = request.META.get('HTTP_REFERER', '') 

126 

127 if not self.is_ignorable_request(request, path, domain, referer): 

128 ua = request.META.get('HTTP_USER_AGENT', '<none>') 

129 ip = request.META.get('REMOTE_ADDR', '<none>') 

130 mail_managers( 

131 "Broken %slink on %s" % ( 

132 ('INTERNAL ' if self.is_internal_request(domain, referer) else ''), 

133 domain 

134 ), 

135 "Referrer: %s\nRequested URL: %s\nUser agent: %s\n" 

136 "IP address: %s\n" % (referer, path, ua, ip), 

137 fail_silently=True, 

138 ) 

139 return response 

140 

141 def is_internal_request(self, domain, referer): 

142 """ 

143 Return True if the referring URL is the same domain as the current 

144 request. 

145 """ 

146 # Different subdomains are treated as different domains. 

147 return bool(re.match("^https?://%s/" % re.escape(domain), referer)) 

148 

149 def is_ignorable_request(self, request, uri, domain, referer): 

150 """ 

151 Return True if the given request *shouldn't* notify the site managers 

152 according to project settings or in situations outlined by the inline 

153 comments. 

154 """ 

155 # The referer is empty. 

156 if not referer: 

157 return True 

158 

159 # APPEND_SLASH is enabled and the referer is equal to the current URL 

160 # without a trailing slash indicating an internal redirect. 

161 if settings.APPEND_SLASH and uri.endswith('/') and referer == uri[:-1]: 

162 return True 

163 

164 # A '?' in referer is identified as a search engine source. 

165 if not self.is_internal_request(domain, referer) and '?' in referer: 165 ↛ 166line 165 didn't jump to line 166, because the condition on line 165 was never true

166 return True 

167 

168 # The referer is equal to the current URL, ignoring the scheme (assumed 

169 # to be a poorly implemented bot). 

170 parsed_referer = urlparse(referer) 

171 if parsed_referer.netloc in ['', domain] and parsed_referer.path == uri: 

172 return True 

173 

174 return any(pattern.search(uri) for pattern in settings.IGNORABLE_404_URLS) 


Hello, I'm Tidiane and I've been using Django for some months now.
I've been digging into the codebase these days to gain more familiriaty with the project and eventually contribute to the project.

I don't know if it's a bug or if there is something I am not getting right but here what I noticed:

I was looking at the coverage of the projects and found that this line is never hit which impacts the coverage of CommonMiddleware. I also attach the coverage report for the file.

Indeed the two conditions can never be met at the same time since the process_request method already handles if a slash should be appended.
When APPEND_SLASH is set to True, if we have a 404 error after process_request has been called, the condition should_redirect_with_slash can never be met.

So is there a need to do a check at the mentionned line or is there something Iam I not getting right?

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-users+unsubscribe@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-users/01b7df9e-ffb2-4015-be6b-27f7492a422en%40googlegroups.com.

No comments:

Post a Comment