def test_parsing(self): self.assertEqual(parse_etags(r'"" , "etag", "e\\tag", W/"weak"'), ['""', '"etag"', r'"e\\tag"', 'W/"weak"']) self.assertEqual(parse_etags('*'), ['*']) # Ignore RFC 2616 ETags that are invalid according to RFC 7232. self.assertEqual(parse_etags(r'"etag", "e\"t\"ag"'), ['"etag"'])
def validate_matching_preconditions(request, meta): """Check that the ETag conforms with the preconditions set.""" etag = meta['hash'] if not UPDATE_MD5 else meta['checksum'] if not etag: etag = None if_match = request.META.get('HTTP_IF_MATCH') if if_match is not None: if etag is None: raise faults.PreconditionFailed('Resource does not exist') if (if_match != '*' and etag not in [x.lower() for x in parse_etags(if_match)]): raise faults.PreconditionFailed('Resource ETag does not match') if_none_match = request.META.get('HTTP_IF_NONE_MATCH') if if_none_match is not None: # TODO: If this passes, must ignore If-Modified-Since header. if etag is not None: if (if_none_match == '*' or etag in [x.lower() for x in parse_etags(if_none_match)]): # TODO: Continue if an If-Modified-Since header is present. if request.method in ('HEAD', 'GET'): raise faults.NotModified('Resource ETag matches') raise faults.PreconditionFailed( 'Resource exists or ETag matches')
def validate_matching_preconditions(request, meta): """Check that the ETag conforms with the preconditions set.""" etag = meta['checksum'] if not etag: etag = None if_match = request.META.get('HTTP_IF_MATCH') if if_match is not None: if etag is None: raise faults.PreconditionFailed('Resource does not exist') if (if_match != '*' and etag not in [x.lower() for x in parse_etags(if_match)]): raise faults.PreconditionFailed('Resource ETag does not match') if_none_match = request.META.get('HTTP_IF_NONE_MATCH') if if_none_match is not None: # TODO: If this passes, must ignore If-Modified-Since header. if etag is not None: if (if_none_match == '*' or etag in [x.lower() for x in parse_etags(if_none_match)]): # TODO: Continue if an If-Modified-Since header is present. if request.method in ('HEAD', 'GET'): raise faults.NotModified('Resource ETag matches') raise faults.PreconditionFailed( 'Resource exists or ETag matches')
def test_parsing(self): self.assertEqual( http.parse_etags(r'"" , "etag", "e\\tag", W/"weak"'), ['""', '"etag"', r'"e\\tag"', 'W/"weak"'] ) self.assertEqual(http.parse_etags("*"), ["*"]) # Ignore RFC 2616 ETags that are invalid according to RFC 7232. self.assertEqual(http.parse_etags(r'"etag", "e\"t\"ag"'), ['"etag"'])
def get_conditional_response(request, etag=None, last_modified=None, response=None): # Only return conditional responses on successful requests. if response and not (200 <= response.status_code < 300): return response # Get HTTP request headers. if_match_etags = parse_etags(request.META.get('HTTP_IF_MATCH', '')) if_unmodified_since = request.META.get('HTTP_IF_UNMODIFIED_SINCE') if if_unmodified_since: if_unmodified_since = parse_http_date_safe(if_unmodified_since) if_none_match_etags = parse_etags(request.META.get('HTTP_IF_NONE_MATCH', '')) if_modified_since = request.META.get('HTTP_IF_MODIFIED_SINCE') if if_modified_since: if_modified_since = parse_http_date_safe(if_modified_since)
def get_not_modified_response(resource_etag): if not request.method in ('GET', 'HEAD'): return None if_none_match = request.META.get("HTTP_IF_NONE_MATCH") if if_none_match: # There can be more than one ETag in the request, so we # consider the list of values. try: etags = parse_etags(if_none_match) except ValueError: # In case of invalid etag ignore all ETag headers. # Apparently Opera sends invalidly quoted headers at times # (we should be returning a 400 response, but that's a # little extreme) -- this is Django bug #10681. if_none_match = None if not if_none_match: return None if not resource_etag: return None if resource_etag in etags: return Response(status=status.HTTP_304_NOT_MODIFIED) else: return None
def get_conditional_response(request, etag=None, last_modified=None, response=None): # Only return conditional responses on successful requests. if response and not (200 <= response.status_code < 300): return response # Get HTTP request headers. if_match_etags = parse_etags(request.META.get('HTTP_IF_MATCH', '')) if_unmodified_since = request.META.get('HTTP_IF_UNMODIFIED_SINCE') if_unmodified_since = if_unmodified_since and parse_http_date_safe( if_unmodified_since) if_none_match_etags = parse_etags( request.META.get('HTTP_IF_NONE_MATCH', '')) if_modified_since = request.META.get('HTTP_IF_MODIFIED_SINCE') if_modified_since = if_modified_since and parse_http_date_safe( if_modified_since) # Step 1 of section 6 of RFC 7232: Test the If-Match precondition. if if_match_etags and not _if_match_passes(etag, if_match_etags): return _precondition_failed(request) # Step 2: Test the If-Unmodified-Since precondition. if (not if_match_etags and if_unmodified_since and not _if_unmodified_since_passes( last_modified, if_unmodified_since)): return _precondition_failed(request) # Step 3: Test the If-None-Match precondition. if if_none_match_etags and not _if_none_match_passes( etag, if_none_match_etags): if request.method in ('GET', 'HEAD'): return _not_modified(request, response) else: return _precondition_failed(request) # Step 4: Test the If-Modified-Since precondition. if (not if_none_match_etags and if_modified_since and not _if_modified_since_passes(last_modified, if_modified_since)): if request.method in ('GET', 'HEAD'): return _not_modified(request, response) # Step 5: Test the If-Range precondition (not supported). # Step 6: Return original response since there isn't a conditional response. return response
def evaluate_conditions(self, res): if not res.exists: return etag = res.get_etag() mtime = res.get_mtime_stamp() cond_if_match = self.request.META.get('HTTP_IF_MATCH', None) if cond_if_match: etags = parse_etags(cond_if_match) if '*' in etags or etag in etags: raise ResponseException(HttpResponsePreconditionFailed()) cond_if_modified_since = self.request.META.get( 'HTTP_IF_MODIFIED_SINCE', False) if cond_if_modified_since: # Parse and evaluate, but don't raise anything just yet... # This might be ignored based on If-None-Match evaluation. cond_if_modified_since = parse_time(cond_if_modified_since) if cond_if_modified_since and cond_if_modified_since > mtime: cond_if_modified_since = True else: cond_if_modified_since = False cond_if_none_match = self.request.META.get('HTTP_IF_NONE_MATCH', None) if cond_if_none_match: etags = parse_etags(cond_if_none_match) if '*' in etags or etag in etags: if self.request.method in ('GET', 'HEAD'): raise ResponseException(HttpResponseNotModified()) raise ResponseException(HttpResponsePreconditionFailed()) # Ignore If-Modified-Since header... cond_if_modified_since = False cond_if_unmodified_since = self.request.META.get( 'HTTP_IF_UNMODIFIED_SINCE', None) if cond_if_unmodified_since: cond_if_unmodified_since = parse_time(cond_if_unmodified_since) if cond_if_unmodified_since and cond_if_unmodified_since <= mtime: raise ResponseException(HttpResponsePreconditionFailed()) if cond_if_modified_since: # This previously evaluated True and is not being ignored... raise ResponseException(HttpResponseNotModified()) # TODO: complete If header handling... cond_if = self.request.META.get('HTTP_IF', None) if cond_if: if not cond_if.startswith('<'): cond_if = '<*>' + cond_if
def inner(request, *args, **kwargs): # Get HTTP request headers if_modified_since = request.META.get("HTTP_IF_MODIFIED_SINCE") if_none_match = request.META.get("HTTP_IF_NONE_MATCH") if_match = request.META.get("HTTP_IF_MATCH") if if_none_match or if_match: # There can be more than one ETag in the request, so we # consider the list of values. etags = parse_etags(if_none_match or if_match) # Compute values (if any) for the requested resource. if etag_func: res_etag = etag_func(request, *args, **kwargs) else: res_etag = None if last_modified_func: dt = last_modified_func(request, *args, **kwargs) if dt: res_last_modified = formatdate(timegm(dt.utctimetuple()))[:26] + 'GMT' else: res_last_modified = None else: res_last_modified = None response = None if not ((if_match and (if_modified_since or if_none_match)) or (if_match and if_none_match)): # We only get here if no undefined combinations of headers are # specified. if ((if_none_match and (res_etag in etags or "*" in etags and res_etag)) and (not if_modified_since or res_last_modified == if_modified_since)): if request.method in ("GET", "HEAD"): response = HttpResponseNotModified() else: response = HttpResponse(status=412) elif if_match and ((not res_etag and "*" in etags) or (res_etag and res_etag not in etags)): response = HttpResponse(status=412) elif (not if_none_match and if_modified_since and request.method == "GET" and res_last_modified == if_modified_since): response = HttpResponseNotModified() if response is None: response = func(request, *args, **kwargs) # Set relevant headers on the response if they don't already exist. if res_last_modified and not response.has_header('Last-Modified'): response['Last-Modified'] = res_last_modified if res_etag and not response.has_header('ETag'): response['ETag'] = quote_etag(res_etag) return response
def set_etag(self, request, response): if 'ETag' in response: etag = parse_etags(response['ETag'])[0] else: etag = hashlib.md5(response.content).hexdigest() response['ETag'] = quote_etag(etag) # Cache the etag for subsequent look ups. This can be cached # indefinitely since these are unique values cache = self.get_cache(request, response) cache.set(etag, 1, MAX_CACHE_AGE)
def evaluate_conditions(self, res): if not res.exists: return etag = res.get_etag() mtime = res.get_mtime_stamp() cond_if_match = self.request.META.get('HTTP_IF_MATCH', None) if cond_if_match: etags = parse_etags(cond_if_match) if '*' in etags or etag in etags: raise ResponseException(HttpResponsePreconditionFailed()) cond_if_modified_since = self.request.META.get('HTTP_IF_MODIFIED_SINCE', False) if cond_if_modified_since: # Parse and evaluate, but don't raise anything just yet... # This might be ignored based on If-None-Match evaluation. cond_if_modified_since = parse_time(cond_if_modified_since) if cond_if_modified_since and cond_if_modified_since > mtime: cond_if_modified_since = True else: cond_if_modified_since = False cond_if_none_match = self.request.META.get('HTTP_IF_NONE_MATCH', None) if cond_if_none_match: etags = parse_etags(cond_if_none_match) if '*' in etags or etag in etags: if self.request.method in ('GET', 'HEAD'): raise ResponseException(HttpResponseNotModified()) raise ResponseException(HttpResponsePreconditionFailed()) # Ignore If-Modified-Since header... cond_if_modified_since = False cond_if_unmodified_since = self.request.META.get('HTTP_IF_UNMODIFIED_SINCE', None) if cond_if_unmodified_since: cond_if_unmodified_since = parse_time(cond_if_unmodified_since) if cond_if_unmodified_since and cond_if_unmodified_since <= mtime: raise ResponseException(HttpResponsePreconditionFailed()) if cond_if_modified_since: # This previously evaluated True and is not being ignored... raise ResponseException(HttpResponseNotModified()) # TODO: complete If header handling... cond_if = self.request.META.get('HTTP_IF', None) if cond_if: if not cond_if.startswith('<'): cond_if = '<*>' + cond_if
def get_conditional_response(request, etag=None, last_modified=None, response=None): # Only return conditional responses on successful requests. if response and not (200 <= response.status_code < 300): return response # Get HTTP request headers. if_match_etags = parse_etags(request.META.get('HTTP_IF_MATCH', '')) if_unmodified_since = request.META.get('HTTP_IF_UNMODIFIED_SINCE') if if_unmodified_since: if_unmodified_since = parse_http_date_safe(if_unmodified_since) if_none_match_etags = parse_etags(request.META.get('HTTP_IF_NONE_MATCH', '')) if_modified_since = request.META.get('HTTP_IF_MODIFIED_SINCE') if if_modified_since: if_modified_since = parse_http_date_safe(if_modified_since) # Step 1 of section 6 of RFC 7232: Test the If-Match precondition. if if_match_etags and not _if_match_passes(etag, if_match_etags): return _precondition_failed(request) # Step 2: Test the If-Unmodified-Since precondition. if (not if_match_etags and if_unmodified_since and not _if_unmodified_since_passes(last_modified, if_unmodified_since)): return _precondition_failed(request) # Step 3: Test the If-None-Match precondition. if if_none_match_etags and not _if_none_match_passes(etag, if_none_match_etags): if request.method in ('GET', 'HEAD'): return _not_modified(request, response) else: return _precondition_failed(request) # Step 4: Test the If-Modified-Since precondition. if (not if_none_match_etags and if_modified_since and not _if_modified_since_passes(last_modified, if_modified_since)): if request.method in ('GET', 'HEAD'): return _not_modified(request, response) # Step 5: Test the If-Range precondition (not supported). # Step 6: Return original response since there isn't a conditional response. return response
def validate_matching_preconditions(request, meta): """Check that the ETag conforms with the preconditions set.""" etag = meta["hash"] if not UPDATE_MD5 else meta["checksum"] if not etag: etag = None if_match = request.META.get("HTTP_IF_MATCH") if if_match is not None: if etag is None: raise faults.PreconditionFailed("Resource does not exist") if if_match != "*" and etag not in [x.lower() for x in parse_etags(if_match)]: raise faults.PreconditionFailed("Resource ETag does not match") if_none_match = request.META.get("HTTP_IF_NONE_MATCH") if if_none_match is not None: # TODO: If this passes, must ignore If-Modified-Since header. if etag is not None: if if_none_match == "*" or etag in [x.lower() for x in parse_etags(if_none_match)]: # TODO: Continue if an If-Modified-Since header is present. if request.method in ("HEAD", "GET"): raise faults.NotModified("Resource ETag matches") raise faults.PreconditionFailed("Resource exists or ETag matches")
def has_etag_changed(self): if not self.etag_attribute: return True etag = self.generate_etag() if not etag: return True match = self.request.META.get('HTTP_IF_NONE_MATCH') if match: values = parse_etags(match) for value in values: # Django appends ";gzip" when gzip is enabled clean_value = value.split(';')[0] if clean_value == '*' or clean_value == etag: return False return True
def get_etags_and_matchers(self, request): etags = None if_none_match = request.META.get(prepare_header_name("if-none-match")) if_match = request.META.get(prepare_header_name("if-match")) if if_none_match or if_match: # There can be more than one ETag in the request, so we # consider the list of values. try: etags = parse_etags(if_none_match or if_match) except ValueError: # In case of invalid etag ignore all ETag headers. # Apparently Opera sends invalidly quoted headers at times # (we should be returning a 400 response, but that's a # little extreme) -- this is Django bug #10681. if_none_match = None if_match = None return etags, if_none_match, if_match
def get_conditional_response(self, request): # etag etag = self.get_etag() if_none_match_etags = set(etag.strip("W/") for etag in parse_etags(request.META.get('HTTP_IF_NONE_MATCH', ''))) if etag and etag.strip("W/") in if_none_match_etags: return self.make_not_modified_response() # last modified last_modified_dt = self.get_last_modified_dt() if_modified_since = request.META.get('HTTP_IF_MODIFIED_SINCE') if_modified_since = if_modified_since and parse_http_date_safe(if_modified_since) if ( not if_none_match_etags and isinstance(last_modified_dt, datetime) and if_modified_since and int(last_modified_dt.timestamp()) <= if_modified_since ): return self.make_not_modified_response() return self.build_and_make_response()
def is_precondition_failed(self, request, response, *args, **kwargs): # ETags are enabled. Check for conditional request headers. The current # ETag value is used for the conditional requests. After the request # method handler has been processed, the new ETag will be calculated. if self.use_etags and 'HTTP_IF_MATCH' in request.META: request_etag = parse_etags(request.META['HTTP_IF_MATCH'])[0] etag = self.get_etag(request, request_etag) if request_etag != etag: return True # Last-Modified date enabled. check for conditional request headers. The # current modification datetime value is used for the conditional # requests. After the request method handler has been processed, the new # Last-Modified datetime will be returned. if self.use_last_modified and 'HTTP_IF_UNMODIFIED_SINCE' in request.META: last_modified = self.get_last_modified(request, *args, **kwargs) known_last_modified = EPOCH_DATE + timedelta(seconds=parse_http_date(request.META['HTTP_IF_UNMODIFIED_SINCE'])) if known_last_modified != last_modified: return True return False
def testParsing(self): etags = http.parse_etags(r'"", "etag", "e\"t\"ag", "e\\tag", W/"weak"') self.assertEqual(etags, ['', 'etag', 'e"t"ag', r'e\tag', 'weak'])
def test_parsing(self): etags = http.parse_etags(r'"", "etag", "e\"t\"ag", "e\\tag", W/"weak"') self.assertEqual(etags, ['', 'etag', 'e"t"ag', r'e\tag', 'weak'])
def test_parsing(self): etags = http.parse_etags(r'"", "etag", "e\"t\"ag", "e\\tag", W/"weak"') self.assertEqual(etags, ["", "etag", 'e"t"ag', r"e\tag", "weak"])
"""
def _document_api_PUT(request, document_slug, document_locale): """ Handle PUT requests for the document_api view. """ # Try parsing one of the supported content types from the request try: content_type = request.META.get('CONTENT_TYPE', '') if content_type.startswith('application/json'): data = json.loads(request.body) elif content_type.startswith('multipart/form-data'): parser = MultiPartParser(request.META, StringIO(request.body), request.upload_handlers, request.encoding) data, files = parser.parse() elif content_type.startswith('text/html'): # TODO: Refactor this into wiki.content ? # First pass: Just assume the request body is an HTML fragment. html = request.body data = dict(content=html) # Second pass: Try parsing the body as a fuller HTML document, # and scrape out some of the interesting parts. try: doc = pq(html) head_title = doc.find('head title') if head_title.length > 0: data['title'] = head_title.text() body_content = doc.find('body') if body_content.length > 0: data['content'] = body_content.html() except Exception: pass else: resp = HttpResponse() resp.status_code = 400 resp.content = ugettext( "Unsupported content-type: %s") % content_type return resp except Exception as e: resp = HttpResponse() resp.status_code = 400 resp.content = ugettext("Request parsing error: %s") % e return resp try: # Look for existing document to edit: doc = Document.objects.get(locale=document_locale, slug=document_slug) section_id = request.GET.get('section', None) is_new = False # Use ETags to detect mid-air edit collision # see: http://www.w3.org/1999/04/Editing/ if_match = request.META.get('HTTP_IF_MATCH') if if_match: try: expected_etags = parse_etags(if_match) except ValueError: expected_etags = [] current_etag = calculate_etag(doc.get_html(section_id)) if current_etag not in expected_etags: resp = HttpResponse() resp.status_code = 412 resp.content = ugettext('ETag precondition failed') return resp except Document.DoesNotExist: # TODO: There should be a model utility for creating a doc... # Let's see if this slug path implies a parent... slug_parts = split_slug(document_slug) if not slug_parts['parent']: # Apparently, this is a root page! parent_doc = None else: # There's a parent implied, so make sure we can find it. parent_doc = get_object_or_404(Document, locale=document_locale, slug=slug_parts['parent']) # Create and save the new document; we'll revise it immediately. doc = Document(slug=document_slug, locale=document_locale, title=data.get('title', document_slug), parent_topic=parent_doc) doc.save() section_id = None # No section editing for new document! is_new = True new_rev = doc.revise(request.user, data, section_id) doc.schedule_rendering('max-age=0') request.authkey.log('created' if is_new else 'updated', new_rev, data.get('summary', None)) resp = HttpResponse() if is_new: resp['Location'] = request.build_absolute_uri(doc.get_absolute_url()) resp.status_code = 201 else: resp.status_code = 205 return resp
def inner(request, *args, **kwargs): # Get HTTP request headers if_modified_since = request.META.get("HTTP_IF_MODIFIED_SINCE") if_none_match = request.META.get("HTTP_IF_NONE_MATCH") if_match = request.META.get("HTTP_IF_MATCH") if if_none_match or if_match: # There can be more than one ETag in the request, so we # consider the list of values. try: etags = parse_etags(if_none_match or if_match) except ValueError: # In case of invalid etag ignore all ETag headers. # Apparently Opera sends invalidly quoted headers at times # (we should be returning a 400 response, but that's a # little extreme) -- this is Django bug #10681. if_none_match = None if_match = None # Compute values (if any) for the requested resource. if etag_func: res_etag = etag_func(request, *args, **kwargs) else: res_etag = None if last_modified_func: dt = last_modified_func(request, *args, **kwargs) if dt: res_last_modified = formatdate(timegm( dt.utctimetuple()))[:26] + 'GMT' else: res_last_modified = None else: res_last_modified = None response = None if not ((if_match and (if_modified_since or if_none_match)) or (if_match and if_none_match)): # We only get here if no undefined combinations of headers are # specified. if ((if_none_match and (res_etag in etags or "*" in etags and res_etag)) and (not if_modified_since or res_last_modified == if_modified_since)): if request.method in ("GET", "HEAD"): response = HttpResponseNotModified() else: logger.warning('Precondition Failed: %s' % request.path, extra={ 'status_code': 412, 'request': request }) response = HttpResponse(status=412) elif if_match and ((not res_etag and "*" in etags) or (res_etag and res_etag not in etags)): logger.warning('Precondition Failed: %s' % request.path, extra={ 'status_code': 412, 'request': request }) response = HttpResponse(status=412) elif (not if_none_match and if_modified_since and request.method == "GET" and res_last_modified == if_modified_since): response = HttpResponseNotModified() if response is None: response = func(request, *args, **kwargs) # Set relevant headers on the response if they don't already exist. if res_last_modified and not response.has_header('Last-Modified'): response['Last-Modified'] = res_last_modified if res_etag and not response.has_header('ETag'): response['ETag'] = quote_etag(res_etag) return response
def inner(request, *args, **kwargs): # Get HTTP request headers if_modified_since = request.META.get("HTTP_IF_MODIFIED_SINCE") if if_modified_since: if_modified_since = parse_http_date_safe(if_modified_since) if_none_match = request.META.get("HTTP_IF_NONE_MATCH") if_match = request.META.get("HTTP_IF_MATCH") if if_none_match or if_match: # There can be more than one ETag in the request, so we # consider the list of values. try: etags = parse_etags(if_none_match or if_match) except ValueError: # In case of invalid etag ignore all ETag headers. # Apparently Opera sends invalidly quoted headers at times # (we should be returning a 400 response, but that's a # little extreme) -- this is Django bug #10681. if_none_match = None if_match = None # Compute values (if any) for the requested resource. if etag_func: res_etag = etag_func(request, *args, **kwargs) else: res_etag = None if last_modified_func: dt = last_modified_func(request, *args, **kwargs) if dt: res_last_modified = timegm(dt.utctimetuple()) else: res_last_modified = None else: res_last_modified = None response = None if not ((if_match and (if_modified_since or if_none_match)) or (if_match and if_none_match)): # We only get here if no undefined combinations of headers are # specified. if ((if_none_match and (res_etag in etags or "*" in etags and res_etag)) and (not if_modified_since or (res_last_modified and if_modified_since and res_last_modified <= if_modified_since))): if request.method in ("GET", "HEAD"): response = HttpResponseNotModified() else: logger.warning('Precondition Failed: %s' % request.path, extra={ 'status_code': 412, 'request': request } ) response = HttpResponse(status=412) elif if_match and ((not res_etag and "*" in etags) or (res_etag and res_etag not in etags)): logger.warning('Precondition Failed: %s' % request.path, extra={ 'status_code': 412, 'request': request } ) response = HttpResponse(status=412) elif (not if_none_match and request.method == "GET" and res_last_modified and if_modified_since and res_last_modified <= if_modified_since): response = HttpResponseNotModified() if response is None: response = func(request, *args, **kwargs) # Set relevant headers on the response if they don't already exist. if res_last_modified and not response.has_header('Last-Modified'): response['Last-Modified'] = http_date(res_last_modified) if res_etag and not response.has_header('ETag'): response['ETag'] = quote_etag(res_etag) return response
def get_conditional_response(request, etag=None, last_modified=None, response=None): # Get HTTP request headers if_modified_since = request.META.get('HTTP_IF_MODIFIED_SINCE') if if_modified_since: if_modified_since = parse_http_date_safe(if_modified_since) if_unmodified_since = request.META.get('HTTP_IF_UNMODIFIED_SINCE') if if_unmodified_since: if_unmodified_since = parse_http_date_safe(if_unmodified_since) if_none_match = request.META.get('HTTP_IF_NONE_MATCH') if_match = request.META.get('HTTP_IF_MATCH') etags = [] if if_none_match or if_match: # There can be more than one ETag in the request, so we # consider the list of values. try: etags = parse_etags(if_none_match or if_match) except ValueError: # In case of an invalid ETag, ignore all ETag headers. # Apparently Opera sends invalidly quoted headers at times # (we should be returning a 400 response, but that's a # little extreme) -- this is bug #10681. if_none_match = None if_match = None # If-None-Match must be ignored if original result would be anything # other than a 2XX or 304 status. 304 status would result in no change. # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26 if response and not (200 <= response.status_code < 300): if_none_match = None if_match = None # If-Modified-Since must be ignored if the original result was not a 200. # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25 if response and response.status_code != 200: if_modified_since = None if_unmodified_since = None if not ((if_match and if_modified_since) or (if_none_match and if_unmodified_since) or (if_modified_since and if_unmodified_since) or (if_match and if_none_match)): # We only get here if no undefined combinations of headers are # specified. if ((if_none_match and (etag in etags or '*' in etags and etag)) and (not if_modified_since or (last_modified and if_modified_since and last_modified <= if_modified_since))): if request.method in ('GET', 'HEAD'): return _not_modified(request, response) else: return _precondition_failed(request) elif (if_match and ((not etag and '*' in etags) or (etag and etag not in etags) or (last_modified and if_unmodified_since and last_modified > if_unmodified_since))): return _precondition_failed(request) elif (not if_none_match and request.method in ('GET', 'HEAD') and last_modified and if_modified_since and last_modified <= if_modified_since): return _not_modified(request, response) elif (not if_match and last_modified and if_unmodified_since and last_modified > if_unmodified_since): return _precondition_failed(request) return response
def inner(request, *args, **kwargs): # Get HTTP request headers if_modified_since = request.META.get("HTTP_IF_MODIFIED_SINCE") if if_modified_since: if_modified_since = parse_http_date_safe(if_modified_since) if_unmodified_since = request.META.get("HTTP_IF_UNMODIFIED_SINCE") if if_unmodified_since: if_unmodified_since = parse_http_date_safe(if_unmodified_since) if_none_match = request.META.get("HTTP_IF_NONE_MATCH") if_match = request.META.get("HTTP_IF_MATCH") etags = [] if if_none_match or if_match: # There can be more than one ETag in the request, so we # consider the list of values. try: etags = parse_etags(if_none_match or if_match) except ValueError: # In case of invalid etag ignore all ETag headers. # Apparently Opera sends invalidly quoted headers at times # (we should be returning a 400 response, but that's a # little extreme) -- this is Django bug #10681. if_none_match = None if_match = None # Compute values (if any) for the requested resource. def get_last_modified(): if last_modified_func: dt = last_modified_func(request, *args, **kwargs) if dt: return timegm(dt.utctimetuple()) res_etag = etag_func(request, *args, ** kwargs) if etag_func else None res_last_modified = get_last_modified() response = None if not ((if_match and if_modified_since) or (if_none_match and if_unmodified_since) or (if_modified_since and if_unmodified_since) or (if_match and if_none_match)): # We only get here if no undefined combinations of headers are # specified. if ((if_none_match and (res_etag in etags or "*" in etags and res_etag)) and (not if_modified_since or (res_last_modified and if_modified_since and res_last_modified <= if_modified_since))): if request.method in ("GET", "HEAD"): response = HttpResponseNotModified() else: response = _precondition_failed(request) elif (if_match and ((not res_etag and "*" in etags) or (res_etag and res_etag not in etags) or (res_last_modified and if_unmodified_since and res_last_modified > if_unmodified_since))): response = _precondition_failed(request) elif (not if_none_match and request.method in ("GET", "HEAD") and res_last_modified and if_modified_since and res_last_modified <= if_modified_since): response = HttpResponseNotModified() elif (not if_match and res_last_modified and if_unmodified_since and res_last_modified > if_unmodified_since): response = _precondition_failed(request) if response is None: response = func(request, *args, **kwargs) # Set relevant headers on the response if they don't already exist. if res_last_modified and not response.has_header('Last-Modified'): response['Last-Modified'] = http_date(res_last_modified) if res_etag and not response.has_header('ETag'): response['ETag'] = quote_etag(res_etag) return response
def _document_api_PUT(request, document_slug, document_locale): """ Handle PUT requests for the document_api view. """ # Try parsing one of the supported content types from the request try: content_type = request.META.get('CONTENT_TYPE', '') if content_type.startswith('application/json'): data = json.loads(request.body) elif content_type.startswith('multipart/form-data'): parser = MultiPartParser(request.META, StringIO(request.body), request.upload_handlers, request.encoding) data, files = parser.parse() elif content_type.startswith('text/html'): # TODO: Refactor this into wiki.content ? # First pass: Just assume the request body is an HTML fragment. html = request.body data = dict(content=html) # Second pass: Try parsing the body as a fuller HTML document, # and scrape out some of the interesting parts. try: doc = pq(html) head_title = doc.find('head title') if head_title.length > 0: data['title'] = head_title.text() body_content = doc.find('body') if body_content.length > 0: data['content'] = to_html(body_content) except Exception: pass else: resp = HttpResponse() resp.status_code = 400 resp.content = ugettext( "Unsupported content-type: %s") % content_type return resp except Exception as e: resp = HttpResponse() resp.status_code = 400 resp.content = ugettext("Request parsing error: %s") % e return resp try: # Look for existing document to edit: doc = Document.objects.get(locale=document_locale, slug=document_slug) section_id = request.GET.get('section', None) is_new = False # Use ETags to detect mid-air edit collision # see: http://www.w3.org/1999/04/Editing/ if_match = request.META.get('HTTP_IF_MATCH') if if_match: try: expected_etags = parse_etags(if_match) except ValueError: expected_etags = [] # Django's parse_etags returns a list of quoted rather than # un-quoted ETags starting with version 1.11. current_etag = quote_etag(calculate_etag(doc.get_html(section_id))) if current_etag not in expected_etags: resp = HttpResponse() resp.status_code = 412 resp.content = ugettext('ETag precondition failed') return resp except Document.DoesNotExist: # TODO: There should be a model utility for creating a doc... # Let's see if this slug path implies a parent... slug_parts = split_slug(document_slug) if not slug_parts['parent']: # Apparently, this is a root page! parent_doc = None else: # There's a parent implied, so make sure we can find it. parent_doc = get_object_or_404(Document, locale=document_locale, slug=slug_parts['parent']) # Create and save the new document; we'll revise it immediately. doc = Document(slug=document_slug, locale=document_locale, title=data.get('title', document_slug), parent_topic=parent_doc) doc.save() section_id = None # No section editing for new document! is_new = True new_rev = doc.revise(request.user, data, section_id) doc.schedule_rendering('max-age=0') request.authkey.log('created' if is_new else 'updated', new_rev, data.get('summary', None)) resp = HttpResponse() if is_new: resp['Location'] = request.build_absolute_uri(doc.get_absolute_url()) resp.status_code = 201 else: resp.status_code = 205 return resp
def test_parse_etags(inp): parse_etags(inp)
def process_request(self, request, *args, **kwargs): # Initilize a new response for this request. Passing the response along # the request cycle allows for gradual modification of the headers. response = HttpResponse() # TODO keep track of a list of request headers used to # determine the resource representation for the 'Vary' # header. # ### 503 Service Unavailable # The server does not need to be unavailable for a resource to be # unavailable... if self.is_service_unavailable(request, response, *args, **kwargs): response.status_code = codes.service_unavailable return response # ### 414 Request URI Too Long _(not implemented)_ # This should be be handled upstream by the Web server # ### 400 Bad Request _(not implemented)_ # Note that many services respond with this code when entities are # unprocessable. This should really be a 422 Unprocessable Entity # Most actualy bad requests are handled upstream by the Web server # when parsing the HTTP message # ### 401 Unauthorized # Check if the request is authorized to access this resource. if self.is_unauthorized(request, response, *args, **kwargs): response.status_code = codes.unauthorized return response # ### 403 Forbidden # Check if this resource is forbidden for the request. if self.is_forbidden(request, response, *args, **kwargs): response.status_code = codes.forbidden return response # ### 501 Not Implemented _(not implemented)_ # This technically refers to a service-wide response for an # unimplemented request method, again this is upstream. # ### 429 Too Many Requests # Both `rate_limit_count` and `rate_limit_seconds` must be none # falsy values to be checked. if self.rate_limit_count and self.rate_limit_seconds: if self.is_too_many_requests(request, response, *args, **kwargs): response.status_code = codes.too_many_requests return response # ### 405 Method Not Allowed if self.is_method_not_allowed(request, response, *args, **kwargs): response.status_code = codes.method_not_allowed return response # ### 406 Not Acceptable # Checks Accept and Accept-* headers if self.is_not_acceptable(request, response, *args, **kwargs): response.status_code = codes.not_acceptable return response # ### Process an _OPTIONS_ request # Enough processing has been performed to allow an OPTIONS request. if request.method == methods.OPTIONS and 'OPTIONS' in self.allowed_methods: return self.options(request, response) # ## Request Entity Checks # Only perform these checks if the request has supplied a body. if 'CONTENT_LENGTH' in request.META and request.META['CONTENT_LENGTH']: # ### 415 Unsupported Media Type # Check if the entity `Content-Type` supported for decoding. if self.is_unsupported_media_type(request, response, *args, **kwargs): response.status_code = codes.unsupported_media_type return response # ### 413 Request Entity Too Large # Check if the entity is too large for processing if self.is_request_entity_too_large(request, response, *args, **kwargs): response.status_code = codes.request_entity_too_large return response # ### 404 Not Found # Check if this resource exists. Note, if this requires a database # lookup or some other expensive lookup, the relevant object may # be _attached_ to the request or response object to be used # dowstream in the handler. This prevents multiple database # hits or filesystem lookups. if self.is_not_found(request, response, *args, **kwargs): response.status_code = codes.not_found return response # ### 410 Gone # Check if this resource used to exist, but does not anymore. A common # strategy for this when dealing with this in a database context is to # have an `archived` or `deleted` flag that can be used associated with # the given lookup key while the rest of the content in the row may be # deleted. if self.is_gone(request, response, *args, **kwargs): response.status_code = codes.gone return response # ### 428 Precondition Required # Prevents the "lost udpate" problem and requires client to confirm # the state of the resource has not changed since the last `GET` # request. This applies to `PUT` and `PATCH` requests. if request.method == methods.PUT or request.method == methods.PATCH: if self.is_precondition_required(request, response, *args, **kwargs): return UncacheableResponse(status=codes.precondition_required) # ### 412 Precondition Failed # Conditional requests applies to GET, HEAD, PUT, and PATCH. # For GET and HEAD, the request checks the either the entity changed # since the last time it requested it, `If-Modified-Since`, or if the # entity tag (ETag) has changed, `If-None-Match`. if request.method == methods.PUT or request.method == methods.PATCH: if self.is_precondition_failed(request, response, *args, **kwargs): return UncacheableResponse(status=codes.precondition_failed) # Check for conditional GET or HEAD request if request.method == methods.GET or request.method == methods.HEAD: # Check Etags before Last-Modified... if self.use_etags and 'HTTP_IF_NONE_MATCH' in request.META: # Parse request Etags (only one is currently supported) request_etag = parse_etags(request.META['HTTP_IF_NONE_MATCH'])[0] # Check if the request Etag is valid. The current Etag is # supplied to enable strategies where the etag does not need # to be used to regenerate the Etag. This may include # generating an MD5 of the resource and storing it as a key # in memcache. etag = self.get_etag(request, response, request_etag, *args, **kwargs) # Nothing has changed, simply return if request_etag == etag: response.status_code = codes.not_modified return response if self.use_last_modified and 'HTTP_IF_MODIFIED_SINCE' in request.META: # Get the last known modified date from the client, compare it # to the last modified date of the resource last_modified = self.get_last_modified(request, *args, **kwargs) known_last_modified = EPOCH_DATE + timedelta(seconds=parse_http_date(request.META['HTTP_IF_MODIFIED_SINCE'])) if known_last_modified >= last_modified: response.status_code = codes.not_modified return response # Check if there is a request body if 'CONTENT_LENGTH' in request.META and request.META['CONTENT_LENGTH']: content_type = request._content_type if content_type in serializers: request.data = serializers.decode(content_type, request.body)