from flask import Flask, request, jsonify, g from flask_limiter import Limiter from flask_limiter.util import get_remote_address import sqlite3 import os import logging import sys import bleach from datetime import datetime import re import hashlib import time app = Flask(__name__) DATABASE = '/var/www/comments/comments.db' # Set up logging logging.basicConfig( level=logging.DEBUG, format='%(asctime)s %(levelname)s: %(message)s', handlers=[ logging.StreamHandler(sys.stderr), logging.FileHandler('/var/log/comments.log', mode='a') ] ) # Initialize rate limiter limiter = Limiter( app=app, key_func=get_remote_address, default_limits=["5 per minute", "100 per day"], storage_uri="memory://" ) def get_db(): db = getattr(g, '_database', None) if db is None: db = g._database = sqlite3.connect(DATABASE) db.row_factory = sqlite3.Row return db @app.teardown_appcontext def close_connection(exception): db = getattr(g, '_database', None) if db is not None: db.close() def is_spam(text): spam_patterns = [ r'https?://\S+', # URLs r'\b(?:viagra|cialis)\b', # Common spam words r'\b(?:buy|sell|cheap)\b.*\b(?:now|online)\b' ] return any(re.search(pattern, text.lower()) for pattern in spam_patterns) def validate_input(data): if not all(k in data for k in ['name', 'comment', 'post_id']): return False if len(data['name']) > 50 or len(data['comment']) > 1000: return False if not data['name'].strip() or not data['comment'].strip(): return False return True @app.route('/api/comments/', methods=['GET']) @limiter.limit("30 per minute") # Rate limit for reading comments def get_comments(post_id): try: app.logger.debug(f"Getting comments for post_id: {post_id}") db = get_db() cursor = db.execute( 'SELECT * FROM comments WHERE post_id = ? ORDER BY created_at DESC', [post_id] ) comments = [dict(row) for row in cursor.fetchall()] app.logger.debug(f"Found {len(comments)} comments") return jsonify(comments) except Exception as e: app.logger.error(f"Error getting comments: {str(e)}") return jsonify({'error': 'Internal server error'}), 500 @app.route('/api/comments', methods=['POST']) @limiter.limit("5 per minute") # Stricter rate limit for posting def add_comment(): try: app.logger.debug("Received comment submission") data = request.get_json() if not data: app.logger.warning("No JSON data received") return jsonify({'error': 'No data provided'}), 400 if not validate_input(data): app.logger.warning("Invalid input data") return jsonify({'error': 'Invalid input'}), 400 if is_spam(data['comment']): app.logger.warning("Spam detected") return jsonify({'error': 'Comment detected as potential spam'}), 400 # Clean input clean_name = bleach.clean(data['name'])[:50] clean_comment = bleach.clean(data['comment'])[:1000] clean_post_id = bleach.clean(data['post_id']) # Create hash for duplicate detection comment_hash = hashlib.md5( f"{clean_name}{clean_comment}".encode() ).hexdigest() db = get_db() # Check for duplicate comments cursor = db.execute('SELECT id FROM comments WHERE comment_hash = ?', [comment_hash]) if cursor.fetchone(): return jsonify({'error': 'Duplicate comment'}), 400 cursor = db.execute( '''INSERT INTO comments (post_id, name, comment, created_at, comment_hash) VALUES (?, ?, ?, ?, ?)''', [clean_post_id, clean_name, clean_comment, datetime.utcnow().isoformat(), comment_hash] ) db.commit() app.logger.debug(f"Comment added successfully with id {cursor.lastrowid}") return jsonify({'status': 'success', 'id': cursor.lastrowid}), 201 except Exception as e: app.logger.error(f"Error adding comment: {str(e)}") return jsonify({'error': 'Internal server error'}), 500 @app.route('/api/comments/', methods=['DELETE']) def delete_comment(comment_id): try: db = get_db() cursor = db.execute('DELETE FROM comments WHERE id = ?', [comment_id]) db.commit() if cursor.rowcount == 0: return jsonify({'error': 'Comment not found'}), 404 return jsonify({'status': 'success', 'message': f'Comment {comment_id} deleted'}), 200 except Exception as e: app.logger.error(f"Error deleting comment: {str(e)}") return jsonify({'error': 'Internal server error'}), 500 if __name__ == '__main__': app.logger.info("Starting Flask application...") app.run(host='127.0.0.1', port=5000)