package handlers import ( "bytes" "context" "fmt" "image" "image/jpeg" "image/png" "io" "net/http" oapi "nyanimedb/api" "os" "path/filepath" "strings" "github.com/disintegration/imaging" log "github.com/sirupsen/logrus" "golang.org/x/image/webp" ) // PostMediaUpload implements oapi.StrictServerInterface. func (s *Server) PostMediaUpload(ctx context.Context, request oapi.PostMediaUploadRequestObject) (oapi.PostMediaUploadResponseObject, error) { // Получаем multipart body mp := request.MultipartBody if mp == nil { log.Errorf("PostMedia without body") return oapi.PostMediaUpload400JSONResponse("Multipart body is required"), nil } // Парсим первую часть (предполагаем, что файл в поле "file") part, err := mp.NextPart() if err != nil { log.Errorf("PostMedia without file") return oapi.PostMediaUpload400JSONResponse("File required"), nil } defer part.Close() // Читаем ВЕСЬ файл в память (для небольших изображений — нормально) // Если файлы могут быть большими — используйте лимитированный буфер (см. ниже) data, err := io.ReadAll(part) if err != nil { log.Errorf("PostMedia cannot read file") return oapi.PostMediaUpload400JSONResponse("File required"), nil } if len(data) == 0 { log.Errorf("PostMedia empty file") return oapi.PostMediaUpload400JSONResponse("Empty file"), nil } // Проверка MIME по первым 512 байтам mimeType := http.DetectContentType(data) if mimeType != "image/jpeg" && mimeType != "image/png" && mimeType != "image/webp" { log.Errorf("PostMedia bad type") return oapi.PostMediaUpload400JSONResponse("Bad data type"), nil } // Декодируем изображение из буфера var img image.Image switch mimeType { case "image/jpeg": { img, err = jpeg.Decode(bytes.NewReader(data)) if err != nil { log.Errorf("PostMedia cannot decode file: %v", err) return oapi.PostMediaUpload500Response{}, nil } } case "image/png": { img, err = png.Decode(bytes.NewReader(data)) if err != nil { log.Errorf("PostMedia cannot decode file: %v", err) return oapi.PostMediaUpload500Response{}, nil } } case "image/webp": { img, err = webp.Decode(bytes.NewReader(data)) if err != nil { log.Errorf("PostMedia cannot decode file: %v", err) return oapi.PostMediaUpload500Response{}, nil } } } // Перекодируем в чистый JPEG (без EXIF, сжатие, RGB) var buf bytes.Buffer err = imaging.Encode(&buf, img, imaging.PNG) if err != nil { log.Errorf("PostMedia failed to re-encode JPEG: %v", err) return oapi.PostMediaUpload500Response{}, nil } // TODO: to delete filename := part.FileName() if filename == "" { filename = "upload_" + generateRandomHex(8) + ".jpg" } else { filename = sanitizeFilename(filename) if !strings.HasSuffix(strings.ToLower(filename), ".jpg") { filename += ".jpg" } } // TODO: пойти на хуй ( вызвать файловую помойку) err = os.WriteFile(filepath.Join("/uploads", filename), buf.Bytes(), 0644) if err != nil { log.Errorf("PostMedia failed to write: %v", err) return oapi.PostMediaUpload500Response{}, nil } return oapi.PostMediaUpload200JSONResponse{}, nil } // Вспомогательные функции — как раньше func generateRandomHex(n int) string { b := make([]byte, n) for i := range b { b[i] = byte('a' + (i % 16)) } return fmt.Sprintf("%x", b) } func sanitizeFilename(name string) string { var clean strings.Builder for _, r := range name { if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '.' || r == '_' || r == '-' { clean.WriteRune(r) } } s := clean.String() if s == "" { return "file" } return s }