summaryrefslogtreecommitdiff
path: root/pkg/caddyscript/lib/http.go
blob: 0e42458a3eb5e91b7133b45e95406b1c1ed9a222 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
package caddyscript

import (
	"fmt"
	"net/http"

	"github.com/starlight-go/starlight/convert"
	"go.starlark.net/starlark"
)

// HTTPRequest represents an http request type in caddyscript.
type HTTPRequest struct{ Req *http.Request }

// AttrNames defines what properties and methods are available on the HTTPRequest type.
func (r HTTPRequest) AttrNames() []string {
	return []string{"header", "query", "url", "method", "host", "tls", "redirect"}
}

func (r HTTPRequest) Freeze()               {}
func (r HTTPRequest) Hash() (uint32, error) { return 0, fmt.Errorf("unhashable: HTTPRequest") }
func (r HTTPRequest) String() string        { return fmt.Sprint(r.Req) }
func (r HTTPRequest) Type() string          { return "HTTPRequest" }
func (r HTTPRequest) Truth() starlark.Bool  { return true }

// Header handles returning a header key.
func (r HTTPRequest) Header(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
	var key string
	err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 1, &key)
	if err != nil {
		return starlark.None, fmt.Errorf("get request header: %v", err.Error())
	}

	return starlark.String(r.Req.Header.Get(key)), nil
}

// Redirect handles an http redirect from starlark code.
func (r HTTPRequest) Redirect(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
	var w starlark.Value
	var req HTTPRequest
	var newURL string
	err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 3, &w, &req, &newURL)
	if err != nil {
		return starlark.None, fmt.Errorf("unpacking arguments: %v", err.Error())
	}

	writer := convert.FromValue(w)
	if w, ok := writer.(http.ResponseWriter); ok {
		http.Redirect(w, req.Req, newURL, http.StatusSeeOther)
		return starlark.None, nil
	}

	return starlark.None, fmt.Errorf("first provided argument is not http.ResponseWriter")
}

// Attr defines what happens when props or methods are called on the HTTPRequest type.
func (r HTTPRequest) Attr(name string) (starlark.Value, error) {
	switch name {
	case "redirect":
		b := starlark.NewBuiltin("Redirect", r.Redirect)
		b = b.BindReceiver(r)

		return b, nil
	case "tls":
		tls := new(starlark.Dict)
		tls.SetKey(starlark.String("cipher_suite"), starlark.MakeUint(uint(r.Req.TLS.CipherSuite)))
		tls.SetKey(starlark.String("did_resume"), starlark.Bool(r.Req.TLS.DidResume))
		tls.SetKey(starlark.String("handshake_complete"), starlark.Bool(r.Req.TLS.HandshakeComplete))
		tls.SetKey(starlark.String("negotiated_protocol"), starlark.String(r.Req.TLS.NegotiatedProtocol))
		tls.SetKey(starlark.String("negotiated_protocol_is_mutual"), starlark.Bool(r.Req.TLS.NegotiatedProtocolIsMutual))
		tls.SetKey(starlark.String("server_name"), starlark.String(r.Req.TLS.ServerName))
		tls.SetKey(starlark.String("version"), starlark.String(r.Req.TLS.Version))

		return tls, nil
	case "header":
		b := starlark.NewBuiltin("Header", r.Header)
		b = b.BindReceiver(r)

		return b, nil
	case "query":
		qVals := r.Req.URL.Query()
		query := starlark.NewDict(len(qVals))

		for k, v := range qVals {
			query.SetKey(starlark.String(k), starlark.String(v[0]))
		}

		return query, nil
	case "url":
		return starlark.String(r.Req.URL.Path), nil
	case "method":
		return starlark.String(r.Req.Method), nil
	case "host":
		return starlark.String(r.Req.Host), nil
	}

	return nil, nil
}