mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-08-04 06:57:48 +00:00
rtl8720dn: add support for http.Get(), http.Post() and cookie
This commit is contained in:
@@ -1,12 +1,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/net"
|
||||
"tinygo.org/x/drivers/net/tls"
|
||||
"tinygo.org/x/drivers/net/http"
|
||||
"tinygo.org/x/drivers/rtl8720dn"
|
||||
)
|
||||
|
||||
@@ -15,14 +16,14 @@ import (
|
||||
// ssid = "your-ssid"
|
||||
// password = "your-password"
|
||||
// debug = true
|
||||
// server = "tinygo.org"
|
||||
// url = "https://www.example.com"
|
||||
// test_root_ca = "..."
|
||||
// }
|
||||
|
||||
var (
|
||||
ssid string
|
||||
password string
|
||||
server string = "www.example.com"
|
||||
url string = "https://www.example.com"
|
||||
debug = false
|
||||
)
|
||||
|
||||
@@ -52,7 +53,7 @@ CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
|
||||
var buf [0x400]byte
|
||||
var buf [0x1000]byte
|
||||
|
||||
var lastRequestTime time.Time
|
||||
var conn net.Conn
|
||||
@@ -73,6 +74,7 @@ func run() error {
|
||||
}
|
||||
rtl.SetRootCA(&test_root_ca)
|
||||
net.UseDriver(rtl)
|
||||
http.SetBuf(buf[:])
|
||||
|
||||
err = rtl.ConnectToAP(ssid, password)
|
||||
if err != nil {
|
||||
@@ -87,55 +89,49 @@ func run() error {
|
||||
fmt.Printf("Mask : %s\r\n", subnet)
|
||||
fmt.Printf("Gateway : %s\r\n", gateway)
|
||||
|
||||
// You can send and receive cookies in the following way
|
||||
// import "tinygo.org/x/drivers/net/http/cookiejar"
|
||||
// jar, err := cookiejar.New(nil)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// client := &http.Client{Jar: jar}
|
||||
// http.DefaultClient = client
|
||||
|
||||
cnt := 0
|
||||
for {
|
||||
readConnection()
|
||||
if time.Now().Sub(lastRequestTime).Milliseconds() >= 10000 {
|
||||
makeHTTPSRequest()
|
||||
cnt++
|
||||
fmt.Printf("-------- %d --------\r\n", cnt)
|
||||
// Various examples are as follows
|
||||
//
|
||||
// -- Get
|
||||
// resp, err := http.Get(url)
|
||||
//
|
||||
// -- Post
|
||||
// body := `cnt=12`
|
||||
// resp, err = http.Post(url, "application/x-www-form-urlencoded", strings.NewReader(body))
|
||||
//
|
||||
// -- Post with JSON
|
||||
// body := `{"msg": "hello"}`
|
||||
// resp, err := http.Post(url, "application/json", strings.NewReader(body))
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func readConnection() {
|
||||
if conn != nil {
|
||||
for n, err := conn.Read(buf[:]); n > 0; n, err = conn.Read(buf[:]) {
|
||||
if err != nil {
|
||||
println("Read error: " + err.Error())
|
||||
} else {
|
||||
print(string(buf[0:n]))
|
||||
}
|
||||
fmt.Printf("%s %s\r\n", resp.Proto, resp.Status)
|
||||
for k, v := range resp.Header {
|
||||
fmt.Printf("%s: %s\r\n", k, strings.Join(v, " "))
|
||||
}
|
||||
fmt.Printf("\r\n")
|
||||
|
||||
scanner := bufio.NewScanner(resp.Body)
|
||||
for scanner.Scan() {
|
||||
fmt.Printf("%s\r\n", scanner.Text())
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
cnt++
|
||||
fmt.Printf("-------- %d --------\r\n", cnt)
|
||||
time.Sleep(10 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func makeHTTPSRequest() {
|
||||
|
||||
var err error
|
||||
if conn != nil {
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
message("\r\n---------------\r\nDialing TCP connection")
|
||||
conn, err = tls.Dial("tcp", server, nil)
|
||||
for ; err != nil; conn, err = tls.Dial("tcp", server, nil) {
|
||||
message("Connection failed: " + err.Error())
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
println("Connected!\r")
|
||||
|
||||
print("Sending HTTPS request...")
|
||||
fmt.Fprintln(conn, "GET / HTTP/1.1")
|
||||
fmt.Fprintln(conn, "Host:", strings.Split(server, ":")[0])
|
||||
fmt.Fprintln(conn, "User-Agent: TinyGo")
|
||||
fmt.Fprintln(conn, "Connection: close")
|
||||
fmt.Fprintln(conn)
|
||||
println("Sent!\r\n\r")
|
||||
|
||||
lastRequestTime = time.Now()
|
||||
}
|
||||
|
||||
func message(msg string) {
|
||||
println(msg, "\r")
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"image/color"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/net"
|
||||
"tinygo.org/x/drivers/rtl8720dn"
|
||||
"tinygo.org/x/drivers/net/http"
|
||||
"tinygo.org/x/tinyfont/proggy"
|
||||
"tinygo.org/x/tinyterm"
|
||||
)
|
||||
@@ -23,8 +25,8 @@ import (
|
||||
var (
|
||||
ssid string
|
||||
password string
|
||||
server string = "tinygo.org"
|
||||
debug = false
|
||||
url = "http://tinygo.org/"
|
||||
debug = false
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -41,10 +43,6 @@ var (
|
||||
|
||||
var buf [0x400]byte
|
||||
|
||||
var lastRequestTime time.Time
|
||||
var conn net.Conn
|
||||
var adaptor *rtl8720dn.RTL8720DN
|
||||
|
||||
func main() {
|
||||
display.FillScreen(black)
|
||||
backlight.High()
|
||||
@@ -73,6 +71,7 @@ func run() error {
|
||||
return err
|
||||
}
|
||||
net.UseDriver(rtl)
|
||||
http.SetBuf(buf[:])
|
||||
|
||||
fmt.Fprintf(terminal, "ConnectToAP()\r\n")
|
||||
err = rtl.ConnectToAP(ssid, password)
|
||||
@@ -89,60 +88,49 @@ func run() error {
|
||||
fmt.Fprintf(terminal, "Mask : %s\r\n", subnet)
|
||||
fmt.Fprintf(terminal, "Gateway : %s\r\n", gateway)
|
||||
|
||||
// You can send and receive cookies in the following way
|
||||
// import "tinygo.org/x/drivers/net/http/cookiejar"
|
||||
// jar, err := cookiejar.New(nil)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// client := &http.Client{Jar: jar}
|
||||
// http.DefaultClient = client
|
||||
|
||||
cnt := 0
|
||||
for {
|
||||
readConnection()
|
||||
if time.Now().Sub(lastRequestTime).Milliseconds() >= 10000 {
|
||||
makeHTTPRequest()
|
||||
cnt++
|
||||
fmt.Fprintf(terminal, "-------- %d --------\r\n", cnt)
|
||||
// Various examples are as follows
|
||||
//
|
||||
// -- Get
|
||||
// resp, err := http.Get(url)
|
||||
//
|
||||
// -- Post
|
||||
// body := `cnt=12`
|
||||
// resp, err = http.Post(url, "application/x-www-form-urlencoded", strings.NewReader(body))
|
||||
//
|
||||
// -- Post with JSON
|
||||
// body := `{"msg": "hello"}`
|
||||
// resp, err := http.Post(url, "application/json", strings.NewReader(body))
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func readConnection() {
|
||||
if conn != nil {
|
||||
for n, err := conn.Read(buf[:]); n > 0; n, err = conn.Read(buf[:]) {
|
||||
if err != nil {
|
||||
fmt.Fprintf(terminal, "Read error: "+err.Error()+"\r\n")
|
||||
} else {
|
||||
fmt.Fprintf(terminal, string(buf[0:n]))
|
||||
}
|
||||
fmt.Fprintf(terminal, "%s %s\r\n", resp.Proto, resp.Status)
|
||||
for k, v := range resp.Header {
|
||||
fmt.Fprintf(terminal, "%s: %s\r\n", k, strings.Join(v, " "))
|
||||
}
|
||||
fmt.Printf("\r\n")
|
||||
|
||||
scanner := bufio.NewScanner(resp.Body)
|
||||
for scanner.Scan() {
|
||||
fmt.Fprintf(terminal, "%s\r\n", scanner.Text())
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
cnt++
|
||||
fmt.Fprintf(terminal, "-------- %d --------\r\n", cnt)
|
||||
time.Sleep(10 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func makeHTTPRequest() {
|
||||
|
||||
var err error
|
||||
if conn != nil {
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
// make TCP connection
|
||||
ip := net.ParseIP(server)
|
||||
raddr := &net.TCPAddr{IP: ip, Port: 80}
|
||||
laddr := &net.TCPAddr{Port: 8080}
|
||||
|
||||
message("\r\n---------------\r\nDialing TCP connection")
|
||||
conn, err = net.DialTCP("tcp", laddr, raddr)
|
||||
for ; err != nil; conn, err = net.DialTCP("tcp", laddr, raddr) {
|
||||
message("Connection failed: " + err.Error())
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
fmt.Fprintf(terminal, "Connected!\r\n")
|
||||
|
||||
fmt.Fprintf(terminal, "Sending HTTP request...")
|
||||
fmt.Fprintln(conn, "GET / HTTP/1.1")
|
||||
fmt.Fprintln(conn, "Host:", server)
|
||||
fmt.Fprintln(conn, "User-Agent: TinyGo")
|
||||
fmt.Fprintln(conn, "Connection: close")
|
||||
fmt.Fprintln(conn)
|
||||
fmt.Fprintf(terminal, "Sent!\r\n\r\n")
|
||||
|
||||
lastRequestTime = time.Now()
|
||||
}
|
||||
|
||||
func message(msg string) {
|
||||
fmt.Fprintf(terminal, "%s\r\n", msg)
|
||||
}
|
||||
|
||||
@@ -1,34 +1,32 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/net"
|
||||
"tinygo.org/x/drivers/rtl8720dn"
|
||||
"tinygo.org/x/drivers/net/http"
|
||||
)
|
||||
|
||||
// You can override the setting with the init() in another source code.
|
||||
// func init() {
|
||||
// ssid = "your-ssid"
|
||||
// password = "your-password"
|
||||
// url = "http://tinygo.org/"
|
||||
// debug = true
|
||||
// server = "tinygo.org"
|
||||
// }
|
||||
|
||||
var (
|
||||
ssid string
|
||||
password string
|
||||
server string = "tinygo.org"
|
||||
debug = false
|
||||
url = "http://tinygo.org/"
|
||||
debug = false
|
||||
)
|
||||
|
||||
var buf [0x400]byte
|
||||
|
||||
var lastRequestTime time.Time
|
||||
var conn net.Conn
|
||||
var adaptor *rtl8720dn.RTL8720DN
|
||||
|
||||
func main() {
|
||||
err := run()
|
||||
for err != nil {
|
||||
@@ -43,6 +41,7 @@ func run() error {
|
||||
return err
|
||||
}
|
||||
net.UseDriver(rtl)
|
||||
http.SetBuf(buf[:])
|
||||
|
||||
err = rtl.ConnectToAP(ssid, password)
|
||||
if err != nil {
|
||||
@@ -57,60 +56,49 @@ func run() error {
|
||||
fmt.Printf("Mask : %s\r\n", subnet)
|
||||
fmt.Printf("Gateway : %s\r\n", gateway)
|
||||
|
||||
// You can send and receive cookies in the following way
|
||||
// import "tinygo.org/x/drivers/net/http/cookiejar"
|
||||
// jar, err := cookiejar.New(nil)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// client := &http.Client{Jar: jar}
|
||||
// http.DefaultClient = client
|
||||
|
||||
cnt := 0
|
||||
for {
|
||||
readConnection()
|
||||
if time.Now().Sub(lastRequestTime).Milliseconds() >= 10000 {
|
||||
makeHTTPRequest()
|
||||
cnt++
|
||||
fmt.Printf("-------- %d --------\r\n", cnt)
|
||||
// Various examples are as follows
|
||||
//
|
||||
// -- Get
|
||||
// resp, err := http.Get(url)
|
||||
//
|
||||
// -- Post
|
||||
// body := `cnt=12`
|
||||
// resp, err = http.Post(url, "application/x-www-form-urlencoded", strings.NewReader(body))
|
||||
//
|
||||
// -- Post with JSON
|
||||
// body := `{"msg": "hello"}`
|
||||
// resp, err := http.Post(url, "application/json", strings.NewReader(body))
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func readConnection() {
|
||||
if conn != nil {
|
||||
for n, err := conn.Read(buf[:]); n > 0; n, err = conn.Read(buf[:]) {
|
||||
if err != nil {
|
||||
println("Read error: " + err.Error())
|
||||
} else {
|
||||
print(string(buf[0:n]))
|
||||
}
|
||||
fmt.Printf("%s %s\r\n", resp.Proto, resp.Status)
|
||||
for k, v := range resp.Header {
|
||||
fmt.Printf("%s: %s\r\n", k, strings.Join(v, " "))
|
||||
}
|
||||
fmt.Printf("\r\n")
|
||||
|
||||
scanner := bufio.NewScanner(resp.Body)
|
||||
for scanner.Scan() {
|
||||
fmt.Printf("%s\r\n", scanner.Text())
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
cnt++
|
||||
fmt.Printf("-------- %d --------\r\n", cnt)
|
||||
time.Sleep(10 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func makeHTTPRequest() {
|
||||
|
||||
var err error
|
||||
if conn != nil {
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
// make TCP connection
|
||||
ip := net.ParseIP(server)
|
||||
raddr := &net.TCPAddr{IP: ip, Port: 80}
|
||||
laddr := &net.TCPAddr{Port: 8080}
|
||||
|
||||
message("\r\n---------------\r\nDialing TCP connection")
|
||||
conn, err = net.DialTCP("tcp", laddr, raddr)
|
||||
for ; err != nil; conn, err = net.DialTCP("tcp", laddr, raddr) {
|
||||
message("Connection failed: " + err.Error())
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
println("Connected!\r")
|
||||
|
||||
print("Sending HTTP request...")
|
||||
fmt.Fprintln(conn, "GET / HTTP/1.1")
|
||||
fmt.Fprintln(conn, "Host:", server)
|
||||
fmt.Fprintln(conn, "User-Agent: TinyGo")
|
||||
fmt.Fprintln(conn, "Connection: close")
|
||||
fmt.Fprintln(conn)
|
||||
println("Sent!\r\n\r")
|
||||
|
||||
lastRequestTime = time.Now()
|
||||
}
|
||||
|
||||
func message(msg string) {
|
||||
println(msg, "\r")
|
||||
}
|
||||
|
||||
@@ -69,6 +69,31 @@ func run() error {
|
||||
}
|
||||
|
||||
func root(w http.ResponseWriter, r *http.Request) {
|
||||
access := 1
|
||||
|
||||
cookie, err := r.Cookie("access")
|
||||
if err != nil {
|
||||
if err == http.ErrNoCookie {
|
||||
cookie = &http.Cookie{
|
||||
Name: "access",
|
||||
Value: "1",
|
||||
}
|
||||
} else {
|
||||
http.Error(w, fmt.Sprintf("%s", err.Error()), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
v, err := strconv.ParseInt(cookie.Value, 10, 0)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("invalid cookie.Value : %s", cookie.Value), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
cookie.Value = fmt.Sprintf("%d", v+1)
|
||||
access = int(v) + 1
|
||||
}
|
||||
http.SetCookie(w, cookie)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
fmt.Fprintf(w, `
|
||||
<html>
|
||||
<head>
|
||||
@@ -89,6 +114,11 @@ func root(w http.ResponseWriter, r *http.Request) {
|
||||
</head>
|
||||
<body onLoad="onLoad()">
|
||||
<h5>TinyGo HTTP Server</h5>
|
||||
|
||||
<p>
|
||||
access: %d
|
||||
</p>
|
||||
|
||||
<a href="/hello">/hello</a><br>
|
||||
<a href="/6">/6</a><br>
|
||||
|
||||
@@ -110,7 +140,7 @@ func root(w http.ResponseWriter, r *http.Request) {
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
`)
|
||||
`, access)
|
||||
}
|
||||
|
||||
func sixlines(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A Client is an HTTP client. Its zero value (DefaultClient) is a
|
||||
// usable client that uses DefaultTransport.
|
||||
//
|
||||
// The Client's Transport typically has internal state (cached TCP
|
||||
// connections), so Clients should be reused instead of created as
|
||||
// needed. Clients are safe for concurrent use by multiple goroutines.
|
||||
//
|
||||
// A Client is higher-level than a RoundTripper (such as Transport)
|
||||
// and additionally handles HTTP details such as cookies and
|
||||
// redirects.
|
||||
//
|
||||
// When following redirects, the Client will forward all headers set on the
|
||||
// initial Request except:
|
||||
//
|
||||
// • when forwarding sensitive headers like "Authorization",
|
||||
// "WWW-Authenticate", and "Cookie" to untrusted targets.
|
||||
// These headers will be ignored when following a redirect to a domain
|
||||
// that is not a subdomain match or exact match of the initial domain.
|
||||
// For example, a redirect from "foo.com" to either "foo.com" or "sub.foo.com"
|
||||
// will forward the sensitive headers, but a redirect to "bar.com" will not.
|
||||
//
|
||||
// • when forwarding the "Cookie" header with a non-nil cookie Jar.
|
||||
// Since each redirect may mutate the state of the cookie jar,
|
||||
// a redirect may possibly alter a cookie set in the initial request.
|
||||
// When forwarding the "Cookie" header, any mutated cookies will be omitted,
|
||||
// with the expectation that the Jar will insert those mutated cookies
|
||||
// with the updated values (assuming the origin matches).
|
||||
// If Jar is nil, the initial cookies are forwarded without change.
|
||||
//
|
||||
type Client struct {
|
||||
// Transport specifies the mechanism by which individual
|
||||
// HTTP requests are made.
|
||||
// If nil, DefaultTransport is used.
|
||||
Transport RoundTripper
|
||||
|
||||
// CheckRedirect specifies the policy for handling redirects.
|
||||
// If CheckRedirect is not nil, the client calls it before
|
||||
// following an HTTP redirect. The arguments req and via are
|
||||
// the upcoming request and the requests made already, oldest
|
||||
// first. If CheckRedirect returns an error, the Client's Get
|
||||
// method returns both the previous Response (with its Body
|
||||
// closed) and CheckRedirect's error (wrapped in a url.Error)
|
||||
// instead of issuing the Request req.
|
||||
// As a special case, if CheckRedirect returns ErrUseLastResponse,
|
||||
// then the most recent response is returned with its body
|
||||
// unclosed, along with a nil error.
|
||||
//
|
||||
// If CheckRedirect is nil, the Client uses its default policy,
|
||||
// which is to stop after 10 consecutive requests.
|
||||
CheckRedirect func(req *Request, via []*Request) error
|
||||
|
||||
// Jar specifies the cookie jar.
|
||||
//
|
||||
// The Jar is used to insert relevant cookies into every
|
||||
// outbound Request and is updated with the cookie values
|
||||
// of every inbound Response. The Jar is consulted for every
|
||||
// redirect that the Client follows.
|
||||
//
|
||||
// If Jar is nil, cookies are only sent if they are explicitly
|
||||
// set on the Request.
|
||||
Jar CookieJar
|
||||
|
||||
// Timeout specifies a time limit for requests made by this
|
||||
// Client. The timeout includes connection time, any
|
||||
// redirects, and reading the response body. The timer remains
|
||||
// running after Get, Head, Post, or Do return and will
|
||||
// interrupt reading of the Response.Body.
|
||||
//
|
||||
// A Timeout of zero means no timeout.
|
||||
//
|
||||
// The Client cancels requests to the underlying Transport
|
||||
// as if the Request's Context ended.
|
||||
//
|
||||
// For compatibility, the Client will also use the deprecated
|
||||
// CancelRequest method on Transport if found. New
|
||||
// RoundTripper implementations should use the Request's Context
|
||||
// for cancellation instead of implementing CancelRequest.
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
// DefaultClient is the default Client and is used by Get, Head, and Post.
|
||||
var DefaultClient = &Client{}
|
||||
|
||||
// RoundTripper is an interface representing the ability to execute a
|
||||
// single HTTP transaction, obtaining the Response for a given Request.
|
||||
//
|
||||
// A RoundTripper must be safe for concurrent use by multiple
|
||||
// goroutines.
|
||||
type RoundTripper interface {
|
||||
// RoundTrip executes a single HTTP transaction, returning
|
||||
// a Response for the provided Request.
|
||||
//
|
||||
// RoundTrip should not attempt to interpret the response. In
|
||||
// particular, RoundTrip must return err == nil if it obtained
|
||||
// a response, regardless of the response's HTTP status code.
|
||||
// A non-nil err should be reserved for failure to obtain a
|
||||
// response. Similarly, RoundTrip should not attempt to
|
||||
// handle higher-level protocol details such as redirects,
|
||||
// authentication, or cookies.
|
||||
//
|
||||
// RoundTrip should not modify the request, except for
|
||||
// consuming and closing the Request's Body. RoundTrip may
|
||||
// read fields of the request in a separate goroutine. Callers
|
||||
// should not mutate or reuse the request until the Response's
|
||||
// Body has been closed.
|
||||
//
|
||||
// RoundTrip must always close the body, including on errors,
|
||||
// but depending on the implementation may do so in a separate
|
||||
// goroutine even after RoundTrip returns. This means that
|
||||
// callers wanting to reuse the body for subsequent requests
|
||||
// must arrange to wait for the Close call before doing so.
|
||||
//
|
||||
// The Request's URL and Header fields must be initialized.
|
||||
RoundTrip(*Request) (*Response, error)
|
||||
}
|
||||
|
||||
// Get issues a GET to the specified URL. If the response is one of
|
||||
// the following redirect codes, Get follows the redirect, up to a
|
||||
// maximum of 10 redirects:
|
||||
//
|
||||
// 301 (Moved Permanently)
|
||||
// 302 (Found)
|
||||
// 303 (See Other)
|
||||
// 307 (Temporary Redirect)
|
||||
// 308 (Permanent Redirect)
|
||||
//
|
||||
// An error is returned if there were too many redirects or if there
|
||||
// was an HTTP protocol error. A non-2xx response doesn't cause an
|
||||
// error. Any returned error will be of type *url.Error. The url.Error
|
||||
// value's Timeout method will report true if request timed out or was
|
||||
// canceled.
|
||||
//
|
||||
// When err is nil, resp always contains a non-nil resp.Body.
|
||||
// Caller should close resp.Body when done reading from it.
|
||||
//
|
||||
// Get is a wrapper around DefaultClient.Get.
|
||||
//
|
||||
// To make a request with custom headers, use NewRequest and
|
||||
// DefaultClient.Do.
|
||||
func Get(url string) (resp *Response, err error) {
|
||||
return DefaultClient.Get(url)
|
||||
}
|
||||
|
||||
// Get issues a GET to the specified URL. If the response is one of the
|
||||
// following redirect codes, Get follows the redirect after calling the
|
||||
// Client's CheckRedirect function:
|
||||
//
|
||||
// 301 (Moved Permanently)
|
||||
// 302 (Found)
|
||||
// 303 (See Other)
|
||||
// 307 (Temporary Redirect)
|
||||
// 308 (Permanent Redirect)
|
||||
//
|
||||
// An error is returned if the Client's CheckRedirect function fails
|
||||
// or if there was an HTTP protocol error. A non-2xx response doesn't
|
||||
// cause an error. Any returned error will be of type *url.Error. The
|
||||
// url.Error value's Timeout method will report true if the request
|
||||
// timed out.
|
||||
//
|
||||
// When err is nil, resp always contains a non-nil resp.Body.
|
||||
// Caller should close resp.Body when done reading from it.
|
||||
//
|
||||
// To make a request with custom headers, use NewRequest and Client.Do.
|
||||
func (c *Client) Get(url string) (resp *Response, err error) {
|
||||
req, err := NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Do(req)
|
||||
}
|
||||
|
||||
// Post issues a POST to the specified URL.
|
||||
//
|
||||
// Caller should close resp.Body when done reading from it.
|
||||
//
|
||||
// If the provided body is an io.Closer, it is closed after the
|
||||
// request.
|
||||
//
|
||||
// Post is a wrapper around DefaultClient.Post.
|
||||
//
|
||||
// To set custom headers, use NewRequest and DefaultClient.Do.
|
||||
//
|
||||
// See the Client.Do method documentation for details on how redirects
|
||||
// are handled.
|
||||
func Post(url, contentType string, body io.Reader) (resp *Response, err error) {
|
||||
return DefaultClient.Post(url, contentType, body)
|
||||
}
|
||||
|
||||
// Post issues a POST to the specified URL.
|
||||
//
|
||||
// Caller should close resp.Body when done reading from it.
|
||||
//
|
||||
// If the provided body is an io.Closer, it is closed after the
|
||||
// request.
|
||||
//
|
||||
// To set custom headers, use NewRequest and Client.Do.
|
||||
//
|
||||
// See the Client.Do method documentation for details on how redirects
|
||||
// are handled.
|
||||
func (c *Client) Post(url, contentType string, body io.Reader) (resp *Response, err error) {
|
||||
req, err := NewRequest("POST", url, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
return c.Do(req)
|
||||
}
|
||||
@@ -0,0 +1,433 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package http
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
"net/textproto"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A Cookie represents an HTTP cookie as sent in the Set-Cookie header of an
|
||||
// HTTP response or the Cookie header of an HTTP request.
|
||||
//
|
||||
// See https://tools.ietf.org/html/rfc6265 for details.
|
||||
type Cookie struct {
|
||||
Name string
|
||||
Value string
|
||||
|
||||
Path string // optional
|
||||
Domain string // optional
|
||||
Expires time.Time // optional
|
||||
RawExpires string // for reading cookies only
|
||||
|
||||
// MaxAge=0 means no 'Max-Age' attribute specified.
|
||||
// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
|
||||
// MaxAge>0 means Max-Age attribute present and given in seconds
|
||||
MaxAge int
|
||||
Secure bool
|
||||
HttpOnly bool
|
||||
SameSite SameSite
|
||||
Raw string
|
||||
Unparsed []string // Raw text of unparsed attribute-value pairs
|
||||
}
|
||||
|
||||
// SameSite allows a server to define a cookie attribute making it impossible for
|
||||
// the browser to send this cookie along with cross-site requests. The main
|
||||
// goal is to mitigate the risk of cross-origin information leakage, and provide
|
||||
// some protection against cross-site request forgery attacks.
|
||||
//
|
||||
// See https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00 for details.
|
||||
type SameSite int
|
||||
|
||||
const (
|
||||
SameSiteDefaultMode SameSite = iota + 1
|
||||
SameSiteLaxMode
|
||||
SameSiteStrictMode
|
||||
SameSiteNoneMode
|
||||
)
|
||||
|
||||
// readSetCookies parses all "Set-Cookie" values from
|
||||
// the header h and returns the successfully parsed Cookies.
|
||||
func readSetCookies(h Header) []*Cookie {
|
||||
cookieCount := len(h["Set-Cookie"])
|
||||
if cookieCount == 0 {
|
||||
return []*Cookie{}
|
||||
}
|
||||
cookies := make([]*Cookie, 0, cookieCount)
|
||||
for _, line := range h["Set-Cookie"] {
|
||||
parts := strings.Split(textproto.TrimString(line), ";")
|
||||
if len(parts) == 1 && parts[0] == "" {
|
||||
continue
|
||||
}
|
||||
parts[0] = textproto.TrimString(parts[0])
|
||||
j := strings.Index(parts[0], "=")
|
||||
if j < 0 {
|
||||
continue
|
||||
}
|
||||
name, value := parts[0][:j], parts[0][j+1:]
|
||||
if !isCookieNameValid(name) {
|
||||
continue
|
||||
}
|
||||
value, ok := parseCookieValue(value, true)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
c := &Cookie{
|
||||
Name: name,
|
||||
Value: value,
|
||||
Raw: line,
|
||||
}
|
||||
for i := 1; i < len(parts); i++ {
|
||||
parts[i] = textproto.TrimString(parts[i])
|
||||
if len(parts[i]) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
attr, val := parts[i], ""
|
||||
if j := strings.Index(attr, "="); j >= 0 {
|
||||
attr, val = attr[:j], attr[j+1:]
|
||||
}
|
||||
lowerAttr := strings.ToLower(attr)
|
||||
val, ok = parseCookieValue(val, false)
|
||||
if !ok {
|
||||
c.Unparsed = append(c.Unparsed, parts[i])
|
||||
continue
|
||||
}
|
||||
switch lowerAttr {
|
||||
case "samesite":
|
||||
lowerVal := strings.ToLower(val)
|
||||
switch lowerVal {
|
||||
case "lax":
|
||||
c.SameSite = SameSiteLaxMode
|
||||
case "strict":
|
||||
c.SameSite = SameSiteStrictMode
|
||||
case "none":
|
||||
c.SameSite = SameSiteNoneMode
|
||||
default:
|
||||
c.SameSite = SameSiteDefaultMode
|
||||
}
|
||||
continue
|
||||
case "secure":
|
||||
c.Secure = true
|
||||
continue
|
||||
case "httponly":
|
||||
c.HttpOnly = true
|
||||
continue
|
||||
case "domain":
|
||||
c.Domain = val
|
||||
continue
|
||||
case "max-age":
|
||||
secs, err := strconv.Atoi(val)
|
||||
if err != nil || secs != 0 && val[0] == '0' {
|
||||
break
|
||||
}
|
||||
if secs <= 0 {
|
||||
secs = -1
|
||||
}
|
||||
c.MaxAge = secs
|
||||
continue
|
||||
case "expires":
|
||||
c.RawExpires = val
|
||||
exptime, err := time.Parse(time.RFC1123, val)
|
||||
if err != nil {
|
||||
exptime, err = time.Parse("Mon, 02-Jan-2006 15:04:05 MST", val)
|
||||
if err != nil {
|
||||
c.Expires = time.Time{}
|
||||
break
|
||||
}
|
||||
}
|
||||
c.Expires = exptime.UTC()
|
||||
continue
|
||||
case "path":
|
||||
c.Path = val
|
||||
continue
|
||||
}
|
||||
c.Unparsed = append(c.Unparsed, parts[i])
|
||||
}
|
||||
cookies = append(cookies, c)
|
||||
}
|
||||
return cookies
|
||||
}
|
||||
|
||||
// SetCookie adds a Set-Cookie header to the provided ResponseWriter's headers.
|
||||
// The provided cookie must have a valid Name. Invalid cookies may be
|
||||
// silently dropped.
|
||||
func SetCookie(w ResponseWriter, cookie *Cookie) {
|
||||
if v := cookie.String(); v != "" {
|
||||
w.Header().Add("Set-Cookie", v)
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the serialization of the cookie for use in a Cookie
|
||||
// header (if only Name and Value are set) or a Set-Cookie response
|
||||
// header (if other fields are set).
|
||||
// If c is nil or c.Name is invalid, the empty string is returned.
|
||||
func (c *Cookie) String() string {
|
||||
if c == nil || !isCookieNameValid(c.Name) {
|
||||
return ""
|
||||
}
|
||||
// extraCookieLength derived from typical length of cookie attributes
|
||||
// see RFC 6265 Sec 4.1.
|
||||
const extraCookieLength = 110
|
||||
var b strings.Builder
|
||||
b.Grow(len(c.Name) + len(c.Value) + len(c.Domain) + len(c.Path) + extraCookieLength)
|
||||
b.WriteString(c.Name)
|
||||
b.WriteRune('=')
|
||||
b.WriteString(sanitizeCookieValue(c.Value))
|
||||
|
||||
if len(c.Path) > 0 {
|
||||
b.WriteString("; Path=")
|
||||
b.WriteString(sanitizeCookiePath(c.Path))
|
||||
}
|
||||
if len(c.Domain) > 0 {
|
||||
if validCookieDomain(c.Domain) {
|
||||
// A c.Domain containing illegal characters is not
|
||||
// sanitized but simply dropped which turns the cookie
|
||||
// into a host-only cookie. A leading dot is okay
|
||||
// but won't be sent.
|
||||
d := c.Domain
|
||||
if d[0] == '.' {
|
||||
d = d[1:]
|
||||
}
|
||||
b.WriteString("; Domain=")
|
||||
b.WriteString(d)
|
||||
} else {
|
||||
log.Printf("net/http: invalid Cookie.Domain %q; dropping domain attribute", c.Domain)
|
||||
}
|
||||
}
|
||||
var buf [len(TimeFormat)]byte
|
||||
if validCookieExpires(c.Expires) {
|
||||
b.WriteString("; Expires=")
|
||||
b.Write(c.Expires.UTC().AppendFormat(buf[:0], TimeFormat))
|
||||
}
|
||||
if c.MaxAge > 0 {
|
||||
b.WriteString("; Max-Age=")
|
||||
b.Write(strconv.AppendInt(buf[:0], int64(c.MaxAge), 10))
|
||||
} else if c.MaxAge < 0 {
|
||||
b.WriteString("; Max-Age=0")
|
||||
}
|
||||
if c.HttpOnly {
|
||||
b.WriteString("; HttpOnly")
|
||||
}
|
||||
if c.Secure {
|
||||
b.WriteString("; Secure")
|
||||
}
|
||||
switch c.SameSite {
|
||||
case SameSiteDefaultMode:
|
||||
// Skip, default mode is obtained by not emitting the attribute.
|
||||
case SameSiteNoneMode:
|
||||
b.WriteString("; SameSite=None")
|
||||
case SameSiteLaxMode:
|
||||
b.WriteString("; SameSite=Lax")
|
||||
case SameSiteStrictMode:
|
||||
b.WriteString("; SameSite=Strict")
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// readCookies parses all "Cookie" values from the header h and
|
||||
// returns the successfully parsed Cookies.
|
||||
//
|
||||
// if filter isn't empty, only cookies of that name are returned
|
||||
func readCookies(h Header, filter string) []*Cookie {
|
||||
lines := h["Cookie"]
|
||||
if len(lines) == 0 {
|
||||
return []*Cookie{}
|
||||
}
|
||||
|
||||
cookies := make([]*Cookie, 0, len(lines)+strings.Count(lines[0], ";"))
|
||||
for _, line := range lines {
|
||||
line = textproto.TrimString(line)
|
||||
|
||||
var part string
|
||||
for len(line) > 0 { // continue since we have rest
|
||||
if splitIndex := strings.Index(line, ";"); splitIndex > 0 {
|
||||
part, line = line[:splitIndex], line[splitIndex+1:]
|
||||
} else {
|
||||
part, line = line, ""
|
||||
}
|
||||
part = textproto.TrimString(part)
|
||||
if len(part) == 0 {
|
||||
continue
|
||||
}
|
||||
name, val := part, ""
|
||||
if j := strings.Index(part, "="); j >= 0 {
|
||||
name, val = name[:j], name[j+1:]
|
||||
}
|
||||
if !isCookieNameValid(name) {
|
||||
continue
|
||||
}
|
||||
if filter != "" && filter != name {
|
||||
continue
|
||||
}
|
||||
val, ok := parseCookieValue(val, true)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
cookies = append(cookies, &Cookie{Name: name, Value: val})
|
||||
}
|
||||
}
|
||||
return cookies
|
||||
}
|
||||
|
||||
// validCookieDomain reports whether v is a valid cookie domain-value.
|
||||
func validCookieDomain(v string) bool {
|
||||
if isCookieDomainName(v) {
|
||||
return true
|
||||
}
|
||||
if net.ParseIP(v) != nil && !strings.Contains(v, ":") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// validCookieExpires reports whether v is a valid cookie expires-value.
|
||||
func validCookieExpires(t time.Time) bool {
|
||||
// IETF RFC 6265 Section 5.1.1.5, the year must not be less than 1601
|
||||
return t.Year() >= 1601
|
||||
}
|
||||
|
||||
// isCookieDomainName reports whether s is a valid domain name or a valid
|
||||
// domain name with a leading dot '.'. It is almost a direct copy of
|
||||
// package net's isDomainName.
|
||||
func isCookieDomainName(s string) bool {
|
||||
if len(s) == 0 {
|
||||
return false
|
||||
}
|
||||
if len(s) > 255 {
|
||||
return false
|
||||
}
|
||||
|
||||
if s[0] == '.' {
|
||||
// A cookie a domain attribute may start with a leading dot.
|
||||
s = s[1:]
|
||||
}
|
||||
last := byte('.')
|
||||
ok := false // Ok once we've seen a letter.
|
||||
partlen := 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
c := s[i]
|
||||
switch {
|
||||
default:
|
||||
return false
|
||||
case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z':
|
||||
// No '_' allowed here (in contrast to package net).
|
||||
ok = true
|
||||
partlen++
|
||||
case '0' <= c && c <= '9':
|
||||
// fine
|
||||
partlen++
|
||||
case c == '-':
|
||||
// Byte before dash cannot be dot.
|
||||
if last == '.' {
|
||||
return false
|
||||
}
|
||||
partlen++
|
||||
case c == '.':
|
||||
// Byte before dot cannot be dot, dash.
|
||||
if last == '.' || last == '-' {
|
||||
return false
|
||||
}
|
||||
if partlen > 63 || partlen == 0 {
|
||||
return false
|
||||
}
|
||||
partlen = 0
|
||||
}
|
||||
last = c
|
||||
}
|
||||
if last == '-' || partlen > 63 {
|
||||
return false
|
||||
}
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
var cookieNameSanitizer = strings.NewReplacer("\n", "-", "\r", "-")
|
||||
|
||||
func sanitizeCookieName(n string) string {
|
||||
return cookieNameSanitizer.Replace(n)
|
||||
}
|
||||
|
||||
// sanitizeCookieValue produces a suitable cookie-value from v.
|
||||
// https://tools.ietf.org/html/rfc6265#section-4.1.1
|
||||
// cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )
|
||||
// cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
|
||||
// ; US-ASCII characters excluding CTLs,
|
||||
// ; whitespace DQUOTE, comma, semicolon,
|
||||
// ; and backslash
|
||||
// We loosen this as spaces and commas are common in cookie values
|
||||
// but we produce a quoted cookie-value if and only if v contains
|
||||
// commas or spaces.
|
||||
// See https://golang.org/issue/7243 for the discussion.
|
||||
func sanitizeCookieValue(v string) string {
|
||||
v = sanitizeOrWarn("Cookie.Value", validCookieValueByte, v)
|
||||
if len(v) == 0 {
|
||||
return v
|
||||
}
|
||||
if strings.IndexByte(v, ' ') >= 0 || strings.IndexByte(v, ',') >= 0 {
|
||||
return `"` + v + `"`
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func validCookieValueByte(b byte) bool {
|
||||
return 0x20 <= b && b < 0x7f && b != '"' && b != ';' && b != '\\'
|
||||
}
|
||||
|
||||
// path-av = "Path=" path-value
|
||||
// path-value = <any CHAR except CTLs or ";">
|
||||
func sanitizeCookiePath(v string) string {
|
||||
return sanitizeOrWarn("Cookie.Path", validCookiePathByte, v)
|
||||
}
|
||||
|
||||
func validCookiePathByte(b byte) bool {
|
||||
return 0x20 <= b && b < 0x7f && b != ';'
|
||||
}
|
||||
|
||||
func sanitizeOrWarn(fieldName string, valid func(byte) bool, v string) string {
|
||||
ok := true
|
||||
for i := 0; i < len(v); i++ {
|
||||
if valid(v[i]) {
|
||||
continue
|
||||
}
|
||||
log.Printf("net/http: invalid byte %q in %s; dropping invalid bytes", v[i], fieldName)
|
||||
ok = false
|
||||
break
|
||||
}
|
||||
if ok {
|
||||
return v
|
||||
}
|
||||
buf := make([]byte, 0, len(v))
|
||||
for i := 0; i < len(v); i++ {
|
||||
if b := v[i]; valid(b) {
|
||||
buf = append(buf, b)
|
||||
}
|
||||
}
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
func parseCookieValue(raw string, allowDoubleQuote bool) (string, bool) {
|
||||
// Strip the quotes, if present.
|
||||
if allowDoubleQuote && len(raw) > 1 && raw[0] == '"' && raw[len(raw)-1] == '"' {
|
||||
raw = raw[1 : len(raw)-1]
|
||||
}
|
||||
for i := 0; i < len(raw); i++ {
|
||||
if !validCookieValueByte(raw[i]) {
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
return raw, true
|
||||
}
|
||||
|
||||
func isCookieNameValid(raw string) bool {
|
||||
if raw == "" {
|
||||
return false
|
||||
}
|
||||
return strings.IndexFunc(raw, isNotToken) < 0
|
||||
}
|
||||
@@ -0,0 +1,504 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package cookiejar implements an in-memory RFC 6265-compliant http.CookieJar.
|
||||
package cookiejar
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/net"
|
||||
"tinygo.org/x/drivers/net/http"
|
||||
)
|
||||
|
||||
// PublicSuffixList provides the public suffix of a domain. For example:
|
||||
// - the public suffix of "example.com" is "com",
|
||||
// - the public suffix of "foo1.foo2.foo3.co.uk" is "co.uk", and
|
||||
// - the public suffix of "bar.pvt.k12.ma.us" is "pvt.k12.ma.us".
|
||||
//
|
||||
// Implementations of PublicSuffixList must be safe for concurrent use by
|
||||
// multiple goroutines.
|
||||
//
|
||||
// An implementation that always returns "" is valid and may be useful for
|
||||
// testing but it is not secure: it means that the HTTP server for foo.com can
|
||||
// set a cookie for bar.com.
|
||||
//
|
||||
// A public suffix list implementation is in the package
|
||||
// golang.org/x/net/publicsuffix.
|
||||
type PublicSuffixList interface {
|
||||
// PublicSuffix returns the public suffix of domain.
|
||||
//
|
||||
// TODO: specify which of the caller and callee is responsible for IP
|
||||
// addresses, for leading and trailing dots, for case sensitivity, and
|
||||
// for IDN/Punycode.
|
||||
PublicSuffix(domain string) string
|
||||
|
||||
// String returns a description of the source of this public suffix
|
||||
// list. The description will typically contain something like a time
|
||||
// stamp or version number.
|
||||
String() string
|
||||
}
|
||||
|
||||
// Options are the options for creating a new Jar.
|
||||
type Options struct {
|
||||
// PublicSuffixList is the public suffix list that determines whether
|
||||
// an HTTP server can set a cookie for a domain.
|
||||
//
|
||||
// A nil value is valid and may be useful for testing but it is not
|
||||
// secure: it means that the HTTP server for foo.co.uk can set a cookie
|
||||
// for bar.co.uk.
|
||||
PublicSuffixList PublicSuffixList
|
||||
}
|
||||
|
||||
// Jar implements the http.CookieJar interface from the net/http package.
|
||||
type Jar struct {
|
||||
psList PublicSuffixList
|
||||
|
||||
// mu locks the remaining fields.
|
||||
mu sync.Mutex
|
||||
|
||||
// entries is a set of entries, keyed by their eTLD+1 and subkeyed by
|
||||
// their name/domain/path.
|
||||
entries map[string]map[string]entry
|
||||
|
||||
// nextSeqNum is the next sequence number assigned to a new cookie
|
||||
// created SetCookies.
|
||||
nextSeqNum uint64
|
||||
}
|
||||
|
||||
// New returns a new cookie jar. A nil *Options is equivalent to a zero
|
||||
// Options.
|
||||
func New(o *Options) (*Jar, error) {
|
||||
jar := &Jar{
|
||||
entries: make(map[string]map[string]entry),
|
||||
}
|
||||
if o != nil {
|
||||
jar.psList = o.PublicSuffixList
|
||||
}
|
||||
return jar, nil
|
||||
}
|
||||
|
||||
// entry is the internal representation of a cookie.
|
||||
//
|
||||
// This struct type is not used outside of this package per se, but the exported
|
||||
// fields are those of RFC 6265.
|
||||
type entry struct {
|
||||
Name string
|
||||
Value string
|
||||
Domain string
|
||||
Path string
|
||||
SameSite string
|
||||
Secure bool
|
||||
HttpOnly bool
|
||||
Persistent bool
|
||||
HostOnly bool
|
||||
Expires time.Time
|
||||
Creation time.Time
|
||||
LastAccess time.Time
|
||||
|
||||
// seqNum is a sequence number so that Cookies returns cookies in a
|
||||
// deterministic order, even for cookies that have equal Path length and
|
||||
// equal Creation time. This simplifies testing.
|
||||
seqNum uint64
|
||||
}
|
||||
|
||||
// id returns the domain;path;name triple of e as an id.
|
||||
func (e *entry) id() string {
|
||||
return fmt.Sprintf("%s;%s;%s", e.Domain, e.Path, e.Name)
|
||||
}
|
||||
|
||||
// shouldSend determines whether e's cookie qualifies to be included in a
|
||||
// request to host/path. It is the caller's responsibility to check if the
|
||||
// cookie is expired.
|
||||
func (e *entry) shouldSend(https bool, host, path string) bool {
|
||||
return e.domainMatch(host) && e.pathMatch(path) && (https || !e.Secure)
|
||||
}
|
||||
|
||||
// domainMatch implements "domain-match" of RFC 6265 section 5.1.3.
|
||||
func (e *entry) domainMatch(host string) bool {
|
||||
if e.Domain == host {
|
||||
return true
|
||||
}
|
||||
return !e.HostOnly && hasDotSuffix(host, e.Domain)
|
||||
}
|
||||
|
||||
// pathMatch implements "path-match" according to RFC 6265 section 5.1.4.
|
||||
func (e *entry) pathMatch(requestPath string) bool {
|
||||
if requestPath == e.Path {
|
||||
return true
|
||||
}
|
||||
if strings.HasPrefix(requestPath, e.Path) {
|
||||
if e.Path[len(e.Path)-1] == '/' {
|
||||
return true // The "/any/" matches "/any/path" case.
|
||||
} else if requestPath[len(e.Path)] == '/' {
|
||||
return true // The "/any" matches "/any/path" case.
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// hasDotSuffix reports whether s ends in "."+suffix.
|
||||
func hasDotSuffix(s, suffix string) bool {
|
||||
return len(s) > len(suffix) && s[len(s)-len(suffix)-1] == '.' && s[len(s)-len(suffix):] == suffix
|
||||
}
|
||||
|
||||
// Cookies implements the Cookies method of the http.CookieJar interface.
|
||||
//
|
||||
// It returns an empty slice if the URL's scheme is not HTTP or HTTPS.
|
||||
func (j *Jar) Cookies(u *url.URL) (cookies []*http.Cookie) {
|
||||
return j.cookies(u, time.Now())
|
||||
}
|
||||
|
||||
// cookies is like Cookies but takes the current time as a parameter.
|
||||
func (j *Jar) cookies(u *url.URL, now time.Time) (cookies []*http.Cookie) {
|
||||
if u.Scheme != "http" && u.Scheme != "https" {
|
||||
return cookies
|
||||
}
|
||||
host, err := canonicalHost(u.Host)
|
||||
if err != nil {
|
||||
return cookies
|
||||
}
|
||||
key := jarKey(host, j.psList)
|
||||
|
||||
j.mu.Lock()
|
||||
defer j.mu.Unlock()
|
||||
|
||||
submap := j.entries[key]
|
||||
if submap == nil {
|
||||
return cookies
|
||||
}
|
||||
|
||||
https := u.Scheme == "https"
|
||||
path := u.Path
|
||||
if path == "" {
|
||||
path = "/"
|
||||
}
|
||||
|
||||
modified := false
|
||||
var selected []entry
|
||||
for id, e := range submap {
|
||||
if e.Persistent && !e.Expires.After(now) {
|
||||
delete(submap, id)
|
||||
modified = true
|
||||
continue
|
||||
}
|
||||
if !e.shouldSend(https, host, path) {
|
||||
continue
|
||||
}
|
||||
e.LastAccess = now
|
||||
submap[id] = e
|
||||
selected = append(selected, e)
|
||||
modified = true
|
||||
}
|
||||
if modified {
|
||||
if len(submap) == 0 {
|
||||
delete(j.entries, key)
|
||||
} else {
|
||||
j.entries[key] = submap
|
||||
}
|
||||
}
|
||||
|
||||
// sort according to RFC 6265 section 5.4 point 2: by longest
|
||||
// path and then by earliest creation time.
|
||||
sort.Slice(selected, func(i, j int) bool {
|
||||
s := selected
|
||||
if len(s[i].Path) != len(s[j].Path) {
|
||||
return len(s[i].Path) > len(s[j].Path)
|
||||
}
|
||||
if !s[i].Creation.Equal(s[j].Creation) {
|
||||
return s[i].Creation.Before(s[j].Creation)
|
||||
}
|
||||
return s[i].seqNum < s[j].seqNum
|
||||
})
|
||||
for _, e := range selected {
|
||||
cookies = append(cookies, &http.Cookie{Name: e.Name, Value: e.Value})
|
||||
}
|
||||
|
||||
return cookies
|
||||
}
|
||||
|
||||
// SetCookies implements the SetCookies method of the http.CookieJar interface.
|
||||
//
|
||||
// It does nothing if the URL's scheme is not HTTP or HTTPS.
|
||||
func (j *Jar) SetCookies(u *url.URL, cookies []*http.Cookie) {
|
||||
j.setCookies(u, cookies, time.Now())
|
||||
}
|
||||
|
||||
// setCookies is like SetCookies but takes the current time as parameter.
|
||||
func (j *Jar) setCookies(u *url.URL, cookies []*http.Cookie, now time.Time) {
|
||||
if len(cookies) == 0 {
|
||||
return
|
||||
}
|
||||
if u.Scheme != "http" && u.Scheme != "https" {
|
||||
return
|
||||
}
|
||||
host, err := canonicalHost(u.Host)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
key := jarKey(host, j.psList)
|
||||
defPath := defaultPath(u.Path)
|
||||
|
||||
j.mu.Lock()
|
||||
defer j.mu.Unlock()
|
||||
|
||||
submap := j.entries[key]
|
||||
|
||||
modified := false
|
||||
for _, cookie := range cookies {
|
||||
e, remove, err := j.newEntry(cookie, now, defPath, host)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
id := e.id()
|
||||
if remove {
|
||||
if submap != nil {
|
||||
if _, ok := submap[id]; ok {
|
||||
delete(submap, id)
|
||||
modified = true
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if submap == nil {
|
||||
submap = make(map[string]entry)
|
||||
}
|
||||
|
||||
if old, ok := submap[id]; ok {
|
||||
e.Creation = old.Creation
|
||||
e.seqNum = old.seqNum
|
||||
} else {
|
||||
e.Creation = now
|
||||
e.seqNum = j.nextSeqNum
|
||||
j.nextSeqNum++
|
||||
}
|
||||
e.LastAccess = now
|
||||
submap[id] = e
|
||||
modified = true
|
||||
}
|
||||
|
||||
if modified {
|
||||
if len(submap) == 0 {
|
||||
delete(j.entries, key)
|
||||
} else {
|
||||
j.entries[key] = submap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// canonicalHost strips port from host if present and returns the canonicalized
|
||||
// host name.
|
||||
func canonicalHost(host string) (string, error) {
|
||||
var err error
|
||||
host = strings.ToLower(host)
|
||||
if hasPort(host) {
|
||||
host, _, err = net.SplitHostPort(host)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
if strings.HasSuffix(host, ".") {
|
||||
// Strip trailing dot from fully qualified domain names.
|
||||
host = host[:len(host)-1]
|
||||
}
|
||||
return toASCII(host)
|
||||
}
|
||||
|
||||
// hasPort reports whether host contains a port number. host may be a host
|
||||
// name, an IPv4 or an IPv6 address.
|
||||
func hasPort(host string) bool {
|
||||
colons := strings.Count(host, ":")
|
||||
if colons == 0 {
|
||||
return false
|
||||
}
|
||||
if colons == 1 {
|
||||
return true
|
||||
}
|
||||
return host[0] == '[' && strings.Contains(host, "]:")
|
||||
}
|
||||
|
||||
// jarKey returns the key to use for a jar.
|
||||
func jarKey(host string, psl PublicSuffixList) string {
|
||||
if isIP(host) {
|
||||
return host
|
||||
}
|
||||
|
||||
var i int
|
||||
if psl == nil {
|
||||
i = strings.LastIndex(host, ".")
|
||||
if i <= 0 {
|
||||
return host
|
||||
}
|
||||
} else {
|
||||
suffix := psl.PublicSuffix(host)
|
||||
if suffix == host {
|
||||
return host
|
||||
}
|
||||
i = len(host) - len(suffix)
|
||||
if i <= 0 || host[i-1] != '.' {
|
||||
// The provided public suffix list psl is broken.
|
||||
// Storing cookies under host is a safe stopgap.
|
||||
return host
|
||||
}
|
||||
// Only len(suffix) is used to determine the jar key from
|
||||
// here on, so it is okay if psl.PublicSuffix("www.buggy.psl")
|
||||
// returns "com" as the jar key is generated from host.
|
||||
}
|
||||
prevDot := strings.LastIndex(host[:i-1], ".")
|
||||
return host[prevDot+1:]
|
||||
}
|
||||
|
||||
// isIP reports whether host is an IP address.
|
||||
func isIP(host string) bool {
|
||||
return net.ParseIP(host) != nil
|
||||
}
|
||||
|
||||
// defaultPath returns the directory part of an URL's path according to
|
||||
// RFC 6265 section 5.1.4.
|
||||
func defaultPath(path string) string {
|
||||
if len(path) == 0 || path[0] != '/' {
|
||||
return "/" // Path is empty or malformed.
|
||||
}
|
||||
|
||||
i := strings.LastIndex(path, "/") // Path starts with "/", so i != -1.
|
||||
if i == 0 {
|
||||
return "/" // Path has the form "/abc".
|
||||
}
|
||||
return path[:i] // Path is either of form "/abc/xyz" or "/abc/xyz/".
|
||||
}
|
||||
|
||||
// newEntry creates an entry from a http.Cookie c. now is the current time and
|
||||
// is compared to c.Expires to determine deletion of c. defPath and host are the
|
||||
// default-path and the canonical host name of the URL c was received from.
|
||||
//
|
||||
// remove records whether the jar should delete this cookie, as it has already
|
||||
// expired with respect to now. In this case, e may be incomplete, but it will
|
||||
// be valid to call e.id (which depends on e's Name, Domain and Path).
|
||||
//
|
||||
// A malformed c.Domain will result in an error.
|
||||
func (j *Jar) newEntry(c *http.Cookie, now time.Time, defPath, host string) (e entry, remove bool, err error) {
|
||||
e.Name = c.Name
|
||||
|
||||
if c.Path == "" || c.Path[0] != '/' {
|
||||
e.Path = defPath
|
||||
} else {
|
||||
e.Path = c.Path
|
||||
}
|
||||
|
||||
e.Domain, e.HostOnly, err = j.domainAndType(host, c.Domain)
|
||||
if err != nil {
|
||||
return e, false, err
|
||||
}
|
||||
|
||||
// MaxAge takes precedence over Expires.
|
||||
if c.MaxAge < 0 {
|
||||
return e, true, nil
|
||||
} else if c.MaxAge > 0 {
|
||||
e.Expires = now.Add(time.Duration(c.MaxAge) * time.Second)
|
||||
e.Persistent = true
|
||||
} else {
|
||||
if c.Expires.IsZero() {
|
||||
e.Expires = endOfTime
|
||||
e.Persistent = false
|
||||
} else {
|
||||
if !c.Expires.After(now) {
|
||||
return e, true, nil
|
||||
}
|
||||
e.Expires = c.Expires
|
||||
e.Persistent = true
|
||||
}
|
||||
}
|
||||
|
||||
e.Value = c.Value
|
||||
e.Secure = c.Secure
|
||||
e.HttpOnly = c.HttpOnly
|
||||
|
||||
switch c.SameSite {
|
||||
case http.SameSiteDefaultMode:
|
||||
e.SameSite = "SameSite"
|
||||
case http.SameSiteStrictMode:
|
||||
e.SameSite = "SameSite=Strict"
|
||||
case http.SameSiteLaxMode:
|
||||
e.SameSite = "SameSite=Lax"
|
||||
}
|
||||
|
||||
return e, false, nil
|
||||
}
|
||||
|
||||
var (
|
||||
errIllegalDomain = errors.New("cookiejar: illegal cookie domain attribute")
|
||||
errMalformedDomain = errors.New("cookiejar: malformed cookie domain attribute")
|
||||
errNoHostname = errors.New("cookiejar: no host name available (IP only)")
|
||||
)
|
||||
|
||||
// endOfTime is the time when session (non-persistent) cookies expire.
|
||||
// This instant is representable in most date/time formats (not just
|
||||
// Go's time.Time) and should be far enough in the future.
|
||||
var endOfTime = time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC)
|
||||
|
||||
// domainAndType determines the cookie's domain and hostOnly attribute.
|
||||
func (j *Jar) domainAndType(host, domain string) (string, bool, error) {
|
||||
if domain == "" {
|
||||
// No domain attribute in the SetCookie header indicates a
|
||||
// host cookie.
|
||||
return host, true, nil
|
||||
}
|
||||
|
||||
if isIP(host) {
|
||||
// According to RFC 6265 domain-matching includes not being
|
||||
// an IP address.
|
||||
// TODO: This might be relaxed as in common browsers.
|
||||
return "", false, errNoHostname
|
||||
}
|
||||
|
||||
// From here on: If the cookie is valid, it is a domain cookie (with
|
||||
// the one exception of a public suffix below).
|
||||
// See RFC 6265 section 5.2.3.
|
||||
if domain[0] == '.' {
|
||||
domain = domain[1:]
|
||||
}
|
||||
|
||||
if len(domain) == 0 || domain[0] == '.' {
|
||||
// Received either "Domain=." or "Domain=..some.thing",
|
||||
// both are illegal.
|
||||
return "", false, errMalformedDomain
|
||||
}
|
||||
domain = strings.ToLower(domain)
|
||||
|
||||
if domain[len(domain)-1] == '.' {
|
||||
// We received stuff like "Domain=www.example.com.".
|
||||
// Browsers do handle such stuff (actually differently) but
|
||||
// RFC 6265 seems to be clear here (e.g. section 4.1.2.3) in
|
||||
// requiring a reject. 4.1.2.3 is not normative, but
|
||||
// "Domain Matching" (5.1.3) and "Canonicalized Host Names"
|
||||
// (5.1.2) are.
|
||||
return "", false, errMalformedDomain
|
||||
}
|
||||
|
||||
// See RFC 6265 section 5.3 #5.
|
||||
if j.psList != nil {
|
||||
if ps := j.psList.PublicSuffix(domain); ps != "" && !hasDotSuffix(domain, ps) {
|
||||
if host == domain {
|
||||
// This is the one exception in which a cookie
|
||||
// with a domain attribute is a host cookie.
|
||||
return host, true, nil
|
||||
}
|
||||
return "", false, errIllegalDomain
|
||||
}
|
||||
}
|
||||
|
||||
// The domain must domain-match host: www.mycompany.com cannot
|
||||
// set cookies for .ourcompetitors.com.
|
||||
if host != domain && !hasDotSuffix(host, domain) {
|
||||
return "", false, errIllegalDomain
|
||||
}
|
||||
|
||||
return domain, false, nil
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cookiejar
|
||||
|
||||
// This file implements the Punycode algorithm from RFC 3492.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// These parameter values are specified in section 5.
|
||||
//
|
||||
// All computation is done with int32s, so that overflow behavior is identical
|
||||
// regardless of whether int is 32-bit or 64-bit.
|
||||
const (
|
||||
base int32 = 36
|
||||
damp int32 = 700
|
||||
initialBias int32 = 72
|
||||
initialN int32 = 128
|
||||
skew int32 = 38
|
||||
tmax int32 = 26
|
||||
tmin int32 = 1
|
||||
)
|
||||
|
||||
// encode encodes a string as specified in section 6.3 and prepends prefix to
|
||||
// the result.
|
||||
//
|
||||
// The "while h < length(input)" line in the specification becomes "for
|
||||
// remaining != 0" in the Go code, because len(s) in Go is in bytes, not runes.
|
||||
func encode(prefix, s string) (string, error) {
|
||||
output := make([]byte, len(prefix), len(prefix)+1+2*len(s))
|
||||
copy(output, prefix)
|
||||
delta, n, bias := int32(0), initialN, initialBias
|
||||
b, remaining := int32(0), int32(0)
|
||||
for _, r := range s {
|
||||
if r < utf8.RuneSelf {
|
||||
b++
|
||||
output = append(output, byte(r))
|
||||
} else {
|
||||
remaining++
|
||||
}
|
||||
}
|
||||
h := b
|
||||
if b > 0 {
|
||||
output = append(output, '-')
|
||||
}
|
||||
for remaining != 0 {
|
||||
m := int32(0x7fffffff)
|
||||
for _, r := range s {
|
||||
if m > r && r >= n {
|
||||
m = r
|
||||
}
|
||||
}
|
||||
delta += (m - n) * (h + 1)
|
||||
if delta < 0 {
|
||||
return "", fmt.Errorf("cookiejar: invalid label %q", s)
|
||||
}
|
||||
n = m
|
||||
for _, r := range s {
|
||||
if r < n {
|
||||
delta++
|
||||
if delta < 0 {
|
||||
return "", fmt.Errorf("cookiejar: invalid label %q", s)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if r > n {
|
||||
continue
|
||||
}
|
||||
q := delta
|
||||
for k := base; ; k += base {
|
||||
t := k - bias
|
||||
if t < tmin {
|
||||
t = tmin
|
||||
} else if t > tmax {
|
||||
t = tmax
|
||||
}
|
||||
if q < t {
|
||||
break
|
||||
}
|
||||
output = append(output, encodeDigit(t+(q-t)%(base-t)))
|
||||
q = (q - t) / (base - t)
|
||||
}
|
||||
output = append(output, encodeDigit(q))
|
||||
bias = adapt(delta, h+1, h == b)
|
||||
delta = 0
|
||||
h++
|
||||
remaining--
|
||||
}
|
||||
delta++
|
||||
n++
|
||||
}
|
||||
return string(output), nil
|
||||
}
|
||||
|
||||
func encodeDigit(digit int32) byte {
|
||||
switch {
|
||||
case 0 <= digit && digit < 26:
|
||||
return byte(digit + 'a')
|
||||
case 26 <= digit && digit < 36:
|
||||
return byte(digit + ('0' - 26))
|
||||
}
|
||||
panic("cookiejar: internal error in punycode encoding")
|
||||
}
|
||||
|
||||
// adapt is the bias adaptation function specified in section 6.1.
|
||||
func adapt(delta, numPoints int32, firstTime bool) int32 {
|
||||
if firstTime {
|
||||
delta /= damp
|
||||
} else {
|
||||
delta /= 2
|
||||
}
|
||||
delta += delta / numPoints
|
||||
k := int32(0)
|
||||
for delta > ((base-tmin)*tmax)/2 {
|
||||
delta /= base - tmin
|
||||
k += base
|
||||
}
|
||||
return k + (base-tmin+1)*delta/(delta+skew)
|
||||
}
|
||||
|
||||
// Strictly speaking, the remaining code below deals with IDNA (RFC 5890 and
|
||||
// friends) and not Punycode (RFC 3492) per se.
|
||||
|
||||
// acePrefix is the ASCII Compatible Encoding prefix.
|
||||
const acePrefix = "xn--"
|
||||
|
||||
// toASCII converts a domain or domain label to its ASCII form. For example,
|
||||
// toASCII("bücher.example.com") is "xn--bcher-kva.example.com", and
|
||||
// toASCII("golang") is "golang".
|
||||
func toASCII(s string) (string, error) {
|
||||
if ascii(s) {
|
||||
return s, nil
|
||||
}
|
||||
labels := strings.Split(s, ".")
|
||||
for i, label := range labels {
|
||||
if !ascii(label) {
|
||||
a, err := encode(acePrefix, label)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
labels[i] = a
|
||||
}
|
||||
}
|
||||
return strings.Join(labels, "."), nil
|
||||
}
|
||||
|
||||
func ascii(s string) bool {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] >= utf8.RuneSelf {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package http
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// A CookieJar manages storage and use of cookies in HTTP requests.
|
||||
//
|
||||
// Implementations of CookieJar must be safe for concurrent use by multiple
|
||||
// goroutines.
|
||||
//
|
||||
// The net/http/cookiejar package provides a CookieJar implementation.
|
||||
type CookieJar interface {
|
||||
// SetCookies handles the receipt of the cookies in a reply for the
|
||||
// given URL. It may or may not choose to save the cookies, depending
|
||||
// on the jar's policy and implementation.
|
||||
SetCookies(u *url.URL, cookies []*Cookie)
|
||||
|
||||
// Cookies returns the cookies to send in a request for the given URL.
|
||||
// It is up to the implementation to honor the standard cookie use
|
||||
// restrictions such as in RFC 6265.
|
||||
Cookies(u *url.URL) []*Cookie
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package http
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
@@ -11,6 +12,7 @@ import (
|
||||
"mime/multipart"
|
||||
"net/textproto"
|
||||
"net/url"
|
||||
urlpkg "net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -248,6 +250,57 @@ func (r *Request) ProtoAtLeast(major, minor int) bool {
|
||||
r.ProtoMajor == major && r.ProtoMinor >= minor
|
||||
}
|
||||
|
||||
// UserAgent returns the client's User-Agent, if sent in the request.
|
||||
func (r *Request) UserAgent() string {
|
||||
return r.Header.Get("User-Agent")
|
||||
}
|
||||
|
||||
// Cookies parses and returns the HTTP cookies sent with the request.
|
||||
func (r *Request) Cookies() []*Cookie {
|
||||
return readCookies(r.Header, "")
|
||||
}
|
||||
|
||||
// ErrNoCookie is returned by Request's Cookie method when a cookie is not found.
|
||||
var ErrNoCookie = errors.New("http: named cookie not present")
|
||||
|
||||
// Cookie returns the named cookie provided in the request or
|
||||
// ErrNoCookie if not found.
|
||||
// If multiple cookies match the given name, only one cookie will
|
||||
// be returned.
|
||||
func (r *Request) Cookie(name string) (*Cookie, error) {
|
||||
for _, c := range readCookies(r.Header, name) {
|
||||
return c, nil
|
||||
}
|
||||
return nil, ErrNoCookie
|
||||
}
|
||||
|
||||
// AddCookie adds a cookie to the request. Per RFC 6265 section 5.4,
|
||||
// AddCookie does not attach more than one Cookie header field. That
|
||||
// means all cookies, if any, are written into the same line,
|
||||
// separated by semicolon.
|
||||
// AddCookie only sanitizes c's name and value, and does not sanitize
|
||||
// a Cookie header already present in the request.
|
||||
func (r *Request) AddCookie(c *Cookie) {
|
||||
s := fmt.Sprintf("%s=%s", sanitizeCookieName(c.Name), sanitizeCookieValue(c.Value))
|
||||
if c := r.Header.Get("Cookie"); c != "" {
|
||||
r.Header.Set("Cookie", c+"; "+s)
|
||||
} else {
|
||||
r.Header.Set("Cookie", s)
|
||||
}
|
||||
}
|
||||
|
||||
// Referer returns the referring URL, if sent in the request.
|
||||
//
|
||||
// Referer is misspelled as in the request itself, a mistake from the
|
||||
// earliest days of HTTP. This value can also be fetched from the
|
||||
// Header map as Header["Referer"]; the benefit of making it available
|
||||
// as a method is that the compiler can diagnose programs that use the
|
||||
// alternate (correct English) spelling req.Referrer() but cannot
|
||||
// diagnose programs that use Header["Referrer"].
|
||||
func (r *Request) Referer() string {
|
||||
return r.Header.Get("Referer")
|
||||
}
|
||||
|
||||
// isH2Upgrade reports whether r represents the http2 "client preface"
|
||||
// magic string.
|
||||
func (r *Request) isH2Upgrade() bool {
|
||||
@@ -299,6 +352,114 @@ func validMethod(method string) bool {
|
||||
return len(method) > 0 && strings.IndexFunc(method, isNotToken) == -1
|
||||
}
|
||||
|
||||
// NewRequest wraps NewRequestWithContext using the background context.
|
||||
func NewRequest(method, url string, body io.Reader) (*Request, error) {
|
||||
return NewRequestWithContext(context.Background(), method, url, body)
|
||||
}
|
||||
|
||||
// NewRequestWithContext returns a new Request given a method, URL, and
|
||||
// optional body.
|
||||
//
|
||||
// If the provided body is also an io.Closer, the returned
|
||||
// Request.Body is set to body and will be closed by the Client
|
||||
// methods Do, Post, and PostForm, and Transport.RoundTrip.
|
||||
//
|
||||
// NewRequestWithContext returns a Request suitable for use with
|
||||
// Client.Do or Transport.RoundTrip. To create a request for use with
|
||||
// testing a Server Handler, either use the NewRequest function in the
|
||||
// net/http/httptest package, use ReadRequest, or manually update the
|
||||
// Request fields. For an outgoing client request, the context
|
||||
// controls the entire lifetime of a request and its response:
|
||||
// obtaining a connection, sending the request, and reading the
|
||||
// response headers and body. See the Request type's documentation for
|
||||
// the difference between inbound and outbound request fields.
|
||||
//
|
||||
// If body is of type *bytes.Buffer, *bytes.Reader, or
|
||||
// *strings.Reader, the returned request's ContentLength is set to its
|
||||
// exact value (instead of -1), GetBody is populated (so 307 and 308
|
||||
// redirects can replay the body), and Body is set to NoBody if the
|
||||
// ContentLength is 0.
|
||||
func NewRequestWithContext(ctx context.Context, method, url string, body io.Reader) (*Request, error) {
|
||||
if method == "" {
|
||||
// We document that "" means "GET" for Request.Method, and people have
|
||||
// relied on that from NewRequest, so keep that working.
|
||||
// We still enforce validMethod for non-empty methods.
|
||||
method = "GET"
|
||||
}
|
||||
if !validMethod(method) {
|
||||
return nil, fmt.Errorf("net/http: invalid method %q", method)
|
||||
}
|
||||
if ctx == nil {
|
||||
return nil, errors.New("net/http: nil Context")
|
||||
}
|
||||
u, err := urlpkg.Parse(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rc, ok := body.(io.ReadCloser)
|
||||
if !ok && body != nil {
|
||||
rc = io.NopCloser(body)
|
||||
}
|
||||
// The host's colon:port should be normalized. See Issue 14836.
|
||||
u.Host = removeEmptyPort(u.Host)
|
||||
req := &Request{
|
||||
ctx: ctx,
|
||||
Method: method,
|
||||
URL: u,
|
||||
Proto: "HTTP/1.1",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
Header: make(Header),
|
||||
Body: rc,
|
||||
Host: u.Host,
|
||||
}
|
||||
if body != nil {
|
||||
switch v := body.(type) {
|
||||
case *bytes.Buffer:
|
||||
req.ContentLength = int64(v.Len())
|
||||
buf := v.Bytes()
|
||||
req.GetBody = func() (io.ReadCloser, error) {
|
||||
r := bytes.NewReader(buf)
|
||||
return io.NopCloser(r), nil
|
||||
}
|
||||
case *bytes.Reader:
|
||||
req.ContentLength = int64(v.Len())
|
||||
snapshot := *v
|
||||
req.GetBody = func() (io.ReadCloser, error) {
|
||||
r := snapshot
|
||||
return io.NopCloser(&r), nil
|
||||
}
|
||||
case *strings.Reader:
|
||||
req.ContentLength = int64(v.Len())
|
||||
snapshot := *v
|
||||
req.GetBody = func() (io.ReadCloser, error) {
|
||||
r := snapshot
|
||||
return io.NopCloser(&r), nil
|
||||
}
|
||||
default:
|
||||
// This is where we'd set it to -1 (at least
|
||||
// if body != NoBody) to mean unknown, but
|
||||
// that broke people during the Go 1.8 testing
|
||||
// period. People depend on it being 0 I
|
||||
// guess. Maybe retry later. See Issue 18117.
|
||||
}
|
||||
// For client requests, Request.ContentLength of 0
|
||||
// means either actually 0, or unknown. The only way
|
||||
// to explicitly say that the ContentLength is zero is
|
||||
// to set the Body to nil. But turns out too much code
|
||||
// depends on NewRequest returning a non-nil Body,
|
||||
// so we use a well-known ReadCloser variable instead
|
||||
// and have the http package also treat that sentinel
|
||||
// variable to mean explicitly zero.
|
||||
if req.GetBody != nil && req.ContentLength == 0 {
|
||||
req.Body = NoBody
|
||||
req.GetBody = func() (io.ReadCloser, error) { return NoBody, nil }
|
||||
}
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// parseRequestLine parses "GET /foo HTTP/1.1" into its three parts.
|
||||
func parseRequestLine(line string) (method, requestURI, proto string, ok bool) {
|
||||
s1 := strings.Index(line, " ")
|
||||
|
||||
@@ -99,6 +99,11 @@ type Response struct {
|
||||
TLS *tls.ConnectionState
|
||||
}
|
||||
|
||||
// Cookies parses and returns the cookies set in the Set-Cookie headers.
|
||||
func (r *Response) Cookies() []*Cookie {
|
||||
return readSetCookies(r.Header)
|
||||
}
|
||||
|
||||
// RFC 7234, section 5.4: Should treat
|
||||
// Pragma: no-cache
|
||||
// like
|
||||
|
||||
@@ -0,0 +1,287 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/net"
|
||||
"tinygo.org/x/drivers/net/tls"
|
||||
)
|
||||
|
||||
var buf []byte
|
||||
|
||||
func SetBuf(b []byte) {
|
||||
buf = b
|
||||
}
|
||||
|
||||
func (c *Client) Do(req *Request) (*Response, error) {
|
||||
switch req.URL.Scheme {
|
||||
case "http":
|
||||
return c.doHTTP(req)
|
||||
case "https":
|
||||
return c.doHTTPS(req)
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid schemer : %s", req.URL.Scheme)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) doHTTP(req *Request) (*Response, error) {
|
||||
if c.Jar != nil {
|
||||
for _, cookie := range c.Jar.Cookies(req.URL) {
|
||||
req.AddCookie(cookie)
|
||||
}
|
||||
}
|
||||
|
||||
// make TCP connection
|
||||
ip := net.ParseIP(req.URL.Host)
|
||||
raddr := &net.TCPAddr{IP: ip, Port: 80}
|
||||
laddr := &net.TCPAddr{Port: 8080}
|
||||
|
||||
conn, err := net.DialTCP("tcp", laddr, raddr)
|
||||
retry := 0
|
||||
for ; err != nil; conn, err = net.DialTCP("tcp", laddr, raddr) {
|
||||
retry++
|
||||
if retry > 10 {
|
||||
return nil, fmt.Errorf("Connection failed: %s", err.Error())
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
p := req.URL.Path
|
||||
if p == "" {
|
||||
p = "/"
|
||||
}
|
||||
fmt.Fprintln(conn, req.Method+" "+p+" HTTP/1.1")
|
||||
fmt.Fprintln(conn, "Host:", req.URL.Host)
|
||||
|
||||
if req.Header.get(`User-Agent`) == "" {
|
||||
fmt.Fprintln(conn, "User-Agent: TinyGo")
|
||||
}
|
||||
|
||||
for k, v := range req.Header {
|
||||
if v == nil || len(v) == 0 {
|
||||
return nil, fmt.Errorf("req.Header error: %s", k)
|
||||
}
|
||||
fmt.Fprintln(conn, k+": "+v[0])
|
||||
}
|
||||
|
||||
if req.Header.get(`Connection`) == "" {
|
||||
fmt.Fprintln(conn, "Connection: close")
|
||||
}
|
||||
|
||||
if req.ContentLength > 0 {
|
||||
fmt.Fprintf(conn, "Content-Length: %d\n", req.ContentLength)
|
||||
}
|
||||
|
||||
fmt.Fprintln(conn)
|
||||
|
||||
if req.ContentLength > 0 {
|
||||
b, err := req.GetBody()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
n, err := b.Read(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn.Write(buf[:n])
|
||||
|
||||
b.Close()
|
||||
|
||||
}
|
||||
|
||||
return c.doResp(conn, req)
|
||||
}
|
||||
|
||||
func (c *Client) doHTTPS(req *Request) (*Response, error) {
|
||||
conn, err := tls.Dial("tcp", req.URL.Host, nil)
|
||||
retry := 0
|
||||
for ; err != nil; conn, err = tls.Dial("tcp", req.URL.Host, nil) {
|
||||
retry++
|
||||
if retry > 10 {
|
||||
return nil, fmt.Errorf("Connection failed: %s", err.Error())
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
p := req.URL.Path
|
||||
if p == "" {
|
||||
p = "/"
|
||||
}
|
||||
fmt.Fprintln(conn, req.Method+" "+p+" HTTP/1.1")
|
||||
fmt.Fprintln(conn, "Host:", req.URL.Host)
|
||||
|
||||
if req.Header.get(`User-Agent`) == "" {
|
||||
fmt.Fprintln(conn, "User-Agent: TinyGo")
|
||||
}
|
||||
|
||||
for k, v := range req.Header {
|
||||
if v == nil || len(v) == 0 {
|
||||
return nil, fmt.Errorf("req.Header error: %s", k)
|
||||
}
|
||||
fmt.Fprintln(conn, k+": "+v[0])
|
||||
}
|
||||
|
||||
if req.Header.get(`Connection`) == "" {
|
||||
fmt.Fprintln(conn, "Connection: close")
|
||||
}
|
||||
|
||||
if req.ContentLength > 0 {
|
||||
fmt.Fprintf(conn, "Content-Length: %d\n", req.ContentLength)
|
||||
}
|
||||
|
||||
fmt.Fprintln(conn)
|
||||
|
||||
if req.ContentLength > 0 {
|
||||
b, err := req.GetBody()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
n, err := b.Read(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn.Write(buf[:n])
|
||||
|
||||
b.Close()
|
||||
|
||||
}
|
||||
|
||||
return c.doResp(conn, req)
|
||||
}
|
||||
|
||||
func (c *Client) doResp(conn net.Conn, req *Request) (*Response, error) {
|
||||
resp := &Response{
|
||||
Header: map[string][]string{},
|
||||
}
|
||||
|
||||
// Header
|
||||
var scanner *bufio.Scanner
|
||||
cont := true
|
||||
ofs := 0
|
||||
remain := int64(0)
|
||||
for cont {
|
||||
for n, err := conn.Read(buf[ofs:]); n > 0; n, err = conn.Read(buf[ofs:]) {
|
||||
if err != nil {
|
||||
println("Read error: " + err.Error())
|
||||
} else {
|
||||
idx := bytes.Index(buf[ofs:ofs+n], []byte("\r\n\r\n"))
|
||||
if idx == -1 {
|
||||
ofs += n
|
||||
continue
|
||||
}
|
||||
idx += ofs + 4
|
||||
|
||||
scanner = bufio.NewScanner(bytes.NewReader(buf[0 : ofs+n]))
|
||||
if resp.Status == "" && scanner.Scan() {
|
||||
status := strings.SplitN(scanner.Text(), " ", 2)
|
||||
if len(status) != 2 {
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("invalid status : %q", scanner.Text())
|
||||
}
|
||||
resp.Proto = status[0]
|
||||
fmt.Sscanf(status[0], "HTTP/%d.%d", &resp.ProtoMajor, &resp.ProtoMinor)
|
||||
|
||||
resp.Status = status[1]
|
||||
fmt.Sscanf(status[1], "%d", &resp.StatusCode)
|
||||
}
|
||||
|
||||
for scanner.Scan() {
|
||||
text := scanner.Text()
|
||||
if text == "" {
|
||||
// end of header
|
||||
if idx < n+ofs {
|
||||
ofs = ofs + n - idx
|
||||
for i := 0; i < ofs; i++ {
|
||||
buf[i] = buf[i+idx]
|
||||
}
|
||||
} else {
|
||||
ofs = 0
|
||||
}
|
||||
break
|
||||
} else {
|
||||
header := strings.SplitN(text, ": ", 2)
|
||||
if len(header) != 2 {
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("invalid header : %q", text)
|
||||
}
|
||||
if resp.Header.Get(header[0]) == "" {
|
||||
resp.Header.Set(header[0], header[1])
|
||||
} else {
|
||||
resp.Header.Add(header[0], header[1])
|
||||
}
|
||||
|
||||
if header[0] == "Content-Length" {
|
||||
resp.ContentLength, err = strconv.ParseInt(header[1], 10, 64)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
remain = resp.ContentLength
|
||||
}
|
||||
}
|
||||
}
|
||||
cont = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Body
|
||||
remain -= int64(ofs)
|
||||
if remain <= 0 {
|
||||
resp.Body = io.NopCloser(bytes.NewReader(buf[:ofs]))
|
||||
if c.Jar != nil {
|
||||
if rc := resp.Cookies(); len(rc) > 0 {
|
||||
c.Jar.SetCookies(req.URL, rc)
|
||||
}
|
||||
}
|
||||
return resp, conn.Close()
|
||||
}
|
||||
|
||||
cont = true
|
||||
lastRequestTime := time.Now()
|
||||
for cont {
|
||||
for {
|
||||
end := ofs + 0x400
|
||||
if len(buf) < end {
|
||||
return nil, fmt.Errorf("slice out of range : use http.SetBuf() to change the allocation to %d bytes or more", end)
|
||||
}
|
||||
n, err := conn.Read(buf[ofs : ofs+0x400])
|
||||
if n == 0 {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
} else {
|
||||
ofs += n
|
||||
remain -= int64(n)
|
||||
if remain <= 0 {
|
||||
resp.Body = io.NopCloser(bytes.NewReader(buf[:ofs]))
|
||||
cont = false
|
||||
break
|
||||
}
|
||||
if time.Now().Sub(lastRequestTime).Milliseconds() >= 1000 {
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("time out")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if c.Jar != nil {
|
||||
if rc := resp.Cookies(); len(rc) > 0 {
|
||||
c.Jar.SetCookies(req.URL, rc)
|
||||
}
|
||||
}
|
||||
|
||||
return resp, conn.Close()
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package net
|
||||
|
||||
import "strings"
|
||||
|
||||
// SplitHostPort splits a network address of the form "host:port",
|
||||
// "host%zone:port", "[host]:port" or "[host%zone]:port" into host or
|
||||
// host%zone and port.
|
||||
//
|
||||
// A literal IPv6 address in hostport must be enclosed in square
|
||||
// brackets, as in "[::1]:80", "[::1%lo0]:80".
|
||||
//
|
||||
// See func Dial for a description of the hostport parameter, and host
|
||||
// and port results.
|
||||
func SplitHostPort(hostport string) (host, port string, err error) {
|
||||
|
||||
if strings.Contains(hostport, ":") {
|
||||
spl := strings.Split(hostport, ":")
|
||||
host = spl[0]
|
||||
port = spl[1]
|
||||
} else {
|
||||
host = hostport
|
||||
port = "80"
|
||||
}
|
||||
|
||||
return host, port, nil
|
||||
}
|
||||
+12
-10
@@ -226,7 +226,12 @@ func (r *RTL8720DN) ReadSocket(b []byte) (n int, err error) {
|
||||
|
||||
switch r.connectionType {
|
||||
case ConnectionTypeTCP:
|
||||
nn, err := r.Rpc_lwip_recv(r.socket, &b, uint32(len(b)), 0x00000008, 0x00002800)
|
||||
length := len(b)
|
||||
if length > maxUartRecvSize-16 {
|
||||
length = maxUartRecvSize - 16
|
||||
}
|
||||
buf := b[:length]
|
||||
nn, err := r.Rpc_lwip_recv(r.socket, &buf, uint32(length), 0x00000008, 0x00002800)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -236,17 +241,14 @@ func (r *RTL8720DN) ReadSocket(b []byte) (n int, err error) {
|
||||
} else if nn == 0 {
|
||||
return 0, r.DisconnectSocket()
|
||||
}
|
||||
if r.length == 0 {
|
||||
header := httpHeader(b[:nn])
|
||||
r.length = header.ContentLength()
|
||||
}
|
||||
r.length -= int(nn)
|
||||
if r.length == 0 {
|
||||
return int(nn), r.DisconnectSocket()
|
||||
}
|
||||
n = int(nn)
|
||||
case ConnectionTypeTLS:
|
||||
nn, err := r.Rpc_wifi_get_ssl_receive(r.client, &b, int32(len(b)))
|
||||
length := len(b)
|
||||
if length > maxUartRecvSize-16 {
|
||||
length = maxUartRecvSize - 16
|
||||
}
|
||||
buf := b[:length]
|
||||
nn, err := r.Rpc_wifi_get_ssl_receive(r.client, &buf, int32(length))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package rtl8720dn
|
||||
|
||||
import "io"
|
||||
|
||||
const maxUartRecvSize = 128
|
||||
|
||||
type RTL8720DN struct {
|
||||
port io.ReadWriter
|
||||
seq uint64
|
||||
|
||||
Reference in New Issue
Block a user