summaryrefslogtreecommitdiff
path: root/workhorse/internal/upload/exif/exif.go
blob: db3c45431c08654a9c4a9d530eccc2056ae0e50a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
package exif

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"io"
	"os"
	"os/exec"
	"regexp"

	"gitlab.com/gitlab-org/labkit/log"
)

var ErrRemovingExif = errors.New("error while removing EXIF")

type cleaner struct {
	ctx    context.Context
	cmd    *exec.Cmd
	stdout io.Reader
	stderr bytes.Buffer
	eof    bool
}

type FileType int

const (
	TypeUnknown FileType = iota
	TypeJPEG
	TypeTIFF
)

func NewCleaner(ctx context.Context, stdin io.Reader) (io.ReadCloser, error) {
	c := &cleaner{ctx: ctx}

	if err := c.startProcessing(stdin); err != nil {
		return nil, err
	}

	return c, nil
}

func (c *cleaner) Close() error {
	if c.cmd == nil {
		return nil
	}

	return c.cmd.Wait()
}

func (c *cleaner) Read(p []byte) (int, error) {
	if c.eof {
		return 0, io.EOF
	}

	n, err := c.stdout.Read(p)
	if err == io.EOF {
		if waitErr := c.cmd.Wait(); waitErr != nil {
			log.WithContextFields(c.ctx, log.Fields{
				"command": c.cmd.Args,
				"stderr":  c.stderr.String(),
				"error":   waitErr.Error(),
			}).Print("exiftool command failed")

			return n, ErrRemovingExif
		}

		c.eof = true
	}

	return n, err
}

func (c *cleaner) startProcessing(stdin io.Reader) error {
	var err error

	whitelisted_tags := []string{
		"-ResolutionUnit",
		"-XResolution",
		"-YResolution",
		"-YCbCrSubSampling",
		"-YCbCrPositioning",
		"-BitsPerSample",
		"-ImageHeight",
		"-ImageWidth",
		"-ImageSize",
		"-Copyright",
		"-CopyrightNotice",
		"-Orientation",
	}

	args := append([]string{"-all=", "--IPTC:all", "--XMP-iptcExt:all", "-tagsFromFile", "@"}, whitelisted_tags...)
	args = append(args, "-")
	c.cmd = exec.CommandContext(c.ctx, "exiftool", args...)

	c.cmd.Stderr = &c.stderr
	c.cmd.Stdin = stdin

	c.stdout, err = c.cmd.StdoutPipe()
	if err != nil {
		return fmt.Errorf("failed to create stdout pipe: %v", err)
	}

	if err = c.cmd.Start(); err != nil {
		return fmt.Errorf("start %v: %v", c.cmd.Args, err)
	}

	return nil
}

func FileTypeFromSuffix(filename string) FileType {
	if os.Getenv("SKIP_EXIFTOOL") == "1" {
		return TypeUnknown
	}

	jpegMatch := regexp.MustCompile(`(?i)^[^\n]*\.(jpg|jpeg)$`)
	if jpegMatch.MatchString(filename) {
		return TypeJPEG
	}

	tiffMatch := regexp.MustCompile(`(?i)^[^\n]*\.tiff$`)
	if tiffMatch.MatchString(filename) {
		return TypeTIFF
	}

	return TypeUnknown
}