mirror-yaf/config.go
Leon Richardt e0afb453a5
feat: add option to scrub EXIF tags on image files
EXIF scrubbing can be enabled via the `ScrubExif` config key. When
enabled, all standard EXIF tags (as defined by the IFD mappings in the
go-exif library) are removed on uploaded JPEG and PNG images.

The `ExifAllowedIds` and `ExifAllowedPaths` config keys can be used to
selectively allow specific tags to survive the scrubbing. This can be
useful if you want to preserve image orientation information for
example. The IDs for standard tags can be found in [1].

The path specification for `ExifAllowedPaths` relies on the format
implemented in go-exif which is "documented" in machine-readable format
in [2]. Multiple paths can be specified, separated by a space. The
path format is as follows:

    1. For tags in the main section: `IFD/<GroupName>/<FieldName>`.
       Examples: `IFD/Orientation`, `IFD/Exif/Flash`,
       `IFD/GPSInfo/GPSTimeStamp`. You will probably want to use both
       [1] and [2] in combination if you plan to specify allowed tags by
       path.
    2. Tags in the thumbnail section follow the same format but paths
       start with `IFD1/` instead of `IFD`.

[1]: https://exiv2.org/tags.html
[2]: a6301f85c8/assets/tags.yaml
2022-08-23 11:40:38 +02:00

154 lines
2.9 KiB
Go

package main
import (
"bufio"
"log"
"os"
"strconv"
"strings"
)
const (
commentPrefix = "#"
)
type Config struct {
Port int
LinkPrefix string
FileDir string
LinkLength int
ScrubExif bool
ExifAllowedIds []uint16
ExifAllowedPaths []string
ExifAbortOnError bool
}
func ConfigFromFile(filePath string) (*Config, error) {
file, err := os.Open(filePath)
if err != nil {
return nil, err
}
defer file.Close()
oldPrefix := log.Prefix()
defer log.SetPrefix(oldPrefix)
log.SetPrefix("config.FromFile > ")
retval := &Config{
Port: 4711,
LinkPrefix: "https://jaf.example.com/",
FileDir: "/var/www/jaf/",
LinkLength: 5,
ScrubExif: true,
ExifAllowedIds: []uint16{},
ExifAllowedPaths: []string{},
ExifAbortOnError: true,
}
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if strings.HasPrefix(line, commentPrefix) {
// Skip comments
continue
}
key, val, found := strings.Cut(line, ":")
if !found {
log.Printf("unexpected line: \"%s\", ignoring\n", line)
continue
}
key = strings.TrimSpace(key)
val = strings.TrimSpace(val)
switch key {
case "Port":
parsed, err := strconv.Atoi(val)
if err != nil {
return nil, err
}
retval.Port = parsed
case "LinkPrefix":
retval.LinkPrefix = val
case "FileDir":
retval.FileDir = val
case "LinkLength":
parsed, err := strconv.Atoi(val)
if err != nil {
return nil, err
}
retval.LinkLength = parsed
case "ScrubExif":
parsed, err := strconv.ParseBool(val)
if err != nil {
return nil, err
}
retval.ScrubExif = parsed
case "ExifAllowedIds":
if val == "" {
// No IDs specified at all
break
}
stringIds := strings.Split(val, " ")
parsedIds := make([]uint16, 0, len(stringIds))
for _, stringId := range stringIds {
var parsed uint64
var err error
if strings.HasPrefix(stringId, "0x") {
// Parse as a hexadecimal number
hexStringId := strings.Replace(stringId, "0x", "", 1)
parsed, err = strconv.ParseUint(hexStringId, 16, 16)
} else {
// Parse as a decimal number
parsed, err = strconv.ParseUint(stringId, 10, 16)
}
if err != nil {
log.Printf(
"Could not parse ID from: \"%s\", ignoring. Error: %s\n",
stringId,
err,
)
continue
}
parsedIds = append(parsedIds, uint16(parsed))
}
retval.ExifAllowedIds = parsedIds
case "ExifAllowedPaths":
if val == "" {
// No paths specified at all
break
}
paths := strings.Split(val, " ")
retval.ExifAllowedPaths = paths
case "ExifAbortOnError":
parsed, err := strconv.ParseBool(val)
if err != nil {
return nil, err
}
retval.ExifAbortOnError = parsed
default:
log.Printf("unexpected key: \"%s\", ignoring\n", key)
}
}
if err := scanner.Err(); err != nil {
return nil, err
}
return retval, nil
}