go-webring/handlers.go
2023-12-04 14:19:56 +00:00

164 lines
4.5 KiB
Go

// SPDX-FileCopyrightText: 2021 Amolith <amolith@secluded.site>
//
// SPDX-License-Identifier: BSD-2-Clause
package main
import (
"fmt"
"html/template"
"log"
"math/rand"
"net/http"
"net/url"
"strings"
)
// Serves the webpage created by createRoot()
func (m Ring) root(writer http.ResponseWriter, request *http.Request) {
if m.modify("ring") {
log.Println("Ring modified, clearing field and re-parsing")
m.parseList()
}
if m.modify("index") {
log.Println("Index modified, clearing field and re-parsing")
m.parseIndex()
}
var table string
for _, member := range m.ring {
table = table + " <tr>\n"
table = table + " <td>" + member.id + "</td>\n"
table = table + " <td>" + link(member.url) + "</td>\n"
table = table + " </tr>\n"
}
m.index.Execute(writer, template.HTML(table))
}
type MemberSelector struct {
id string // exact match
urlpart string
}
func parseQuery(query url.Values) (*MemberSelector, error) {
urlpart := query.Get("urlpart")
if urlpart == "" {
urlpart = query.Get("host")
}
n_valid := 0
if urlpart != "" {
n_valid += 1
}
id := query.Get("id")
if id != "" {
n_valid += 1
}
if n_valid != 1 {
return nil, fmt.Errorf("Please specify urlpart=xxx or id=xxx")
}
return &MemberSelector{
id: id,
urlpart: urlpart,
}, nil
}
func (m Ring) match(selector *MemberSelector, item RingMember) bool {
if selector.id != "" {
return item.id == selector.id
}
if selector.urlpart != "" {
return strings.Contains(item.url, selector.urlpart)
}
panic("unreachable")
}
// Redirects the visitor to the next member, wrapping around the list if the
// next would be out-of-bounds, and ensuring the destination returns a 200 OK
// status before performing the redirect.
func (m Ring) next(writer http.ResponseWriter, request *http.Request) {
if m.modify("ring") {
log.Println("Ring modified, clearing field and re-parsing")
m.parseList()
}
success := false
query := request.URL.Query()
selector, err := parseQuery(query)
if err != nil {
http.Error(writer, fmt.Errorf("invalid query: %w", err).Error(), http.StatusBadRequest)
}
length := len(m.ring)
for i, item := range m.ring {
if m.match(selector, item) {
for j := i + 1; j < length+i; j++ {
dest := m.ring[j%length].url
log.Println("Checking '" + dest + "'")
if isTargetValid(request.URL, dest) {
log.Println("Redirecting visitor to '" + dest + "'")
http.Redirect(writer, request, dest, http.StatusFound)
success = true
break
}
log.Println("Something went wrong accessing '" + dest + "', skipping site")
}
}
}
if !success {
http.Error(writer, "Ring member '"+query.Encode()+"' not found.", 404)
}
}
// Redirects the visitor to the previous member, wrapping around the list if the
// next would be out-of-bounds, and ensuring the destination returns a 200 OK
// status before performing the redirect.
func (m Ring) previous(writer http.ResponseWriter, request *http.Request) {
if m.modify("ring") {
log.Println("Ring modified, clearing field and re-parsing")
m.parseList()
}
query := request.URL.Query()
selector, err := parseQuery(query)
if err != nil {
http.Error(writer, fmt.Errorf("invalid query: %w", err).Error(), http.StatusBadRequest)
}
length := len(m.ring)
for index, item := range m.ring {
if m.match(selector, item) {
// from here to start of list
for i := index - 1; i > 0; i-- {
dest := m.ring[i].url
if isTargetValid(request.URL, dest) {
log.Println("Redirecting visitor to '" + dest + "'")
http.Redirect(writer, request, dest, http.StatusFound)
return
}
}
// from end of list to here
for i := length - 1; i > index; i-- {
dest := m.ring[i].url
if isTargetValid(request.URL, dest) {
log.Println("Redirecting visitor to '" + dest + "'")
http.Redirect(writer, request, dest, http.StatusFound)
return
}
}
http.Error(writer, `It would appear that either none of the ring members are accessible
(unlikely) or the backend is broken (more likely). In either case,
please email amolith@secluded.site and let him (me) know what's up.`, 500)
return
}
}
http.Error(writer, "Ring member '"+query.Encode()+"' not )found.", 404)
}
// Redirects the visitor to a random member
func (m Ring) random(writer http.ResponseWriter, request *http.Request) {
// todo: isTargetValid
if m.modify("ring") {
log.Println("Ring modified, clearing field and re-parsing")
m.parseList()
}
dest := m.ring[rand.Intn(len(m.ring)-1)].url
http.Redirect(writer, request, dest, http.StatusFound)
}