generated from VLADIMIR/template
121 lines
4.4 KiB
Go
121 lines
4.4 KiB
Go
package cudy
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func clientRow(id, mac, band, signal string) string {
|
|
return fmt.Sprintf(`<tr id="cbi-table-%s"><td><div id="cbi-table-%s-hostname"><p>PRIVATE NAME<br><span>%s</span></p></div></td><td><div id="cbi-table-%s-ipmac"><p>192.168.10.9<br>%s</p><p>192.168.10.9<br>%s</p></div></td><td><div id="cbi-table-%s-signal"><p>%s</p><p>%s</p></div></td></tr>`, id, id, band, id, mac, mac, id, signal, signal)
|
|
}
|
|
func clientTable(rows string) string {
|
|
return `<table><thead><tr><th>IP / MAC Address</th></tr></thead><tbody>` + rows + `</tbody></table>`
|
|
}
|
|
|
|
func TestParseClients(t *testing.T) {
|
|
rows := clientRow("1", "AA:BB:CC:DD:EE:02", "2.4G WiFi", "-60 dBm") + clientRow("2", "AA:BB:CC:DD:EE:01", "5G WiFi", "—") + clientRow("3", "AA:BB:CC:DD:EE:03", "Ethernet", "—")
|
|
clients, err := parseClients([]byte(clientTable(rows)))
|
|
if err != nil || len(clients) != 2 {
|
|
t.Fatalf("reading: %v, count %d", err, len(clients))
|
|
}
|
|
if clients[0].MAC != "AA:BB:CC:DD:EE:01" || clients[0].Signal != nil || clients[1].Signal == nil || *clients[1].Signal != -60 {
|
|
t.Fatalf("unexpected snapshot: %+v", clients)
|
|
}
|
|
data, _ := json.Marshal(clients)
|
|
if strings.Contains(string(data), "PRIVATE") {
|
|
t.Fatal("hostname leaked")
|
|
}
|
|
for _, text := range []string{`<form>Login</form>`, clientTable(clientRow("1", "bad", "5G WiFi", "-60 dBm")), clientTable(strings.ReplaceAll(clientRow("1", "AA:BB:CC:DD:EE:01", "5G WiFi", "-60 dBm"), "cbi-table-", "changed-")), clientTable(clientRow("1", "AA:BB:CC:DD:EE:01", "5 GHz", "-60 dBm")), clientTable(clientRow("1", "AA:BB:CC:DD:EE:01", "", "-60 dBm")), clientTable(rows + clientRow("4", "AA:BB:CC:DD:EE:01", "5G WiFi", "-70 dBm"))} {
|
|
if _, err := parseClients([]byte(text)); !errors.Is(err, ErrRead) {
|
|
t.Fatalf("invalid DOM accepted: %v", err)
|
|
}
|
|
}
|
|
clients, err = parseClients([]byte(clientTable("")))
|
|
if err != nil || len(clients) != 0 {
|
|
t.Fatal("empty table rejected")
|
|
}
|
|
}
|
|
|
|
func TestReaderSessionAndSafeErrors(t *testing.T) {
|
|
sessions := 0
|
|
expire := false
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/cgi-bin/luci/", func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method == "POST" {
|
|
if err := r.ParseForm(); err != nil {
|
|
t.Error(err)
|
|
}
|
|
first := sha256.Sum256([]byte("private-password" + "salt"))
|
|
want := sha256.Sum256([]byte(hex.EncodeToString(first[:]) + "token"))
|
|
if r.Form.Get("luci_password") != hex.EncodeToString(want[:]) || r.Form.Get("_csrf") != "csrf" || r.Form.Get("luci_language") != "en" {
|
|
t.Error("incorrect login")
|
|
}
|
|
sessions++
|
|
http.SetCookie(w, &http.Cookie{Name: "sysauth", Value: "session", Path: "/"})
|
|
fmt.Fprint(w, "authenticated")
|
|
return
|
|
}
|
|
w.WriteHeader(403)
|
|
fmt.Fprint(w, `<form><input type="hidden" name="salt" value="salt"><input type="hidden" name="_csrf" value="csrf"></form>`)
|
|
})
|
|
mux.HandleFunc("/cgi-bin/luci/admin/get_token", func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "POST" {
|
|
t.Error("token method")
|
|
}
|
|
fmt.Fprint(w, "token")
|
|
})
|
|
mux.HandleFunc("/cgi-bin/luci/admin/network/devices/devlist", func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "GET" || r.URL.RawQuery != "detail=1" {
|
|
t.Error("not a read-only request")
|
|
}
|
|
if _, err := r.Cookie("sysauth"); err != nil {
|
|
t.Error("session cookie missing")
|
|
}
|
|
if expire {
|
|
expire = false
|
|
w.WriteHeader(403)
|
|
return
|
|
}
|
|
fmt.Fprint(w, clientTable(clientRow("1", "AA:BB:CC:DD:EE:01", "5G WiFi", "-55 dBm")))
|
|
})
|
|
server := httptest.NewServer(mux)
|
|
defer server.Close()
|
|
reader, err := NewReader(server.URL, "private-password")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for i := 0; i < 2; i++ {
|
|
if _, err := reader.Read(context.Background()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if sessions != 1 {
|
|
t.Fatal("session not reused")
|
|
}
|
|
expire = true
|
|
if _, err := reader.Read(context.Background()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if sessions != 2 {
|
|
t.Fatal("expired session not refreshed once")
|
|
}
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
if _, err := reader.Read(ctx); !errors.Is(err, context.Canceled) {
|
|
t.Fatal("cancellation lost")
|
|
}
|
|
for _, base := range []string{"https://example.com", "http://user:secret@192.168.10.1", "http://192.168.10.1/path", "http://8.8.8.8"} {
|
|
if _, err := NewReader(base, "secret"); !errors.Is(err, ErrRead) {
|
|
t.Fatal("untrusted URL accepted")
|
|
}
|
|
}
|
|
}
|