backend.go (view raw)
1//
2// Copyright (c) 2019 Ted Unangst <tedu@tedunangst.com>
3//
4// Permission to use, copy, modify, and distribute this software for any
5// purpose with or without fee is hereby granted, provided that the above
6// copyright notice and this permission notice appear in all copies.
7//
8// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
16package main
17
18import (
19 "bytes"
20 "errors"
21 "net"
22 "net/http"
23 "net/rpc"
24 "os"
25 "os/exec"
26 "os/signal"
27 "strings"
28 "sync"
29 "syscall"
30
31 "humungus.tedunangst.com/r/webs/gate"
32 "humungus.tedunangst.com/r/webs/image"
33)
34
35type Shrinker struct {
36}
37
38type ShrinkerArgs struct {
39 Buf []byte
40 Params image.Params
41}
42
43type ShrinkerResult struct {
44 Image *image.Image
45}
46
47var shrinkgate = gate.NewLimiter(4)
48
49func (s *Shrinker) Shrink(args *ShrinkerArgs, res *ShrinkerResult) error {
50 shrinkgate.Start()
51 defer shrinkgate.Finish()
52 img, err := image.Vacuum(bytes.NewReader(args.Buf), args.Params)
53 if err != nil {
54 return err
55 }
56 res.Image = img
57 return nil
58}
59
60func backendSockname() string {
61 return dataDir + "/backend.sock"
62}
63
64var bomFuck = []byte{0xef, 0xbb, 0xbf}
65
66func isSVG(data []byte) bool {
67 if bytes.HasPrefix(data, bomFuck) {
68 data = data[3:]
69 }
70 ct := http.DetectContentType(data)
71 if strings.HasPrefix(ct, "text/xml") || strings.HasPrefix(ct, "text/plain") {
72 // this seems suboptimal
73 prefixes := []string{
74 `<svg `,
75 `<!DOCTYPE svg PUBLIC`,
76 `<?xml version="1.0" encoding="UTF-8"?> <svg `,
77 }
78 for _, pre := range prefixes {
79 if bytes.HasPrefix(data, []byte(pre)) {
80 return true
81 }
82 }
83 }
84 return ct == "image/svg+xml"
85}
86
87func imageFromSVG(data []byte) (*image.Image, error) {
88 if bytes.HasPrefix(data, bomFuck) {
89 data = data[3:]
90 }
91 if len(data) > 100000 {
92 return nil, errors.New("my svg is too big")
93 }
94 svg := &image.Image{
95 Data: data,
96 Format: "svg+xml",
97 }
98 return svg, nil
99}
100
101func callshrink(data []byte, params image.Params) (*image.Image, error) {
102 if isSVG(data) {
103 return imageFromSVG(data)
104 }
105 cl, err := rpc.Dial("unix", backendSockname())
106 if err != nil {
107 return nil, err
108 }
109 defer cl.Close()
110 var res ShrinkerResult
111 err = cl.Call("Shrinker.Shrink", &ShrinkerArgs{
112 Buf: data,
113 Params: params,
114 }, &res)
115 if err != nil {
116 return nil, err
117 }
118 return res.Image, nil
119}
120
121func lilshrink(data []byte) (*image.Image, error) {
122 params := image.Params{
123 LimitSize: 14200 * 4200,
124 MaxWidth: 256,
125 MaxHeight: 256,
126 MaxSize: 16 * 1024,
127 }
128 return callshrink(data, params)
129}
130func bigshrink(data []byte) (*image.Image, error) {
131 params := image.Params{
132 LimitSize: 14200 * 4200,
133 MaxWidth: 2600,
134 MaxHeight: 2048,
135 MaxSize: 768 * 1024,
136 }
137 return callshrink(data, params)
138}
139
140func shrinkit(data []byte) (*image.Image, error) {
141 params := image.Params{
142 LimitSize: 4200 * 4200,
143 MaxWidth: 2048,
144 MaxHeight: 2048,
145 }
146 return callshrink(data, params)
147}
148
149func orphancheck() {
150 var b [1]byte
151 os.Stdin.Read(b[:])
152 dlog.Printf("backend shutting down")
153 os.Exit(0)
154}
155
156func backendServer() {
157 dlog.Printf("backend server running")
158 closedatabases()
159 go orphancheck()
160 signal.Ignore(syscall.SIGINT)
161 shrinker := new(Shrinker)
162 srv := rpc.NewServer()
163 err := srv.Register(shrinker)
164 if err != nil {
165 elog.Panicf("unable to register shrinker: %s", err)
166 }
167
168 sockname := backendSockname()
169 err = os.Remove(sockname)
170 if err != nil && !os.IsNotExist(err) {
171 elog.Panicf("unable to unlink socket: %s", err)
172 }
173
174 lis, err := net.Listen("unix", sockname)
175 if err != nil {
176 elog.Panicf("unable to register shrinker: %s", err)
177 }
178 err = setLimits()
179 if err != nil {
180 elog.Printf("error setting backend limits: %s", err)
181 }
182 securitizebackend()
183 srv.Accept(lis)
184}
185
186func runBackendServer() {
187 r, w, err := os.Pipe()
188 if err != nil {
189 elog.Panicf("can't pipe: %s", err)
190 }
191 proc := exec.Command(os.Args[0], reexecArgs("backend")...)
192 proc.Stdout = os.Stdout
193 proc.Stderr = os.Stderr
194 proc.Stdin = r
195 err = proc.Start()
196 if err != nil {
197 elog.Panicf("can't exec backend: %s", err)
198 }
199 workinprogress++
200 var mtx sync.Mutex
201 go func() {
202 <-endoftheworld
203 mtx.Lock()
204 defer mtx.Unlock()
205 w.Close()
206 w = nil
207 readyalready <- true
208 }()
209 go func() {
210 err := proc.Wait()
211 mtx.Lock()
212 defer mtx.Unlock()
213 if w != nil {
214 elog.Printf("lost the backend: %s", err)
215 w.Close()
216 }
217 }()
218}