]> Sergey Matveev's repositories - tofuproxy.git/blobdiff - main.go
WebP/JPEG-XL transcoding
[tofuproxy.git] / main.go
diff --git a/main.go b/main.go
index 16fae3d6c0742f5ab38723ce1dfa4ae6f704cf35..79e355ebdc131ae2bc32f9d89607c1a0865ec9cf 100644 (file)
--- a/main.go
+++ b/main.go
@@ -26,9 +26,12 @@ import (
        "flag"
        "fmt"
        "io"
+       "io/ioutil"
        "log"
        "net"
        "net/http"
+       "os"
+       "os/exec"
        "strings"
        "sync"
        "time"
@@ -56,6 +59,9 @@ var (
        acceptedM sync.RWMutex
        rejected  = make(map[string]string)
        rejectedM sync.RWMutex
+
+       CmdDWebP = "dwebp"
+       CmdDJXL  = "djxl"
 )
 
 func spkiHash(cert *x509.Certificate) string {
@@ -128,6 +134,7 @@ func roundTrip(w http.ResponseWriter, req *http.Request) {
        }
        sinkReq <- fmt.Sprintf("%s %s", req.Method, req.URL.String())
        host := strings.TrimSuffix(req.URL.Host, ":443")
+
        for _, spy := range SpyDomains {
                if strings.HasSuffix(host, spy) {
                        http.NotFound(w, req)
@@ -140,11 +147,13 @@ func roundTrip(w http.ResponseWriter, req *http.Request) {
                        return
                }
        }
+
        if strings.HasPrefix(req.URL.Host, "www.reddit.com") {
                req.URL.Host = "old.reddit.com"
                http.Redirect(w, req, req.URL.String(), http.StatusMovedPermanently)
                return
        }
+
        resp, err := transport.RoundTrip(req)
        if err != nil {
                sinkErr <- fmt.Sprintf("%s\t%s", req.URL.Host, err.Error())
@@ -152,8 +161,17 @@ func roundTrip(w http.ResponseWriter, req *http.Request) {
                w.Write([]byte(err.Error()))
                return
        }
-       contentType := resp.Header.Get("Content-Type")
-       switch contentType {
+
+       for k, vs := range resp.Header {
+               if k == "Location" || k == "Content-Type" || k == "Content-Length" {
+                       continue
+               }
+               for _, v := range vs {
+                       w.Header().Add(k, v)
+               }
+       }
+
+       switch resp.Header.Get("Content-Type") {
        case "application/font-woff", "application/font-sfnt":
                // Those are deprecated types
                fallthrough
@@ -167,15 +185,79 @@ func roundTrip(w http.ResponseWriter, req *http.Request) {
                )
                resp.Body.Close()
                return
-       }
-       for k, vs := range resp.Header {
-               if k == "Location" || k == "Content-Type" || k == "Content-Length" {
-                       continue
+       case "image/webp":
+               if strings.Contains(req.Header.Get("User-Agent"), "AppleWebKit/538.15") {
+                       // My Xombrero
+                       break
                }
-               for _, v := range vs {
-                       w.Header().Add(k, v)
+               tmpFd, err := ioutil.TempFile("", "tofuproxy.*.webp")
+               if err != nil {
+                       log.Fatalln(err)
+               }
+               defer tmpFd.Close()
+               defer os.Remove(tmpFd.Name())
+               defer resp.Body.Close()
+               if _, err = io.Copy(tmpFd, resp.Body); err != nil {
+                       log.Printf("Error during %s: %+v\n", req.URL, err)
+                       http.Error(w, err.Error(), http.StatusBadGateway)
+                       return
+               }
+               tmpFd.Close()
+               cmd := exec.Command(CmdDWebP, tmpFd.Name(), "-o", "-")
+               data, err := cmd.Output()
+               if err != nil {
+                       http.Error(w, err.Error(), http.StatusBadGateway)
+                       return
                }
+               w.Header().Add("Content-Type", "image/png")
+               w.WriteHeader(http.StatusOK)
+               w.Write(data)
+               sinkOther <- fmt.Sprintf(
+                       "%s %s\t%d\tWebP transcoded to PNG",
+                       req.Method,
+                       req.URL.String(),
+                       http.StatusOK,
+               )
+               return
+       case "image/jxl":
+               tmpFd, err := ioutil.TempFile("", "tofuproxy.*.jxl")
+               if err != nil {
+                       log.Fatalln(err)
+               }
+               defer tmpFd.Close()
+               defer os.Remove(tmpFd.Name())
+               defer resp.Body.Close()
+               if _, err = io.Copy(tmpFd, resp.Body); err != nil {
+                       log.Printf("Error during %s: %+v\n", req.URL, err)
+                       http.Error(w, err.Error(), http.StatusBadGateway)
+                       return
+               }
+               tmpFd.Close()
+               dstFn := tmpFd.Name() + ".png"
+               cmd := exec.Command(CmdDJXL, tmpFd.Name(), dstFn)
+               err = cmd.Run()
+               defer os.Remove(dstFn)
+               if err != nil {
+                       http.Error(w, err.Error(), http.StatusBadGateway)
+                       return
+               }
+               data, err := ioutil.ReadFile(dstFn)
+               if err != nil {
+                       http.Error(w, err.Error(), http.StatusBadGateway)
+                       return
+               }
+               w.Header().Add("Content-Type", "image/png")
+               w.WriteHeader(http.StatusOK)
+               w.Write(data)
+               sinkOther <- fmt.Sprintf(
+                       "%s %s\t%d\tJPEG XL transcoded to PNG",
+                       req.Method,
+                       req.URL.String(),
+                       http.StatusOK,
+               )
+               return
        }
+
        if req.Method == http.MethodGet {
                var redirType string
                switch resp.StatusCode {
@@ -209,6 +291,7 @@ func roundTrip(w http.ResponseWriter, req *http.Request) {
                )
                return
        }
+
 NoRedir:
        for _, h := range []string{"Location", "Content-Type", "Content-Length"} {
                if v := resp.Header.Get(h); v != "" {