Skip to main content

kemal-upload

Handling file uploads and storage in Kemal using body size safety configuration and established project patterns.

Datos de origen

Repositorio
kemalcr/kemal
Última actividad en el origen
8 de septiembre de 2026 a las 14:03
Idioma detectado de SKILL.md
inglés
Estrellas
3923
Forks
206

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
name
kemal-upload
description
Handling file uploads and storage in Kemal using body size safety configuration and established project patterns.
license
MIT
# Kemal File Uploads & Storage This skill provides expert guidance on implementing file uploads and storage in Kemal, with explicit safety bounds and validation, strictly following patterns from [`kemal-by-example/file-upload-storage`](https://github.com/sdogruyol/kemal-by-example/tree/master/file-upload-storage). ## Version Notes - `Kemal.config.max_request_body_size`: since Kemal 1.9. - `Kemal.config.max_multipart_form_field_size`: since Kemal 1.11. - Upload tempfile cleanup in `Kemal::InitHandler` (runs for every request, including halted filters): since Kemal 1.13.0. ## Core Mandates - **Global Size Limit Configuration (Security in Kemal 1.9+ & 1.11+):** Set global request and multipart limits in the entrypoint file: ```crystal # Maximum overall request body size (Kemal 1.9+, e.g. 50MB) Kemal.config.max_request_body_size = 50 * 1024 * 1024 # Maximum individual multipart form field size (Kemal 1.11+, e.g. 8MB) Kemal.config.max_multipart_form_field_size = 8 * 1024 * 1024 ``` - **Uploading:** Use `env.params.files.has_key?("file")` to check for file presence, then `env.params.files["file"]` to access it: ```crystal unless env.params.files.has_key?("file") env.redirect "/files?error=Please+choose+a+file+to+upload." next "" end uploaded_file = env.params.files["file"] ``` - **Pre-Validation:** Check the size of the upload before storage (`size` is the byte count written to disk): ```crystal max_size = 50_u64 * 1024 * 1024 upload_size = uploaded_file.size || 0_u64 if upload_size > max_size env.redirect "/files?error=File+size+must+be+50MB+or+smaller." next "" end ``` - **Extension Validation:** Check allowed extensions before processing: ```crystal unless StoredFile.allowed_extension?(original_name) env.redirect "/files?error=Only+images,+PDF,+and+TXT+files+are+allowed." next "" end ``` - **Handling Files:** ```crystal extension = ::File.extname(original_name).downcase stored_name = "#{Random::Secure.hex(16)}#{extension}" destination = ::File.join(Kemal.config.public_folder, "uploads", stored_name) uploaded_file.open do |upload| ::File.open(destination, "w") do |file| IO.copy(upload, file) end end ``` - **MIME Type Detection:** Use a fallback for missing Content-Type headers: ```crystal mime_type = uploaded_file.headers["Content-Type"]? || "application/octet-stream" ``` - **File Deletion:** Always check existence before deleting: ```crystal ::File.delete(stored_file.storage_path) if ::File.exists?(stored_file.storage_path) ``` ## Patterns from Source Code ### Upload Route (file-upload-storage/src/routes/files.cr) ```crystal post "/files/upload" do |env| unless env.params.files.has_key?("file") env.redirect "/files?error=Please+choose+a+file+to+upload." next "" end uploaded_file = env.params.files["file"] original_name = uploaded_file.filename || "upload" unless StoredFile.allowed_extension?(original_name) env.redirect "/files?error=Only+images,+PDF,+and+TXT+files+are+allowed." next "" end max_size = 50_u64 * 1024 * 1024 # Check size before copying: upload_size = uploaded_file.size || 0_u64 if upload_size > max_size env.redirect "/files?error=File+size+must+be+50MB+or+smaller." next "" end extension = ::File.extname(original_name).downcase stored_name = "#{Random::Secure.hex(16)}#{extension}" destination = ::File.join(Kemal.config.public_folder, "uploads", stored_name) uploaded_file.open do |upload| ::File.open(destination, "w") do |file| IO.copy(upload, file) end end mime_type = uploaded_file.headers["Content-Type"]? || "application/octet-stream" StoredFile.create(original_name, stored_name, mime_type, upload_size) env.redirect "/files?notice=File+uploaded+successfully." end ``` ### StoredFile Model with Extension Validation (file-upload-storage/src/models/stored_file.cr) ```crystal class StoredFile include DB::Serializable ALLOWED_EXTENSIONS = %w[.jpg .jpeg .png .gif .pdf .txt] getter id : Int64? getter original_name : String getter stored_name : String getter mime_type : String getter size_bytes : Int64 getter created_at : String def storage_path : String ::File.join(Kemal.config.public_folder, "uploads", stored_name) end def self.allowed_extension?(filename : String) : Bool ext = ::File.extname(filename).downcase ALLOWED_EXTENSIONS.includes?(ext) end end ``` ### File Deletion Route ```crystal post "/files/:id/delete" do |env| id = env.params.url["id"]?.try(&.to_i64?) stored_file = id ? StoredFile.find(id) : nil if stored_file ::File.delete(stored_file.storage_path) if ::File.exists?(stored_file.storage_path) stored_file.delete env.redirect "/files?notice=File+deleted+successfully." else env.response.status = :not_found "File not found" end end ``` ## Best Practices - **Configuration:** Always configure `Kemal.config.max_request_body_size` and `Kemal.config.max_multipart_form_field_size` globally to mitigate resource exhaustion / DoS attacks. - **Extension Validation:** Use a helper like `StoredFile.allowed_extension?(name)` to validate extensions (e.g., `.jpg`, `.png`, `.pdf`, `.txt`). - **Unique Storage Names:** Always generate unique filenames using `Random::Secure.hex(16)` to prevent collisions. - **Cleanup:** Always use `::File.delete(path) if ::File.exists?(path)` when deleting files. - **Early Validation:** Check file presence and extension before processing to fail fast. ## When to Use - When implementing file upload features. - When configuring Kemal to handle larger request bodies safely. - When managing stored files (e.g., deleting after use).
Ver en GitHub