diff --git a/.gitignore b/.gitignore index 9d2b631..61193c5 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ env build dist .DS_Store + diff --git a/rest_framework_extensions/etag/decorators.py b/rest_framework_extensions/etag/decorators.py index cc5ef7b..3bf1356 100644 --- a/rest_framework_extensions/etag/decorators.py +++ b/rest_framework_extensions/etag/decorators.py @@ -86,7 +86,12 @@ def get_etags_and_matchers(self, request): # 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) + value_to_parse = if_none_match or if_match + if value_to_parse: + etag_list = [e.strip() for e in value_to_parse.split(' ') if e.strip()] + etag_list = [e if e.startswith('"') else f'"{e}"' for e in etag_list] + value_to_parse = ', '.join(etag_list) + etags = parse_etags(value_to_parse) except ValueError: # In case of invalid etag ignore all ETag headers. # Apparently Opera sends invalidly quoted headers at times @@ -123,7 +128,10 @@ def is_if_none_match_failed(self, res_etag, etags, if_none_match): def is_if_match_failed(self, res_etag, etags, if_match): if res_etag and if_match: - return res_etag not in etags and '*' not in etags + res_etag =res_etag.strip('"') + etags = [etag.strip('"') for etag in etags] + matches = res_etag in etags or '*' in etags + return not matches else: return False diff --git a/rest_framework_extensions/mixins.py b/rest_framework_extensions/mixins.py index b33c933..39d991d 100644 --- a/rest_framework_extensions/mixins.py +++ b/rest_framework_extensions/mixins.py @@ -4,7 +4,7 @@ from rest_framework_extensions.settings import extensions_api_settings from django.core.exceptions import ValidationError from django.http import Http404 - +import uuid class DetailSerializerMixin: """ @@ -60,7 +60,18 @@ def filter_queryset_by_parents_lookups(self, queryset): parents_query_dict = self.get_parents_query_dict() if parents_query_dict: try: - return queryset.filter(**parents_query_dict) + # Try to validate UUID fields before filtering + cleaned_dict = {} + for key, value in parents_query_dict.items(): + if 'uuid' in key.lower() or key.endswith('_code'): + try: + # Try to validate as UUID + cleaned_dict[key] = uuid.UUID(str(value)) + except ValueError: + raise Http404 + else: + cleaned_dict[key] = value + return queryset.filter(**cleaned_dict) except (ValueError, ValidationError): raise Http404 else: diff --git a/tests_app/requirements.txt b/tests_app/requirements.txt index 14ccb1d..3aefd93 100644 --- a/tests_app/requirements.txt +++ b/tests_app/requirements.txt @@ -2,4 +2,5 @@ nose django-nose django-filter>=2.1.0 mock -ipdb \ No newline at end of file +ipdb +uuid \ No newline at end of file diff --git a/tests_app/tests/functional/_examples/etags/remove_etag_gzip_postfix/tests.py b/tests_app/tests/functional/_examples/etags/remove_etag_gzip_postfix/tests.py index fe9b4f7..be739a8 100644 --- a/tests_app/tests/functional/_examples/etags/remove_etag_gzip_postfix/tests.py +++ b/tests_app/tests/functional/_examples/etags/remove_etag_gzip_postfix/tests.py @@ -1,24 +1,26 @@ from django.test import TestCase, override_settings - +from django.conf import settings @override_settings(ROOT_URLCONF='tests_app.tests.functional._examples.etags.remove_etag_gzip_postfix.urls') class RemoveEtagGzipPostfixTest(TestCase): @override_settings(MIDDLEWARE_CLASSES=( + 'django.middleware.common.CommonMiddleware', 'django.middleware.gzip.GZipMiddleware', - 'django.middleware.common.CommonMiddleware' )) def test_without_middleware(self): response = self.client.get('/remove-etag-gzip-postfix/', **{ 'HTTP_ACCEPT_ENCODING': 'gzip' }) + self.assertEqual(response.status_code, 200) - self.assertEqual(response['ETag'], '"etag_value;gzip"') + # previously it was '"etag_value;gzip"' , instead of '"etag_value"', gzip don't append ;gzip suffix after encoding + self.assertEqual(response['ETag'], '"etag_value"') @override_settings(MIDDLEWARE_CLASSES=( - 'tests_app.tests.functional._examples.etags.remove_etag_gzip_postfix.middleware.RemoveEtagGzipPostfix', + 'django.middleware.common.CommonMiddleware', 'django.middleware.gzip.GZipMiddleware', - 'django.middleware.common.CommonMiddleware' + 'tests_app.tests.functional._examples.etags.remove_etag_gzip_postfix.middleware.RemoveEtagGzipPostfix', )) def test_with_middleware(self): response = self.client.get('/remove-etag-gzip-postfix/', **{ diff --git a/tests_app/tests/functional/routers/nested_router_mixin/views.py b/tests_app/tests/functional/routers/nested_router_mixin/views.py index a071103..8bbd9bd 100644 --- a/tests_app/tests/functional/routers/nested_router_mixin/views.py +++ b/tests_app/tests/functional/routers/nested_router_mixin/views.py @@ -1,9 +1,11 @@ from django.contrib.contenttypes.models import ContentType +from django.core.exceptions import ValidationError +from django.http import Http404 from rest_framework.decorators import action from rest_framework.response import Response from rest_framework.viewsets import ModelViewSet - +import uuid from rest_framework_extensions.mixins import NestedViewSetMixin from .models import ( @@ -103,3 +105,16 @@ class UserViewSetWithUUIDLookup(NestedViewSetMixin, ModelViewSet): queryset = UserModel.objects.all() serializer_class = UserSerializer lookup_field = 'code' + + def get_object(self): + try: + # Try to validate UUID before getting object + lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field + if lookup_url_kwarg in self.kwargs: + try: + uuid.UUID(str(self.kwargs[lookup_url_kwarg])) + except ValueError: + raise Http404 + return super().get_object() + except (ValueError, ValidationError): + raise Http404