krz/skunky-art

Alternative privacy frontend for DeviantArt.

clone: git clone https://gitbay.org/krz/skunky-art.git

v1.3.3: app/router.go · raw

  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}