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