mirror of
https://github.com/pocket-id/pocket-id.git
synced 2025-12-08 17:23:23 +03:00
133 lines
2.8 KiB
Go
133 lines
2.8 KiB
Go
package utils
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"mime"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
)
|
|
|
|
func GetFileExtension(filename string) string {
|
|
ext := filepath.Ext(filename)
|
|
if len(ext) > 0 && ext[0] == '.' {
|
|
return ext[1:]
|
|
}
|
|
return filename
|
|
}
|
|
|
|
// SplitFileName splits a full file name into name and extension.
|
|
func SplitFileName(fullName string) (name, ext string) {
|
|
dot := strings.LastIndex(fullName, ".")
|
|
if dot == -1 || dot == 0 {
|
|
return fullName, "" // no extension or hidden file like .gitignore
|
|
}
|
|
return fullName[:dot], fullName[dot+1:]
|
|
}
|
|
|
|
func GetImageMimeType(ext string) string {
|
|
switch ext {
|
|
case "jpg", "jpeg":
|
|
return "image/jpeg"
|
|
case "png":
|
|
return "image/png"
|
|
case "svg":
|
|
return "image/svg+xml"
|
|
case "ico":
|
|
return "image/x-icon"
|
|
case "gif":
|
|
return "image/gif"
|
|
case "webp":
|
|
return "image/webp"
|
|
case "avif":
|
|
return "image/avif"
|
|
case "heic":
|
|
return "image/heic"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func GetImageExtensionFromMimeType(mimeType string) string {
|
|
// Normalize and strip parameters like `; charset=utf-8`
|
|
mt := strings.TrimSpace(strings.ToLower(mimeType))
|
|
if v, _, err := mime.ParseMediaType(mt); err == nil {
|
|
mt = v
|
|
}
|
|
switch mt {
|
|
case "image/jpeg", "image/jpg":
|
|
return "jpg"
|
|
case "image/png":
|
|
return "png"
|
|
case "image/svg+xml":
|
|
return "svg"
|
|
case "image/x-icon", "image/vnd.microsoft.icon":
|
|
return "ico"
|
|
case "image/gif":
|
|
return "gif"
|
|
case "image/webp":
|
|
return "webp"
|
|
case "image/avif":
|
|
return "avif"
|
|
case "image/heic", "image/heif":
|
|
return "heic"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
// FileExists returns true if a file exists on disk and is a regular file
|
|
func FileExists(path string) (bool, error) {
|
|
s, err := os.Stat(path)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
err = nil
|
|
}
|
|
return false, err
|
|
}
|
|
return !s.IsDir(), nil
|
|
}
|
|
|
|
// IsWritableDir checks if a directory exists and is writable
|
|
func IsWritableDir(dir string) (bool, error) {
|
|
// Check if directory exists and it's actually a directory
|
|
info, err := os.Stat(dir)
|
|
if os.IsNotExist(err) {
|
|
return false, nil
|
|
} else if err != nil {
|
|
return false, fmt.Errorf("failed to stat '%s': %w", dir, err)
|
|
}
|
|
if !info.IsDir() {
|
|
return false, nil
|
|
}
|
|
|
|
// Generate a random suffix for the test file to avoid conflicts
|
|
randomBytes := make([]byte, 8)
|
|
_, err = io.ReadFull(rand.Reader, randomBytes)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to generate random bytes: %w", err)
|
|
}
|
|
|
|
// Check if directory is writable by trying to create a temporary file
|
|
testFile := filepath.Join(dir, ".pocketid_test_write_"+hex.EncodeToString(randomBytes))
|
|
defer os.Remove(testFile)
|
|
|
|
file, err := os.Create(testFile)
|
|
if err != nil {
|
|
if os.IsPermission(err) || errors.Is(err, syscall.EROFS) {
|
|
return false, nil
|
|
}
|
|
|
|
return false, fmt.Errorf("failed to create test file: %w", err)
|
|
}
|
|
|
|
_ = file.Close()
|
|
|
|
return true, nil
|
|
}
|