Ctrl K
ComunidadGuías…

engine/canonpath_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package engine

import (
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"github.com/xyproto/pinterface/v2"
)

// prefixPerm rejects requests to paths that start with the given prefixes,
// the same way the permission packages do.
type prefixPerm struct {
	rejectPrefixes []string
}

func (p *prefixPerm) Rejected(_ http.ResponseWriter, req *http.Request) bool {
	lowerPath := strings.ToLower(req.URL.Path)
	for _, prefix := range p.rejectPrefixes {
		if strings.HasPrefix(lowerPath, prefix) {
			return true
		}
	}
	return false
}

func (p *prefixPerm) DenyFunction() http.HandlerFunc {
	return func(w http.ResponseWriter, _ *http.Request) {
		http.Error(w, "Permission denied.", http.StatusForbidden)
	}
}

func (p *prefixPerm) AddAdminPath(string)                                            {}
func (p *prefixPerm) AddPublicPath(string)                                           {}
func (p *prefixPerm) AddUserPath(string)                                             {}
func (p *prefixPerm) Clear()                                                         {}
func (p *prefixPerm) ServeHTTP(http.ResponseWriter, *http.Request, http.HandlerFunc) {}
func (p *prefixPerm) SetAdminPath([]string)                                          {}
func (p *prefixPerm) SetDenyFunction(http.HandlerFunc)                               {}
func (p *prefixPerm) SetPublicPath([]string)                                         {}
func (p *prefixPerm) SetUserPath([]string)                                           {}
func (p *prefixPerm) UserState() pinterface.IUserState                               { return nil }

// serveWithGuards wraps a mux that records the path that the handlers saw
// with the middleware chain that every server uses.
func serveWithGuards(ac *Config, seen *string) http.Handler {
	mux := http.NewServeMux()
	record := func(w http.ResponseWriter, req *http.Request) {
		*seen = req.URL.Path
		w.WriteHeader(http.StatusOK)
	}
	mux.HandleFunc("/", record)
	mux.HandleFunc(hmrUpdatePrefix, record)
	return ac.guardHandler(mux)
}

// A percent-encoded slash must not be able to sneak past the permission check.
func TestCanonicalPathMiddlewareEncodedSlash(t *testing.T) {
	ac := &Config{perm: &prefixPerm{rejectPrefixes: []string{"/admin"}}}

	tests := []struct {
		target string
		status int
		seen   string
	}{
		{"/admin/secret.lua", http.StatusForbidden, ""},
		{"/%2fadmin/secret.lua", http.StatusForbidden, ""},
		{"/%2e%2e/admin/secret.lua", http.StatusForbidden, ""},
		{"/%2f%2fadmin/secret.lua", http.StatusForbidden, ""},
		{"/ADMIN/secret.lua", http.StatusForbidden, ""},
		{"/index.html", http.StatusOK, "/index.html"},
	}
	for _, tt := range tests {
		var seen string
		w := httptest.NewRecorder()
		serveWithGuards(ac, &seen).ServeHTTP(w, httptest.NewRequest("GET", tt.target, nil))
		if w.Code != tt.status {
			t.Errorf("GET %s gave status %d, want %d", tt.target, w.Code, tt.status)
		}
		if seen != tt.seen {
			t.Errorf("GET %s reached the handler as %q, want %q", tt.target, seen, tt.seen)
		}
	}
}

// Routes registered outside of RegisterHandlers must be guarded as well.
func TestPermissionMiddlewareGuardsHMR(t *testing.T) {
	ac := &Config{perm: &prefixPerm{rejectPrefixes: []string{"/"}}}

	var seen string
	w := httptest.NewRecorder()
	serveWithGuards(ac, &seen).ServeHTTP(w, httptest.NewRequest("GET", hmrUpdatePrefix+"app.js", nil))

	if w.Code != http.StatusForbidden {
		t.Errorf("GET %sapp.js gave status %d, want %d", hmrUpdatePrefix, w.Code, http.StatusForbidden)
	}
	if seen != "" {
		t.Errorf("GET %sapp.js reached the handler as %q", hmrUpdatePrefix, seen)
	}
}

// Requests with control characters in the path are refused
func TestCanonicalPathMiddlewareControlCharacters(t *testing.T) {
	var seen string
	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/", nil)
	req.URL.Path = "/a\x00b"
	serveWithGuards(&Config{}, &seen).ServeHTTP(w, req)

	if w.Code != http.StatusBadRequest {
		t.Errorf("Path with a NUL byte gave status %d, want %d", w.Code, http.StatusBadRequest)
	}
}

// A Lua DenyHandler that only writes a body must still send 403 Forbidden.
func TestForbiddenWriterDefaultsTo403(t *testing.T) {
	rec := httptest.NewRecorder()
	fw := &forbiddenWriter{ResponseWriter: rec}
	fw.Write([]byte("denied"))
	if rec.Code != http.StatusForbidden {
		t.Errorf("status = %d, want %d", rec.Code, http.StatusForbidden)
	}
	rec = httptest.NewRecorder()
	fw = &forbiddenWriter{ResponseWriter: rec}
	fw.WriteHeader(http.StatusUnauthorized)
	fw.Write([]byte("log in first"))
	if rec.Code != http.StatusUnauthorized {
		t.Errorf("status = %d, want %d", rec.Code, http.StatusUnauthorized)
	}
	fw.finish()
	if rec.Code != http.StatusUnauthorized {
		t.Errorf("status after finish = %d, want %d", rec.Code, http.StatusUnauthorized)
	}
}

// A Lua DenyHandler that writes nothing must still send 403 Forbidden.
func TestForbiddenWriterEmptyBody(t *testing.T) {
	rec := httptest.NewRecorder()
	fw := &forbiddenWriter{ResponseWriter: rec}
	fw.finish()
	if rec.Code != http.StatusForbidden {
		t.Errorf("status = %d, want %d", rec.Code, http.StatusForbidden)
	}
}