diff --git a/Web/app.py b/Web/app.py
index 0d18962..6cc2388 100755
--- a/Web/app.py
+++ b/Web/app.py
@@ -9961,25 +9961,28 @@ def fetch_book_info(isbn):
app.logger.error(f"Error fetching book data: {e}")
return jsonify({"error": f"Failed to fetch book information"}), 500
+
@app.route('/download_book_cover', methods=['POST'])
def download_book_cover():
"""
- API endpoint to download and save a book cover image from URL
+ API endpoint to download a book cover image from URL
+ and save it directly to MongoDB GridFS.
"""
if 'username' not in session:
return jsonify({"error": "Not authorized"}), 403
+
current_permissions = us.get_effective_permissions(session['username'])
-
+
if not current_permissions['actions'].get('can_insert', False):
- flash('Ihnen fehlen die nötigen Berechtigungen, um diese Aktion auszuführen.', 'error')
- return redirect(url_for('library_view'))
+ return jsonify({"error": "Ihnen fehlen die nötigen Berechtigungen."}), 403
+
if not cfg.MODULES.is_enabled('library'):
return jsonify({"error": "Bibliotheks-Modul ist deaktiviert."}), 403
-
+
try:
data = request.get_json()
image_url = data.get('url')
-
+
if not image_url:
return jsonify({"error": "No image URL provided"}), 400
@@ -9987,71 +9990,69 @@ def download_book_cover():
if parsed_url.scheme != 'https' or not parsed_url.netloc:
return jsonify({"error": "Only public HTTPS URLs are allowed"}), 400
- # 2. SSRF Protection: Strict Allowlist Check
- # if parsed_url.netloc not in ALLOWED_COVER_DOMAINS:
- # return jsonify({"error": "Target host is not an allowed book cover provider"}), 403
-
- # Download the image (allow_redirects=False prevents redirecting to internal IPs)
response = requests.get(image_url, stream=True, timeout=10, allow_redirects=False)
-
+
if response.status_code != 200:
return jsonify({"error": f"Failed to download image: Status {response.status_code}"}), 400
-
- # Check content type
+
content_type = response.headers.get('content-type', '')
allowed_types = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif']
-
+
if not any(allowed_type in content_type.lower() for allowed_type in allowed_types):
return jsonify({
"error": f"Nicht unterstütztes Bildformat: {content_type}. Erlaubte Formate: JPG, JPEG, PNG, GIF"
}), 400
- # Check content length header
content_length = response.headers.get('Content-Length')
if content_length:
try:
if int(content_length) > 5 * 1024 * 1024:
- return jsonify({"error": "Image is too large"}), 413
+ return jsonify({"error": "Image is too large (max 5MB)"}), 413
except ValueError:
pass
-
- # Generate a fully unique filename
+
unique_id = str(uuid.uuid4())
timestamp = time.strftime("%Y%m%d%H%M%S")
-
+
extension = '.jpg' # default
if 'image/png' in content_type.lower():
extension = '.png'
elif 'image/gif' in content_type.lower():
extension = '.gif'
-
+
filename = f"book_cover_{unique_id}_{timestamp}{extension}"
- filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
-
- # Save image in chunks (prevents memory exhaustion and enforces size limits)
- with open(filepath, 'wb') as f:
- written = 0
- for chunk in response.iter_content(chunk_size=8192):
- written += len(chunk)
- if written > 5 * 1024 * 1024:
- # Clean up the partial file before aborting
- os.remove(filepath)
- return jsonify({"error": "Image is too large"}), 413
- f.write(chunk)
-
+
+ image_data = io.BytesIO()
+ written = 0
+
+ for chunk in response.iter_content(chunk_size=8192):
+ written += len(chunk)
+ if written > 5 * 1024 * 1024:
+ return jsonify({"error": "Image is too large (max 5MB)"}), 413
+ image_data.write(chunk)
+
+ image_data.seek(0)
+
+ fs = get_gridfs()
+ fs.put(
+ image_data,
+ filename=filename,
+ content_type=content_type
+ )
+
return jsonify({
"success": True,
"filename": filename,
- "message": "Image downloaded successfully"
+ "message": "Image downloaded and stored directly in database"
})
-
+
except requests.exceptions.RequestException as e:
app.logger.error(f"Network error downloading book cover: {e}")
return jsonify({"error": "Netzwerkfehler beim Herunterladen des Bildes."}), 500
except Exception as e:
app.logger.error(f"Error downloading book cover: {e}")
- # Fixed syntax here: Removed the injected HTML that was appended to this line
- return jsonify({"error": f"Failed to download image"}), 500
+ return jsonify({"error": "Failed to download image"}), 500
+
"""
@app.route('/proxy_image')
def proxy_image():
diff --git a/Web/templates/upload_admin.html b/Web/templates/upload_admin.html
index edf12a9..3c33787 100755
--- a/Web/templates/upload_admin.html
+++ b/Web/templates/upload_admin.html
@@ -876,12 +876,14 @@
-
+
@@ -1669,23 +1671,24 @@
}, 3000);
}
- // Function to download book cover image
function downloadBookCover(imageUrl) {
if (!imageUrl) {
console.log('No image URL provided');
return;
}
-
- // Show loading indicator for image download
- const imagePreviewContainer = document.getElementById('image-preview-container');
- if (imagePreviewContainer) {
- const loadingDiv = document.createElement('div');
- loadingDiv.className = 'image-loading';
- loadingDiv.innerHTML = 'Buchcover wird heruntergeladen...
';
- imagePreviewContainer.appendChild(loadingDiv);
+
+ const coverPreviewContainer = document.getElementById('book-cover-preview-container');
+
+ if (!coverPreviewContainer) {
+ console.error('Error: "book-cover-preview-container" not found in the DOM.');
+ return;
}
-
- // Download the image via backend
+
+ const loadingDiv = document.createElement('div');
+ loadingDiv.className = 'image-loading';
+ loadingDiv.innerHTML = 'Buchcover wird heruntergeladen...
';
+ coverPreviewContainer.appendChild(loadingDiv);
+
fetch('/download_book_cover', {
method: 'POST',
headers: {
@@ -1693,78 +1696,63 @@
},
body: JSON.stringify({ url: imageUrl })
})
- .then(response => response.json())
- .then(data => {
- // Remove loading indicator
- const loadingDiv = imagePreviewContainer?.querySelector('.image-loading');
- if (loadingDiv) {
- loadingDiv.remove();
- }
-
- if (data.success) {
- // Create a preview of the downloaded image
- const imagePreview = document.createElement('div');
- imagePreview.className = 'book-cover-preview';
- imagePreview.innerHTML = `
-
-
 }}${data.filename})
-
Buchcover automatisch heruntergeladen
-
-
-
- `;
-
- if (imagePreviewContainer) {
- imagePreviewContainer.appendChild(imagePreview);
+ .then(response => response.json())
+ .then(data => {
+ const currentLoadingDiv = coverPreviewContainer.querySelector('.image-loading');
+ if (currentLoadingDiv) {
+ currentLoadingDiv.remove();
}
-
- console.log('Book cover downloaded successfully:', data.filename);
- } else {
- console.error('Failed to download book cover:', data.error);
- // Show error message to user
- if (imagePreviewContainer) {
- const errorDiv = document.createElement('div');
- errorDiv.className = 'error-message';
- errorDiv.textContent = 'Fehler beim Herunterladen des Buchcovers: ' + data.error;
- errorDiv.style.fontSize = '0.8em';
- errorDiv.style.padding = '5px';
- errorDiv.style.marginTop = '5px';
- imagePreviewContainer.appendChild(errorDiv);
-
- // Remove error message after 5 seconds
- setTimeout(() => errorDiv.remove(), 5000);
+
+ if (data.success) {
+ coverPreviewContainer.innerHTML = '';
+
+ const imagePreview = document.createElement('div');
+ imagePreview.className = 'book-cover-preview';
+ imagePreview.innerHTML = `
+
+

+
Buchcover automatisch heruntergeladen
+
+
+
+ `;
+
+ coverPreviewContainer.appendChild(imagePreview);
+ console.log('Book cover downloaded successfully:', data.filename);
+ } else {
+ console.error('Failed to download book cover:', data.error);
+ showCoverError(coverPreviewContainer, 'Fehler beim Herunterladen des Buchcovers: ' + data.error);
}
- }
- })
- .catch(error => {
- console.error('Error downloading book cover:', error);
- // Remove loading indicator
- const loadingDiv = imagePreviewContainer?.querySelector('.image-loading');
- if (loadingDiv) {
- loadingDiv.remove();
- }
-
- // Show error message
- if (imagePreviewContainer) {
- const errorDiv = document.createElement('div');
- errorDiv.className = 'error-message';
- errorDiv.textContent = 'Netzwerkfehler beim Herunterladen des Buchcovers';
- errorDiv.style.fontSize = '0.8em';
- errorDiv.style.padding = '5px';
- errorDiv.style.marginTop = '5px';
- imagePreviewContainer.appendChild(errorDiv);
-
- // Remove error message after 5 seconds
- setTimeout(() => errorDiv.remove(), 5000);
- }
- });
+ })
+ .catch(error => {
+ console.error('Error downloading book cover:', error);
+
+ const currentLoadingDiv = coverPreviewContainer.querySelector('.image-loading');
+ if (currentLoadingDiv) {
+ currentLoadingDiv.remove();
+ }
+
+ showCoverError(coverPreviewContainer, 'Netzwerkfehler beim Herunterladen des Buchcovers');
+ });
}
-
- // Function to remove downloaded book cover
+
+ function showCoverError(container, message) {
+ const errorDiv = document.createElement('div');
+ errorDiv.className = 'error-message';
+ errorDiv.textContent = message;
+ errorDiv.style.fontSize = '0.8em';
+ errorDiv.style.color = 'red';
+ errorDiv.style.padding = '5px';
+ errorDiv.style.marginTop = '5px';
+ container.appendChild(errorDiv);
+
+ setTimeout(() => errorDiv.remove(), 5000);
+ }
+
function removeBookCover(button) {
const previewItem = button.closest('.preview-item');
if (previewItem) {
@@ -1772,7 +1760,6 @@
}
}
- // Code validation functions
function checkCodeUnique(code, excludeId, callback) {
if (!code || code.trim() === '') {
callback(true);