diff --git a/cookbook/integration/cookbookapp.py b/cookbook/integration/cookbookapp.py index 898887d3..f22e9d45 100644 --- a/cookbook/integration/cookbookapp.py +++ b/cookbook/integration/cookbookapp.py @@ -6,6 +6,7 @@ from gettext import gettext as _ from io import BytesIO import requests +import validators import yaml from cookbook.helper.ingredient_parser import IngredientParser @@ -59,8 +60,10 @@ class CookBookApp(Integration): if len(images) > 0: try: - response = requests.get(images[0]) - self.import_recipe_image(recipe, BytesIO(response.content)) + url = images[0] + if validators.url(url, public=True): + response = requests.get(url) + self.import_recipe_image(recipe, BytesIO(response.content)) except Exception as e: print('failed to import image ', str(e)) diff --git a/cookbook/integration/cookmate.py b/cookbook/integration/cookmate.py index fa1e9ce2..e804180a 100644 --- a/cookbook/integration/cookmate.py +++ b/cookbook/integration/cookmate.py @@ -5,6 +5,7 @@ from io import BytesIO from gettext import gettext as _ import requests +import validators from lxml import etree from cookbook.helper.ingredient_parser import IngredientParser @@ -64,7 +65,9 @@ class Cookmate(Integration): if recipe_xml.find('imageurl') is not None: try: - response = requests.get(recipe_xml.find('imageurl').text.strip()) + url = recipe_xml.find('imageurl').text.strip() + if validators.url(url, public=True): + response = requests.get(url) self.import_recipe_image(recipe, BytesIO(response.content)) except Exception as e: print('failed to import image ', str(e)) diff --git a/cookbook/integration/recettetek.py b/cookbook/integration/recettetek.py index 7eb93a8e..c774b3d5 100644 --- a/cookbook/integration/recettetek.py +++ b/cookbook/integration/recettetek.py @@ -5,6 +5,7 @@ from io import BytesIO from zipfile import ZipFile import requests +import validators from django.utils.translation import gettext as _ from cookbook.helper.image_processing import get_filetype @@ -123,11 +124,13 @@ class RecetteTek(Integration): self.import_recipe_image(recipe, BytesIO(import_zip.read(image_file_name)), filetype=get_filetype(image_file_name)) else: if file['originalPicture'] != '': - response = requests.get(file['originalPicture']) - if imghdr.what(BytesIO(response.content)) is not None: - self.import_recipe_image(recipe, BytesIO(response.content), filetype=get_filetype(file['originalPicture'])) - else: - raise Exception("Original image failed to download.") + url = file['originalPicture'] + if validators.url(url, public=True): + response = requests.get(url) + if imghdr.what(BytesIO(response.content)) is not None: + self.import_recipe_image(recipe, BytesIO(response.content), filetype=get_filetype(file['originalPicture'])) + else: + raise Exception("Original image failed to download.") except Exception as e: print(recipe.name, ': failed to import image ', str(e)) diff --git a/cookbook/integration/recipesage.py b/cookbook/integration/recipesage.py index 29bf0eb4..149c1c5d 100644 --- a/cookbook/integration/recipesage.py +++ b/cookbook/integration/recipesage.py @@ -2,6 +2,7 @@ import json from io import BytesIO import requests +import validators from cookbook.helper.ingredient_parser import IngredientParser from cookbook.integration.integration import Integration @@ -51,8 +52,10 @@ class RecipeSage(Integration): if len(file['image']) > 0: try: - response = requests.get(file['image'][0]) - self.import_recipe_image(recipe, BytesIO(response.content)) + url = file['image'][0] + if validators.url(url, public=True): + response = requests.get(url) + self.import_recipe_image(recipe, BytesIO(response.content)) except Exception as e: print('failed to import image ', str(e)) diff --git a/cookbook/provider/dropbox.py b/cookbook/provider/dropbox.py index ca6994b9..7d8dcc75 100644 --- a/cookbook/provider/dropbox.py +++ b/cookbook/provider/dropbox.py @@ -4,6 +4,8 @@ import os from datetime import datetime import requests +import validators + from cookbook.models import Recipe, RecipeImport, SyncLog from cookbook.provider.provider import Provider @@ -104,9 +106,11 @@ class Dropbox(Provider): recipe.link = Dropbox.get_share_link(recipe) recipe.save() - response = requests.get(recipe.link.replace('www.dropbox.', 'dl.dropboxusercontent.')) + url = recipe.link.replace('www.dropbox.', 'dl.dropboxusercontent.') + if validators.url(url, public=True): + response = requests.get(url) - return io.BytesIO(response.content) + return io.BytesIO(response.content) @staticmethod def rename_file(recipe, new_name): diff --git a/cookbook/provider/nextcloud.py b/cookbook/provider/nextcloud.py index e7b100df..9399e104 100644 --- a/cookbook/provider/nextcloud.py +++ b/cookbook/provider/nextcloud.py @@ -4,6 +4,7 @@ import tempfile from datetime import datetime import requests +import validators import webdav3.client as wc from cookbook.models import Recipe, RecipeImport, SyncLog from cookbook.provider.provider import Provider @@ -92,20 +93,21 @@ class Nextcloud(Provider): "Content-Type": "application/json" } - r = requests.get( - url, - headers=headers, - auth=HTTPBasicAuth( - recipe.storage.username, recipe.storage.password + if validators.url(url, public=True): + r = requests.get( + url, + headers=headers, + auth=HTTPBasicAuth( + recipe.storage.username, recipe.storage.password + ) ) - ) - response_json = r.json() - for element in response_json['ocs']['data']: - if element['share_type'] == '3': - return element['url'] + response_json = r.json() + for element in response_json['ocs']['data']: + if element['share_type'] == '3': + return element['url'] - return Nextcloud.create_share_link(recipe) + return Nextcloud.create_share_link(recipe) @staticmethod def get_file(recipe): diff --git a/cookbook/views/api.py b/cookbook/views/api.py index b07aaf35..3d1ec493 100644 --- a/cookbook/views/api.py +++ b/cookbook/views/api.py @@ -6,6 +6,7 @@ import uuid from collections import OrderedDict import requests +import validators from PIL import UnidentifiedImageError from annoying.decorators import ajax_request from annoying.functions import get_object_or_None @@ -14,7 +15,7 @@ from django.contrib.auth.models import User from django.contrib.postgres.search import TrigramSimilarity from django.core.exceptions import FieldError, ValidationError from django.core.files import File -from django.db.models import (Case, Count, Exists, F, IntegerField, OuterRef, ProtectedError, Q, +from django.db.models import (Case, Count, Exists, OuterRef, ProtectedError, Q, Subquery, Value, When) from django.db.models.fields.related import ForeignObjectRel from django.db.models.functions import Coalesce, Lower @@ -24,7 +25,6 @@ from django.urls import reverse from django.utils.translation import gettext as _ from django_scopes import scopes_disabled from icalendar import Calendar, Event -from recipe_scrapers import NoSchemaFoundInWildMode, WebsiteNotImplementedError, scrape_me from requests.exceptions import MissingSchema from rest_framework import decorators, status, viewsets from rest_framework.exceptions import APIException, PermissionDenied @@ -34,6 +34,7 @@ from rest_framework.renderers import JSONRenderer, TemplateHTMLRenderer from rest_framework.response import Response from rest_framework.viewsets import ViewSetMixin from treebeard.exceptions import InvalidMoveToDescendant, InvalidPosition, PathOverflow +from validators import ValidationFailure from cookbook.helper.HelperFunctions import str2bool from cookbook.helper.image_processing import handle_image @@ -43,7 +44,6 @@ from cookbook.helper.permission_helper import (CustomIsAdmin, CustomIsGuest, Cus group_required) from cookbook.helper.recipe_html_import import get_recipe_from_source from cookbook.helper.recipe_search import RecipeFacet, RecipeSearch, old_search -from cookbook.helper.recipe_url_import import get_from_scraper from cookbook.helper.shopping_helper import RecipeShoppingEditor, shopping_helper from cookbook.models import (Automation, BookmarkletImport, CookLog, CustomFilter, ExportLog, Food, FoodInheritField, ImportLog, Ingredient, Keyword, MealPlan, MealType, @@ -774,16 +774,18 @@ class RecipeViewSet(viewsets.ModelViewSet): if serializer.is_valid(): serializer.save() image = None - filetype = ".jpeg" # fall-back to .jpeg, even if wrong, at least users will know it's an image and most image viewers can open it correctly anyways + filetype = ".jpeg" # fall-back to .jpeg, even if wrong, at least users will know it's an image and most image viewers can open it correctly anyways if 'image' in serializer.validated_data: image = obj.image filetype = mimetypes.guess_extension(serializer.validated_data['image'].content_type) or filetype elif 'image_url' in serializer.validated_data: try: - response = requests.get(serializer.validated_data['image_url']) - image = File(io.BytesIO(response.content)) - filetype = mimetypes.guess_extension(response.headers['content-type']) or filetype + url = serializer.validated_data['image_url'] + if validators.url(url, public=True): + response = requests.get(url) + image = File(io.BytesIO(response.content)) + filetype = mimetypes.guess_extension(response.headers['content-type']) or filetype except UnidentifiedImageError as e: print(e) pass @@ -1188,7 +1190,13 @@ def recipe_from_source(request): # in manual mode request complete page to return it later if url: try: - data = requests.get(url, headers=external_request_headers).content + if validators.url(url, public=True): + data = requests.get(url, headers=external_request_headers).content + else: + return JsonResponse({ + 'error': True, + 'msg': _('Invalid Url') + }, status=400) except requests.exceptions.ConnectionError: return JsonResponse({ 'error': True, @@ -1199,6 +1207,7 @@ def recipe_from_source(request): 'error': True, 'msg': _('Bad URL Schema.') }, status=400) + recipe_json, recipe_tree, recipe_html, recipe_images = get_recipe_from_source(data, url, request) if len(recipe_tree) == 0 and len(recipe_json) == 0: return JsonResponse({ diff --git a/requirements.txt b/requirements.txt index 08c729fe..5b9871c8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -43,3 +43,4 @@ python-ldap==3.4.0 django-auth-ldap==4.0.0 pytest-factoryboy==2.1.0 pyppeteer==1.0.2 +validators==0.19.0 \ No newline at end of file