krz/skunky-art
Alternative privacy frontend for DeviantArt.
clone: git clone https://gitbay.org/krz/skunky-art.git
1package app
2
3import (
4 "io"
5 "net/http"
6 url "net/url"
7 "skunkyart/static"
8 "strconv"
9 "strings"
10 "time"
11)
12
13// Host is the scheme and host that generated links are built from. It is set per
14// request from the Host header and X-Forwarded-Proto.
15var Host string
16
17// Router registers the single catch-all handler that dispatches every path, then
18// serves until the process exits. It does not return on success.
19func Router() {
20 parsepath := func(path string) map[int]string {
21 if l := len(CFG.URI); len(path) > l {
22 path = path[l-1:]
23 } else {
24 path = "/"
25 }
26
27 parsedpath := make(map[int]string)
28 for x := 0; true; x++ {
29 slash := strings.Index(path, "/") + 1
30 content := path[:slash]
31 path = path[slash:]
32 if slash == 0 {
33 parsedpath[x] = path
34 break
35 }
36 parsedpath[x] = content[:slash-1]
37 }
38 return parsedpath
39 }
40
41 next := func(path map[int]string, from int) string {
42 var out strings.Builder
43 for x, l := from, len(path)-1; x <= l; x++ {
44 out.WriteString(path[x])
45 if x != l {
46 out.WriteString("/")
47 }
48 }
49 return out.String()
50 }
51
52 open := func(name string) []byte {
53 file, err := static.Templates.Open(name)
54 if err != nil {
55 try(err)
56 return nil
57 }
58 defer func() { try(file.Close()) }()
59
60 fileReaded, err := io.ReadAll(file)
61 if err != nil {
62 try(err)
63 return nil
64 }
65 return fileReaded
66 }
67
68 // the function that drives everything
69 handle := func(w http.ResponseWriter, r *http.Request) {
70 path := parsepath(r.URL.Path)
71 Host = "http://" + r.Host
72 if h := r.Header["X-Forwarded-Proto"]; len(h) != 0 && h[0] == "https" {
73 Host = "https://" + r.Host
74 }
75
76 var skunky = skunkyart{Version: Release.Version}
77 skunky._pth = r.URL.Path
78
79 skunky.Args = r.URL.Query()
80 arg := skunky.Args.Get
81 p, _ := strconv.Atoi(arg("p"))
82
83 skunky.Endpoint = path[1]
84 skunky.API.main = &skunky
85 skunky.Writer = w
86 skunky.BasePath = CFG.URI
87 skunky.QueryRaw = arg("q")
88 skunky.Query = url.QueryEscape(skunky.QueryRaw)
89 skunky.Page = p
90
91 if t := arg("type"); len(t) > 0 {
92 skunky.Type = rune(t[0])
93 }
94
95 if arg("atom") == "true" {
96 skunky.Atom = true
97 }
98
99 if CFG.Proxy {
100 w.Header().Add("Content-Security-Policy", "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'")
101 } else {
102 w.Header().Add("Content-Security-Policy", "default-src 'self'; img-src 'self' *.wixmp.com; script-src 'none'; style-src 'self' 'unsafe-inline'")
103 }
104
105 w.Header().Add("X-Frame-Options", "DENY")
106
107 switch skunky.Endpoint {
108 // main
109 case "":
110 skunky.ExecuteTemplate("index.htm", "html", &CFG.URI)
111 case "about":
112 skunky.Templates.About = About
113 skunky.ExecuteTemplate("about.htm", "html", &skunky)
114 case "post":
115 skunky.Deviation(path[2], path[3])
116 case "search":
117 skunky.Search()
118 case "dd":
119 skunky.DD()
120 case "group_user":
121 skunky.GRUser()
122
123 // media
124 case "media":
125 switch path[2] {
126 case "file":
127 if a := arg("filename"); a != "" {
128 skunky.SetFilename(a)
129 }
130 skunky.DownloadAndSendMedia(path[3], next(path, 4))
131 case "emojitar":
132 skunky.Emojitar(path[3])
133 }
134 case "stylesheet":
135 w.Header().Add("Content-Type", "text/css")
136 _, _ = w.Write(open("css/skunky.css"))
137 case "favicon.ico":
138 _, _ = w.Write(open("images/logo.png"))
139
140 // API
141 case "api":
142 w.Header().Add("Content-Type", "application/json")
143 switch path[2] {
144 case "instance":
145 skunky.API.Info()
146 case "random":
147 skunky.API.Random()
148 default:
149 skunky.API.Error("Not Found", 404)
150 }
151
152 // 404
153 default:
154 skunky.ReturnHTTPError(404)
155 }
156 }
157
158 http.HandleFunc("/", handle)
159 println("SkunkyArt is listening on", CFG.Listen)
160
161 // Explicit timeouts: the bare http.ListenAndServe has none, so a slow client
162 // can hold a connection (and its handler) open indefinitely. WriteTimeout is
163 // generous because media proxying streams large files through a handler.
164 srv := &http.Server{
165 Addr: CFG.Listen,
166 ReadHeaderTimeout: 10 * time.Second,
167 ReadTimeout: 30 * time.Second,
168 WriteTimeout: 120 * time.Second,
169 IdleTimeout: 120 * time.Second,
170 }
171 tryWithExitStatus(srv.ListenAndServe(), 1)
172}