mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
fix: address remaining code review findings (C-3, H-5, H-6, M-1 through M-16)
- C-3: inject setupLimiter into NewAdminAPI instead of package-level global - H-5: generateRandomKey returns error instead of panicking - H-6: replace init() bcrypt with sync.Once lazy initialization - M-2: remove unsafe-inline from admin CSP script-src and style-src - M-3: sanitize upload filenames (strip control chars, truncate to 255) - M-5: truncate User-Agent to 512 bytes before storing as device - M-10: MaxBodySizeUnless uses prefix matching instead of exact path - M-12: wrap seedExistingDatabase in a single transaction - M-13: use errors.Is for EOF check in upload handler - M-14: log writeJSON encoding errors instead of discarding - M-16: standardize error codes to INTERNAL_ERROR across all handlers
This commit is contained in:
@@ -1,14 +1,17 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
_ "image/gif"
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
"io"
|
||||
"log/slog"
|
||||
"mime"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -29,6 +32,29 @@ type uploadResponse struct {
|
||||
Height *int `json:"height,omitempty"`
|
||||
}
|
||||
|
||||
// sanitizeUploadFilename cleans an upload filename: strips control characters,
|
||||
// removes path separators, and truncates to a safe length.
|
||||
func sanitizeUploadFilename(name string) string {
|
||||
// Strip path components — use only the base name.
|
||||
name = filepath.Base(name)
|
||||
// Remove control characters.
|
||||
var sb strings.Builder
|
||||
for _, r := range name {
|
||||
if r >= 32 && r != 127 { // exclude control chars and DEL
|
||||
sb.WriteRune(r)
|
||||
}
|
||||
}
|
||||
name = strings.TrimSpace(sb.String())
|
||||
// Truncate to 255 characters (filesystem limit).
|
||||
if len(name) > 255 {
|
||||
name = name[:255]
|
||||
}
|
||||
if name == "" || name == "." || name == ".." {
|
||||
name = "unnamed"
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
// MountUploadRoutes registers upload and file-serving endpoints.
|
||||
// allowedOrigins controls the Access-Control-Allow-Origin header on served files.
|
||||
func MountUploadRoutes(r chi.Router, database *db.DB, store *storage.Storage, allowedOrigins []string) {
|
||||
@@ -68,7 +94,7 @@ func handleUpload(database *db.DB, store *storage.Storage) http.HandlerFunc {
|
||||
// Detect MIME type from actual file bytes (never trust client header).
|
||||
var sniffBuf [512]byte
|
||||
n, readErr := file.Read(sniffBuf[:])
|
||||
if readErr != nil && readErr.Error() != "EOF" && readErr.Error() != "unexpected EOF" {
|
||||
if readErr != nil && !errors.Is(readErr, io.EOF) && !errors.Is(readErr, io.ErrUnexpectedEOF) {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{
|
||||
"error": "BAD_REQUEST",
|
||||
"message": "failed to read uploaded file",
|
||||
@@ -114,7 +140,8 @@ func handleUpload(database *db.DB, store *storage.Storage) http.HandlerFunc {
|
||||
}
|
||||
|
||||
// Insert attachment record in DB (unlinked — message_id is NULL).
|
||||
if err := database.CreateAttachment(fileID, header.Filename, fileID, mime, header.Size, width, height); err != nil {
|
||||
safeFilename := sanitizeUploadFilename(header.Filename)
|
||||
if err := database.CreateAttachment(fileID, safeFilename, fileID, mime, header.Size, width, height); err != nil {
|
||||
// Clean up stored file on DB failure.
|
||||
_ = store.Delete(fileID)
|
||||
slog.Error("failed to create attachment record", "error", err)
|
||||
@@ -125,11 +152,11 @@ func handleUpload(database *db.DB, store *storage.Storage) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
slog.Info("file uploaded", "id", fileID, "filename", header.Filename, "size", header.Size, "mime", mime)
|
||||
slog.Info("file uploaded", "id", fileID, "filename", safeFilename, "size", header.Size, "mime", mime)
|
||||
|
||||
writeJSON(w, http.StatusCreated, uploadResponse{
|
||||
ID: fileID,
|
||||
Filename: header.Filename,
|
||||
Filename: safeFilename,
|
||||
Size: header.Size,
|
||||
Mime: mime,
|
||||
URL: "/api/v1/files/" + fileID,
|
||||
|
||||
Reference in New Issue
Block a user