mirror of
https://github.com/WiiLink24/wfc-server.git
synced 2026-08-25 18:14:39 -05:00
Rework HTTP request handling entirely
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"wwfc/common"
|
||||
"wwfc/database"
|
||||
)
|
||||
@@ -30,3 +31,11 @@ func StartServer(reload bool) {
|
||||
func Shutdown() {
|
||||
db.Close()
|
||||
}
|
||||
|
||||
func RegisterHandlers(mux *http.ServeMux) {
|
||||
mux.HandleFunc("/api/groups", HandleGroups)
|
||||
mux.HandleFunc("/api/stats", HandleStats)
|
||||
mux.HandleFunc("/api/ban", HandleBan)
|
||||
mux.HandleFunc("/api/unban", HandleUnban)
|
||||
mux.HandleFunc("/api/kick", HandleKick)
|
||||
}
|
||||
|
||||
13
nas/auth.go
13
nas/auth.go
@@ -23,7 +23,9 @@ var (
|
||||
dlcDir = "./dlc"
|
||||
)
|
||||
|
||||
func handleAuthRequest(moduleName string, w http.ResponseWriter, r *http.Request) {
|
||||
func handleAuthRequest(w http.ResponseWriter, r *http.Request) {
|
||||
moduleName := getModuleName(r)
|
||||
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
logging.Error(moduleName, "Failed to parse form")
|
||||
@@ -88,7 +90,8 @@ func handleAuthRequest(moduleName string, w http.ResponseWriter, r *http.Request
|
||||
reply := map[string]string{}
|
||||
var response []byte
|
||||
|
||||
if r.URL.String() == "/ac" {
|
||||
switch r.URL.String() {
|
||||
case "/ac":
|
||||
action, ok := fields["action"]
|
||||
if !ok || action == "" {
|
||||
logging.Error(moduleName, "No action in form")
|
||||
@@ -114,7 +117,8 @@ func handleAuthRequest(moduleName string, w http.ResponseWriter, r *http.Request
|
||||
"returncd": "109",
|
||||
}
|
||||
}
|
||||
} else if r.URL.String() == "/pr" {
|
||||
|
||||
case "/pr":
|
||||
words, ok := fields["words"]
|
||||
if words == "" || !ok {
|
||||
logging.Error(moduleName, "No words in form")
|
||||
@@ -123,7 +127,8 @@ func handleAuthRequest(moduleName string, w http.ResponseWriter, r *http.Request
|
||||
}
|
||||
|
||||
reply = handleProfanity(r.PostForm, unitcd)
|
||||
} else if r.URL.String() == "/download" {
|
||||
|
||||
case "/download":
|
||||
action, ok := fields["action"]
|
||||
if !ok || action == "" {
|
||||
logging.Error(moduleName, "No action in form")
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func handleConnectionTest(w http.ResponseWriter) {
|
||||
func handleConnectionTest(w http.ResponseWriter, r *http.Request) {
|
||||
response := "\n"
|
||||
response += ` <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">` + "\n"
|
||||
response += ` <html>` + "\n"
|
||||
|
||||
93
nas/listener.go
Normal file
93
nas/listener.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package nas
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"wwfc/common"
|
||||
"wwfc/logging"
|
||||
|
||||
"github.com/logrusorgru/aurora/v3"
|
||||
)
|
||||
|
||||
type nasListener struct {
|
||||
net.Listener
|
||||
}
|
||||
|
||||
type nasConn struct {
|
||||
net.Conn
|
||||
reader io.Reader
|
||||
}
|
||||
|
||||
func (l *nasListener) Accept() (net.Conn, error) {
|
||||
conn, err := l.Listener.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &nasConn{
|
||||
Conn: conn,
|
||||
reader: filterDuplicateHost(conn),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *nasConn) Read(b []byte) (int, error) {
|
||||
return c.reader.Read(b)
|
||||
}
|
||||
|
||||
// filterDuplicateHost wraps a net.Conn and filters out duplicate Host headers from the HTTP request,
|
||||
// making the invalid requests sent by DWC acceptable to the standard library's HTTP server.
|
||||
func filterDuplicateHost(c net.Conn) io.Reader {
|
||||
r := bufio.NewReader(c)
|
||||
|
||||
// Read the first line of the HTTP request
|
||||
line, err := r.ReadString('\n')
|
||||
if err != nil {
|
||||
return io.MultiReader(strings.NewReader(line), r)
|
||||
}
|
||||
// Is this an HTTP request?
|
||||
if !strings.HasSuffix(line, "HTTP/1.1\r\n") {
|
||||
return io.MultiReader(strings.NewReader(line), r)
|
||||
}
|
||||
|
||||
// Iterate through the HTTP headers and remove any duplicate Host headers
|
||||
var headers bytes.Buffer
|
||||
hostSeen := false
|
||||
for {
|
||||
headerLine, err := r.ReadString('\n')
|
||||
if err != nil || headerLine == "\r\n" {
|
||||
headers.WriteString(headerLine)
|
||||
break
|
||||
}
|
||||
if strings.HasPrefix(strings.ToLower(headerLine), "host:") {
|
||||
if hostSeen {
|
||||
continue
|
||||
}
|
||||
hostSeen = true
|
||||
}
|
||||
headers.WriteString(headerLine)
|
||||
}
|
||||
|
||||
return io.MultiReader(strings.NewReader(line+headers.String()), r)
|
||||
}
|
||||
|
||||
func listenAndServe(address string) {
|
||||
logging.Notice("NAS", "Starting HTTP server on", aurora.BrightCyan(address))
|
||||
|
||||
l, err := net.Listen("tcp", address)
|
||||
common.ShouldNotError(err)
|
||||
listener := &nasListener{Listener: l}
|
||||
|
||||
defer func() {
|
||||
common.ShouldNotError(listener.Close())
|
||||
}()
|
||||
|
||||
err = server.Serve(listener)
|
||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
273
nas/main.go
273
nas/main.go
@@ -1,16 +1,10 @@
|
||||
package nas
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"wwfc/api"
|
||||
"wwfc/common"
|
||||
@@ -28,23 +22,18 @@ var (
|
||||
payloadServerAddress string
|
||||
)
|
||||
|
||||
type nasListener struct {
|
||||
net.Listener
|
||||
}
|
||||
|
||||
type nasConn struct {
|
||||
net.Conn
|
||||
reader io.Reader
|
||||
}
|
||||
var (
|
||||
authMux = http.NewServeMux()
|
||||
sakeMux = http.NewServeMux()
|
||||
gamestatsMux = http.NewServeMux()
|
||||
raceMux = http.NewServeMux()
|
||||
)
|
||||
|
||||
func StartServer(reload bool) {
|
||||
// Get config
|
||||
config := common.GetConfig()
|
||||
|
||||
serverName = config.ServerName
|
||||
|
||||
address := *config.NASAddress + ":" + config.NASPort
|
||||
|
||||
payloadServerAddress = config.PayloadServerAddress
|
||||
|
||||
if config.EnableHTTPS {
|
||||
@@ -63,22 +52,36 @@ func StartServer(reload bool) {
|
||||
ReadTimeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
logging.Notice("NAS", "Starting HTTP server on", aurora.BrightCyan(address))
|
||||
authMux.HandleFunc("/ac", handleAuthRequest)
|
||||
authMux.HandleFunc("/pr", handleAuthRequest)
|
||||
authMux.HandleFunc("/download", handleAuthRequest)
|
||||
|
||||
l, err := net.Listen("tcp", address)
|
||||
common.ShouldNotError(err)
|
||||
listener := &nasListener{Listener: l}
|
||||
if payloadServerAddress != "" {
|
||||
// Forward the request to the payload server
|
||||
authMux.HandleFunc("/payload", forwardPayloadRequest)
|
||||
} else {
|
||||
authMux.HandleFunc("/payload", handlePayloadRequest)
|
||||
}
|
||||
|
||||
go func(l net.Listener) {
|
||||
defer func() {
|
||||
common.ShouldNotError(listener.Close())
|
||||
}()
|
||||
for i := 0; i <= 9; i++ {
|
||||
authMux.HandleFunc("/w"+strconv.Itoa(i), downloadStage1)
|
||||
}
|
||||
|
||||
err := server.Serve(listener)
|
||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
panic(err)
|
||||
}
|
||||
}(listener)
|
||||
authMux.HandleFunc("/nastest.jsp", handleNASTest)
|
||||
|
||||
http.HandleFunc("GET conntest.nintendowifi.net/", handleConnectionTest)
|
||||
|
||||
api.RegisterHandlers(http.DefaultServeMux)
|
||||
sake.RegisterHandlers(sakeMux)
|
||||
race.RegisterHandlers(raceMux)
|
||||
gamestatsMux.HandleFunc("/", gamestats.HandleWebRequest)
|
||||
|
||||
http.HandleFunc("/", handleUnknown)
|
||||
authMux.HandleFunc("/", handleUnknown)
|
||||
sakeMux.HandleFunc("/", handleUnknown)
|
||||
raceMux.HandleFunc("/", handleUnknown)
|
||||
|
||||
go listenAndServe(address)
|
||||
}
|
||||
|
||||
func Shutdown() {
|
||||
@@ -95,161 +98,27 @@ func Shutdown() {
|
||||
}
|
||||
}
|
||||
|
||||
func (l *nasListener) Accept() (net.Conn, error) {
|
||||
conn, err := l.Listener.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &nasConn{
|
||||
Conn: conn,
|
||||
reader: filterDuplicateHost(conn),
|
||||
}, nil
|
||||
var hostMuxes = map[*regexp.Regexp]*http.ServeMux{
|
||||
regexp.MustCompile(`^(nas|naswii|dls1)\.`): authMux,
|
||||
regexp.MustCompile(`(\.|^)gamestats2?\.(gs\.|gamespy\.com$)`): gamestatsMux,
|
||||
regexp.MustCompile(`(\.|^)sake\.(gs\.|gamespy\.com$)`): sakeMux,
|
||||
regexp.MustCompile(`(\.|^)race\.(gs\.|gamespy\.com$)`): raceMux,
|
||||
}
|
||||
|
||||
func (c *nasConn) Read(b []byte) (int, error) {
|
||||
return c.reader.Read(b)
|
||||
}
|
||||
|
||||
// filterDuplicateHost wraps a net.Conn and filters out duplicate Host headers from the HTTP request,
|
||||
// making the invalid requests sent by DWC acceptable to the standard library's HTTP server.
|
||||
func filterDuplicateHost(c net.Conn) io.Reader {
|
||||
r := bufio.NewReader(c)
|
||||
|
||||
// Read the first line of the HTTP request
|
||||
line, err := r.ReadString('\n')
|
||||
if err != nil {
|
||||
return io.MultiReader(strings.NewReader(line), r)
|
||||
}
|
||||
// Is this an HTTP request?
|
||||
if !strings.HasSuffix(line, "HTTP/1.1\r\n") {
|
||||
return io.MultiReader(strings.NewReader(line), r)
|
||||
}
|
||||
|
||||
// Iterate through the HTTP headers and remove any duplicate Host headers
|
||||
var headers bytes.Buffer
|
||||
hostSeen := false
|
||||
for {
|
||||
headerLine, err := r.ReadString('\n')
|
||||
if err != nil || headerLine == "\r\n" {
|
||||
headers.WriteString(headerLine)
|
||||
break
|
||||
}
|
||||
if strings.HasPrefix(strings.ToLower(headerLine), "host:") {
|
||||
if hostSeen {
|
||||
continue
|
||||
}
|
||||
hostSeen = true
|
||||
}
|
||||
headers.WriteString(headerLine)
|
||||
}
|
||||
|
||||
return io.MultiReader(strings.NewReader(line+headers.String()), r)
|
||||
}
|
||||
|
||||
var regexRaceHost = regexp.MustCompile(`(\.|^)race\.(gs\.|gamespy\.com$)`)
|
||||
var regexSakeHost = regexp.MustCompile(`(\.|^)sake\.(gs\.|gamespy\.com$)`)
|
||||
var regexGamestatsHost = regexp.MustCompile(`(\.|^)gamestats2?\.(gs\.|gamespy\.com$)`)
|
||||
var regexStage1URL = regexp.MustCompile(`^/w([0-9])$`)
|
||||
|
||||
func handleRequest(w http.ResponseWriter, r *http.Request) {
|
||||
// Check for *.sake.gs.* or sake.gs.*
|
||||
if regexSakeHost.MatchString(r.Host) {
|
||||
// Redirect to the sake server
|
||||
sake.HandleRequest(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Check for *.gamestats(2).gs.* or gamestats(2).gs.*
|
||||
if regexGamestatsHost.MatchString(r.Host) {
|
||||
// Redirect to the gamestats server
|
||||
gamestats.HandleWebRequest(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Check for *.race.gs.* or race.gs.*
|
||||
if regexRaceHost.MatchString(r.Host) {
|
||||
// Redirect to the race server
|
||||
race.HandleRequest(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
moduleName := "NAS:" + r.RemoteAddr
|
||||
|
||||
// Handle conntest server
|
||||
if strings.HasPrefix(r.Host, "conntest.") {
|
||||
handleConnectionTest(w)
|
||||
return
|
||||
}
|
||||
|
||||
// Handle DWC auth requests
|
||||
if r.URL.String() == "/ac" || r.URL.String() == "/pr" || r.URL.String() == "/download" {
|
||||
handleAuthRequest(moduleName, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Handle /nastest.jsp
|
||||
if r.URL.Path == "/nastest.jsp" {
|
||||
handleNASTest(w)
|
||||
return
|
||||
}
|
||||
|
||||
// Check for /payload
|
||||
if strings.HasPrefix(r.URL.String(), "/payload") {
|
||||
logging.Info("NAS", aurora.Yellow(r.Method), aurora.Cyan(r.URL), "via", aurora.Cyan(r.Host), "from", aurora.BrightCyan(r.RemoteAddr))
|
||||
if payloadServerAddress != "" {
|
||||
// Forward the request to the payload server
|
||||
forwardPayloadRequest(moduleName, w, r)
|
||||
} else {
|
||||
handlePayloadRequest(moduleName, w, r)
|
||||
// Check for host-specific muxes
|
||||
for regex, mux := range hostMuxes {
|
||||
if regex.MatchString(r.Host) {
|
||||
mux.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Stage 1
|
||||
if match := regexStage1URL.FindStringSubmatch(r.URL.String()); match != nil {
|
||||
val, err := strconv.Atoi(match[1])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
http.DefaultServeMux.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
logging.Info("NAS", "Get stage 1:", aurora.Yellow(r.Method), aurora.Cyan(r.URL), "via", aurora.Cyan(r.Host), "from", aurora.BrightCyan(r.RemoteAddr))
|
||||
downloadStage1(w, val)
|
||||
return
|
||||
}
|
||||
|
||||
// Check for /api/groups
|
||||
if r.URL.Path == "/api/groups" {
|
||||
api.HandleGroups(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Check for /api/stats
|
||||
if r.URL.Path == "/api/stats" {
|
||||
api.HandleStats(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Check for /api/ban
|
||||
if r.URL.Path == "/api/ban" {
|
||||
api.HandleBan(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Check for /api/unban
|
||||
if r.URL.Path == "/api/unban" {
|
||||
api.HandleUnban(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Check for /api/kick
|
||||
if r.URL.Path == "/api/kick" {
|
||||
api.HandleKick(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
logging.Info("NAS", aurora.Yellow(r.Method), aurora.Cyan(r.URL), "via", aurora.Cyan(r.Host), "from", aurora.BrightCyan(r.RemoteAddr))
|
||||
replyHTTPError(w, 404, "404 Not Found")
|
||||
func getModuleName(r *http.Request) string {
|
||||
return "NAS:" + r.RemoteAddr
|
||||
}
|
||||
|
||||
func replyHTTPError(w http.ResponseWriter, errorCode int, errorString string) {
|
||||
@@ -269,7 +138,12 @@ func replyHTTPError(w http.ResponseWriter, errorCode int, errorString string) {
|
||||
_, _ = w.Write([]byte(response))
|
||||
}
|
||||
|
||||
func handleNASTest(w http.ResponseWriter) {
|
||||
func handleUnknown(w http.ResponseWriter, r *http.Request) {
|
||||
logging.Info(getModuleName(r), "Unknown request:", aurora.Yellow(r.Method), aurora.Cyan(r.Host+r.URL.Path))
|
||||
replyHTTPError(w, http.StatusNotFound, "404 Not Found")
|
||||
}
|
||||
|
||||
func handleNASTest(w http.ResponseWriter, r *http.Request) {
|
||||
response := "" +
|
||||
"<html>\n" +
|
||||
"<body>\n" +
|
||||
@@ -287,44 +161,3 @@ func handleNASTest(w http.ResponseWriter) {
|
||||
w.WriteHeader(200)
|
||||
_, _ = w.Write([]byte(response))
|
||||
}
|
||||
|
||||
func forwardPayloadRequest(moduleName string, w http.ResponseWriter, r *http.Request) {
|
||||
client := &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
r.URL.Scheme = "http"
|
||||
r.URL.Host = payloadServerAddress
|
||||
r.RequestURI = ""
|
||||
r.Host = payloadServerAddress
|
||||
|
||||
resp, err := client.Do(r)
|
||||
if err != nil {
|
||||
logging.Error(moduleName, "Error forwarding payload request:", err)
|
||||
replyHTTPError(w, http.StatusBadGateway, "502 Bad Gateway")
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
// Copy the response headers and status code
|
||||
for key, values := range resp.Header {
|
||||
for _, value := range values {
|
||||
w.Header().Add(key, value)
|
||||
}
|
||||
}
|
||||
w.WriteHeader(resp.StatusCode)
|
||||
|
||||
// Copy the response body
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
logging.Error(moduleName, "Error reading payload response body:", err)
|
||||
replyHTTPError(w, http.StatusInternalServerError, "500 Internal Server Error")
|
||||
return
|
||||
}
|
||||
_, err = w.Write(body)
|
||||
if err != nil {
|
||||
logging.Error(moduleName, "Error writing payload response body:", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,19 +9,23 @@ import (
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
"wwfc/logging"
|
||||
|
||||
"github.com/logrusorgru/aurora/v3"
|
||||
)
|
||||
|
||||
func downloadStage1(w http.ResponseWriter, stage1Ver int) {
|
||||
func downloadStage1(w http.ResponseWriter, r *http.Request) {
|
||||
stage1Ver := r.URL.Path[len(r.URL.Path)-1] - '0'
|
||||
|
||||
path := "payload/stage1.bin"
|
||||
if stage1Ver != 0 {
|
||||
path = "payload/stage1v" + strconv.Itoa(stage1Ver) + ".bin"
|
||||
path = "payload/stage1v" + strconv.Itoa(int(stage1Ver)) + ".bin"
|
||||
}
|
||||
dat, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
@@ -38,10 +42,55 @@ func downloadStage1(w http.ResponseWriter, stage1Ver int) {
|
||||
}
|
||||
}
|
||||
|
||||
func handlePayloadRequest(moduleName string, w http.ResponseWriter, r *http.Request) {
|
||||
func forwardPayloadRequest(w http.ResponseWriter, r *http.Request) {
|
||||
moduleName := getModuleName(r)
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
r.URL.Scheme = "http"
|
||||
r.URL.Host = payloadServerAddress
|
||||
r.RequestURI = ""
|
||||
r.Host = payloadServerAddress
|
||||
|
||||
resp, err := client.Do(r)
|
||||
if err != nil {
|
||||
logging.Error(moduleName, "Error forwarding payload request:", err)
|
||||
replyHTTPError(w, http.StatusBadGateway, "502 Bad Gateway")
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
// Copy the response headers and status code
|
||||
for key, values := range resp.Header {
|
||||
for _, value := range values {
|
||||
w.Header().Add(key, value)
|
||||
}
|
||||
}
|
||||
w.WriteHeader(resp.StatusCode)
|
||||
|
||||
// Copy the response body
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
logging.Error(moduleName, "Error reading payload response body:", err)
|
||||
replyHTTPError(w, http.StatusInternalServerError, "500 Internal Server Error")
|
||||
return
|
||||
}
|
||||
_, err = w.Write(body)
|
||||
if err != nil {
|
||||
logging.Error(moduleName, "Error writing payload response body:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func handlePayloadRequest(w http.ResponseWriter, r *http.Request) {
|
||||
// Example request:
|
||||
// GET /payload?g=RMCPD00&s=4e44b095817f8cfb62e6cffd57e9cfd411004a492784039ea4b2b7ca64717c91&h=9fdb6f60
|
||||
|
||||
moduleName := getModuleName(r)
|
||||
|
||||
u, err := url.Parse(r.URL.String())
|
||||
if err != nil {
|
||||
logging.Error(moduleName, "Failed to parse URL")
|
||||
|
||||
14
race/main.go
14
race/main.go
@@ -2,12 +2,8 @@ package race
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"wwfc/common"
|
||||
"wwfc/database"
|
||||
"wwfc/logging"
|
||||
|
||||
"github.com/logrusorgru/aurora/v3"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -28,12 +24,6 @@ func Shutdown() {
|
||||
db.Close()
|
||||
}
|
||||
|
||||
func HandleRequest(responseWriter http.ResponseWriter, request *http.Request) {
|
||||
logging.Info("RACE", aurora.Yellow(request.Method), aurora.Cyan(request.URL), "via", aurora.Cyan(request.Host), "from", aurora.BrightCyan(request.RemoteAddr))
|
||||
|
||||
switch {
|
||||
case strings.HasSuffix(request.URL.Path, "NintendoRacingService.asmx"):
|
||||
moduleName := "RACE:RacingService:" + request.RemoteAddr
|
||||
handleNintendoRacingServiceRequest(moduleName, responseWriter, request)
|
||||
}
|
||||
func RegisterHandlers(mux *http.ServeMux) {
|
||||
mux.HandleFunc("POST /RaceService/NintendoRacingService.asmx", handleNintendoRacingServiceRequest)
|
||||
}
|
||||
|
||||
@@ -72,30 +72,32 @@ const (
|
||||
xmlNamespace = "http://gamespy.net/RaceService/"
|
||||
)
|
||||
|
||||
var marioKartWiiGameID = common.GetGameIDOrPanic("mariokartwii") // 1687
|
||||
var marioKartWiiGameID = 1687
|
||||
|
||||
func handleNintendoRacingServiceRequest(moduleName string, responseWriter http.ResponseWriter, request *http.Request) {
|
||||
soapActionHeader := request.Header.Get("SOAPAction")
|
||||
func handleNintendoRacingServiceRequest(w http.ResponseWriter, r *http.Request) {
|
||||
moduleName := "RACE:RacingService:" + r.RemoteAddr
|
||||
|
||||
soapActionHeader := r.Header.Get("SOAPAction")
|
||||
if soapActionHeader == "" {
|
||||
logging.Error(moduleName, "No SOAPAction header")
|
||||
writeErrorResponse(raceServiceResultParseError, responseWriter)
|
||||
writeErrorResponse(raceServiceResultParseError, w)
|
||||
return
|
||||
}
|
||||
|
||||
slashIndex := strings.LastIndex(soapActionHeader, "/")
|
||||
if slashIndex == -1 {
|
||||
logging.Error(moduleName, "Invalid SOAPAction header")
|
||||
writeErrorResponse(raceServiceResultParseError, responseWriter)
|
||||
writeErrorResponse(raceServiceResultParseError, w)
|
||||
return
|
||||
}
|
||||
quotationMarkIndex := strings.Index(soapActionHeader[slashIndex+1:], "\"")
|
||||
if quotationMarkIndex == -1 {
|
||||
logging.Error(moduleName, "Invalid SOAPAction header")
|
||||
writeErrorResponse(raceServiceResultParseError, responseWriter)
|
||||
writeErrorResponse(raceServiceResultParseError, w)
|
||||
return
|
||||
}
|
||||
|
||||
requestBody, err := io.ReadAll(request.Body)
|
||||
requestBody, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -103,7 +105,7 @@ func handleNintendoRacingServiceRequest(moduleName string, responseWriter http.R
|
||||
soapAction := soapActionHeader[slashIndex+1 : slashIndex+1+quotationMarkIndex]
|
||||
switch soapAction {
|
||||
case "GetTopTenRankings":
|
||||
handleGetTopTenRankingsRequest(moduleName, responseWriter, requestBody)
|
||||
handleGetTopTenRankingsRequest(moduleName, w, requestBody)
|
||||
|
||||
// TODO SubmitScores
|
||||
default:
|
||||
|
||||
44
sake/file.go
44
sake/file.go
@@ -24,32 +24,40 @@ var fileUploadHandlers = map[int]func(string, http.ResponseWriter, *http.Request
|
||||
common.GetGameIDOrPanic("mariokartwii"): handleMarioKartWiiFileUploadRequest,
|
||||
}
|
||||
|
||||
func handleFileRequest(moduleName string, responseWriter http.ResponseWriter, request *http.Request,
|
||||
fileRequest FileRequest) {
|
||||
func handleFileDownloadRequest(w http.ResponseWriter, r *http.Request) {
|
||||
moduleName := "SAKE:File:" + r.RemoteAddr
|
||||
|
||||
gameIdString := request.URL.Query().Get("gameid")
|
||||
gameIdString := r.URL.Query().Get("gameid")
|
||||
gameId, err := strconv.Atoi(gameIdString)
|
||||
if err != nil {
|
||||
logging.Error(moduleName, "Invalid GameSpy game id")
|
||||
return
|
||||
}
|
||||
|
||||
var handler func(string, http.ResponseWriter, *http.Request)
|
||||
var handlerExists bool
|
||||
switch fileRequest {
|
||||
case FileRequestDownload:
|
||||
handler, handlerExists = fileDownloadHandlers[gameId]
|
||||
case FileRequestUpload:
|
||||
handler, handlerExists = fileUploadHandlers[gameId]
|
||||
default:
|
||||
logging.Error(moduleName, "Invalid file request")
|
||||
logging.Error(moduleName, "Invalid GameSpy game ID:", aurora.Cyan(gameIdString))
|
||||
return
|
||||
}
|
||||
|
||||
handler, handlerExists := fileDownloadHandlers[gameId]
|
||||
if !handlerExists {
|
||||
logging.Warn(moduleName, "Unhandled file request for GameSpy game id:", aurora.Cyan(gameId))
|
||||
logging.Warn(moduleName, "Unhandled file download request for GameSpy game ID:", aurora.Cyan(gameId))
|
||||
return
|
||||
}
|
||||
|
||||
handler(moduleName, responseWriter, request)
|
||||
handler(moduleName, w, r)
|
||||
}
|
||||
|
||||
func handleFileUploadRequest(w http.ResponseWriter, r *http.Request) {
|
||||
moduleName := "SAKE:File:" + r.RemoteAddr
|
||||
|
||||
gameIdString := r.URL.Query().Get("gameid")
|
||||
gameId, err := strconv.Atoi(gameIdString)
|
||||
if err != nil {
|
||||
logging.Error(moduleName, "Invalid GameSpy game ID:", aurora.Cyan(gameIdString))
|
||||
return
|
||||
}
|
||||
|
||||
handler, handlerExists := fileUploadHandlers[gameId]
|
||||
if !handlerExists {
|
||||
logging.Warn(moduleName, "Unhandled file upload request for GameSpy game ID:", aurora.Cyan(gameId))
|
||||
return
|
||||
}
|
||||
|
||||
handler(moduleName, w, r)
|
||||
}
|
||||
|
||||
24
sake/main.go
24
sake/main.go
@@ -2,12 +2,8 @@ package sake
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"wwfc/common"
|
||||
"wwfc/database"
|
||||
"wwfc/logging"
|
||||
|
||||
"github.com/logrusorgru/aurora/v3"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -28,19 +24,9 @@ func Shutdown() {
|
||||
db.Close()
|
||||
}
|
||||
|
||||
func HandleRequest(w http.ResponseWriter, r *http.Request) {
|
||||
logging.Info("SAKE", aurora.Yellow(r.Method), aurora.Cyan(r.URL), "via", aurora.Cyan(r.Host), "from", aurora.BrightCyan(r.RemoteAddr))
|
||||
|
||||
urlPath := r.URL.Path
|
||||
switch {
|
||||
case urlPath == "/SakeStorageServer/StorageServer.asmx":
|
||||
moduleName := "SAKE:Storage:" + r.RemoteAddr
|
||||
handleStorageRequest(moduleName, w, r)
|
||||
case strings.HasSuffix(urlPath, "download.aspx"):
|
||||
moduleName := "SAKE:File:" + r.RemoteAddr
|
||||
handleFileRequest(moduleName, w, r, FileRequestDownload)
|
||||
case strings.HasSuffix(urlPath, "upload.aspx"):
|
||||
moduleName := "SAKE:File:" + r.RemoteAddr
|
||||
handleFileRequest(moduleName, w, r, FileRequestUpload)
|
||||
}
|
||||
func RegisterHandlers(mux *http.ServeMux) {
|
||||
mux.HandleFunc("POST /SakeStorageServer/StorageServer.asmx", handleStorageRequest)
|
||||
mux.HandleFunc("GET /SakeFileServer/download.aspx", handleFileDownloadRequest)
|
||||
mux.HandleFunc("POST /SakeFileServer/upload.aspx", handleFileUploadRequest)
|
||||
mux.HandleFunc("GET /SakeFileServer/ghostdownload.aspx", handleMarioKartWiiGhostDownloadRequest)
|
||||
}
|
||||
|
||||
@@ -176,11 +176,6 @@ func getMarioKartWiiStoredGhostDataRecord(moduleName string, request StorageRequ
|
||||
}
|
||||
|
||||
func handleMarioKartWiiFileDownloadRequest(moduleName string, responseWriter http.ResponseWriter, request *http.Request) {
|
||||
if strings.HasSuffix(request.URL.Path, "ghostdownload.aspx") {
|
||||
handleMarioKartWiiGhostDownloadRequest(moduleName, responseWriter, request)
|
||||
return
|
||||
}
|
||||
|
||||
query := request.URL.Query()
|
||||
|
||||
fileIdString := query.Get("fileid")
|
||||
@@ -214,7 +209,9 @@ func handleMarioKartWiiFileDownloadRequest(moduleName string, responseWriter htt
|
||||
}
|
||||
}
|
||||
|
||||
func handleMarioKartWiiGhostDownloadRequest(moduleName string, responseWriter http.ResponseWriter, request *http.Request) {
|
||||
func handleMarioKartWiiGhostDownloadRequest(responseWriter http.ResponseWriter, request *http.Request) {
|
||||
moduleName := "SAKE:File:GhostDownload:" + request.RemoteAddr
|
||||
|
||||
query := request.URL.Query()
|
||||
|
||||
regionIdString := query.Get("region")
|
||||
|
||||
@@ -175,7 +175,9 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func handleStorageRequest(moduleName string, w http.ResponseWriter, r *http.Request) {
|
||||
func handleStorageRequest(w http.ResponseWriter, r *http.Request) {
|
||||
moduleName := "SAKE:Storage:" + r.RemoteAddr
|
||||
|
||||
headerAction := r.Header.Get("SOAPAction")
|
||||
if headerAction == "" {
|
||||
logging.Error(moduleName, "No SOAPAction in header")
|
||||
|
||||
Reference in New Issue
Block a user