]> Sergey Matveev's repositories - tofuproxy.git/blob - rounds/redirectHTML.go
Alternative Gemini's relative URL forming
[tofuproxy.git] / rounds / redirectHTML.go
1 // tofuproxy -- flexible HTTP/HTTPS proxy, TLS terminator, X.509 TOFU
2 //              manager, WARC/geminispace browser
3 // Copyright (C) 2021-2024 Sergey Matveev <stargrave@stargrave.org>
4 //
5 // This program is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, version 3 of the License.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
17 package rounds
18
19 import (
20         "fmt"
21         "net/http"
22         "path/filepath"
23         "strings"
24
25         "go.stargrave.org/tofuproxy/fifos"
26 )
27
28 var imageExts = map[string]struct{}{
29         ".apng": {},
30         ".avif": {},
31         ".gif":  {},
32         ".heic": {},
33         ".jp2":  {},
34         ".jpeg": {},
35         ".jpg":  {},
36         ".jxl":  {},
37         ".mng":  {},
38         ".png":  {},
39         ".svg":  {},
40         ".tif":  {},
41         ".tiff": {},
42         ".webp": {},
43 }
44
45 func isFeeder(req *http.Request) bool {
46         if strings.Contains(req.Header.Get("User-Agent"), "newsboat/") {
47                 return true
48         }
49         if strings.Contains(req.Header.Get("User-Agent"), "stargrave.org-feeder/") {
50                 return true
51         }
52         return false
53 }
54
55 func RoundRedirectHTML(
56         host string,
57         resp *http.Response,
58         w http.ResponseWriter,
59         req *http.Request,
60 ) (bool, error) {
61         if req.Method != http.MethodGet {
62                 return true, nil
63         }
64         var redirType string
65         switch resp.StatusCode {
66         case http.StatusMovedPermanently, http.StatusPermanentRedirect:
67                 redirType = "permanent"
68         case http.StatusFound, http.StatusSeeOther, http.StatusTemporaryRedirect:
69                 if isFeeder(req) {
70                         return true, nil
71                 }
72                 if _, ok := imageExts[filepath.Ext(req.URL.Path)]; ok {
73                         return true, nil
74                 }
75                 redirType = "temporary"
76         default:
77                 return true, nil
78         }
79         resp.Body.Close()
80         w.Header().Add("Content-Type", "text/html")
81         w.WriteHeader(http.StatusOK)
82         location := resp.Header.Get("Location")
83         fmt.Fprintf(
84                 w, `<!DOCTYPE html>
85 <html><head><title>%d %s: %s redirection</title></head>
86 <body><a href="%s">%s</a></body></html>`,
87                 resp.StatusCode, http.StatusText(resp.StatusCode),
88                 redirType, location, location,
89         )
90         fifos.LogRedir <- fmt.Sprintf(
91                 "%s %s\t%s\t%s", req.Method, req.URL, resp.Status, location,
92         )
93         return false, nil
94 }