aboutsummaryrefslogtreecommitdiff
path: root/request.go
blob: cd6c84f8464dac4448da68bc2d61baaac21d0a30 (plain) (blame)
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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package gemini

import (
	"bufio"
	"context"
	"crypto/tls"
	"io"
	"net"
	"net/url"
)

// A Request represents a Gemini request received by a server or to be sent
// by a client.
//
// The field semantics differ slightly between client and server usage.
type Request struct {
	// URL specifies the URL being requested (for server
	// requests) or the URL to access (for client requests).
	URL *url.URL

	// For client requests, Host specifies the server to connect to.
	// Host must contain a port.
	// This field is ignored by the Gemini server.
	Host string

	// For client requests, Certificate optionally specifies the
	// TLS certificate to present to the other side of the connection.
	// This field is ignored by the Gemini server.
	Certificate *tls.Certificate

	// RemoteAddr allows Gemini servers and other software to record
	// the network address that sent the request, usually for
	// logging. This field is not filled in by ReadRequest and
	// has no defined format. The Gemini server in this package
	// sets RemoteAddr to an "IP:port" address before invoking a
	// handler.
	// This field is ignored by the Gemini client.
	RemoteAddr net.Addr

	// TLS allows Gemini servers and other software to record
	// information about the TLS connection on which the request
	// was received. This field is not filled in by ReadRequest.
	// The Gemini server in this package sets the field for
	// TLS-enabled connections before invoking a handler;
	// otherwise it leaves the field nil.
	// This field is ignored by the Gemini client.
	TLS *tls.ConnectionState

	// Context specifies the context to use for outgoing requests.
	// The context controls the entire lifetime of a request and its
	// response: obtaining a connection, sending the request, and
	// reading the response header and body.
	// If Context is nil, the background context will be used.
	// This field is ignored by the Gemini server.
	Context context.Context
}

// NewRequest returns a new request. The host is inferred from the URL.
//
// The returned Request is suitable for use with Client.Do.
func NewRequest(rawurl string) (*Request, error) {
	u, err := url.Parse(rawurl)
	if err != nil {
		return nil, err
	}
	return NewRequestFromURL(u), nil
}

// NewRequestFromURL returns a new request for the given URL.
// The host is inferred from the URL.
//
// Callers should be careful that the URL query is properly escaped.
// See the documentation for QueryEscape for more information.
func NewRequestFromURL(url *url.URL) *Request {
	host := url.Host
	if url.Port() == "" {
		host += ":1965"
	}
	return &Request{
		URL:  url,
		Host: host,
	}
}

// ReadRequest reads and parses an incoming request from r.
//
// ReadRequest is a low-level function and should only be used
// for specialized applications; most code should use the Server
// to read requests and handle them via the Handler interface.
func ReadRequest(r io.Reader) (*Request, error) {
	// Read URL
	br := bufio.NewReaderSize(r, 1026)
	rawurl, err := br.ReadString('\r')
	if err != nil {
		return nil, err
	}
	// Read terminating line feed
	if b, err := br.ReadByte(); err != nil {
		return nil, err
	} else if b != '\n' {
		return nil, ErrInvalidRequest
	}
	// Trim carriage return
	rawurl = rawurl[:len(rawurl)-1]
	// Validate URL
	if len(rawurl) > 1024 {
		return nil, ErrInvalidRequest
	}
	u, err := url.Parse(rawurl)
	if err != nil {
		return nil, err
	}
	if u.User != nil {
		// User is not allowed
		return nil, ErrInvalidURL
	}
	return &Request{URL: u}, nil
}

// Write writes a Gemini request in wire format.
// This method consults the request URL only.
func (r *Request) Write(w *bufio.Writer) error {
	url := r.URL.String()
	// User is invalid
	if r.URL.User != nil || len(url) > 1024 {
		return ErrInvalidURL
	}
	if _, err := w.WriteString(url); err != nil {
		return err
	}
	if _, err := w.Write(crlf); err != nil {
		return err
	}
	return nil
}