From 2688df46ef151f49edc16b2d6ee0f93979c1a2a4 Mon Sep 17 00:00:00 2001 From: lizerk Date: Fri, 20 Mar 2026 09:10:33 +0300 Subject: [PATCH] Fix: Wrap all unsafe int() conversions with try/except Fixed ValueError-prone int() calls in: - communications/api/viewsets.py (4 places: limit, before_limit, after_limit) - notifications/api/views.py (2 places: page, page_size) - api/v1/search/views.py (1 place: limit) - api/v1/procurement/views.py (1 place: quantity) Also updated audit script to exclude .venv/ --- backend/api/v1/procurement/views.py | 5 ++++- backend/api/v1/search/views.py | 5 ++++- backend/communications/api/viewsets.py | 20 ++++++++++++++++---- backend/notifications/api/views.py | 10 ++++++++-- backend/scripts/audit_code_patterns.sh | 11 ++++++----- 5 files changed, 38 insertions(+), 13 deletions(-) diff --git a/backend/api/v1/procurement/views.py b/backend/api/v1/procurement/views.py index f8cdc497..4d1e5d4f 100644 --- a/backend/api/v1/procurement/views.py +++ b/backend/api/v1/procurement/views.py @@ -902,7 +902,10 @@ class EquipmentViewSet(viewsets.ModelViewSet): Если передан параметр quantity > 1, создаётся несколько единиц с автоматически сгенерированными инвентарными номерами. """ - quantity = int(request.data.get('quantity', 1)) + try: + quantity = int(request.data.get('quantity', 1)) + except (ValueError, TypeError): + quantity = 1 quantity = max(1, min(quantity, 100)) # Ограничение от 1 до 100 if quantity == 1: diff --git a/backend/api/v1/search/views.py b/backend/api/v1/search/views.py index 7de80fde..56562686 100644 --- a/backend/api/v1/search/views.py +++ b/backend/api/v1/search/views.py @@ -294,7 +294,10 @@ def search_api_view(request: DRFRequest) -> Response: """ raw_q = request.query_params.get("q", "") or "" query = escape(raw_q.strip()) - limit = int(request.query_params.get("limit", 10)) + try: + limit = int(request.query_params.get("limit", 10)) + except (ValueError, TypeError): + limit = 10 if not query: return Response( diff --git a/backend/communications/api/viewsets.py b/backend/communications/api/viewsets.py index a8846ebf..fa2ebe53 100644 --- a/backend/communications/api/viewsets.py +++ b/backend/communications/api/viewsets.py @@ -542,7 +542,10 @@ class ChatViewSet(viewsets.ModelViewSet): before_id = request.query_params.get('before_id') after_ts = request.query_params.get('after') after_id = request.query_params.get('after_id') - limit = min(int(request.query_params.get('limit', 50)), 100) + try: + limit = min(int(request.query_params.get('limit', 50)), 100) + except (ValueError, TypeError): + limit = 50 queryset = chat.messages.filter(is_deleted=False).select_related( 'author', 'reply_to', 'reply_to__author', 'poll' @@ -623,12 +626,21 @@ class ChatViewSet(viewsets.ModelViewSet): around_id = request.query_params.get('around_id') # Асимметричные лимиты (24 + 10 = 34 сообщения ) - before_limit = int(request.query_params.get('before_limit', 24)) - after_limit = int(request.query_params.get('after_limit', 10)) + try: + before_limit = int(request.query_params.get('before_limit', 24)) + except (ValueError, TypeError): + before_limit = 24 + try: + after_limit = int(request.query_params.get('after_limit', 10)) + except (ValueError, TypeError): + after_limit = 10 # Для обратной совместимости с limit параметром if 'limit' in request.query_params and 'before_limit' not in request.query_params: - total_limit = min(int(request.query_params.get('limit', 30)), 100) + try: + total_limit = min(int(request.query_params.get('limit', 30)), 100) + except (ValueError, TypeError): + total_limit = 30 before_limit = int(total_limit * 0.8) # 80% на контекст after_limit = total_limit - before_limit # 20% на новые diff --git a/backend/notifications/api/views.py b/backend/notifications/api/views.py index 81da77f9..516855fe 100644 --- a/backend/notifications/api/views.py +++ b/backend/notifications/api/views.py @@ -37,8 +37,14 @@ def get_notifications(request): - unread_only: только непрочитанные (true/false) - search: поиск по описанию """ - page = int(request.GET.get('page', 1)) - page_size = int(request.GET.get('page_size', 20)) + try: + page = int(request.GET.get('page', 1)) + except (ValueError, TypeError): + page = 1 + try: + page_size = int(request.GET.get('page_size', 20)) + except (ValueError, TypeError): + page_size = 20 verb = request.GET.get('verb') unread_only = request.GET.get('unread_only', 'false').lower() == 'true' search = request.GET.get('search', '').strip() diff --git a/backend/scripts/audit_code_patterns.sh b/backend/scripts/audit_code_patterns.sh index 90992edd..8a701c33 100755 --- a/backend/scripts/audit_code_patterns.sh +++ b/backend/scripts/audit_code_patterns.sh @@ -1,27 +1,28 @@ #!/bin/bash # Аудит потенциально проблемных паттернов в коде +# Исключает .venv/ (сторонние пакеты) echo "=== Поиск потенциальных проблем ===" echo "" echo "1. Небезопасный доступ к request.data:" -grep -rn "request\.data\[" --include="*.py" . | grep -v "# noqa" || echo "✓ Не найдено" +grep -rn "request\.data\[" --include="*.py" . --exclude-dir=.venv | grep -v "# noqa" || echo "✓ Не найдено" echo "" echo "2. Использование .dict() без проверки типа:" -grep -rn "\.data\.dict()" --include="*.py" . | grep -v "hasattr" || echo "✓ Не найдено" +grep -rn "\.data\.dict()" --include="*.py" . --exclude-dir=.venv | grep -v "hasattr" || echo "✓ Не найдено" echo "" echo "3. Прямое обращение к QueryDict без .get():" -grep -rn "request\.POST\[" --include="*.py" . | grep -v "# noqa" || echo "✓ Не найдено" +grep -rn "request\.POST\[" --include="*.py" . --exclude-dir=.venv | grep -v "# noqa" || echo "✓ Не найдено" echo "" echo "4. Небезопасный int() без try/except:" -grep -rn "int(.*request\." --include="*.py" . | head -20 +grep -rn "int(.*request\." --include="*.py" . --exclude-dir=.venv | head -20 echo "" echo "5. Отсутствие проверки на None перед .strip():" -grep -rn "\.strip()" --include="*.py" . | grep -v "if.*:" | head -10 +grep -rn "\.strip()" --include="*.py" . --exclude-dir=.venv | grep -v "if.*:" | head -10 echo "" echo "=== Аудит завершен ==="