all repos — honk @ v1.2.2

my fork of honk

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}