package storage import ( "fmt" "io" "mime/multipart" "net/http" "os" "path/filepath" "strings" ) type Client struct { RootDir string MaxObjectSize int64 } func New(root string, maxObjectSize int64) (*Client, error) { if maxObjectSize <= 0 { return nil, fmt.Errorf("maxObjectSize must be greater than 0") } info, err := os.Stat(root) if os.IsNotExist(err) { return nil, fmt.Errorf("root directory does not exist: %s", root) } if err != nil { return nil, fmt.Errorf("failed to stat root directory: %v", err) } if !info.IsDir() { return nil, fmt.Errorf("root path is not a directory: %s", root) } testFile := filepath.Join(root, ".axiom-storage-check") f, err := os.Create(testFile) if err != nil { return nil, fmt.Errorf("root directory is not writable: %v", err) } f.Close() os.Remove(testFile) return &Client{ RootDir: root, MaxObjectSize: maxObjectSize, }, nil } func (s *Client) getPath(bucket, key string) string { return filepath.Join(s.RootDir, bucket, key) } func (s *Client) CreateBucket(bucket string) error { path := filepath.Join(s.RootDir, bucket) return os.MkdirAll(path, 0755) } func (s *Client) PutObject(bucket, key string, reader io.Reader) error { fullPath := s.getPath(bucket, key) if err := os.MkdirAll(filepath.Dir(fullPath), 0755); err != nil { return err } f, err := os.Create(fullPath) if err != nil { return err } defer f.Close() limitedReader := io.LimitReader(reader, s.MaxObjectSize+1) written, err := io.Copy(f, limitedReader) if err != nil { return err } if written > s.MaxObjectSize { os.Remove(fullPath) return fmt.Errorf("object size exceeds maximum allowed: %d bytes", s.MaxObjectSize) } return nil } func (s *Client) GetObject(bucket, key string) (*os.File, error) { fullPath := s.getPath(bucket, key) return os.Open(fullPath) } func (s *Client) DeleteObject(bucket, key string) error { fullPath := s.getPath(bucket, key) return os.Remove(fullPath) } func DetectFileType(file multipart.File) (string, []byte, error) { const sniffLen = 512 buffer := make([]byte, sniffLen) n, err := file.Read(buffer) if err != nil && err != io.EOF { return "", nil, err } if seeker, ok := file.(io.Seeker); ok { _, _ = seeker.Seek(0, io.SeekStart) } else { return "", nil, fmt.Errorf("file is not seekable") } mimeType := http.DetectContentType(buffer[:n]) return mimeType, buffer[:n], nil } func VerifyFileType(file multipart.File, allowedMimeTypes []string) error { mimeType, _, err := DetectFileType(file) if err != nil { return err } for _, mt := range allowedMimeTypes { if strings.EqualFold(mimeType, mt) { return nil } } return fmt.Errorf("invalid file type: %s", mimeType) }