├── .gitignore ├── LICENSE ├── README.md ├── dial.go ├── http ├── client.go ├── clone.go ├── cookie.go ├── fs.go ├── header.go ├── http.go ├── httptest │ ├── httptest.go │ ├── recorder.go │ └── server.go ├── internal │ ├── ascii │ │ ├── print.go │ │ └── print_test.go │ ├── chunked.go │ └── chunked_test.go ├── jar.go ├── method.go ├── request.go ├── response.go ├── server.go ├── sniff.go ├── status.go ├── transfer.go └── transport.go ├── interface.go ├── ip.go ├── iprawsock.go ├── ipsock.go ├── lookup.go ├── lookup_unix.go ├── lookup_windows.go ├── mac.go ├── mac_test.go ├── net.go ├── netdev.go ├── parse.go ├── pipe.go ├── tcpsock.go ├── tlssock.go ├── udpsock.go └── unixsock.go /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2023, TinyGo 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # net 2 | This is a port of Go's "net" package. The port offers a subset of Go's "net" 3 | package. The subset maintains Go 1 compatiblity guarantee. 4 | 5 | The "net" package is modified to use netdev, TinyGo's network device driver interface. 6 | Netdev replaces the OS syscall interface for I/O access to the networking 7 | device. See drivers/netdev for more information on netdev. 8 | 9 | #### Table of Contents 10 | 11 | - [Using "net" and "net/http" Packages](#using-net-and-nethttp-packages) 12 | - ["net" Package](#net-package) 13 | - [Maintaining "net"](#maintaining-net) 14 | 15 | ## Using "net" and "net/http" Packages 16 | 17 | See README-net.md in drivers repo to more details on using "net" and "net/http" 18 | packages in a TinyGo application. 19 | 20 | ## "net" Package 21 | 22 | The "net" package is ported from Go 1.21.4. The tree listings below shows the 23 | files copied. If the file is marked with an '\*', it is copied _and_ modified 24 | to work with netdev. If the file is marked with an '+', the file is new. If 25 | there is no mark, it is a straight copy. 26 | 27 | ``` 28 | src/net 29 | ├── dial.go * 30 | ├── http 31 | │   ├── httptest 32 | │   │   ├── httptest.go * 33 | │   │   ├── recorder.go 34 | │   │   └── server.go * 35 | │   ├── client.go * 36 | │   ├── clone.go 37 | │   ├── cookie.go 38 | │   ├── fs.go 39 | │   ├── header.go * 40 | │   ├── http.go 41 | │   ├── internal 42 | │   │   ├── ascii 43 | │   │   │   ├── print.go 44 | │   │   │   └── print_test.go 45 | │   │   ├── chunked.go 46 | │   │   └── chunked_test.go 47 | │   ├── jar.go 48 | │   ├── method.go 49 | │   ├── request.go * 50 | │   ├── response.go * 51 | │   ├── server.go * 52 | │   ├── sniff.go 53 | │   ├── status.go 54 | │   ├── transfer.go * 55 | │   └── transport.go * 56 | ├── interface.go * 57 | ├── ip.go 58 | ├── iprawsock.go * 59 | ├── ipsock.go * 60 | ├── lookup.go * 61 | ├── mac.go 62 | ├── mac_test.go 63 | ├── netdev.go + 64 | ├── net.go * 65 | ├── parse.go 66 | ├── pipe.go 67 | ├── README.md 68 | ├── tcpsock.go * 69 | ├── tlssock.go + 70 | ├── udpsock.go * 71 | └── unixsock.go * 72 | 73 | src/crypto/tls/ 74 | ├── common.go * 75 | ├── ticket.go * 76 | └── tls.go * 77 | ``` 78 | 79 | The modifications to "net" are to basically wrap TCPConn, UDPConn, and TLSConn 80 | around netdev socket calls. In Go, these net.Conns call out to OS syscalls for 81 | the socket operations. In TinyGo, the OS syscalls aren't available, so netdev 82 | socket calls are substituted. 83 | 84 | The modifications to "net/http" are on the client and the server side. On the 85 | client side, the TinyGo code changes remove the back-end round-tripper code and 86 | replaces it with direct calls to TCPConns/TLSConns. All of Go's http 87 | request/response handling code is intact and operational in TinyGo. Same holds 88 | true for the server side. The server side supports the normal server features 89 | like ServeMux and Hijacker (for websockets). 90 | 91 | ## Maintaining "net" 92 | 93 | As Go progresses, changes to the "net" package need to be periodically 94 | back-ported to TinyGo's "net" package. This is to pick up any upstream bug 95 | fixes or security fixes. 96 | 97 | Changes "net" package files are marked with // TINYGO comments. 98 | 99 | The files that are marked modified * may contain only a subset of the original 100 | file. Basically only the parts necessary to compile and run the example/net 101 | examples are copied (and maybe modified). 102 | 103 | ### Upgrade Steps 104 | 105 | Let's define some versions: 106 | 107 | MIN = TinyGo minimum Go version supported (e.g. 1.15) 108 | CUR = TinyGo "net" current version (e.g. 1.20.5) 109 | UPSTREAM = Latest upstream Go version to upgrade to (e.g. 1.21) 110 | NEW = TinyGo "net" new version, after upgrade 111 | 112 | In example, we'll upgrade from CUR (1.20.5) to UPSTREAM (1.21). 113 | 114 | These are the steps to promote TinyGos "net" to latest Go upstream version. 115 | These steps should be done when: 116 | 117 | - MIN moved forward 118 | - TinyGo major release 119 | - TinyGo minor release to pick up security fixes in UPSTREAM 120 | 121 | Step 1: 122 | 123 | Backport differences from Go UPSTREAM to Go CUR. Since TinyGo CUR isn't the 124 | full Go "net" implementation, only backport differences, don't add new stuff 125 | from UPSTREAM (unless it's needed in the NEW release). 126 | 127 | NEW = CUR + diff(CUR, UPSTREAM) 128 | 129 | If NEW contains updates not compatible with MIN, then NEW will need to revert 130 | just those updates back to the CUR version, and annotate with a TINYGO comment. 131 | If MIN moves forord, NEW can pull in the UPSTREAM changes. 132 | 133 | Step 2: 134 | 135 | As a double check, compare NEW against UPSTREAM. The only differences at this 136 | point should be excluded (not ported) code from UPSTREAM that wasn't in CUR in 137 | the first place, and differences due to changes held back for MIN support. 138 | 139 | Step 3: 140 | 141 | Test NEW against example/net examples. If everything checks out, then CUR 142 | becomes NEW, and we can push to TinyGo. 143 | 144 | CUR = NEW 145 | -------------------------------------------------------------------------------- /dial.go: -------------------------------------------------------------------------------- 1 | // TINYGO: The following is copied and modified from Go 1.21.4 official implementation. 2 | 3 | // TINYGO: Omit DualStack support 4 | // TINYGO: Omit Fast Fallback support 5 | // TINYGO: Don't allow alternate resolver 6 | // TINYGO: Omit DialTimeout 7 | // TINYGO: Omit Multipath TCP 8 | 9 | // Copyright 2010 The Go Authors. All rights reserved. 10 | // Use of this source code is governed by a BSD-style 11 | // license that can be found in the LICENSE file. 12 | 13 | package net 14 | 15 | import ( 16 | "context" 17 | "errors" 18 | "fmt" 19 | "internal/bytealg" 20 | "syscall" 21 | "time" 22 | ) 23 | 24 | const ( 25 | // defaultTCPKeepAlive is a default constant value for TCPKeepAlive times 26 | // See go.dev/issue/31510 27 | defaultTCPKeepAlive = 15 * time.Second 28 | ) 29 | 30 | // mptcpStatus is a tristate for Multipath TCP, see go.dev/issue/56539 31 | type mptcpStatus uint8 32 | 33 | // A Dialer contains options for connecting to an address. 34 | // 35 | // The zero value for each field is equivalent to dialing 36 | // without that option. Dialing with the zero value of Dialer 37 | // is therefore equivalent to just calling the Dial function. 38 | // 39 | // It is safe to call Dialer's methods concurrently. 40 | type Dialer struct { 41 | // Timeout is the maximum amount of time a dial will wait for 42 | // a connect to complete. If Deadline is also set, it may fail 43 | // earlier. 44 | // 45 | // The default is no timeout. 46 | // 47 | // When using TCP and dialing a host name with multiple IP 48 | // addresses, the timeout may be divided between them. 49 | // 50 | // With or without a timeout, the operating system may impose 51 | // its own earlier timeout. For instance, TCP timeouts are 52 | // often around 3 minutes. 53 | Timeout time.Duration 54 | 55 | // Deadline is the absolute point in time after which dials 56 | // will fail. If Timeout is set, it may fail earlier. 57 | // Zero means no deadline, or dependent on the operating system 58 | // as with the Timeout option. 59 | Deadline time.Time 60 | 61 | // LocalAddr is the local address to use when dialing an 62 | // address. The address must be of a compatible type for the 63 | // network being dialed. 64 | // If nil, a local address is automatically chosen. 65 | LocalAddr Addr 66 | 67 | // KeepAlive specifies the interval between keep-alive 68 | // probes for an active network connection. 69 | // If zero, keep-alive probes are sent with a default value 70 | // (currently 15 seconds), if supported by the protocol and operating 71 | // system. Network protocols or operating systems that do 72 | // not support keep-alives ignore this field. 73 | // If negative, keep-alive probes are disabled. 74 | KeepAlive time.Duration 75 | } 76 | 77 | // Dial connects to the address on the named network. 78 | // 79 | // See Go "net" package Dial() for more information. 80 | // 81 | // Note: Tinygo Dial supports a subset of networks supported by Go Dial, 82 | // specifically: "tcp", "tcp4", "udp", and "udp4". IP and unix networks are 83 | // not supported. 84 | func Dial(network, address string) (Conn, error) { 85 | var d Dialer 86 | return d.Dial(network, address) 87 | } 88 | 89 | // DialTimeout acts like Dial but takes a timeout. 90 | // 91 | // The timeout includes name resolution, if required. 92 | // When using TCP, and the host in the address parameter resolves to 93 | // multiple IP addresses, the timeout is spread over each consecutive 94 | // dial, such that each is given an appropriate fraction of the time 95 | // to connect. 96 | // 97 | // See func Dial for a description of the network and address 98 | // parameters. 99 | func DialTimeout(network, address string, timeout time.Duration) (Conn, error) { 100 | d := Dialer{Timeout: timeout} 101 | return d.Dial(network, address) 102 | } 103 | 104 | // Dial connects to the address on the named network. 105 | // 106 | // See func Dial for a description of the network and address 107 | // parameters. 108 | // 109 | // Dial uses context.Background internally; to specify the context, use 110 | // DialContext. 111 | func (d *Dialer) Dial(network, address string) (Conn, error) { 112 | return d.DialContext(context.Background(), network, address) 113 | } 114 | 115 | // DialContext connects to the address on the named network using 116 | // the provided context. 117 | // 118 | // The provided Context must be non-nil. If the context expires before 119 | // the connection is complete, an error is returned. Once successfully 120 | // connected, any expiration of the context will not affect the 121 | // connection. 122 | // 123 | // When using TCP, and the host in the address parameter resolves to multiple 124 | // network addresses, any dial timeout (from d.Timeout or ctx) is spread 125 | // over each consecutive dial, such that each is given an appropriate 126 | // fraction of the time to connect. 127 | // For example, if a host has 4 IP addresses and the timeout is 1 minute, 128 | // the connect to each single address will be given 15 seconds to complete 129 | // before trying the next one. 130 | // 131 | // See func Dial for a description of the network and address 132 | // parameters. 133 | func (d *Dialer) DialContext(ctx context.Context, network, address string) (Conn, error) { 134 | 135 | // TINYGO: Ignoring context 136 | 137 | switch network { 138 | case "tcp", "tcp4": 139 | raddr, err := ResolveTCPAddr(network, address) 140 | if err != nil { 141 | return nil, err 142 | } 143 | return DialTCP(network, nil, raddr) 144 | case "udp", "udp4": 145 | raddr, err := ResolveUDPAddr(network, address) 146 | if err != nil { 147 | return nil, err 148 | } 149 | return DialUDP(network, nil, raddr) 150 | } 151 | 152 | return nil, fmt.Errorf("Network %s not supported", network) 153 | } 154 | 155 | // ListenConfig contains options for listening to an address. 156 | type ListenConfig struct { 157 | // If Control is not nil, it is called after creating the network 158 | // connection but before binding it to the operating system. 159 | // 160 | // Network and address parameters passed to Control method are not 161 | // necessarily the ones passed to Listen. For example, passing "tcp" to 162 | // Listen will cause the Control function to be called with "tcp4" or "tcp6". 163 | Control func(network, address string, c syscall.RawConn) error 164 | 165 | // KeepAlive specifies the keep-alive period for network 166 | // connections accepted by this listener. 167 | // If zero, keep-alives are enabled if supported by the protocol 168 | // and operating system. Network protocols or operating systems 169 | // that do not support keep-alives ignore this field. 170 | // If negative, keep-alives are disabled. 171 | KeepAlive time.Duration 172 | 173 | // If mptcpStatus is set to a value allowing Multipath TCP (MPTCP) to be 174 | // used, any call to Listen with "tcp(4|6)" as network will use MPTCP if 175 | // supported by the operating system. 176 | mptcpStatus mptcpStatus 177 | } 178 | 179 | // Listen announces on the local network address. 180 | // 181 | // See func Listen for a description of the network and address 182 | // parameters. 183 | func (lc *ListenConfig) Listen(ctx context.Context, network, address string) (Listener, error) { 184 | return nil, errors.New("dial:ListenConfig:Listen not implemented") 185 | } 186 | 187 | // ListenPacket announces on the local network address. 188 | // 189 | // See func ListenPacket for a description of the network and address 190 | // parameters. 191 | func (lc *ListenConfig) ListenPacket(ctx context.Context, network, address string) (PacketConn, error) { 192 | return nil, errors.New("dial:ListenConfig:ListenPacket not implemented") 193 | } 194 | 195 | func parseNetwork(ctx context.Context, network string, needsProto bool) (afnet string, proto int, err error) { 196 | i := bytealg.LastIndexByteString(network, ':') 197 | if i < 0 { // no colon 198 | switch network { 199 | case "tcp", "tcp4", "tcp6": 200 | case "udp", "udp4", "udp6": 201 | case "ip", "ip4", "ip6": 202 | if needsProto { 203 | return "", 0, UnknownNetworkError(network) 204 | } 205 | case "unix", "unixgram", "unixpacket": 206 | default: 207 | return "", 0, UnknownNetworkError(network) 208 | } 209 | return network, 0, nil 210 | } 211 | afnet = network[:i] 212 | switch afnet { 213 | case "ip", "ip4", "ip6": 214 | protostr := network[i+1:] 215 | proto, i, ok := dtoi(protostr) 216 | if !ok || i != len(protostr) { 217 | proto, err = lookupProtocol(ctx, protostr) 218 | if err != nil { 219 | return "", 0, err 220 | } 221 | } 222 | return afnet, proto, nil 223 | } 224 | return "", 0, UnknownNetworkError(network) 225 | } 226 | 227 | // Listen announces on the local network address. 228 | // 229 | // See Go "net" package Listen() for more information. 230 | // 231 | // Note: Tinygo Listen supports a subset of networks supported by Go Listen, 232 | // specifically: "tcp", "tcp4". "tcp6" and unix networks are not supported. 233 | func Listen(network, address string) (Listener, error) { 234 | 235 | // println("Listen", address) 236 | switch network { 237 | case "tcp", "tcp4": 238 | default: 239 | return nil, fmt.Errorf("Network %s not supported", network) 240 | } 241 | 242 | laddr, err := ResolveTCPAddr(network, address) 243 | if err != nil { 244 | return nil, err 245 | } 246 | 247 | return listenTCP(laddr) 248 | } 249 | -------------------------------------------------------------------------------- /http/clone.go: -------------------------------------------------------------------------------- 1 | // TINYGO: The following is copied from Go 1.21.4 official implementation. 2 | 3 | // Copyright 2019 The Go Authors. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | 7 | package http 8 | 9 | import ( 10 | "mime/multipart" 11 | "net/textproto" 12 | "net/url" 13 | ) 14 | 15 | func cloneURLValues(v url.Values) url.Values { 16 | if v == nil { 17 | return nil 18 | } 19 | // http.Header and url.Values have the same representation, so temporarily 20 | // treat it like http.Header, which does have a clone: 21 | return url.Values(Header(v).Clone()) 22 | } 23 | 24 | func cloneURL(u *url.URL) *url.URL { 25 | if u == nil { 26 | return nil 27 | } 28 | u2 := new(url.URL) 29 | *u2 = *u 30 | if u.User != nil { 31 | u2.User = new(url.Userinfo) 32 | *u2.User = *u.User 33 | } 34 | return u2 35 | } 36 | 37 | func cloneMultipartForm(f *multipart.Form) *multipart.Form { 38 | if f == nil { 39 | return nil 40 | } 41 | f2 := &multipart.Form{ 42 | Value: (map[string][]string)(Header(f.Value).Clone()), 43 | } 44 | if f.File != nil { 45 | m := make(map[string][]*multipart.FileHeader) 46 | for k, vv := range f.File { 47 | vv2 := make([]*multipart.FileHeader, len(vv)) 48 | for i, v := range vv { 49 | vv2[i] = cloneMultipartFileHeader(v) 50 | } 51 | m[k] = vv2 52 | } 53 | f2.File = m 54 | } 55 | return f2 56 | } 57 | 58 | func cloneMultipartFileHeader(fh *multipart.FileHeader) *multipart.FileHeader { 59 | if fh == nil { 60 | return nil 61 | } 62 | fh2 := new(multipart.FileHeader) 63 | *fh2 = *fh 64 | fh2.Header = textproto.MIMEHeader(Header(fh.Header).Clone()) 65 | return fh2 66 | } 67 | 68 | // cloneOrMakeHeader invokes Header.Clone but if the 69 | // result is nil, it'll instead make and return a non-nil Header. 70 | func cloneOrMakeHeader(hdr Header) Header { 71 | clone := hdr.Clone() 72 | if clone == nil { 73 | clone = make(Header) 74 | } 75 | return clone 76 | } 77 | -------------------------------------------------------------------------------- /http/cookie.go: -------------------------------------------------------------------------------- 1 | // TINYGO: The following is copied and modified from Go 1.21.4 official implementation. 2 | 3 | // Copyright 2009 The Go Authors. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | 7 | package http 8 | 9 | import ( 10 | "errors" 11 | "fmt" 12 | "log" 13 | "net" 14 | "net/http/internal/ascii" 15 | "net/textproto" 16 | "strconv" 17 | "strings" 18 | "time" 19 | ) 20 | 21 | // A Cookie represents an HTTP cookie as sent in the Set-Cookie header of an 22 | // HTTP response or the Cookie header of an HTTP request. 23 | // 24 | // See https://tools.ietf.org/html/rfc6265 for details. 25 | type Cookie struct { 26 | Name string 27 | Value string 28 | 29 | Path string // optional 30 | Domain string // optional 31 | Expires time.Time // optional 32 | RawExpires string // for reading cookies only 33 | 34 | // MaxAge=0 means no 'Max-Age' attribute specified. 35 | // MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0' 36 | // MaxAge>0 means Max-Age attribute present and given in seconds 37 | MaxAge int 38 | Secure bool 39 | HttpOnly bool 40 | SameSite SameSite 41 | Raw string 42 | Unparsed []string // Raw text of unparsed attribute-value pairs 43 | } 44 | 45 | // SameSite allows a server to define a cookie attribute making it impossible for 46 | // the browser to send this cookie along with cross-site requests. The main 47 | // goal is to mitigate the risk of cross-origin information leakage, and provide 48 | // some protection against cross-site request forgery attacks. 49 | // 50 | // See https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00 for details. 51 | type SameSite int 52 | 53 | const ( 54 | SameSiteDefaultMode SameSite = iota + 1 55 | SameSiteLaxMode 56 | SameSiteStrictMode 57 | SameSiteNoneMode 58 | ) 59 | 60 | // readSetCookies parses all "Set-Cookie" values from 61 | // the header h and returns the successfully parsed Cookies. 62 | func readSetCookies(h Header) []*Cookie { 63 | cookieCount := len(h["Set-Cookie"]) 64 | if cookieCount == 0 { 65 | return []*Cookie{} 66 | } 67 | cookies := make([]*Cookie, 0, cookieCount) 68 | for _, line := range h["Set-Cookie"] { 69 | parts := strings.Split(textproto.TrimString(line), ";") 70 | if len(parts) == 1 && parts[0] == "" { 71 | continue 72 | } 73 | parts[0] = textproto.TrimString(parts[0]) 74 | name, value, ok := strings.Cut(parts[0], "=") 75 | if !ok { 76 | continue 77 | } 78 | name = textproto.TrimString(name) 79 | if !isCookieNameValid(name) { 80 | continue 81 | } 82 | value, ok = parseCookieValue(value, true) 83 | if !ok { 84 | continue 85 | } 86 | c := &Cookie{ 87 | Name: name, 88 | Value: value, 89 | Raw: line, 90 | } 91 | for i := 1; i < len(parts); i++ { 92 | parts[i] = textproto.TrimString(parts[i]) 93 | if len(parts[i]) == 0 { 94 | continue 95 | } 96 | 97 | attr, val, _ := strings.Cut(parts[i], "=") 98 | lowerAttr, isASCII := ascii.ToLower(attr) 99 | if !isASCII { 100 | continue 101 | } 102 | val, ok = parseCookieValue(val, false) 103 | if !ok { 104 | c.Unparsed = append(c.Unparsed, parts[i]) 105 | continue 106 | } 107 | 108 | switch lowerAttr { 109 | case "samesite": 110 | lowerVal, ascii := ascii.ToLower(val) 111 | if !ascii { 112 | c.SameSite = SameSiteDefaultMode 113 | continue 114 | } 115 | switch lowerVal { 116 | case "lax": 117 | c.SameSite = SameSiteLaxMode 118 | case "strict": 119 | c.SameSite = SameSiteStrictMode 120 | case "none": 121 | c.SameSite = SameSiteNoneMode 122 | default: 123 | c.SameSite = SameSiteDefaultMode 124 | } 125 | continue 126 | case "secure": 127 | c.Secure = true 128 | continue 129 | case "httponly": 130 | c.HttpOnly = true 131 | continue 132 | case "domain": 133 | c.Domain = val 134 | continue 135 | case "max-age": 136 | secs, err := strconv.Atoi(val) 137 | if err != nil || secs != 0 && val[0] == '0' { 138 | break 139 | } 140 | if secs <= 0 { 141 | secs = -1 142 | } 143 | c.MaxAge = secs 144 | continue 145 | case "expires": 146 | c.RawExpires = val 147 | exptime, err := time.Parse(time.RFC1123, val) 148 | if err != nil { 149 | exptime, err = time.Parse("Mon, 02-Jan-2006 15:04:05 MST", val) 150 | if err != nil { 151 | c.Expires = time.Time{} 152 | break 153 | } 154 | } 155 | c.Expires = exptime.UTC() 156 | continue 157 | case "path": 158 | c.Path = val 159 | continue 160 | } 161 | c.Unparsed = append(c.Unparsed, parts[i]) 162 | } 163 | cookies = append(cookies, c) 164 | } 165 | return cookies 166 | } 167 | 168 | // SetCookie adds a Set-Cookie header to the provided ResponseWriter's headers. 169 | // The provided cookie must have a valid Name. Invalid cookies may be 170 | // silently dropped. 171 | func SetCookie(w ResponseWriter, cookie *Cookie) { 172 | if v := cookie.String(); v != "" { 173 | w.Header().Add("Set-Cookie", v) 174 | } 175 | } 176 | 177 | // String returns the serialization of the cookie for use in a Cookie 178 | // header (if only Name and Value are set) or a Set-Cookie response 179 | // header (if other fields are set). 180 | // If c is nil or c.Name is invalid, the empty string is returned. 181 | func (c *Cookie) String() string { 182 | if c == nil || !isCookieNameValid(c.Name) { 183 | return "" 184 | } 185 | // extraCookieLength derived from typical length of cookie attributes 186 | // see RFC 6265 Sec 4.1. 187 | const extraCookieLength = 110 188 | var b strings.Builder 189 | b.Grow(len(c.Name) + len(c.Value) + len(c.Domain) + len(c.Path) + extraCookieLength) 190 | b.WriteString(c.Name) 191 | b.WriteRune('=') 192 | b.WriteString(sanitizeCookieValue(c.Value)) 193 | 194 | if len(c.Path) > 0 { 195 | b.WriteString("; Path=") 196 | b.WriteString(sanitizeCookiePath(c.Path)) 197 | } 198 | if len(c.Domain) > 0 { 199 | if validCookieDomain(c.Domain) { 200 | // A c.Domain containing illegal characters is not 201 | // sanitized but simply dropped which turns the cookie 202 | // into a host-only cookie. A leading dot is okay 203 | // but won't be sent. 204 | d := c.Domain 205 | if d[0] == '.' { 206 | d = d[1:] 207 | } 208 | b.WriteString("; Domain=") 209 | b.WriteString(d) 210 | } else { 211 | log.Printf("net/http: invalid Cookie.Domain %q; dropping domain attribute", c.Domain) 212 | } 213 | } 214 | var buf [len(TimeFormat)]byte 215 | if validCookieExpires(c.Expires) { 216 | b.WriteString("; Expires=") 217 | b.Write(c.Expires.UTC().AppendFormat(buf[:0], TimeFormat)) 218 | } 219 | if c.MaxAge > 0 { 220 | b.WriteString("; Max-Age=") 221 | b.Write(strconv.AppendInt(buf[:0], int64(c.MaxAge), 10)) 222 | } else if c.MaxAge < 0 { 223 | b.WriteString("; Max-Age=0") 224 | } 225 | if c.HttpOnly { 226 | b.WriteString("; HttpOnly") 227 | } 228 | if c.Secure { 229 | b.WriteString("; Secure") 230 | } 231 | switch c.SameSite { 232 | case SameSiteDefaultMode: 233 | // Skip, default mode is obtained by not emitting the attribute. 234 | case SameSiteNoneMode: 235 | b.WriteString("; SameSite=None") 236 | case SameSiteLaxMode: 237 | b.WriteString("; SameSite=Lax") 238 | case SameSiteStrictMode: 239 | b.WriteString("; SameSite=Strict") 240 | } 241 | return b.String() 242 | } 243 | 244 | // Valid reports whether the cookie is valid. 245 | func (c *Cookie) Valid() error { 246 | if c == nil { 247 | return errors.New("http: nil Cookie") 248 | } 249 | if !isCookieNameValid(c.Name) { 250 | return errors.New("http: invalid Cookie.Name") 251 | } 252 | if !c.Expires.IsZero() && !validCookieExpires(c.Expires) { 253 | return errors.New("http: invalid Cookie.Expires") 254 | } 255 | for i := 0; i < len(c.Value); i++ { 256 | if !validCookieValueByte(c.Value[i]) { 257 | return fmt.Errorf("http: invalid byte %q in Cookie.Value", c.Value[i]) 258 | } 259 | } 260 | if len(c.Path) > 0 { 261 | for i := 0; i < len(c.Path); i++ { 262 | if !validCookiePathByte(c.Path[i]) { 263 | return fmt.Errorf("http: invalid byte %q in Cookie.Path", c.Path[i]) 264 | } 265 | } 266 | } 267 | if len(c.Domain) > 0 { 268 | if !validCookieDomain(c.Domain) { 269 | return errors.New("http: invalid Cookie.Domain") 270 | } 271 | } 272 | return nil 273 | } 274 | 275 | // readCookies parses all "Cookie" values from the header h and 276 | // returns the successfully parsed Cookies. 277 | // 278 | // if filter isn't empty, only cookies of that name are returned. 279 | func readCookies(h Header, filter string) []*Cookie { 280 | lines := h["Cookie"] 281 | if len(lines) == 0 { 282 | return []*Cookie{} 283 | } 284 | 285 | cookies := make([]*Cookie, 0, len(lines)+strings.Count(lines[0], ";")) 286 | for _, line := range lines { 287 | line = textproto.TrimString(line) 288 | 289 | var part string 290 | for len(line) > 0 { // continue since we have rest 291 | part, line, _ = strings.Cut(line, ";") 292 | part = textproto.TrimString(part) 293 | if part == "" { 294 | continue 295 | } 296 | name, val, _ := strings.Cut(part, "=") 297 | name = textproto.TrimString(name) 298 | if !isCookieNameValid(name) { 299 | continue 300 | } 301 | if filter != "" && filter != name { 302 | continue 303 | } 304 | val, ok := parseCookieValue(val, true) 305 | if !ok { 306 | continue 307 | } 308 | cookies = append(cookies, &Cookie{Name: name, Value: val}) 309 | } 310 | } 311 | return cookies 312 | } 313 | 314 | // validCookieDomain reports whether v is a valid cookie domain-value. 315 | func validCookieDomain(v string) bool { 316 | if isCookieDomainName(v) { 317 | return true 318 | } 319 | if net.ParseIP(v) != nil && !strings.Contains(v, ":") { 320 | return true 321 | } 322 | return false 323 | } 324 | 325 | // validCookieExpires reports whether v is a valid cookie expires-value. 326 | func validCookieExpires(t time.Time) bool { 327 | // IETF RFC 6265 Section 5.1.1.5, the year must not be less than 1601 328 | return t.Year() >= 1601 329 | } 330 | 331 | // isCookieDomainName reports whether s is a valid domain name or a valid 332 | // domain name with a leading dot '.'. It is almost a direct copy of 333 | // package net's isDomainName. 334 | func isCookieDomainName(s string) bool { 335 | if len(s) == 0 { 336 | return false 337 | } 338 | if len(s) > 255 { 339 | return false 340 | } 341 | 342 | if s[0] == '.' { 343 | // A cookie a domain attribute may start with a leading dot. 344 | s = s[1:] 345 | } 346 | last := byte('.') 347 | ok := false // Ok once we've seen a letter. 348 | partlen := 0 349 | for i := 0; i < len(s); i++ { 350 | c := s[i] 351 | switch { 352 | default: 353 | return false 354 | case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z': 355 | // No '_' allowed here (in contrast to package net). 356 | ok = true 357 | partlen++ 358 | case '0' <= c && c <= '9': 359 | // fine 360 | partlen++ 361 | case c == '-': 362 | // Byte before dash cannot be dot. 363 | if last == '.' { 364 | return false 365 | } 366 | partlen++ 367 | case c == '.': 368 | // Byte before dot cannot be dot, dash. 369 | if last == '.' || last == '-' { 370 | return false 371 | } 372 | if partlen > 63 || partlen == 0 { 373 | return false 374 | } 375 | partlen = 0 376 | } 377 | last = c 378 | } 379 | if last == '-' || partlen > 63 { 380 | return false 381 | } 382 | 383 | return ok 384 | } 385 | 386 | var cookieNameSanitizer = strings.NewReplacer("\n", "-", "\r", "-") 387 | 388 | func sanitizeCookieName(n string) string { 389 | return cookieNameSanitizer.Replace(n) 390 | } 391 | 392 | // sanitizeCookieValue produces a suitable cookie-value from v. 393 | // https://tools.ietf.org/html/rfc6265#section-4.1.1 394 | // 395 | // cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE ) 396 | // cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E 397 | // ; US-ASCII characters excluding CTLs, 398 | // ; whitespace DQUOTE, comma, semicolon, 399 | // ; and backslash 400 | // 401 | // We loosen this as spaces and commas are common in cookie values 402 | // but we produce a quoted cookie-value if and only if v contains 403 | // commas or spaces. 404 | // See https://golang.org/issue/7243 for the discussion. 405 | func sanitizeCookieValue(v string) string { 406 | v = sanitizeOrWarn("Cookie.Value", validCookieValueByte, v) 407 | if len(v) == 0 { 408 | return v 409 | } 410 | if strings.ContainsAny(v, " ,") { 411 | return `"` + v + `"` 412 | } 413 | return v 414 | } 415 | 416 | func validCookieValueByte(b byte) bool { 417 | return 0x20 <= b && b < 0x7f && b != '"' && b != ';' && b != '\\' 418 | } 419 | 420 | // path-av = "Path=" path-value 421 | // path-value = 422 | func sanitizeCookiePath(v string) string { 423 | return sanitizeOrWarn("Cookie.Path", validCookiePathByte, v) 424 | } 425 | 426 | func validCookiePathByte(b byte) bool { 427 | return 0x20 <= b && b < 0x7f && b != ';' 428 | } 429 | 430 | func sanitizeOrWarn(fieldName string, valid func(byte) bool, v string) string { 431 | ok := true 432 | for i := 0; i < len(v); i++ { 433 | if valid(v[i]) { 434 | continue 435 | } 436 | log.Printf("net/http: invalid byte %q in %s; dropping invalid bytes", v[i], fieldName) 437 | ok = false 438 | break 439 | } 440 | if ok { 441 | return v 442 | } 443 | buf := make([]byte, 0, len(v)) 444 | for i := 0; i < len(v); i++ { 445 | if b := v[i]; valid(b) { 446 | buf = append(buf, b) 447 | } 448 | } 449 | return string(buf) 450 | } 451 | 452 | func parseCookieValue(raw string, allowDoubleQuote bool) (string, bool) { 453 | // Strip the quotes, if present. 454 | if allowDoubleQuote && len(raw) > 1 && raw[0] == '"' && raw[len(raw)-1] == '"' { 455 | raw = raw[1 : len(raw)-1] 456 | } 457 | for i := 0; i < len(raw); i++ { 458 | if !validCookieValueByte(raw[i]) { 459 | return "", false 460 | } 461 | } 462 | return raw, true 463 | } 464 | 465 | func isCookieNameValid(raw string) bool { 466 | if raw == "" { 467 | return false 468 | } 469 | return strings.IndexFunc(raw, isNotToken) < 0 470 | } 471 | -------------------------------------------------------------------------------- /http/header.go: -------------------------------------------------------------------------------- 1 | // TINYGO: The following is copied and modified from Go 1.21.4 official implementation. 2 | 3 | // TINYGO: Removed trace stuff 4 | 5 | // Copyright 2010 The Go Authors. All rights reserved. 6 | // Use of this source code is governed by a BSD-style 7 | // license that can be found in the LICENSE file. 8 | 9 | package http 10 | 11 | import ( 12 | "io" 13 | "net/http/internal/ascii" 14 | "net/textproto" 15 | "sort" 16 | "strings" 17 | "sync" 18 | "time" 19 | 20 | "golang.org/x/net/http/httpguts" 21 | ) 22 | 23 | // A Header represents the key-value pairs in an HTTP header. 24 | // 25 | // The keys should be in canonical form, as returned by 26 | // CanonicalHeaderKey. 27 | type Header map[string][]string 28 | 29 | // Add adds the key, value pair to the header. 30 | // It appends to any existing values associated with key. 31 | // The key is case insensitive; it is canonicalized by 32 | // CanonicalHeaderKey. 33 | func (h Header) Add(key, value string) { 34 | textproto.MIMEHeader(h).Add(key, value) 35 | } 36 | 37 | // Set sets the header entries associated with key to the 38 | // single element value. It replaces any existing values 39 | // associated with key. The key is case insensitive; it is 40 | // canonicalized by textproto.CanonicalMIMEHeaderKey. 41 | // To use non-canonical keys, assign to the map directly. 42 | func (h Header) Set(key, value string) { 43 | textproto.MIMEHeader(h).Set(key, value) 44 | } 45 | 46 | // Get gets the first value associated with the given key. If 47 | // there are no values associated with the key, Get returns "". 48 | // It is case insensitive; textproto.CanonicalMIMEHeaderKey is 49 | // used to canonicalize the provided key. Get assumes that all 50 | // keys are stored in canonical form. To use non-canonical keys, 51 | // access the map directly. 52 | func (h Header) Get(key string) string { 53 | return textproto.MIMEHeader(h).Get(key) 54 | } 55 | 56 | // Values returns all values associated with the given key. 57 | // It is case insensitive; textproto.CanonicalMIMEHeaderKey is 58 | // used to canonicalize the provided key. To use non-canonical 59 | // keys, access the map directly. 60 | // The returned slice is not a copy. 61 | func (h Header) Values(key string) []string { 62 | return textproto.MIMEHeader(h).Values(key) 63 | } 64 | 65 | // get is like Get, but key must already be in CanonicalHeaderKey form. 66 | func (h Header) get(key string) string { 67 | if v := h[key]; len(v) > 0 { 68 | return v[0] 69 | } 70 | return "" 71 | } 72 | 73 | // has reports whether h has the provided key defined, even if it's 74 | // set to 0-length slice. 75 | func (h Header) has(key string) bool { 76 | _, ok := h[key] 77 | return ok 78 | } 79 | 80 | // Del deletes the values associated with key. 81 | // The key is case insensitive; it is canonicalized by 82 | // CanonicalHeaderKey. 83 | func (h Header) Del(key string) { 84 | textproto.MIMEHeader(h).Del(key) 85 | } 86 | 87 | // Write writes a header in wire format. 88 | func (h Header) Write(w io.Writer) error { 89 | return h.write(w) 90 | } 91 | 92 | func (h Header) write(w io.Writer) error { 93 | return h.writeSubset(w, nil) 94 | } 95 | 96 | // Clone returns a copy of h or nil if h is nil. 97 | func (h Header) Clone() Header { 98 | if h == nil { 99 | return nil 100 | } 101 | 102 | // Find total number of values. 103 | nv := 0 104 | for _, vv := range h { 105 | nv += len(vv) 106 | } 107 | sv := make([]string, nv) // shared backing array for headers' values 108 | h2 := make(Header, len(h)) 109 | for k, vv := range h { 110 | if vv == nil { 111 | // Preserve nil values. ReverseProxy distinguishes 112 | // between nil and zero-length header values. 113 | h2[k] = nil 114 | continue 115 | } 116 | n := copy(sv, vv) 117 | h2[k] = sv[:n:n] 118 | sv = sv[n:] 119 | } 120 | return h2 121 | } 122 | 123 | var timeFormats = []string{ 124 | TimeFormat, 125 | time.RFC850, 126 | time.ANSIC, 127 | } 128 | 129 | // ParseTime parses a time header (such as the Date: header), 130 | // trying each of the three formats allowed by HTTP/1.1: 131 | // TimeFormat, time.RFC850, and time.ANSIC. 132 | func ParseTime(text string) (t time.Time, err error) { 133 | for _, layout := range timeFormats { 134 | t, err = time.Parse(layout, text) 135 | if err == nil { 136 | return 137 | } 138 | } 139 | return 140 | } 141 | 142 | var headerNewlineToSpace = strings.NewReplacer("\n", " ", "\r", " ") 143 | 144 | // stringWriter implements WriteString on a Writer. 145 | type stringWriter struct { 146 | w io.Writer 147 | } 148 | 149 | func (w stringWriter) WriteString(s string) (n int, err error) { 150 | return w.w.Write([]byte(s)) 151 | } 152 | 153 | type keyValues struct { 154 | key string 155 | values []string 156 | } 157 | 158 | // A headerSorter implements sort.Interface by sorting a []keyValues 159 | // by key. It's used as a pointer, so it can fit in a sort.Interface 160 | // interface value without allocation. 161 | type headerSorter struct { 162 | kvs []keyValues 163 | } 164 | 165 | func (s *headerSorter) Len() int { return len(s.kvs) } 166 | func (s *headerSorter) Swap(i, j int) { s.kvs[i], s.kvs[j] = s.kvs[j], s.kvs[i] } 167 | func (s *headerSorter) Less(i, j int) bool { return s.kvs[i].key < s.kvs[j].key } 168 | 169 | var headerSorterPool = sync.Pool{ 170 | New: func() any { return new(headerSorter) }, 171 | } 172 | 173 | // sortedKeyValues returns h's keys sorted in the returned kvs 174 | // slice. The headerSorter used to sort is also returned, for possible 175 | // return to headerSorterCache. 176 | func (h Header) sortedKeyValues(exclude map[string]bool) (kvs []keyValues, hs *headerSorter) { 177 | hs = headerSorterPool.Get().(*headerSorter) 178 | if cap(hs.kvs) < len(h) { 179 | hs.kvs = make([]keyValues, 0, len(h)) 180 | } 181 | kvs = hs.kvs[:0] 182 | for k, vv := range h { 183 | if !exclude[k] { 184 | kvs = append(kvs, keyValues{k, vv}) 185 | } 186 | } 187 | hs.kvs = kvs 188 | sort.Sort(hs) 189 | return kvs, hs 190 | } 191 | 192 | // WriteSubset writes a header in wire format. 193 | // If exclude is not nil, keys where exclude[key] == true are not written. 194 | // Keys are not canonicalized before checking the exclude map. 195 | func (h Header) WriteSubset(w io.Writer, exclude map[string]bool) error { 196 | return h.writeSubset(w, exclude) 197 | } 198 | 199 | func (h Header) writeSubset(w io.Writer, exclude map[string]bool) error { 200 | ws, ok := w.(io.StringWriter) 201 | if !ok { 202 | ws = stringWriter{w} 203 | } 204 | kvs, sorter := h.sortedKeyValues(exclude) 205 | for _, kv := range kvs { 206 | if !httpguts.ValidHeaderFieldName(kv.key) { 207 | // This could be an error. In the common case of 208 | // writing response headers, however, we have no good 209 | // way to provide the error back to the server 210 | // handler, so just drop invalid headers instead. 211 | continue 212 | } 213 | for _, v := range kv.values { 214 | v = headerNewlineToSpace.Replace(v) 215 | v = textproto.TrimString(v) 216 | for _, s := range []string{kv.key, ": ", v, "\r\n"} { 217 | if _, err := ws.WriteString(s); err != nil { 218 | headerSorterPool.Put(sorter) 219 | return err 220 | } 221 | } 222 | } 223 | } 224 | headerSorterPool.Put(sorter) 225 | return nil 226 | } 227 | 228 | // CanonicalHeaderKey returns the canonical format of the 229 | // header key s. The canonicalization converts the first 230 | // letter and any letter following a hyphen to upper case; 231 | // the rest are converted to lowercase. For example, the 232 | // canonical key for "accept-encoding" is "Accept-Encoding". 233 | // If s contains a space or invalid header field bytes, it is 234 | // returned without modifications. 235 | func CanonicalHeaderKey(s string) string { return textproto.CanonicalMIMEHeaderKey(s) } 236 | 237 | // hasToken reports whether token appears with v, ASCII 238 | // case-insensitive, with space or comma boundaries. 239 | // token must be all lowercase. 240 | // v may contain mixed cased. 241 | func hasToken(v, token string) bool { 242 | if len(token) > len(v) || token == "" { 243 | return false 244 | } 245 | if v == token { 246 | return true 247 | } 248 | for sp := 0; sp <= len(v)-len(token); sp++ { 249 | // Check that first character is good. 250 | // The token is ASCII, so checking only a single byte 251 | // is sufficient. We skip this potential starting 252 | // position if both the first byte and its potential 253 | // ASCII uppercase equivalent (b|0x20) don't match. 254 | // False positives ('^' => '~') are caught by EqualFold. 255 | if b := v[sp]; b != token[0] && b|0x20 != token[0] { 256 | continue 257 | } 258 | // Check that start pos is on a valid token boundary. 259 | if sp > 0 && !isTokenBoundary(v[sp-1]) { 260 | continue 261 | } 262 | // Check that end pos is on a valid token boundary. 263 | if endPos := sp + len(token); endPos != len(v) && !isTokenBoundary(v[endPos]) { 264 | continue 265 | } 266 | if ascii.EqualFold(v[sp:sp+len(token)], token) { 267 | return true 268 | } 269 | } 270 | return false 271 | } 272 | 273 | func isTokenBoundary(b byte) bool { 274 | return b == ' ' || b == ',' || b == '\t' 275 | } 276 | -------------------------------------------------------------------------------- /http/http.go: -------------------------------------------------------------------------------- 1 | // TINYGO: The following is copied from Go 1.21.4 official implementation. 2 | 3 | // Copyright 2016 The Go Authors. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | 7 | //go:generate bundle -o=h2_bundle.go -prefix=http2 -tags=!nethttpomithttp2 golang.org/x/net/http2 8 | 9 | package http 10 | 11 | import ( 12 | "io" 13 | "strconv" 14 | "strings" 15 | "time" 16 | "unicode/utf8" 17 | 18 | "golang.org/x/net/http/httpguts" 19 | ) 20 | 21 | // incomparable is a zero-width, non-comparable type. Adding it to a struct 22 | // makes that struct also non-comparable, and generally doesn't add 23 | // any size (as long as it's first). 24 | type incomparable [0]func() 25 | 26 | // maxInt64 is the effective "infinite" value for the Server and 27 | // Transport's byte-limiting readers. 28 | const maxInt64 = 1<<63 - 1 29 | 30 | // aLongTimeAgo is a non-zero time, far in the past, used for 31 | // immediate cancellation of network operations. 32 | var aLongTimeAgo = time.Unix(1, 0) 33 | 34 | // omitBundledHTTP2 is set by omithttp2.go when the nethttpomithttp2 35 | // build tag is set. That means h2_bundle.go isn't compiled in and we 36 | // shouldn't try to use it. 37 | var omitBundledHTTP2 bool 38 | 39 | // TODO(bradfitz): move common stuff here. The other files have accumulated 40 | // generic http stuff in random places. 41 | 42 | // contextKey is a value for use with context.WithValue. It's used as 43 | // a pointer so it fits in an interface{} without allocation. 44 | type contextKey struct { 45 | name string 46 | } 47 | 48 | func (k *contextKey) String() string { return "net/http context value " + k.name } 49 | 50 | // Given a string of the form "host", "host:port", or "[ipv6::address]:port", 51 | // return true if the string includes a port. 52 | func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") } 53 | 54 | // removeEmptyPort strips the empty port in ":port" to "" 55 | // as mandated by RFC 3986 Section 6.2.3. 56 | func removeEmptyPort(host string) string { 57 | if hasPort(host) { 58 | return strings.TrimSuffix(host, ":") 59 | } 60 | return host 61 | } 62 | 63 | func isNotToken(r rune) bool { 64 | return !httpguts.IsTokenRune(r) 65 | } 66 | 67 | // stringContainsCTLByte reports whether s contains any ASCII control character. 68 | func stringContainsCTLByte(s string) bool { 69 | for i := 0; i < len(s); i++ { 70 | b := s[i] 71 | if b < ' ' || b == 0x7f { 72 | return true 73 | } 74 | } 75 | return false 76 | } 77 | 78 | func hexEscapeNonASCII(s string) string { 79 | newLen := 0 80 | for i := 0; i < len(s); i++ { 81 | if s[i] >= utf8.RuneSelf { 82 | newLen += 3 83 | } else { 84 | newLen++ 85 | } 86 | } 87 | if newLen == len(s) { 88 | return s 89 | } 90 | b := make([]byte, 0, newLen) 91 | var pos int 92 | for i := 0; i < len(s); i++ { 93 | if s[i] >= utf8.RuneSelf { 94 | if pos < i { 95 | b = append(b, s[pos:i]...) 96 | } 97 | b = append(b, '%') 98 | b = strconv.AppendInt(b, int64(s[i]), 16) 99 | pos = i + 1 100 | } 101 | } 102 | if pos < len(s) { 103 | b = append(b, s[pos:]...) 104 | } 105 | return string(b) 106 | } 107 | 108 | // NoBody is an io.ReadCloser with no bytes. Read always returns EOF 109 | // and Close always returns nil. It can be used in an outgoing client 110 | // request to explicitly signal that a request has zero bytes. 111 | // An alternative, however, is to simply set Request.Body to nil. 112 | var NoBody = noBody{} 113 | 114 | type noBody struct{} 115 | 116 | func (noBody) Read([]byte) (int, error) { return 0, io.EOF } 117 | func (noBody) Close() error { return nil } 118 | func (noBody) WriteTo(io.Writer) (int64, error) { return 0, nil } 119 | 120 | var ( 121 | // verify that an io.Copy from NoBody won't require a buffer: 122 | _ io.WriterTo = NoBody 123 | _ io.ReadCloser = NoBody 124 | ) 125 | 126 | // PushOptions describes options for Pusher.Push. 127 | type PushOptions struct { 128 | // Method specifies the HTTP method for the promised request. 129 | // If set, it must be "GET" or "HEAD". Empty means "GET". 130 | Method string 131 | 132 | // Header specifies additional promised request headers. This cannot 133 | // include HTTP/2 pseudo header fields like ":path" and ":scheme", 134 | // which will be added automatically. 135 | Header Header 136 | } 137 | 138 | // Pusher is the interface implemented by ResponseWriters that support 139 | // HTTP/2 server push. For more background, see 140 | // https://tools.ietf.org/html/rfc7540#section-8.2. 141 | type Pusher interface { 142 | // Push initiates an HTTP/2 server push. This constructs a synthetic 143 | // request using the given target and options, serializes that request 144 | // into a PUSH_PROMISE frame, then dispatches that request using the 145 | // server's request handler. If opts is nil, default options are used. 146 | // 147 | // The target must either be an absolute path (like "/path") or an absolute 148 | // URL that contains a valid host and the same scheme as the parent request. 149 | // If the target is a path, it will inherit the scheme and host of the 150 | // parent request. 151 | // 152 | // The HTTP/2 spec disallows recursive pushes and cross-authority pushes. 153 | // Push may or may not detect these invalid pushes; however, invalid 154 | // pushes will be detected and canceled by conforming clients. 155 | // 156 | // Handlers that wish to push URL X should call Push before sending any 157 | // data that may trigger a request for URL X. This avoids a race where the 158 | // client issues requests for X before receiving the PUSH_PROMISE for X. 159 | // 160 | // Push will run in a separate goroutine making the order of arrival 161 | // non-deterministic. Any required synchronization needs to be implemented 162 | // by the caller. 163 | // 164 | // Push returns ErrNotSupported if the client has disabled push or if push 165 | // is not supported on the underlying connection. 166 | Push(target string, opts *PushOptions) error 167 | } 168 | -------------------------------------------------------------------------------- /http/httptest/httptest.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package httptest provides utilities for HTTP testing. 6 | package httptest 7 | 8 | import ( 9 | "bufio" 10 | "bytes" 11 | "io" 12 | "net/http" 13 | "strings" 14 | ) 15 | 16 | // NewRequest returns a new incoming server Request, suitable 17 | // for passing to an http.Handler for testing. 18 | // 19 | // The target is the RFC 7230 "request-target": it may be either a 20 | // path or an absolute URL. If target is an absolute URL, the host name 21 | // from the URL is used. Otherwise, "example.com" is used. 22 | // 23 | // The TLS field is set to a non-nil dummy value if target has scheme 24 | // "https". 25 | // 26 | // The Request.Proto is always HTTP/1.1. 27 | // 28 | // An empty method means "GET". 29 | // 30 | // The provided body may be nil. If the body is of type *bytes.Reader, 31 | // *strings.Reader, or *bytes.Buffer, the Request.ContentLength is 32 | // set. 33 | // 34 | // NewRequest panics on error for ease of use in testing, where a 35 | // panic is acceptable. 36 | // 37 | // To generate a client HTTP request instead of a server request, see 38 | // the NewRequest function in the net/http package. 39 | func NewRequest(method, target string, body io.Reader) *http.Request { 40 | if method == "" { 41 | method = "GET" 42 | } 43 | req, err := http.ReadRequest(bufio.NewReader(strings.NewReader(method + " " + target + " HTTP/1.0\r\n\r\n"))) 44 | if err != nil { 45 | panic("invalid NewRequest arguments; " + err.Error()) 46 | } 47 | 48 | // HTTP/1.0 was used above to avoid needing a Host field. Change it to 1.1 here. 49 | req.Proto = "HTTP/1.1" 50 | req.ProtoMinor = 1 51 | req.Close = false 52 | 53 | if body != nil { 54 | switch v := body.(type) { 55 | case *bytes.Buffer: 56 | req.ContentLength = int64(v.Len()) 57 | case *bytes.Reader: 58 | req.ContentLength = int64(v.Len()) 59 | case *strings.Reader: 60 | req.ContentLength = int64(v.Len()) 61 | default: 62 | req.ContentLength = -1 63 | } 64 | if rc, ok := body.(io.ReadCloser); ok { 65 | req.Body = rc 66 | } else { 67 | req.Body = io.NopCloser(body) 68 | } 69 | } 70 | 71 | // 192.0.2.0/24 is "TEST-NET" in RFC 5737 for use solely in 72 | // documentation and example source code and should not be 73 | // used publicly. 74 | req.RemoteAddr = "192.0.2.1:1234" 75 | 76 | if req.Host == "" { 77 | req.Host = "example.com" 78 | } 79 | 80 | if strings.HasPrefix(target, "https://") { 81 | // TINYGO: Removed https support 82 | panic("not implemented yet") 83 | } 84 | 85 | return req 86 | } 87 | -------------------------------------------------------------------------------- /http/httptest/recorder.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package httptest 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "io" 11 | "net/http" 12 | "net/textproto" 13 | "strconv" 14 | "strings" 15 | ) 16 | 17 | // ResponseRecorder is an implementation of http.ResponseWriter that 18 | // records its mutations for later inspection in tests. 19 | type ResponseRecorder struct { 20 | // Code is the HTTP response code set by WriteHeader. 21 | // 22 | // Note that if a Handler never calls WriteHeader or Write, 23 | // this might end up being 0, rather than the implicit 24 | // http.StatusOK. To get the implicit value, use the Result 25 | // method. 26 | Code int 27 | 28 | // HeaderMap contains the headers explicitly set by the Handler. 29 | // It is an internal detail. 30 | // 31 | // Deprecated: HeaderMap exists for historical compatibility 32 | // and should not be used. To access the headers returned by a handler, 33 | // use the Response.Header map as returned by the Result method. 34 | HeaderMap http.Header 35 | 36 | // Body is the buffer to which the Handler's Write calls are sent. 37 | // If nil, the Writes are silently discarded. 38 | Body *bytes.Buffer 39 | 40 | // Flushed is whether the Handler called Flush. 41 | Flushed bool 42 | 43 | result *http.Response // cache of Result's return value 44 | snapHeader http.Header // snapshot of HeaderMap at first Write 45 | wroteHeader bool 46 | } 47 | 48 | // NewRecorder returns an initialized ResponseRecorder. 49 | func NewRecorder() *ResponseRecorder { 50 | return &ResponseRecorder{ 51 | HeaderMap: make(http.Header), 52 | Body: new(bytes.Buffer), 53 | Code: 200, 54 | } 55 | } 56 | 57 | // DefaultRemoteAddr is the default remote address to return in RemoteAddr if 58 | // an explicit DefaultRemoteAddr isn't set on ResponseRecorder. 59 | const DefaultRemoteAddr = "1.2.3.4" 60 | 61 | // Header implements http.ResponseWriter. It returns the response 62 | // headers to mutate within a handler. To test the headers that were 63 | // written after a handler completes, use the Result method and see 64 | // the returned Response value's Header. 65 | func (rw *ResponseRecorder) Header() http.Header { 66 | m := rw.HeaderMap 67 | if m == nil { 68 | m = make(http.Header) 69 | rw.HeaderMap = m 70 | } 71 | return m 72 | } 73 | 74 | // writeHeader writes a header if it was not written yet and 75 | // detects Content-Type if needed. 76 | // 77 | // bytes or str are the beginning of the response body. 78 | // We pass both to avoid unnecessarily generate garbage 79 | // in rw.WriteString which was created for performance reasons. 80 | // Non-nil bytes win. 81 | func (rw *ResponseRecorder) writeHeader(b []byte, str string) { 82 | if rw.wroteHeader { 83 | return 84 | } 85 | if len(str) > 512 { 86 | str = str[:512] 87 | } 88 | 89 | m := rw.Header() 90 | 91 | _, hasType := m["Content-Type"] 92 | hasTE := m.Get("Transfer-Encoding") != "" 93 | if !hasType && !hasTE { 94 | if b == nil { 95 | b = []byte(str) 96 | } 97 | m.Set("Content-Type", http.DetectContentType(b)) 98 | } 99 | 100 | rw.WriteHeader(200) 101 | } 102 | 103 | // Write implements http.ResponseWriter. The data in buf is written to 104 | // rw.Body, if not nil. 105 | func (rw *ResponseRecorder) Write(buf []byte) (int, error) { 106 | rw.writeHeader(buf, "") 107 | if rw.Body != nil { 108 | rw.Body.Write(buf) 109 | } 110 | return len(buf), nil 111 | } 112 | 113 | // WriteString implements io.StringWriter. The data in str is written 114 | // to rw.Body, if not nil. 115 | func (rw *ResponseRecorder) WriteString(str string) (int, error) { 116 | rw.writeHeader(nil, str) 117 | if rw.Body != nil { 118 | rw.Body.WriteString(str) 119 | } 120 | return len(str), nil 121 | } 122 | 123 | func checkWriteHeaderCode(code int) { 124 | // Issue 22880: require valid WriteHeader status codes. 125 | // For now we only enforce that it's three digits. 126 | // In the future we might block things over 599 (600 and above aren't defined 127 | // at https://httpwg.org/specs/rfc7231.html#status.codes) 128 | // and we might block under 200 (once we have more mature 1xx support). 129 | // But for now any three digits. 130 | // 131 | // We used to send "HTTP/1.1 000 0" on the wire in responses but there's 132 | // no equivalent bogus thing we can realistically send in HTTP/2, 133 | // so we'll consistently panic instead and help people find their bugs 134 | // early. (We can't return an error from WriteHeader even if we wanted to.) 135 | if code < 100 || code > 999 { 136 | panic(fmt.Sprintf("invalid WriteHeader code %v", code)) 137 | } 138 | } 139 | 140 | // WriteHeader implements http.ResponseWriter. 141 | func (rw *ResponseRecorder) WriteHeader(code int) { 142 | if rw.wroteHeader { 143 | return 144 | } 145 | 146 | checkWriteHeaderCode(code) 147 | rw.Code = code 148 | rw.wroteHeader = true 149 | if rw.HeaderMap == nil { 150 | rw.HeaderMap = make(http.Header) 151 | } 152 | rw.snapHeader = rw.HeaderMap.Clone() 153 | } 154 | 155 | // Flush implements http.Flusher. To test whether Flush was 156 | // called, see rw.Flushed. 157 | func (rw *ResponseRecorder) Flush() { 158 | if !rw.wroteHeader { 159 | rw.WriteHeader(200) 160 | } 161 | rw.Flushed = true 162 | } 163 | 164 | // Result returns the response generated by the handler. 165 | // 166 | // The returned Response will have at least its StatusCode, 167 | // Header, Body, and optionally Trailer populated. 168 | // More fields may be populated in the future, so callers should 169 | // not DeepEqual the result in tests. 170 | // 171 | // The Response.Header is a snapshot of the headers at the time of the 172 | // first write call, or at the time of this call, if the handler never 173 | // did a write. 174 | // 175 | // The Response.Body is guaranteed to be non-nil and Body.Read call is 176 | // guaranteed to not return any error other than io.EOF. 177 | // 178 | // Result must only be called after the handler has finished running. 179 | func (rw *ResponseRecorder) Result() *http.Response { 180 | if rw.result != nil { 181 | return rw.result 182 | } 183 | if rw.snapHeader == nil { 184 | rw.snapHeader = rw.HeaderMap.Clone() 185 | } 186 | res := &http.Response{ 187 | Proto: "HTTP/1.1", 188 | ProtoMajor: 1, 189 | ProtoMinor: 1, 190 | StatusCode: rw.Code, 191 | Header: rw.snapHeader, 192 | } 193 | rw.result = res 194 | if res.StatusCode == 0 { 195 | res.StatusCode = 200 196 | } 197 | res.Status = fmt.Sprintf("%03d %s", res.StatusCode, http.StatusText(res.StatusCode)) 198 | if rw.Body != nil { 199 | res.Body = io.NopCloser(bytes.NewReader(rw.Body.Bytes())) 200 | } else { 201 | res.Body = http.NoBody 202 | } 203 | res.ContentLength = parseContentLength(res.Header.Get("Content-Length")) 204 | 205 | if trailers, ok := rw.snapHeader["Trailer"]; ok { 206 | res.Trailer = make(http.Header, len(trailers)) 207 | for _, k := range trailers { 208 | for _, k := range strings.Split(k, ",") { 209 | k = http.CanonicalHeaderKey(textproto.TrimString(k)) 210 | if !validTrailerHeader(k) { 211 | // Ignore since forbidden by RFC 7230, section 4.1.2. 212 | continue 213 | } 214 | vv, ok := rw.HeaderMap[k] 215 | if !ok { 216 | continue 217 | } 218 | vv2 := make([]string, len(vv)) 219 | copy(vv2, vv) 220 | res.Trailer[k] = vv2 221 | } 222 | } 223 | } 224 | for k, vv := range rw.HeaderMap { 225 | if !strings.HasPrefix(k, http.TrailerPrefix) { 226 | continue 227 | } 228 | if res.Trailer == nil { 229 | res.Trailer = make(http.Header) 230 | } 231 | for _, v := range vv { 232 | res.Trailer.Add(strings.TrimPrefix(k, http.TrailerPrefix), v) 233 | } 234 | } 235 | return res 236 | } 237 | 238 | // parseContentLength trims whitespace from s and returns -1 if no value 239 | // is set, or the value if it's >= 0. 240 | // 241 | // This a modified version of same function found in net/http/transfer.go. This 242 | // one just ignores an invalid header. 243 | func parseContentLength(cl string) int64 { 244 | cl = textproto.TrimString(cl) 245 | if cl == "" { 246 | return -1 247 | } 248 | n, err := strconv.ParseUint(cl, 10, 63) 249 | if err != nil { 250 | return -1 251 | } 252 | return int64(n) 253 | } 254 | 255 | // ValidTrailerHeader reports whether name is a valid header field name to appear 256 | // in trailers. 257 | // See RFC 7230, Section 4.1.2 258 | // Copied from golang.org/x/net/http/httpguts 259 | func validTrailerHeader(name string) bool { 260 | name = textproto.CanonicalMIMEHeaderKey(name) 261 | if strings.HasPrefix(name, "If-") || badTrailer[name] { 262 | return false 263 | } 264 | return true 265 | } 266 | 267 | var badTrailer = map[string]bool{ 268 | "Authorization": true, 269 | "Cache-Control": true, 270 | "Connection": true, 271 | "Content-Encoding": true, 272 | "Content-Length": true, 273 | "Content-Range": true, 274 | "Content-Type": true, 275 | "Expect": true, 276 | "Host": true, 277 | "Keep-Alive": true, 278 | "Max-Forwards": true, 279 | "Pragma": true, 280 | "Proxy-Authenticate": true, 281 | "Proxy-Authorization": true, 282 | "Proxy-Connection": true, 283 | "Range": true, 284 | "Realm": true, 285 | "Te": true, 286 | "Trailer": true, 287 | "Transfer-Encoding": true, 288 | "Www-Authenticate": true, 289 | } 290 | -------------------------------------------------------------------------------- /http/httptest/server.go: -------------------------------------------------------------------------------- 1 | // TINYGO: Removed https support 2 | // TINYGO: Removed closeIdleTransport interface 3 | 4 | // Copyright 2011 The Go Authors. All rights reserved. 5 | // Use of this source code is governed by a BSD-style 6 | // license that can be found in the LICENSE file. 7 | 8 | // Implementation of Server 9 | 10 | package httptest 11 | 12 | import ( 13 | "flag" 14 | "fmt" 15 | "log" 16 | "net" 17 | "net/http" 18 | "os" 19 | "strings" 20 | "sync" 21 | "time" 22 | ) 23 | 24 | // A Server is an HTTP server listening on a system-chosen port on the 25 | // local loopback interface, for use in end-to-end HTTP tests. 26 | type Server struct { 27 | URL string // base URL of form http://ipaddr:port with no trailing slash 28 | Listener net.Listener 29 | 30 | // EnableHTTP2 controls whether HTTP/2 is enabled 31 | // on the server. It must be set between calling 32 | // NewUnstartedServer and calling Server.StartTLS. 33 | EnableHTTP2 bool 34 | 35 | // Config may be changed after calling NewUnstartedServer and 36 | // before Start or StartTLS. 37 | Config *http.Server 38 | 39 | // TINYGO: Removed TLS and certificate properties. 40 | 41 | // wg counts the number of outstanding HTTP requests on this server. 42 | // Close blocks until all requests are finished. 43 | wg sync.WaitGroup 44 | 45 | mu sync.Mutex // guards closed and conns 46 | closed bool 47 | conns map[net.Conn]http.ConnState // except terminal states 48 | 49 | // client is configured for use with the server. 50 | // Its transport is automatically closed when Close is called. 51 | client *http.Client 52 | } 53 | 54 | func newLocalListener() net.Listener { 55 | if serveFlag != "" { 56 | l, err := net.Listen("tcp", serveFlag) 57 | if err != nil { 58 | panic(fmt.Sprintf("httptest: failed to listen on %v: %v", serveFlag, err)) 59 | } 60 | return l 61 | } 62 | l, err := net.Listen("tcp", "127.0.0.1:0") 63 | if err != nil { 64 | if l, err = net.Listen("tcp6", "[::1]:0"); err != nil { 65 | panic(fmt.Sprintf("httptest: failed to listen on a port: %v", err)) 66 | } 67 | } 68 | return l 69 | } 70 | 71 | // When debugging a particular http server-based test, 72 | // this flag lets you run 73 | // 74 | // go test -run=BrokenTest -httptest.serve=127.0.0.1:8000 75 | // 76 | // to start the broken server so you can interact with it manually. 77 | // We only register this flag if it looks like the caller knows about it 78 | // and is trying to use it as we don't want to pollute flags and this 79 | // isn't really part of our API. Don't depend on this. 80 | var serveFlag string 81 | 82 | func init() { 83 | if strSliceContainsPrefix(os.Args, "-httptest.serve=") || strSliceContainsPrefix(os.Args, "--httptest.serve=") { 84 | flag.StringVar(&serveFlag, "httptest.serve", "", "if non-empty, httptest.NewServer serves on this address and blocks.") 85 | } 86 | } 87 | 88 | func strSliceContainsPrefix(v []string, pre string) bool { 89 | for _, s := range v { 90 | if strings.HasPrefix(s, pre) { 91 | return true 92 | } 93 | } 94 | return false 95 | } 96 | 97 | // NewServer starts and returns a new Server. 98 | // The caller should call Close when finished, to shut it down. 99 | func NewServer(handler http.Handler) *Server { 100 | ts := NewUnstartedServer(handler) 101 | ts.Start() 102 | return ts 103 | } 104 | 105 | // NewUnstartedServer returns a new Server but doesn't start it. 106 | // 107 | // After changing its configuration, the caller should call Start or 108 | // StartTLS. 109 | // 110 | // The caller should call Close when finished, to shut it down. 111 | func NewUnstartedServer(handler http.Handler) *Server { 112 | return &Server{ 113 | Listener: newLocalListener(), 114 | Config: &http.Server{Handler: handler}, 115 | } 116 | } 117 | 118 | // Start starts a server from NewUnstartedServer. 119 | func (s *Server) Start() { 120 | if s.URL != "" { 121 | panic("Server already started") 122 | } 123 | if s.client == nil { 124 | // TINYGO: Removed transport 125 | s.client = &http.Client{} 126 | } 127 | s.URL = "http://" + s.Listener.Addr().String() 128 | s.wrap() 129 | s.goServe() 130 | if serveFlag != "" { 131 | fmt.Fprintln(os.Stderr, "httptest: serving on", s.URL) 132 | select {} 133 | } 134 | } 135 | 136 | // Close shuts down the server and blocks until all outstanding 137 | // requests on this server have completed. 138 | func (s *Server) Close() { 139 | s.mu.Lock() 140 | if !s.closed { 141 | s.closed = true 142 | s.Listener.Close() 143 | s.Config.SetKeepAlivesEnabled(false) 144 | for c, st := range s.conns { 145 | // Force-close any idle connections (those between 146 | // requests) and new connections (those which connected 147 | // but never sent a request). StateNew connections are 148 | // super rare and have only been seen (in 149 | // previously-flaky tests) in the case of 150 | // socket-late-binding races from the http Client 151 | // dialing this server and then getting an idle 152 | // connection before the dial completed. There is thus 153 | // a connected connection in StateNew with no 154 | // associated Request. We only close StateIdle and 155 | // StateNew because they're not doing anything. It's 156 | // possible StateNew is about to do something in a few 157 | // milliseconds, but a previous CL to check again in a 158 | // few milliseconds wasn't liked (early versions of 159 | // https://golang.org/cl/15151) so now we just 160 | // forcefully close StateNew. The docs for Server.Close say 161 | // we wait for "outstanding requests", so we don't close things 162 | // in StateActive. 163 | if st == http.StateIdle || st == http.StateNew { 164 | s.closeConn(c) 165 | } 166 | } 167 | // If this server doesn't shut down in 5 seconds, tell the user why. 168 | t := time.AfterFunc(5*time.Second, s.logCloseHangDebugInfo) 169 | defer t.Stop() 170 | } 171 | s.mu.Unlock() 172 | 173 | // TINYGO: Removed idle connection closing 174 | 175 | s.wg.Wait() 176 | } 177 | 178 | func (s *Server) logCloseHangDebugInfo() { 179 | s.mu.Lock() 180 | defer s.mu.Unlock() 181 | var buf strings.Builder 182 | buf.WriteString("httptest.Server blocked in Close after 5 seconds, waiting for connections:\n") 183 | for c, st := range s.conns { 184 | fmt.Fprintf(&buf, " %T %p %v in state %v\n", c, c, c.RemoteAddr(), st) 185 | } 186 | log.Print(buf.String()) 187 | } 188 | 189 | // CloseClientConnections closes any open HTTP connections to the test Server. 190 | func (s *Server) CloseClientConnections() { 191 | s.mu.Lock() 192 | nconn := len(s.conns) 193 | ch := make(chan struct{}, nconn) 194 | for c := range s.conns { 195 | go s.closeConnChan(c, ch) 196 | } 197 | s.mu.Unlock() 198 | 199 | // Wait for outstanding closes to finish. 200 | // 201 | // Out of paranoia for making a late change in Go 1.6, we 202 | // bound how long this can wait, since golang.org/issue/14291 203 | // isn't fully understood yet. At least this should only be used 204 | // in tests. 205 | timer := time.NewTimer(5 * time.Second) 206 | defer timer.Stop() 207 | for i := 0; i < nconn; i++ { 208 | select { 209 | case <-ch: 210 | case <-timer.C: 211 | // Too slow. Give up. 212 | return 213 | } 214 | } 215 | } 216 | 217 | // Client returns an HTTP client configured for making requests to the server. 218 | // It is configured to trust the server's TLS test certificate and will 219 | // close its idle connections on Server.Close. 220 | func (s *Server) Client() *http.Client { 221 | return s.client 222 | } 223 | 224 | func (s *Server) goServe() { 225 | s.wg.Add(1) 226 | go func() { 227 | defer s.wg.Done() 228 | s.Config.Serve(s.Listener) 229 | }() 230 | } 231 | 232 | // wrap installs the connection state-tracking hook to know which 233 | // connections are idle. 234 | func (s *Server) wrap() { 235 | oldHook := s.Config.ConnState 236 | s.Config.ConnState = func(c net.Conn, cs http.ConnState) { 237 | s.mu.Lock() 238 | defer s.mu.Unlock() 239 | 240 | switch cs { 241 | case http.StateNew: 242 | if _, exists := s.conns[c]; exists { 243 | panic("invalid state transition") 244 | } 245 | if s.conns == nil { 246 | s.conns = make(map[net.Conn]http.ConnState) 247 | } 248 | // Add c to the set of tracked conns and increment it to the 249 | // waitgroup. 250 | s.wg.Add(1) 251 | s.conns[c] = cs 252 | if s.closed { 253 | // Probably just a socket-late-binding dial from 254 | // the default transport that lost the race (and 255 | // thus this connection is now idle and will 256 | // never be used). 257 | s.closeConn(c) 258 | } 259 | case http.StateActive: 260 | if oldState, ok := s.conns[c]; ok { 261 | if oldState != http.StateNew && oldState != http.StateIdle { 262 | panic("invalid state transition") 263 | } 264 | s.conns[c] = cs 265 | } 266 | case http.StateIdle: 267 | if oldState, ok := s.conns[c]; ok { 268 | if oldState != http.StateActive { 269 | panic("invalid state transition") 270 | } 271 | s.conns[c] = cs 272 | } 273 | if s.closed { 274 | s.closeConn(c) 275 | } 276 | case http.StateHijacked, http.StateClosed: 277 | // Remove c from the set of tracked conns and decrement it from the 278 | // waitgroup, unless it was previously removed. 279 | if _, ok := s.conns[c]; ok { 280 | delete(s.conns, c) 281 | // Keep Close from returning until the user's ConnState hook 282 | // (if any) finishes. 283 | defer s.wg.Done() 284 | } 285 | } 286 | if oldHook != nil { 287 | oldHook(c, cs) 288 | } 289 | } 290 | } 291 | 292 | // closeConn closes c. 293 | // s.mu must be held. 294 | func (s *Server) closeConn(c net.Conn) { s.closeConnChan(c, nil) } 295 | 296 | // closeConnChan is like closeConn, but takes an optional channel to receive a value 297 | // when the goroutine closing c is done. 298 | func (s *Server) closeConnChan(c net.Conn, done chan<- struct{}) { 299 | c.Close() 300 | if done != nil { 301 | done <- struct{}{} 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /http/internal/ascii/print.go: -------------------------------------------------------------------------------- 1 | // TINYGO: The following is copied from Go 1.21.4 official implementation. 2 | 3 | // Copyright 2021 The Go Authors. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | 7 | package ascii 8 | 9 | import ( 10 | "strings" 11 | "unicode" 12 | ) 13 | 14 | // EqualFold is strings.EqualFold, ASCII only. It reports whether s and t 15 | // are equal, ASCII-case-insensitively. 16 | func EqualFold(s, t string) bool { 17 | if len(s) != len(t) { 18 | return false 19 | } 20 | for i := 0; i < len(s); i++ { 21 | if lower(s[i]) != lower(t[i]) { 22 | return false 23 | } 24 | } 25 | return true 26 | } 27 | 28 | // lower returns the ASCII lowercase version of b. 29 | func lower(b byte) byte { 30 | if 'A' <= b && b <= 'Z' { 31 | return b + ('a' - 'A') 32 | } 33 | return b 34 | } 35 | 36 | // IsPrint returns whether s is ASCII and printable according to 37 | // https://tools.ietf.org/html/rfc20#section-4.2. 38 | func IsPrint(s string) bool { 39 | for i := 0; i < len(s); i++ { 40 | if s[i] < ' ' || s[i] > '~' { 41 | return false 42 | } 43 | } 44 | return true 45 | } 46 | 47 | // Is returns whether s is ASCII. 48 | func Is(s string) bool { 49 | for i := 0; i < len(s); i++ { 50 | if s[i] > unicode.MaxASCII { 51 | return false 52 | } 53 | } 54 | return true 55 | } 56 | 57 | // ToLower returns the lowercase version of s if s is ASCII and printable. 58 | func ToLower(s string) (lower string, ok bool) { 59 | if !IsPrint(s) { 60 | return "", false 61 | } 62 | return strings.ToLower(s), true 63 | } 64 | -------------------------------------------------------------------------------- /http/internal/ascii/print_test.go: -------------------------------------------------------------------------------- 1 | // TINYGO: The following is copied from Go 1.21.4 official implementation. 2 | 3 | // Copyright 2021 The Go Authors. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | 7 | package ascii 8 | 9 | import "testing" 10 | 11 | func TestEqualFold(t *testing.T) { 12 | var tests = []struct { 13 | name string 14 | a, b string 15 | want bool 16 | }{ 17 | { 18 | name: "empty", 19 | want: true, 20 | }, 21 | { 22 | name: "simple match", 23 | a: "CHUNKED", 24 | b: "chunked", 25 | want: true, 26 | }, 27 | { 28 | name: "same string", 29 | a: "chunked", 30 | b: "chunked", 31 | want: true, 32 | }, 33 | { 34 | name: "Unicode Kelvin symbol", 35 | a: "chunKed", // This "K" is 'KELVIN SIGN' (\u212A) 36 | b: "chunked", 37 | want: false, 38 | }, 39 | } 40 | for _, tt := range tests { 41 | t.Run(tt.name, func(t *testing.T) { 42 | if got := EqualFold(tt.a, tt.b); got != tt.want { 43 | t.Errorf("AsciiEqualFold(%q,%q): got %v want %v", tt.a, tt.b, got, tt.want) 44 | } 45 | }) 46 | } 47 | } 48 | 49 | func TestIsPrint(t *testing.T) { 50 | var tests = []struct { 51 | name string 52 | in string 53 | want bool 54 | }{ 55 | { 56 | name: "empty", 57 | want: true, 58 | }, 59 | { 60 | name: "ASCII low", 61 | in: "This is a space: ' '", 62 | want: true, 63 | }, 64 | { 65 | name: "ASCII high", 66 | in: "This is a tilde: '~'", 67 | want: true, 68 | }, 69 | { 70 | name: "ASCII low non-print", 71 | in: "This is a unit separator: \x1F", 72 | want: false, 73 | }, 74 | { 75 | name: "Ascii high non-print", 76 | in: "This is a Delete: \x7F", 77 | want: false, 78 | }, 79 | { 80 | name: "Unicode letter", 81 | in: "Today it's 280K outside: it's freezing!", // This "K" is 'KELVIN SIGN' (\u212A) 82 | want: false, 83 | }, 84 | { 85 | name: "Unicode emoji", 86 | in: "Gophers like 🧀", 87 | want: false, 88 | }, 89 | } 90 | for _, tt := range tests { 91 | t.Run(tt.name, func(t *testing.T) { 92 | if got := IsPrint(tt.in); got != tt.want { 93 | t.Errorf("IsASCIIPrint(%q): got %v want %v", tt.in, got, tt.want) 94 | } 95 | }) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /http/internal/chunked.go: -------------------------------------------------------------------------------- 1 | // TINYGO: The following is copied from Go 1.21.4 official implementation. 2 | 3 | // Copyright 2009 The Go Authors. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | 7 | // The wire protocol for HTTP's "chunked" Transfer-Encoding. 8 | 9 | // Package internal contains HTTP internals shared by net/http and 10 | // net/http/httputil. 11 | package internal 12 | 13 | import ( 14 | "bufio" 15 | "bytes" 16 | "errors" 17 | "fmt" 18 | "io" 19 | ) 20 | 21 | const maxLineLength = 4096 // assumed <= bufio.defaultBufSize 22 | 23 | var ErrLineTooLong = errors.New("header line too long") 24 | 25 | // NewChunkedReader returns a new chunkedReader that translates the data read from r 26 | // out of HTTP "chunked" format before returning it. 27 | // The chunkedReader returns io.EOF when the final 0-length chunk is read. 28 | // 29 | // NewChunkedReader is not needed by normal applications. The http package 30 | // automatically decodes chunking when reading response bodies. 31 | func NewChunkedReader(r io.Reader) io.Reader { 32 | br, ok := r.(*bufio.Reader) 33 | if !ok { 34 | br = bufio.NewReader(r) 35 | } 36 | return &chunkedReader{r: br} 37 | } 38 | 39 | type chunkedReader struct { 40 | r *bufio.Reader 41 | n uint64 // unread bytes in chunk 42 | err error 43 | buf [2]byte 44 | checkEnd bool // whether need to check for \r\n chunk footer 45 | } 46 | 47 | func (cr *chunkedReader) beginChunk() { 48 | // chunk-size CRLF 49 | var line []byte 50 | line, cr.err = readChunkLine(cr.r) 51 | if cr.err != nil { 52 | return 53 | } 54 | cr.n, cr.err = parseHexUint(line) 55 | if cr.err != nil { 56 | return 57 | } 58 | if cr.n == 0 { 59 | cr.err = io.EOF 60 | } 61 | } 62 | 63 | func (cr *chunkedReader) chunkHeaderAvailable() bool { 64 | n := cr.r.Buffered() 65 | if n > 0 { 66 | peek, _ := cr.r.Peek(n) 67 | return bytes.IndexByte(peek, '\n') >= 0 68 | } 69 | return false 70 | } 71 | 72 | func (cr *chunkedReader) Read(b []uint8) (n int, err error) { 73 | for cr.err == nil { 74 | if cr.checkEnd { 75 | if n > 0 && cr.r.Buffered() < 2 { 76 | // We have some data. Return early (per the io.Reader 77 | // contract) instead of potentially blocking while 78 | // reading more. 79 | break 80 | } 81 | if _, cr.err = io.ReadFull(cr.r, cr.buf[:2]); cr.err == nil { 82 | if string(cr.buf[:]) != "\r\n" { 83 | cr.err = errors.New("malformed chunked encoding") 84 | break 85 | } 86 | } else { 87 | if cr.err == io.EOF { 88 | cr.err = io.ErrUnexpectedEOF 89 | } 90 | break 91 | } 92 | cr.checkEnd = false 93 | } 94 | if cr.n == 0 { 95 | if n > 0 && !cr.chunkHeaderAvailable() { 96 | // We've read enough. Don't potentially block 97 | // reading a new chunk header. 98 | break 99 | } 100 | cr.beginChunk() 101 | continue 102 | } 103 | if len(b) == 0 { 104 | break 105 | } 106 | rbuf := b 107 | if uint64(len(rbuf)) > cr.n { 108 | rbuf = rbuf[:cr.n] 109 | } 110 | var n0 int 111 | n0, cr.err = cr.r.Read(rbuf) 112 | n += n0 113 | b = b[n0:] 114 | cr.n -= uint64(n0) 115 | // If we're at the end of a chunk, read the next two 116 | // bytes to verify they are "\r\n". 117 | if cr.n == 0 && cr.err == nil { 118 | cr.checkEnd = true 119 | } else if cr.err == io.EOF { 120 | cr.err = io.ErrUnexpectedEOF 121 | } 122 | } 123 | return n, cr.err 124 | } 125 | 126 | // Read a line of bytes (up to \n) from b. 127 | // Give up if the line exceeds maxLineLength. 128 | // The returned bytes are owned by the bufio.Reader 129 | // so they are only valid until the next bufio read. 130 | func readChunkLine(b *bufio.Reader) ([]byte, error) { 131 | p, err := b.ReadSlice('\n') 132 | if err != nil { 133 | // We always know when EOF is coming. 134 | // If the caller asked for a line, there should be a line. 135 | if err == io.EOF { 136 | err = io.ErrUnexpectedEOF 137 | } else if err == bufio.ErrBufferFull { 138 | err = ErrLineTooLong 139 | } 140 | return nil, err 141 | } 142 | if len(p) >= maxLineLength { 143 | return nil, ErrLineTooLong 144 | } 145 | p = trimTrailingWhitespace(p) 146 | p, err = removeChunkExtension(p) 147 | if err != nil { 148 | return nil, err 149 | } 150 | return p, nil 151 | } 152 | 153 | func trimTrailingWhitespace(b []byte) []byte { 154 | for len(b) > 0 && isASCIISpace(b[len(b)-1]) { 155 | b = b[:len(b)-1] 156 | } 157 | return b 158 | } 159 | 160 | func isASCIISpace(b byte) bool { 161 | return b == ' ' || b == '\t' || b == '\n' || b == '\r' 162 | } 163 | 164 | var semi = []byte(";") 165 | 166 | // removeChunkExtension removes any chunk-extension from p. 167 | // For example, 168 | // 169 | // "0" => "0" 170 | // "0;token" => "0" 171 | // "0;token=val" => "0" 172 | // `0;token="quoted string"` => "0" 173 | func removeChunkExtension(p []byte) ([]byte, error) { 174 | p, _, _ = bytes.Cut(p, semi) 175 | // TODO: care about exact syntax of chunk extensions? We're 176 | // ignoring and stripping them anyway. For now just never 177 | // return an error. 178 | return p, nil 179 | } 180 | 181 | // NewChunkedWriter returns a new chunkedWriter that translates writes into HTTP 182 | // "chunked" format before writing them to w. Closing the returned chunkedWriter 183 | // sends the final 0-length chunk that marks the end of the stream but does 184 | // not send the final CRLF that appears after trailers; trailers and the last 185 | // CRLF must be written separately. 186 | // 187 | // NewChunkedWriter is not needed by normal applications. The http 188 | // package adds chunking automatically if handlers don't set a 189 | // Content-Length header. Using newChunkedWriter inside a handler 190 | // would result in double chunking or chunking with a Content-Length 191 | // length, both of which are wrong. 192 | func NewChunkedWriter(w io.Writer) io.WriteCloser { 193 | return &chunkedWriter{w} 194 | } 195 | 196 | // Writing to chunkedWriter translates to writing in HTTP chunked Transfer 197 | // Encoding wire format to the underlying Wire chunkedWriter. 198 | type chunkedWriter struct { 199 | Wire io.Writer 200 | } 201 | 202 | // Write the contents of data as one chunk to Wire. 203 | // NOTE: Note that the corresponding chunk-writing procedure in Conn.Write has 204 | // a bug since it does not check for success of io.WriteString 205 | func (cw *chunkedWriter) Write(data []byte) (n int, err error) { 206 | 207 | // Don't send 0-length data. It looks like EOF for chunked encoding. 208 | if len(data) == 0 { 209 | return 0, nil 210 | } 211 | 212 | if _, err = fmt.Fprintf(cw.Wire, "%x\r\n", len(data)); err != nil { 213 | return 0, err 214 | } 215 | if n, err = cw.Wire.Write(data); err != nil { 216 | return 217 | } 218 | if n != len(data) { 219 | err = io.ErrShortWrite 220 | return 221 | } 222 | if _, err = io.WriteString(cw.Wire, "\r\n"); err != nil { 223 | return 224 | } 225 | if bw, ok := cw.Wire.(*FlushAfterChunkWriter); ok { 226 | err = bw.Flush() 227 | } 228 | return 229 | } 230 | 231 | func (cw *chunkedWriter) Close() error { 232 | _, err := io.WriteString(cw.Wire, "0\r\n") 233 | return err 234 | } 235 | 236 | // FlushAfterChunkWriter signals from the caller of NewChunkedWriter 237 | // that each chunk should be followed by a flush. It is used by the 238 | // http.Transport code to keep the buffering behavior for headers and 239 | // trailers, but flush out chunks aggressively in the middle for 240 | // request bodies which may be generated slowly. See Issue 6574. 241 | type FlushAfterChunkWriter struct { 242 | *bufio.Writer 243 | } 244 | 245 | func parseHexUint(v []byte) (n uint64, err error) { 246 | for i, b := range v { 247 | switch { 248 | case '0' <= b && b <= '9': 249 | b = b - '0' 250 | case 'a' <= b && b <= 'f': 251 | b = b - 'a' + 10 252 | case 'A' <= b && b <= 'F': 253 | b = b - 'A' + 10 254 | default: 255 | return 0, errors.New("invalid byte in chunk length") 256 | } 257 | if i == 16 { 258 | return 0, errors.New("http chunk length too large") 259 | } 260 | n <<= 4 261 | n |= uint64(b) 262 | } 263 | return 264 | } 265 | -------------------------------------------------------------------------------- /http/internal/chunked_test.go: -------------------------------------------------------------------------------- 1 | // TINYGO: The following is copied from Go 1.21.4 official implementation. 2 | 3 | // Copyright 2011 The Go Authors. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | 7 | package internal 8 | 9 | import ( 10 | "bufio" 11 | "bytes" 12 | "fmt" 13 | "io" 14 | "strings" 15 | "testing" 16 | "testing/iotest" 17 | ) 18 | 19 | func TestChunk(t *testing.T) { 20 | var b bytes.Buffer 21 | 22 | w := NewChunkedWriter(&b) 23 | const chunk1 = "hello, " 24 | const chunk2 = "world! 0123456789abcdef" 25 | w.Write([]byte(chunk1)) 26 | w.Write([]byte(chunk2)) 27 | w.Close() 28 | 29 | if g, e := b.String(), "7\r\nhello, \r\n17\r\nworld! 0123456789abcdef\r\n0\r\n"; g != e { 30 | t.Fatalf("chunk writer wrote %q; want %q", g, e) 31 | } 32 | 33 | r := NewChunkedReader(&b) 34 | data, err := io.ReadAll(r) 35 | if err != nil { 36 | t.Logf(`data: "%s"`, data) 37 | t.Fatalf("ReadAll from reader: %v", err) 38 | } 39 | if g, e := string(data), chunk1+chunk2; g != e { 40 | t.Errorf("chunk reader read %q; want %q", g, e) 41 | } 42 | } 43 | 44 | func TestChunkReadMultiple(t *testing.T) { 45 | // Bunch of small chunks, all read together. 46 | { 47 | var b bytes.Buffer 48 | w := NewChunkedWriter(&b) 49 | w.Write([]byte("foo")) 50 | w.Write([]byte("bar")) 51 | w.Close() 52 | 53 | r := NewChunkedReader(&b) 54 | buf := make([]byte, 10) 55 | n, err := r.Read(buf) 56 | if n != 6 || err != io.EOF { 57 | t.Errorf("Read = %d, %v; want 6, EOF", n, err) 58 | } 59 | buf = buf[:n] 60 | if string(buf) != "foobar" { 61 | t.Errorf("Read = %q; want %q", buf, "foobar") 62 | } 63 | } 64 | 65 | // One big chunk followed by a little chunk, but the small bufio.Reader size 66 | // should prevent the second chunk header from being read. 67 | { 68 | var b bytes.Buffer 69 | w := NewChunkedWriter(&b) 70 | // fillBufChunk is 11 bytes + 3 bytes header + 2 bytes footer = 16 bytes, 71 | // the same as the bufio ReaderSize below (the minimum), so even 72 | // though we're going to try to Read with a buffer larger enough to also 73 | // receive "foo", the second chunk header won't be read yet. 74 | const fillBufChunk = "0123456789a" 75 | const shortChunk = "foo" 76 | w.Write([]byte(fillBufChunk)) 77 | w.Write([]byte(shortChunk)) 78 | w.Close() 79 | 80 | r := NewChunkedReader(bufio.NewReaderSize(&b, 16)) 81 | buf := make([]byte, len(fillBufChunk)+len(shortChunk)) 82 | n, err := r.Read(buf) 83 | if n != len(fillBufChunk) || err != nil { 84 | t.Errorf("Read = %d, %v; want %d, nil", n, err, len(fillBufChunk)) 85 | } 86 | buf = buf[:n] 87 | if string(buf) != fillBufChunk { 88 | t.Errorf("Read = %q; want %q", buf, fillBufChunk) 89 | } 90 | 91 | n, err = r.Read(buf) 92 | if n != len(shortChunk) || err != io.EOF { 93 | t.Errorf("Read = %d, %v; want %d, EOF", n, err, len(shortChunk)) 94 | } 95 | } 96 | 97 | // And test that we see an EOF chunk, even though our buffer is already full: 98 | { 99 | r := NewChunkedReader(bufio.NewReader(strings.NewReader("3\r\nfoo\r\n0\r\n"))) 100 | buf := make([]byte, 3) 101 | n, err := r.Read(buf) 102 | if n != 3 || err != io.EOF { 103 | t.Errorf("Read = %d, %v; want 3, EOF", n, err) 104 | } 105 | if string(buf) != "foo" { 106 | t.Errorf("buf = %q; want foo", buf) 107 | } 108 | } 109 | } 110 | 111 | func TestChunkReaderAllocs(t *testing.T) { 112 | if testing.Short() { 113 | t.Skip("skipping in short mode") 114 | } 115 | var buf bytes.Buffer 116 | w := NewChunkedWriter(&buf) 117 | a, b, c := []byte("aaaaaa"), []byte("bbbbbbbbbbbb"), []byte("cccccccccccccccccccccccc") 118 | w.Write(a) 119 | w.Write(b) 120 | w.Write(c) 121 | w.Close() 122 | 123 | readBuf := make([]byte, len(a)+len(b)+len(c)+1) 124 | byter := bytes.NewReader(buf.Bytes()) 125 | bufr := bufio.NewReader(byter) 126 | mallocs := testing.AllocsPerRun(100, func() { 127 | byter.Seek(0, io.SeekStart) 128 | bufr.Reset(byter) 129 | r := NewChunkedReader(bufr) 130 | n, err := io.ReadFull(r, readBuf) 131 | if n != len(readBuf)-1 { 132 | t.Fatalf("read %d bytes; want %d", n, len(readBuf)-1) 133 | } 134 | if err != io.ErrUnexpectedEOF { 135 | t.Fatalf("read error = %v; want ErrUnexpectedEOF", err) 136 | } 137 | }) 138 | if mallocs > 1.5 { 139 | t.Errorf("mallocs = %v; want 1", mallocs) 140 | } 141 | } 142 | 143 | func TestParseHexUint(t *testing.T) { 144 | type testCase struct { 145 | in string 146 | want uint64 147 | wantErr string 148 | } 149 | tests := []testCase{ 150 | {"x", 0, "invalid byte in chunk length"}, 151 | {"0000000000000000", 0, ""}, 152 | {"0000000000000001", 1, ""}, 153 | {"ffffffffffffffff", 1<<64 - 1, ""}, 154 | {"000000000000bogus", 0, "invalid byte in chunk length"}, 155 | {"00000000000000000", 0, "http chunk length too large"}, // could accept if we wanted 156 | {"10000000000000000", 0, "http chunk length too large"}, 157 | {"00000000000000001", 0, "http chunk length too large"}, // could accept if we wanted 158 | } 159 | for i := uint64(0); i <= 1234; i++ { 160 | tests = append(tests, testCase{in: fmt.Sprintf("%x", i), want: i}) 161 | } 162 | for _, tt := range tests { 163 | got, err := parseHexUint([]byte(tt.in)) 164 | if tt.wantErr != "" { 165 | if !strings.Contains(fmt.Sprint(err), tt.wantErr) { 166 | t.Errorf("parseHexUint(%q) = %v, %v; want error %q", tt.in, got, err, tt.wantErr) 167 | } 168 | } else { 169 | if err != nil || got != tt.want { 170 | t.Errorf("parseHexUint(%q) = %v, %v; want %v", tt.in, got, err, tt.want) 171 | } 172 | } 173 | } 174 | } 175 | 176 | func TestChunkReadingIgnoresExtensions(t *testing.T) { 177 | in := "7;ext=\"some quoted string\"\r\n" + // token=quoted string 178 | "hello, \r\n" + 179 | "17;someext\r\n" + // token without value 180 | "world! 0123456789abcdef\r\n" + 181 | "0;someextension=sometoken\r\n" // token=token 182 | data, err := io.ReadAll(NewChunkedReader(strings.NewReader(in))) 183 | if err != nil { 184 | t.Fatalf("ReadAll = %q, %v", data, err) 185 | } 186 | if g, e := string(data), "hello, world! 0123456789abcdef"; g != e { 187 | t.Errorf("read %q; want %q", g, e) 188 | } 189 | } 190 | 191 | // Issue 17355: ChunkedReader shouldn't block waiting for more data 192 | // if it can return something. 193 | func TestChunkReadPartial(t *testing.T) { 194 | pr, pw := io.Pipe() 195 | go func() { 196 | pw.Write([]byte("7\r\n1234567")) 197 | }() 198 | cr := NewChunkedReader(pr) 199 | readBuf := make([]byte, 7) 200 | n, err := cr.Read(readBuf) 201 | if err != nil { 202 | t.Fatal(err) 203 | } 204 | want := "1234567" 205 | if n != 7 || string(readBuf) != want { 206 | t.Fatalf("Read: %v %q; want %d, %q", n, readBuf[:n], len(want), want) 207 | } 208 | go func() { 209 | pw.Write([]byte("xx")) 210 | }() 211 | _, err = cr.Read(readBuf) 212 | if got := fmt.Sprint(err); !strings.Contains(got, "malformed") { 213 | t.Fatalf("second read = %v; want malformed error", err) 214 | } 215 | 216 | } 217 | 218 | // Issue 48861: ChunkedReader should report incomplete chunks 219 | func TestIncompleteChunk(t *testing.T) { 220 | const valid = "4\r\nabcd\r\n" + "5\r\nabc\r\n\r\n" + "0\r\n" 221 | 222 | for i := 0; i < len(valid); i++ { 223 | incomplete := valid[:i] 224 | r := NewChunkedReader(strings.NewReader(incomplete)) 225 | if _, err := io.ReadAll(r); err != io.ErrUnexpectedEOF { 226 | t.Errorf("expected io.ErrUnexpectedEOF for %q, got %v", incomplete, err) 227 | } 228 | } 229 | 230 | r := NewChunkedReader(strings.NewReader(valid)) 231 | if _, err := io.ReadAll(r); err != nil { 232 | t.Errorf("unexpected error for %q: %v", valid, err) 233 | } 234 | } 235 | 236 | func TestChunkEndReadError(t *testing.T) { 237 | readErr := fmt.Errorf("chunk end read error") 238 | 239 | r := NewChunkedReader(io.MultiReader(strings.NewReader("4\r\nabcd"), iotest.ErrReader(readErr))) 240 | if _, err := io.ReadAll(r); err != readErr { 241 | t.Errorf("expected %v, got %v", readErr, err) 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /http/jar.go: -------------------------------------------------------------------------------- 1 | // TINYGO: The following is copied and modified from Go 1.21.4 official implementation. 2 | 3 | // Copyright 2011 The Go Authors. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | 7 | package http 8 | 9 | import ( 10 | "net/url" 11 | ) 12 | 13 | // A CookieJar manages storage and use of cookies in HTTP requests. 14 | // 15 | // Implementations of CookieJar must be safe for concurrent use by multiple 16 | // goroutines. 17 | // 18 | // The net/http/cookiejar package provides a CookieJar implementation. 19 | type CookieJar interface { 20 | // SetCookies handles the receipt of the cookies in a reply for the 21 | // given URL. It may or may not choose to save the cookies, depending 22 | // on the jar's policy and implementation. 23 | SetCookies(u *url.URL, cookies []*Cookie) 24 | 25 | // Cookies returns the cookies to send in a request for the given URL. 26 | // It is up to the implementation to honor the standard cookie use 27 | // restrictions such as in RFC 6265. 28 | Cookies(u *url.URL) []*Cookie 29 | } 30 | -------------------------------------------------------------------------------- /http/method.go: -------------------------------------------------------------------------------- 1 | // TINYGO: The following is copied from Go 1.21.4 official implementation. 2 | 3 | // Copyright 2015 The Go Authors. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | 7 | package http 8 | 9 | // Common HTTP methods. 10 | // 11 | // Unless otherwise noted, these are defined in RFC 7231 section 4.3. 12 | const ( 13 | MethodGet = "GET" 14 | MethodHead = "HEAD" 15 | MethodPost = "POST" 16 | MethodPut = "PUT" 17 | MethodPatch = "PATCH" // RFC 5789 18 | MethodDelete = "DELETE" 19 | MethodConnect = "CONNECT" 20 | MethodOptions = "OPTIONS" 21 | MethodTrace = "TRACE" 22 | ) 23 | -------------------------------------------------------------------------------- /http/response.go: -------------------------------------------------------------------------------- 1 | // TINYGO: The following is copied and modified from Go 1.21.4 official implementation. 2 | 3 | // TINYGO: Removed TLS connection state 4 | // TINYGO: Added onEOF hook to get callback when response has been read 5 | 6 | // Copyright 2009 The Go Authors. All rights reserved. 7 | // Use of this source code is governed by a BSD-style 8 | // license that can be found in the LICENSE file. 9 | 10 | // HTTP Response reading and parsing. 11 | 12 | package http 13 | 14 | import ( 15 | "bufio" 16 | "bytes" 17 | "errors" 18 | "fmt" 19 | "io" 20 | "net/textproto" 21 | "net/url" 22 | "strconv" 23 | "strings" 24 | 25 | "golang.org/x/net/http/httpguts" 26 | ) 27 | 28 | var respExcludeHeader = map[string]bool{ 29 | "Content-Length": true, 30 | "Transfer-Encoding": true, 31 | "Trailer": true, 32 | } 33 | 34 | // Response represents the response from an HTTP request. 35 | // 36 | // The Client and Transport return Responses from servers once 37 | // the response headers have been received. The response body 38 | // is streamed on demand as the Body field is read. 39 | type Response struct { 40 | Status string // e.g. "200 OK" 41 | StatusCode int // e.g. 200 42 | Proto string // e.g. "HTTP/1.0" 43 | ProtoMajor int // e.g. 1 44 | ProtoMinor int // e.g. 0 45 | 46 | // Header maps header keys to values. If the response had multiple 47 | // headers with the same key, they may be concatenated, with comma 48 | // delimiters. (RFC 7230, section 3.2.2 requires that multiple headers 49 | // be semantically equivalent to a comma-delimited sequence.) When 50 | // Header values are duplicated by other fields in this struct (e.g., 51 | // ContentLength, TransferEncoding, Trailer), the field values are 52 | // authoritative. 53 | // 54 | // Keys in the map are canonicalized (see CanonicalHeaderKey). 55 | Header Header 56 | 57 | // Body represents the response body. 58 | // 59 | // The response body is streamed on demand as the Body field 60 | // is read. If the network connection fails or the server 61 | // terminates the response, Body.Read calls return an error. 62 | // 63 | // The http Client and Transport guarantee that Body is always 64 | // non-nil, even on responses without a body or responses with 65 | // a zero-length body. It is the caller's responsibility to 66 | // close Body. The default HTTP client's Transport may not 67 | // reuse HTTP/1.x "keep-alive" TCP connections if the Body is 68 | // not read to completion and closed. 69 | // 70 | // The Body is automatically dechunked if the server replied 71 | // with a "chunked" Transfer-Encoding. 72 | // 73 | // As of Go 1.12, the Body will also implement io.Writer 74 | // on a successful "101 Switching Protocols" response, 75 | // as used by WebSockets and HTTP/2's "h2c" mode. 76 | Body io.ReadCloser 77 | 78 | // ContentLength records the length of the associated content. The 79 | // value -1 indicates that the length is unknown. Unless Request.Method 80 | // is "HEAD", values >= 0 indicate that the given number of bytes may 81 | // be read from Body. 82 | ContentLength int64 83 | 84 | // Contains transfer encodings from outer-most to inner-most. Value is 85 | // nil, means that "identity" encoding is used. 86 | TransferEncoding []string 87 | 88 | // Close records whether the header directed that the connection be 89 | // closed after reading Body. The value is advice for clients: neither 90 | // ReadResponse nor Response.Write ever closes a connection. 91 | Close bool 92 | 93 | // Uncompressed reports whether the response was sent compressed but 94 | // was decompressed by the http package. When true, reading from 95 | // Body yields the uncompressed content instead of the compressed 96 | // content actually set from the server, ContentLength is set to -1, 97 | // and the "Content-Length" and "Content-Encoding" fields are deleted 98 | // from the responseHeader. To get the original response from 99 | // the server, set Transport.DisableCompression to true. 100 | Uncompressed bool 101 | 102 | // Trailer maps trailer keys to values in the same 103 | // format as Header. 104 | // 105 | // The Trailer initially contains only nil values, one for 106 | // each key specified in the server's "Trailer" header 107 | // value. Those values are not added to Header. 108 | // 109 | // Trailer must not be accessed concurrently with Read calls 110 | // on the Body. 111 | // 112 | // After Body.Read has returned io.EOF, Trailer will contain 113 | // any trailer values sent by the server. 114 | Trailer Header 115 | 116 | // Request is the request that was sent to obtain this Response. 117 | // Request's Body is nil (having already been consumed). 118 | // This is only populated for Client requests. 119 | Request *Request 120 | } 121 | 122 | // Cookies parses and returns the cookies set in the Set-Cookie headers. 123 | func (r *Response) Cookies() []*Cookie { 124 | return readSetCookies(r.Header) 125 | } 126 | 127 | // ErrNoLocation is returned by Response's Location method 128 | // when no Location header is present. 129 | var ErrNoLocation = errors.New("http: no Location header in response") 130 | 131 | // Location returns the URL of the response's "Location" header, 132 | // if present. Relative redirects are resolved relative to 133 | // the Response's Request. ErrNoLocation is returned if no 134 | // Location header is present. 135 | func (r *Response) Location() (*url.URL, error) { 136 | lv := r.Header.Get("Location") 137 | if lv == "" { 138 | return nil, ErrNoLocation 139 | } 140 | if r.Request != nil && r.Request.URL != nil { 141 | return r.Request.URL.Parse(lv) 142 | } 143 | return url.Parse(lv) 144 | } 145 | 146 | // ReadResponse reads and returns an HTTP response from r. 147 | // The req parameter optionally specifies the Request that corresponds 148 | // to this Response. If nil, a GET request is assumed. 149 | // Clients must call resp.Body.Close when finished reading resp.Body. 150 | // After that call, clients can inspect resp.Trailer to find key/value 151 | // pairs included in the response trailer. 152 | 153 | // TINYGO: Added onEOF func to be called when response body is closed 154 | // TINYGO: so we can clean up the connection (r) 155 | 156 | func ReadResponse(r *bufio.Reader, req *Request) (*Response, error) { 157 | tp := textproto.NewReader(r) 158 | resp := &Response{ 159 | Request: req, 160 | } 161 | 162 | // Parse the first line of the response. 163 | line, err := tp.ReadLine() 164 | if err != nil { 165 | if err == io.EOF { 166 | err = io.ErrUnexpectedEOF 167 | } 168 | return nil, err 169 | } 170 | proto, status, ok := strings.Cut(line, " ") 171 | if !ok { 172 | return nil, badStringError("malformed HTTP response", line) 173 | } 174 | resp.Proto = proto 175 | resp.Status = strings.TrimLeft(status, " ") 176 | 177 | statusCode, _, _ := strings.Cut(resp.Status, " ") 178 | if len(statusCode) != 3 { 179 | return nil, badStringError("malformed HTTP status code", statusCode) 180 | } 181 | resp.StatusCode, err = strconv.Atoi(statusCode) 182 | if err != nil || resp.StatusCode < 0 { 183 | return nil, badStringError("malformed HTTP status code", statusCode) 184 | } 185 | if resp.ProtoMajor, resp.ProtoMinor, ok = ParseHTTPVersion(resp.Proto); !ok { 186 | return nil, badStringError("malformed HTTP version", resp.Proto) 187 | } 188 | 189 | // Parse the response headers. 190 | mimeHeader, err := tp.ReadMIMEHeader() 191 | if err != nil { 192 | if err == io.EOF { 193 | err = io.ErrUnexpectedEOF 194 | } 195 | return nil, err 196 | } 197 | resp.Header = Header(mimeHeader) 198 | 199 | fixPragmaCacheControl(resp.Header) 200 | 201 | err = readTransfer(resp, r, req.onEOF) 202 | if err != nil { 203 | return nil, err 204 | } 205 | 206 | return resp, nil 207 | } 208 | 209 | // RFC 7234, section 5.4: Should treat 210 | // 211 | // Pragma: no-cache 212 | // 213 | // like 214 | // 215 | // Cache-Control: no-cache 216 | func fixPragmaCacheControl(header Header) { 217 | if hp, ok := header["Pragma"]; ok && len(hp) > 0 && hp[0] == "no-cache" { 218 | if _, presentcc := header["Cache-Control"]; !presentcc { 219 | header["Cache-Control"] = []string{"no-cache"} 220 | } 221 | } 222 | } 223 | 224 | // ProtoAtLeast reports whether the HTTP protocol used 225 | // in the response is at least major.minor. 226 | func (r *Response) ProtoAtLeast(major, minor int) bool { 227 | return r.ProtoMajor > major || 228 | r.ProtoMajor == major && r.ProtoMinor >= minor 229 | } 230 | 231 | // Write writes r to w in the HTTP/1.x server response format, 232 | // including the status line, headers, body, and optional trailer. 233 | // 234 | // This method consults the following fields of the response r: 235 | // 236 | // StatusCode 237 | // ProtoMajor 238 | // ProtoMinor 239 | // Request.Method 240 | // TransferEncoding 241 | // Trailer 242 | // Body 243 | // ContentLength 244 | // Header, values for non-canonical keys will have unpredictable behavior 245 | // 246 | // The Response Body is closed after it is sent. 247 | func (r *Response) Write(w io.Writer) error { 248 | // Status line 249 | text := r.Status 250 | if text == "" { 251 | text = StatusText(r.StatusCode) 252 | if text == "" { 253 | text = "status code " + strconv.Itoa(r.StatusCode) 254 | } 255 | } else { 256 | // Just to reduce stutter, if user set r.Status to "200 OK" and StatusCode to 200. 257 | // Not important. 258 | text = strings.TrimPrefix(text, strconv.Itoa(r.StatusCode)+" ") 259 | } 260 | 261 | if _, err := fmt.Fprintf(w, "HTTP/%d.%d %03d %s\r\n", r.ProtoMajor, r.ProtoMinor, r.StatusCode, text); err != nil { 262 | return err 263 | } 264 | 265 | // Clone it, so we can modify r1 as needed. 266 | r1 := new(Response) 267 | *r1 = *r 268 | if r1.ContentLength == 0 && r1.Body != nil { 269 | // Is it actually 0 length? Or just unknown? 270 | var buf [1]byte 271 | n, err := r1.Body.Read(buf[:]) 272 | if err != nil && err != io.EOF { 273 | return err 274 | } 275 | if n == 0 { 276 | // Reset it to a known zero reader, in case underlying one 277 | // is unhappy being read repeatedly. 278 | r1.Body = NoBody 279 | } else { 280 | r1.ContentLength = -1 281 | r1.Body = struct { 282 | io.Reader 283 | io.Closer 284 | }{ 285 | io.MultiReader(bytes.NewReader(buf[:1]), r.Body), 286 | r.Body, 287 | } 288 | } 289 | } 290 | // If we're sending a non-chunked HTTP/1.1 response without a 291 | // content-length, the only way to do that is the old HTTP/1.0 292 | // way, by noting the EOF with a connection close, so we need 293 | // to set Close. 294 | if r1.ContentLength == -1 && !r1.Close && r1.ProtoAtLeast(1, 1) && !chunked(r1.TransferEncoding) && !r1.Uncompressed { 295 | r1.Close = true 296 | } 297 | 298 | // Process Body,ContentLength,Close,Trailer 299 | tw, err := newTransferWriter(r1) 300 | if err != nil { 301 | return err 302 | } 303 | err = tw.writeHeader(w) 304 | if err != nil { 305 | return err 306 | } 307 | 308 | // Rest of header 309 | err = r.Header.WriteSubset(w, respExcludeHeader) 310 | if err != nil { 311 | return err 312 | } 313 | 314 | // contentLengthAlreadySent may have been already sent for 315 | // POST/PUT requests, even if zero length. See Issue 8180. 316 | contentLengthAlreadySent := tw.shouldSendContentLength() 317 | if r1.ContentLength == 0 && !chunked(r1.TransferEncoding) && !contentLengthAlreadySent && bodyAllowedForStatus(r.StatusCode) { 318 | if _, err := io.WriteString(w, "Content-Length: 0\r\n"); err != nil { 319 | return err 320 | } 321 | } 322 | 323 | // End-of-header 324 | if _, err := io.WriteString(w, "\r\n"); err != nil { 325 | return err 326 | } 327 | 328 | // Write body and trailer 329 | err = tw.writeBody(w) 330 | if err != nil { 331 | return err 332 | } 333 | 334 | // Success 335 | return nil 336 | } 337 | 338 | func (r *Response) closeBody() { 339 | if r.Body != nil { 340 | r.Body.Close() 341 | } 342 | } 343 | 344 | // bodyIsWritable reports whether the Body supports writing. The 345 | // Transport returns Writable bodies for 101 Switching Protocols 346 | // responses. 347 | // The Transport uses this method to determine whether a persistent 348 | // connection is done being managed from its perspective. Once we 349 | // return a writable response body to a user, the net/http package is 350 | // done managing that connection. 351 | func (r *Response) bodyIsWritable() bool { 352 | _, ok := r.Body.(io.Writer) 353 | return ok 354 | } 355 | 356 | // isProtocolSwitch reports whether the response code and header 357 | // indicate a successful protocol upgrade response. 358 | func (r *Response) isProtocolSwitch() bool { 359 | return isProtocolSwitchResponse(r.StatusCode, r.Header) 360 | } 361 | 362 | // isProtocolSwitchResponse reports whether the response code and 363 | // response header indicate a successful protocol upgrade response. 364 | func isProtocolSwitchResponse(code int, h Header) bool { 365 | return code == StatusSwitchingProtocols && isProtocolSwitchHeader(h) 366 | } 367 | 368 | // isProtocolSwitchHeader reports whether the request or response header 369 | // is for a protocol switch. 370 | func isProtocolSwitchHeader(h Header) bool { 371 | return h.Get("Upgrade") != "" && 372 | httpguts.HeaderValuesContainsToken(h["Connection"], "Upgrade") 373 | } 374 | -------------------------------------------------------------------------------- /http/sniff.go: -------------------------------------------------------------------------------- 1 | // TINYGO: The following is copied from Go 1.21.4 official implementation. 2 | 3 | // Copyright 2011 The Go Authors. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | 7 | package http 8 | 9 | import ( 10 | "bytes" 11 | "encoding/binary" 12 | ) 13 | 14 | // The algorithm uses at most sniffLen bytes to make its decision. 15 | const sniffLen = 512 16 | 17 | // DetectContentType implements the algorithm described 18 | // at https://mimesniff.spec.whatwg.org/ to determine the 19 | // Content-Type of the given data. It considers at most the 20 | // first 512 bytes of data. DetectContentType always returns 21 | // a valid MIME type: if it cannot determine a more specific one, it 22 | // returns "application/octet-stream". 23 | func DetectContentType(data []byte) string { 24 | if len(data) > sniffLen { 25 | data = data[:sniffLen] 26 | } 27 | 28 | // Index of the first non-whitespace byte in data. 29 | firstNonWS := 0 30 | for ; firstNonWS < len(data) && isWS(data[firstNonWS]); firstNonWS++ { 31 | } 32 | 33 | for _, sig := range sniffSignatures { 34 | if ct := sig.match(data, firstNonWS); ct != "" { 35 | return ct 36 | } 37 | } 38 | 39 | return "application/octet-stream" // fallback 40 | } 41 | 42 | // isWS reports whether the provided byte is a whitespace byte (0xWS) 43 | // as defined in https://mimesniff.spec.whatwg.org/#terminology. 44 | func isWS(b byte) bool { 45 | switch b { 46 | case '\t', '\n', '\x0c', '\r', ' ': 47 | return true 48 | } 49 | return false 50 | } 51 | 52 | // isTT reports whether the provided byte is a tag-terminating byte (0xTT) 53 | // as defined in https://mimesniff.spec.whatwg.org/#terminology. 54 | func isTT(b byte) bool { 55 | switch b { 56 | case ' ', '>': 57 | return true 58 | } 59 | return false 60 | } 61 | 62 | type sniffSig interface { 63 | // match returns the MIME type of the data, or "" if unknown. 64 | match(data []byte, firstNonWS int) string 65 | } 66 | 67 | // Data matching the table in section 6. 68 | var sniffSignatures = []sniffSig{ 69 | htmlSig(" bits { 85 | return nil 86 | } 87 | l := bits / 8 88 | m := make(IPMask, l) 89 | n := uint(ones) 90 | for i := 0; i < l; i++ { 91 | if n >= 8 { 92 | m[i] = 0xff 93 | n -= 8 94 | continue 95 | } 96 | m[i] = ^byte(0xff >> n) 97 | n = 0 98 | } 99 | return m 100 | } 101 | 102 | // Well-known IPv4 addresses 103 | var ( 104 | IPv4bcast = IPv4(255, 255, 255, 255) // limited broadcast 105 | IPv4allsys = IPv4(224, 0, 0, 1) // all systems 106 | IPv4allrouter = IPv4(224, 0, 0, 2) // all routers 107 | IPv4zero = IPv4(0, 0, 0, 0) // all zeros 108 | ) 109 | 110 | // Well-known IPv6 addresses 111 | var ( 112 | IPv6zero = IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} 113 | IPv6unspecified = IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} 114 | IPv6loopback = IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} 115 | IPv6interfacelocalallnodes = IP{0xff, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01} 116 | IPv6linklocalallnodes = IP{0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01} 117 | IPv6linklocalallrouters = IP{0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x02} 118 | ) 119 | 120 | // IsUnspecified reports whether ip is an unspecified address, either 121 | // the IPv4 address "0.0.0.0" or the IPv6 address "::". 122 | func (ip IP) IsUnspecified() bool { 123 | return ip.Equal(IPv4zero) || ip.Equal(IPv6unspecified) 124 | } 125 | 126 | // IsLoopback reports whether ip is a loopback address. 127 | func (ip IP) IsLoopback() bool { 128 | if ip4 := ip.To4(); ip4 != nil { 129 | return ip4[0] == 127 130 | } 131 | return ip.Equal(IPv6loopback) 132 | } 133 | 134 | // IsPrivate reports whether ip is a private address, according to 135 | // RFC 1918 (IPv4 addresses) and RFC 4193 (IPv6 addresses). 136 | func (ip IP) IsPrivate() bool { 137 | if ip4 := ip.To4(); ip4 != nil { 138 | // Following RFC 1918, Section 3. Private Address Space which says: 139 | // The Internet Assigned Numbers Authority (IANA) has reserved the 140 | // following three blocks of the IP address space for private internets: 141 | // 10.0.0.0 - 10.255.255.255 (10/8 prefix) 142 | // 172.16.0.0 - 172.31.255.255 (172.16/12 prefix) 143 | // 192.168.0.0 - 192.168.255.255 (192.168/16 prefix) 144 | return ip4[0] == 10 || 145 | (ip4[0] == 172 && ip4[1]&0xf0 == 16) || 146 | (ip4[0] == 192 && ip4[1] == 168) 147 | } 148 | // Following RFC 4193, Section 8. IANA Considerations which says: 149 | // The IANA has assigned the FC00::/7 prefix to "Unique Local Unicast". 150 | return len(ip) == IPv6len && ip[0]&0xfe == 0xfc 151 | } 152 | 153 | // IsMulticast reports whether ip is a multicast address. 154 | func (ip IP) IsMulticast() bool { 155 | if ip4 := ip.To4(); ip4 != nil { 156 | return ip4[0]&0xf0 == 0xe0 157 | } 158 | return len(ip) == IPv6len && ip[0] == 0xff 159 | } 160 | 161 | // IsInterfaceLocalMulticast reports whether ip is 162 | // an interface-local multicast address. 163 | func (ip IP) IsInterfaceLocalMulticast() bool { 164 | return len(ip) == IPv6len && ip[0] == 0xff && ip[1]&0x0f == 0x01 165 | } 166 | 167 | // IsLinkLocalMulticast reports whether ip is a link-local 168 | // multicast address. 169 | func (ip IP) IsLinkLocalMulticast() bool { 170 | if ip4 := ip.To4(); ip4 != nil { 171 | return ip4[0] == 224 && ip4[1] == 0 && ip4[2] == 0 172 | } 173 | return len(ip) == IPv6len && ip[0] == 0xff && ip[1]&0x0f == 0x02 174 | } 175 | 176 | // IsLinkLocalUnicast reports whether ip is a link-local 177 | // unicast address. 178 | func (ip IP) IsLinkLocalUnicast() bool { 179 | if ip4 := ip.To4(); ip4 != nil { 180 | return ip4[0] == 169 && ip4[1] == 254 181 | } 182 | return len(ip) == IPv6len && ip[0] == 0xfe && ip[1]&0xc0 == 0x80 183 | } 184 | 185 | // IsGlobalUnicast reports whether ip is a global unicast 186 | // address. 187 | // 188 | // The identification of global unicast addresses uses address type 189 | // identification as defined in RFC 1122, RFC 4632 and RFC 4291 with 190 | // the exception of IPv4 directed broadcast addresses. 191 | // It returns true even if ip is in IPv4 private address space or 192 | // local IPv6 unicast address space. 193 | func (ip IP) IsGlobalUnicast() bool { 194 | return (len(ip) == IPv4len || len(ip) == IPv6len) && 195 | !ip.Equal(IPv4bcast) && 196 | !ip.IsUnspecified() && 197 | !ip.IsLoopback() && 198 | !ip.IsMulticast() && 199 | !ip.IsLinkLocalUnicast() 200 | } 201 | 202 | // Is p all zeros? 203 | func isZeros(p IP) bool { 204 | for i := 0; i < len(p); i++ { 205 | if p[i] != 0 { 206 | return false 207 | } 208 | } 209 | return true 210 | } 211 | 212 | // To4 converts the IPv4 address ip to a 4-byte representation. 213 | // If ip is not an IPv4 address, To4 returns nil. 214 | func (ip IP) To4() IP { 215 | if len(ip) == IPv4len { 216 | return ip 217 | } 218 | if len(ip) == IPv6len && 219 | isZeros(ip[0:10]) && 220 | ip[10] == 0xff && 221 | ip[11] == 0xff { 222 | return ip[12:16] 223 | } 224 | return nil 225 | } 226 | 227 | // To16 converts the IP address ip to a 16-byte representation. 228 | // If ip is not an IP address (it is the wrong length), To16 returns nil. 229 | func (ip IP) To16() IP { 230 | if len(ip) == IPv4len { 231 | return IPv4(ip[0], ip[1], ip[2], ip[3]) 232 | } 233 | if len(ip) == IPv6len { 234 | return ip 235 | } 236 | return nil 237 | } 238 | 239 | // Default route masks for IPv4. 240 | var ( 241 | classAMask = IPv4Mask(0xff, 0, 0, 0) 242 | classBMask = IPv4Mask(0xff, 0xff, 0, 0) 243 | classCMask = IPv4Mask(0xff, 0xff, 0xff, 0) 244 | ) 245 | 246 | // DefaultMask returns the default IP mask for the IP address ip. 247 | // Only IPv4 addresses have default masks; DefaultMask returns 248 | // nil if ip is not a valid IPv4 address. 249 | func (ip IP) DefaultMask() IPMask { 250 | if ip = ip.To4(); ip == nil { 251 | return nil 252 | } 253 | switch { 254 | case ip[0] < 0x80: 255 | return classAMask 256 | case ip[0] < 0xC0: 257 | return classBMask 258 | default: 259 | return classCMask 260 | } 261 | } 262 | 263 | func allFF(b []byte) bool { 264 | for _, c := range b { 265 | if c != 0xff { 266 | return false 267 | } 268 | } 269 | return true 270 | } 271 | 272 | // Mask returns the result of masking the IP address ip with mask. 273 | func (ip IP) Mask(mask IPMask) IP { 274 | if len(mask) == IPv6len && len(ip) == IPv4len && allFF(mask[:12]) { 275 | mask = mask[12:] 276 | } 277 | if len(mask) == IPv4len && len(ip) == IPv6len && bytealg.Equal(ip[:12], v4InV6Prefix) { 278 | ip = ip[12:] 279 | } 280 | n := len(ip) 281 | if n != len(mask) { 282 | return nil 283 | } 284 | out := make(IP, n) 285 | for i := 0; i < n; i++ { 286 | out[i] = ip[i] & mask[i] 287 | } 288 | return out 289 | } 290 | 291 | // String returns the string form of the IP address ip. 292 | // It returns one of 4 forms: 293 | // - "", if ip has length 0 294 | // - dotted decimal ("192.0.2.1"), if ip is an IPv4 or IP4-mapped IPv6 address 295 | // - IPv6 conforming to RFC 5952 ("2001:db8::1"), if ip is a valid IPv6 address 296 | // - the hexadecimal form of ip, without punctuation, if no other cases apply 297 | func (ip IP) String() string { 298 | if len(ip) == 0 { 299 | return "" 300 | } 301 | 302 | if len(ip) != IPv4len && len(ip) != IPv6len { 303 | return "?" + hexString(ip) 304 | } 305 | // If IPv4, use dotted notation. 306 | if p4 := ip.To4(); len(p4) == IPv4len { 307 | return netip.AddrFrom4([4]byte(p4)).String() 308 | } 309 | return netip.AddrFrom16([16]byte(ip)).String() 310 | } 311 | 312 | func hexString(b []byte) string { 313 | s := make([]byte, len(b)*2) 314 | for i, tn := range b { 315 | s[i*2], s[i*2+1] = hexDigit[tn>>4], hexDigit[tn&0xf] 316 | } 317 | return string(s) 318 | } 319 | 320 | // ipEmptyString is like ip.String except that it returns 321 | // an empty string when ip is unset. 322 | func ipEmptyString(ip IP) string { 323 | if len(ip) == 0 { 324 | return "" 325 | } 326 | return ip.String() 327 | } 328 | 329 | // MarshalText implements the encoding.TextMarshaler interface. 330 | // The encoding is the same as returned by String, with one exception: 331 | // When len(ip) is zero, it returns an empty slice. 332 | func (ip IP) MarshalText() ([]byte, error) { 333 | if len(ip) == 0 { 334 | return []byte(""), nil 335 | } 336 | if len(ip) != IPv4len && len(ip) != IPv6len { 337 | return nil, &AddrError{Err: "invalid IP address", Addr: hexString(ip)} 338 | } 339 | return []byte(ip.String()), nil 340 | } 341 | 342 | // UnmarshalText implements the encoding.TextUnmarshaler interface. 343 | // The IP address is expected in a form accepted by ParseIP. 344 | func (ip *IP) UnmarshalText(text []byte) error { 345 | if len(text) == 0 { 346 | *ip = nil 347 | return nil 348 | } 349 | s := string(text) 350 | x := ParseIP(s) 351 | if x == nil { 352 | return &ParseError{Type: "IP address", Text: s} 353 | } 354 | *ip = x 355 | return nil 356 | } 357 | 358 | // Equal reports whether ip and x are the same IP address. 359 | // An IPv4 address and that same address in IPv6 form are 360 | // considered to be equal. 361 | func (ip IP) Equal(x IP) bool { 362 | if len(ip) == len(x) { 363 | return bytealg.Equal(ip, x) 364 | } 365 | if len(ip) == IPv4len && len(x) == IPv6len { 366 | return bytealg.Equal(x[0:12], v4InV6Prefix) && bytealg.Equal(ip, x[12:]) 367 | } 368 | if len(ip) == IPv6len && len(x) == IPv4len { 369 | return bytealg.Equal(ip[0:12], v4InV6Prefix) && bytealg.Equal(ip[12:], x) 370 | } 371 | return false 372 | } 373 | 374 | func (ip IP) matchAddrFamily(x IP) bool { 375 | return ip.To4() != nil && x.To4() != nil || ip.To16() != nil && ip.To4() == nil && x.To16() != nil && x.To4() == nil 376 | } 377 | 378 | // If mask is a sequence of 1 bits followed by 0 bits, 379 | // return the number of 1 bits. 380 | func simpleMaskLength(mask IPMask) int { 381 | var n int 382 | for i, v := range mask { 383 | if v == 0xff { 384 | n += 8 385 | continue 386 | } 387 | // found non-ff byte 388 | // count 1 bits 389 | for v&0x80 != 0 { 390 | n++ 391 | v <<= 1 392 | } 393 | // rest must be 0 bits 394 | if v != 0 { 395 | return -1 396 | } 397 | for i++; i < len(mask); i++ { 398 | if mask[i] != 0 { 399 | return -1 400 | } 401 | } 402 | break 403 | } 404 | return n 405 | } 406 | 407 | // Size returns the number of leading ones and total bits in the mask. 408 | // If the mask is not in the canonical form--ones followed by zeros--then 409 | // Size returns 0, 0. 410 | func (m IPMask) Size() (ones, bits int) { 411 | ones, bits = simpleMaskLength(m), len(m)*8 412 | if ones == -1 { 413 | return 0, 0 414 | } 415 | return 416 | } 417 | 418 | // String returns the hexadecimal form of m, with no punctuation. 419 | func (m IPMask) String() string { 420 | if len(m) == 0 { 421 | return "" 422 | } 423 | return hexString(m) 424 | } 425 | 426 | func networkNumberAndMask(n *IPNet) (ip IP, m IPMask) { 427 | if ip = n.IP.To4(); ip == nil { 428 | ip = n.IP 429 | if len(ip) != IPv6len { 430 | return nil, nil 431 | } 432 | } 433 | m = n.Mask 434 | switch len(m) { 435 | case IPv4len: 436 | if len(ip) != IPv4len { 437 | return nil, nil 438 | } 439 | case IPv6len: 440 | if len(ip) == IPv4len { 441 | m = m[12:] 442 | } 443 | default: 444 | return nil, nil 445 | } 446 | return 447 | } 448 | 449 | // Contains reports whether the network includes ip. 450 | func (n *IPNet) Contains(ip IP) bool { 451 | nn, m := networkNumberAndMask(n) 452 | if x := ip.To4(); x != nil { 453 | ip = x 454 | } 455 | l := len(ip) 456 | if l != len(nn) { 457 | return false 458 | } 459 | for i := 0; i < l; i++ { 460 | if nn[i]&m[i] != ip[i]&m[i] { 461 | return false 462 | } 463 | } 464 | return true 465 | } 466 | 467 | // Network returns the address's network name, "ip+net". 468 | func (n *IPNet) Network() string { return "ip+net" } 469 | 470 | // String returns the CIDR notation of n like "192.0.2.0/24" 471 | // or "2001:db8::/48" as defined in RFC 4632 and RFC 4291. 472 | // If the mask is not in the canonical form, it returns the 473 | // string which consists of an IP address, followed by a slash 474 | // character and a mask expressed as hexadecimal form with no 475 | // punctuation like "198.51.100.0/c000ff00". 476 | func (n *IPNet) String() string { 477 | if n == nil { 478 | return "" 479 | } 480 | nn, m := networkNumberAndMask(n) 481 | if nn == nil || m == nil { 482 | return "" 483 | } 484 | l := simpleMaskLength(m) 485 | if l == -1 { 486 | return nn.String() + "/" + m.String() 487 | } 488 | return nn.String() + "/" + itoa.Uitoa(uint(l)) 489 | } 490 | 491 | // ParseIP parses s as an IP address, returning the result. 492 | // The string s can be in IPv4 dotted decimal ("192.0.2.1"), IPv6 493 | // ("2001:db8::68"), or IPv4-mapped IPv6 ("::ffff:192.0.2.1") form. 494 | // If s is not a valid textual representation of an IP address, 495 | // ParseIP returns nil. 496 | func ParseIP(s string) IP { 497 | if addr, valid := parseIP(s); valid { 498 | return IP(addr[:]) 499 | } 500 | return nil 501 | } 502 | 503 | func parseIP(s string) ([16]byte, bool) { 504 | ip, err := netip.ParseAddr(s) 505 | if err != nil || ip.Zone() != "" { 506 | return [16]byte{}, false 507 | } 508 | return ip.As16(), true 509 | } 510 | 511 | // ParseCIDR parses s as a CIDR notation IP address and prefix length, 512 | // like "192.0.2.0/24" or "2001:db8::/32", as defined in 513 | // RFC 4632 and RFC 4291. 514 | // 515 | // It returns the IP address and the network implied by the IP and 516 | // prefix length. 517 | // For example, ParseCIDR("192.0.2.1/24") returns the IP address 518 | // 192.0.2.1 and the network 192.0.2.0/24. 519 | func ParseCIDR(s string) (IP, *IPNet, error) { 520 | i := bytealg.IndexByteString(s, '/') 521 | if i < 0 { 522 | return nil, nil, &ParseError{Type: "CIDR address", Text: s} 523 | } 524 | addr, mask := s[:i], s[i+1:] 525 | 526 | ipAddr, err := netip.ParseAddr(addr) 527 | if err != nil || ipAddr.Zone() != "" { 528 | return nil, nil, &ParseError{Type: "CIDR address", Text: s} 529 | } 530 | 531 | n, i, ok := dtoi(mask) 532 | if !ok || i != len(mask) || n < 0 || n > ipAddr.BitLen() { 533 | return nil, nil, &ParseError{Type: "CIDR address", Text: s} 534 | } 535 | m := CIDRMask(n, ipAddr.BitLen()) 536 | addr16 := ipAddr.As16() 537 | return IP(addr16[:]), &IPNet{IP: IP(addr16[:]).Mask(m), Mask: m}, nil 538 | } 539 | 540 | func copyIP(x IP) IP { 541 | y := make(IP, len(x)) 542 | copy(y, x) 543 | return y 544 | } 545 | -------------------------------------------------------------------------------- /iprawsock.go: -------------------------------------------------------------------------------- 1 | // TINYGO: The following is copied and modified from Go 1.21.4 official implementation. 2 | 3 | // Copyright 2010 The Go Authors. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | 7 | package net 8 | 9 | import ( 10 | "errors" 11 | "syscall" 12 | ) 13 | 14 | // BUG(mikio): On every POSIX platform, reads from the "ip4" network 15 | // using the ReadFrom or ReadFromIP method might not return a complete 16 | // IPv4 packet, including its header, even if there is space 17 | // available. This can occur even in cases where Read or ReadMsgIP 18 | // could return a complete packet. For this reason, it is recommended 19 | // that you do not use these methods if it is important to receive a 20 | // full packet. 21 | // 22 | // The Go 1 compatibility guidelines make it impossible for us to 23 | // change the behavior of these methods; use Read or ReadMsgIP 24 | // instead. 25 | 26 | // BUG(mikio): On JS and Plan 9, methods and functions related 27 | // to IPConn are not implemented. 28 | 29 | // BUG(mikio): On Windows, the File method of IPConn is not 30 | // implemented. 31 | 32 | // IPAddr represents the address of an IP end point. 33 | type IPAddr struct { 34 | IP IP 35 | Zone string // IPv6 scoped addressing zone 36 | } 37 | 38 | // Network returns the address's network name, "ip". 39 | func (a *IPAddr) Network() string { return "ip" } 40 | 41 | func (a *IPAddr) String() string { 42 | if a == nil { 43 | return "" 44 | } 45 | ip := ipEmptyString(a.IP) 46 | if a.Zone != "" { 47 | return ip + "%" + a.Zone 48 | } 49 | return ip 50 | } 51 | 52 | func (a *IPAddr) isWildcard() bool { 53 | if a == nil || a.IP == nil { 54 | return true 55 | } 56 | return a.IP.IsUnspecified() 57 | } 58 | 59 | func (a *IPAddr) opAddr() Addr { 60 | if a == nil { 61 | return nil 62 | } 63 | return a 64 | } 65 | 66 | // ResolveIPAddr returns an address of IP end point. 67 | // 68 | // The network must be an IP network name. 69 | // 70 | // If the host in the address parameter is not a literal IP address, 71 | // ResolveIPAddr resolves the address to an address of IP end point. 72 | // Otherwise, it parses the address as a literal IP address. 73 | // The address parameter can use a host name, but this is not 74 | // recommended, because it will return at most one of the host name's 75 | // IP addresses. 76 | // 77 | // See func [Dial] for a description of the network and address 78 | // parameters. 79 | func ResolveIPAddr(network, address string) (*IPAddr, error) { 80 | return nil, errors.New("ResolveIPAddr not implemented") 81 | } 82 | 83 | // IPConn is the implementation of the Conn and PacketConn interfaces 84 | // for IP network connections. 85 | type IPConn struct { 86 | conn 87 | } 88 | 89 | // SyscallConn returns a raw network connection. 90 | // This implements the syscall.Conn interface. 91 | func (c *IPConn) SyscallConn() (syscall.RawConn, error) { 92 | return nil, errors.New("SyscallConn not implemented") 93 | } 94 | 95 | // ReadMsgIP reads a message from c, copying the payload into b and 96 | // the associated out-of-band data into oob. It returns the number of 97 | // bytes copied into b, the number of bytes copied into oob, the flags 98 | // that were set on the message and the source address of the message. 99 | // 100 | // The packages golang.org/x/net/ipv4 and golang.org/x/net/ipv6 can be 101 | // used to manipulate IP-level socket options in oob. 102 | func (c *IPConn) ReadMsgIP(b, oob []byte) (n, oobn, flags int, addr *IPAddr, err error) { 103 | err = errors.New("ReadMsgIP not implemented") 104 | return 105 | } 106 | 107 | // ReadFrom implements the PacketConn ReadFrom method. 108 | func (c *IPConn) ReadFrom(b []byte) (int, Addr, error) { 109 | return 0, nil, errors.New("ReadFrom not implemented") 110 | } 111 | 112 | // WriteToIP acts like WriteTo but takes an IPAddr. 113 | func (c *IPConn) WriteToIP(b []byte, addr *IPAddr) (int, error) { 114 | return 0, errors.New("WriteToIP not implemented") 115 | } 116 | 117 | // WriteTo implements the PacketConn WriteTo method. 118 | func (c *IPConn) WriteTo(b []byte, addr Addr) (int, error) { 119 | return 0, errors.New("WriteTo not implemented") 120 | } 121 | -------------------------------------------------------------------------------- /ipsock.go: -------------------------------------------------------------------------------- 1 | // TINYGO: The following is copied and modified from Go 1.21.4 official implementation. 2 | 3 | // Copyright 2009 The Go Authors. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | 7 | package net 8 | 9 | import ( 10 | "internal/bytealg" 11 | ) 12 | 13 | // SplitHostPort splits a network address of the form "host:port", 14 | // "host%zone:port", "[host]:port" or "[host%zone]:port" into host or 15 | // host%zone and port. 16 | // 17 | // A literal IPv6 address in hostport must be enclosed in square 18 | // brackets, as in "[::1]:80", "[::1%lo0]:80". 19 | // 20 | // See func Dial for a description of the hostport parameter, and host 21 | // and port results. 22 | func SplitHostPort(hostport string) (host, port string, err error) { 23 | const ( 24 | missingPort = "missing port in address" 25 | tooManyColons = "too many colons in address" 26 | ) 27 | addrErr := func(addr, why string) (host, port string, err error) { 28 | return "", "", &AddrError{Err: why, Addr: addr} 29 | } 30 | j, k := 0, 0 31 | 32 | // The port starts after the last colon. 33 | i := last(hostport, ':') 34 | if i < 0 { 35 | return addrErr(hostport, missingPort) 36 | } 37 | 38 | if hostport[0] == '[' { 39 | // Expect the first ']' just before the last ':'. 40 | end := bytealg.IndexByteString(hostport, ']') 41 | if end < 0 { 42 | return addrErr(hostport, "missing ']' in address") 43 | } 44 | switch end + 1 { 45 | case len(hostport): 46 | // There can't be a ':' behind the ']' now. 47 | return addrErr(hostport, missingPort) 48 | case i: 49 | // The expected result. 50 | default: 51 | // Either ']' isn't followed by a colon, or it is 52 | // followed by a colon that is not the last one. 53 | if hostport[end+1] == ':' { 54 | return addrErr(hostport, tooManyColons) 55 | } 56 | return addrErr(hostport, missingPort) 57 | } 58 | host = hostport[1:end] 59 | j, k = 1, end+1 // there can't be a '[' resp. ']' before these positions 60 | } else { 61 | host = hostport[:i] 62 | if bytealg.IndexByteString(host, ':') >= 0 { 63 | return addrErr(hostport, tooManyColons) 64 | } 65 | } 66 | if bytealg.IndexByteString(hostport[j:], '[') >= 0 { 67 | return addrErr(hostport, "unexpected '[' in address") 68 | } 69 | if bytealg.IndexByteString(hostport[k:], ']') >= 0 { 70 | return addrErr(hostport, "unexpected ']' in address") 71 | } 72 | 73 | port = hostport[i+1:] 74 | return host, port, nil 75 | } 76 | 77 | func splitHostZone(s string) (host, zone string) { 78 | // The IPv6 scoped addressing zone identifier starts after the 79 | // last percent sign. 80 | if i := last(s, '%'); i > 0 { 81 | host, zone = s[:i], s[i+1:] 82 | } else { 83 | host = s 84 | } 85 | return 86 | } 87 | 88 | // JoinHostPort combines host and port into a network address of the 89 | // form "host:port". If host contains a colon, as found in literal 90 | // IPv6 addresses, then JoinHostPort returns "[host]:port". 91 | // 92 | // See func Dial for a description of the host and port parameters. 93 | func JoinHostPort(host, port string) string { 94 | // We assume that host is a literal IPv6 address if host has 95 | // colons. 96 | if bytealg.IndexByteString(host, ':') >= 0 { 97 | return "[" + host + "]:" + port 98 | } 99 | return host + ":" + port 100 | } 101 | -------------------------------------------------------------------------------- /lookup.go: -------------------------------------------------------------------------------- 1 | // TINYGO: The following is copied and modified from Go 1.21.4 official implementation. 2 | 3 | // Copyright 2012 The Go Authors. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | 7 | package net 8 | 9 | import ( 10 | "errors" 11 | ) 12 | 13 | // LookupPort looks up the port for the given network and service. 14 | // 15 | // LookupPort uses context.Background internally; to specify the context, use 16 | // Resolver.LookupPort. 17 | func LookupPort(network, service string) (port int, err error) { 18 | return 0, errors.New("net:LookupPort not implemented") 19 | } 20 | -------------------------------------------------------------------------------- /lookup_unix.go: -------------------------------------------------------------------------------- 1 | // TINYGO: The following is copied and modified from Go 1.22.0 official implementation. 2 | 3 | // Copyright 2011 The Go Authors. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | 7 | //go:build unix || js || wasip1 8 | 9 | package net 10 | 11 | import ( 12 | "context" 13 | "errors" 14 | ) 15 | 16 | // lookupProtocol looks up IP protocol name in /etc/protocols and 17 | // returns correspondent protocol number. 18 | func lookupProtocol(_ context.Context, name string) (int, error) { 19 | return 0, errors.New("net:lookupProtocol not implemented") 20 | } 21 | -------------------------------------------------------------------------------- /lookup_windows.go: -------------------------------------------------------------------------------- 1 | // TINYGO: The following is copied and modified from Go 1.22.0 official implementation. 2 | 3 | // Copyright 2009 The Go Authors. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | 7 | package net 8 | 9 | import ( 10 | "context" 11 | "errors" 12 | ) 13 | 14 | // lookupProtocol looks up IP protocol name and returns correspondent protocol number. 15 | func lookupProtocol(ctx context.Context, name string) (int, error) { 16 | return 0, errors.New("net:lookupProtocol not implemented") 17 | } 18 | -------------------------------------------------------------------------------- /mac.go: -------------------------------------------------------------------------------- 1 | // TINYGO: The following is copied from Go 1.21.4 official implementation. 2 | 3 | // Copyright 2011 The Go Authors. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | 7 | package net 8 | 9 | const hexDigit = "0123456789abcdef" 10 | 11 | // A HardwareAddr represents a physical hardware address. 12 | type HardwareAddr []byte 13 | 14 | func (a HardwareAddr) String() string { 15 | if len(a) == 0 { 16 | return "" 17 | } 18 | buf := make([]byte, 0, len(a)*3-1) 19 | for i, b := range a { 20 | if i > 0 { 21 | buf = append(buf, ':') 22 | } 23 | buf = append(buf, hexDigit[b>>4]) 24 | buf = append(buf, hexDigit[b&0xF]) 25 | } 26 | return string(buf) 27 | } 28 | 29 | // ParseMAC parses s as an IEEE 802 MAC-48, EUI-48, EUI-64, or a 20-octet 30 | // IP over InfiniBand link-layer address using one of the following formats: 31 | // 32 | // 00:00:5e:00:53:01 33 | // 02:00:5e:10:00:00:00:01 34 | // 00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01 35 | // 00-00-5e-00-53-01 36 | // 02-00-5e-10-00-00-00-01 37 | // 00-00-00-00-fe-80-00-00-00-00-00-00-02-00-5e-10-00-00-00-01 38 | // 0000.5e00.5301 39 | // 0200.5e10.0000.0001 40 | // 0000.0000.fe80.0000.0000.0000.0200.5e10.0000.0001 41 | func ParseMAC(s string) (hw HardwareAddr, err error) { 42 | if len(s) < 14 { 43 | goto error 44 | } 45 | 46 | if s[2] == ':' || s[2] == '-' { 47 | if (len(s)+1)%3 != 0 { 48 | goto error 49 | } 50 | n := (len(s) + 1) / 3 51 | if n != 6 && n != 8 && n != 20 { 52 | goto error 53 | } 54 | hw = make(HardwareAddr, n) 55 | for x, i := 0, 0; i < n; i++ { 56 | var ok bool 57 | if hw[i], ok = xtoi2(s[x:], s[2]); !ok { 58 | goto error 59 | } 60 | x += 3 61 | } 62 | } else if s[4] == '.' { 63 | if (len(s)+1)%5 != 0 { 64 | goto error 65 | } 66 | n := 2 * (len(s) + 1) / 5 67 | if n != 6 && n != 8 && n != 20 { 68 | goto error 69 | } 70 | hw = make(HardwareAddr, n) 71 | for x, i := 0, 0; i < n; i += 2 { 72 | var ok bool 73 | if hw[i], ok = xtoi2(s[x:x+2], 0); !ok { 74 | goto error 75 | } 76 | if hw[i+1], ok = xtoi2(s[x+2:], s[4]); !ok { 77 | goto error 78 | } 79 | x += 5 80 | } 81 | } else { 82 | goto error 83 | } 84 | return hw, nil 85 | 86 | error: 87 | return nil, &AddrError{Err: "invalid MAC address", Addr: s} 88 | } 89 | -------------------------------------------------------------------------------- /mac_test.go: -------------------------------------------------------------------------------- 1 | // TINYGO: The following is copied from Go 1.21.4 official implementation. 2 | 3 | // Copyright 2011 The Go Authors. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | 7 | package net 8 | 9 | import ( 10 | "reflect" 11 | "strings" 12 | "testing" 13 | ) 14 | 15 | var parseMACTests = []struct { 16 | in string 17 | out HardwareAddr 18 | err string 19 | }{ 20 | // See RFC 7042, Section 2.1.1. 21 | {"00:00:5e:00:53:01", HardwareAddr{0x00, 0x00, 0x5e, 0x00, 0x53, 0x01}, ""}, 22 | {"00-00-5e-00-53-01", HardwareAddr{0x00, 0x00, 0x5e, 0x00, 0x53, 0x01}, ""}, 23 | {"0000.5e00.5301", HardwareAddr{0x00, 0x00, 0x5e, 0x00, 0x53, 0x01}, ""}, 24 | 25 | // See RFC 7042, Section 2.2.2. 26 | {"02:00:5e:10:00:00:00:01", HardwareAddr{0x02, 0x00, 0x5e, 0x10, 0x00, 0x00, 0x00, 0x01}, ""}, 27 | {"02-00-5e-10-00-00-00-01", HardwareAddr{0x02, 0x00, 0x5e, 0x10, 0x00, 0x00, 0x00, 0x01}, ""}, 28 | {"0200.5e10.0000.0001", HardwareAddr{0x02, 0x00, 0x5e, 0x10, 0x00, 0x00, 0x00, 0x01}, ""}, 29 | 30 | // See RFC 4391, Section 9.1.1. 31 | { 32 | "00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01", 33 | HardwareAddr{ 34 | 0x00, 0x00, 0x00, 0x00, 35 | 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 36 | 0x02, 0x00, 0x5e, 0x10, 0x00, 0x00, 0x00, 0x01, 37 | }, 38 | "", 39 | }, 40 | { 41 | "00-00-00-00-fe-80-00-00-00-00-00-00-02-00-5e-10-00-00-00-01", 42 | HardwareAddr{ 43 | 0x00, 0x00, 0x00, 0x00, 44 | 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 45 | 0x02, 0x00, 0x5e, 0x10, 0x00, 0x00, 0x00, 0x01, 46 | }, 47 | "", 48 | }, 49 | { 50 | "0000.0000.fe80.0000.0000.0000.0200.5e10.0000.0001", 51 | HardwareAddr{ 52 | 0x00, 0x00, 0x00, 0x00, 53 | 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 54 | 0x02, 0x00, 0x5e, 0x10, 0x00, 0x00, 0x00, 0x01, 55 | }, 56 | "", 57 | }, 58 | 59 | {"ab:cd:ef:AB:CD:EF", HardwareAddr{0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef}, ""}, 60 | {"ab:cd:ef:AB:CD:EF:ab:cd", HardwareAddr{0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef, 0xab, 0xcd}, ""}, 61 | { 62 | "ab:cd:ef:AB:CD:EF:ab:cd:ef:AB:CD:EF:ab:cd:ef:AB:CD:EF:ab:cd", 63 | HardwareAddr{ 64 | 0xab, 0xcd, 0xef, 0xab, 65 | 0xcd, 0xef, 0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef, 66 | 0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef, 0xab, 0xcd, 67 | }, 68 | "", 69 | }, 70 | 71 | {"01.02.03.04.05.06", nil, "invalid MAC address"}, 72 | {"01:02:03:04:05:06:", nil, "invalid MAC address"}, 73 | {"x1:02:03:04:05:06", nil, "invalid MAC address"}, 74 | {"01002:03:04:05:06", nil, "invalid MAC address"}, 75 | {"01:02003:04:05:06", nil, "invalid MAC address"}, 76 | {"01:02:03004:05:06", nil, "invalid MAC address"}, 77 | {"01:02:03:04005:06", nil, "invalid MAC address"}, 78 | {"01:02:03:04:05006", nil, "invalid MAC address"}, 79 | {"01-02:03:04:05:06", nil, "invalid MAC address"}, 80 | {"01:02-03-04-05-06", nil, "invalid MAC address"}, 81 | {"0123:4567:89AF", nil, "invalid MAC address"}, 82 | {"0123-4567-89AF", nil, "invalid MAC address"}, 83 | } 84 | 85 | func TestParseMAC(t *testing.T) { 86 | match := func(err error, s string) bool { 87 | if s == "" { 88 | return err == nil 89 | } 90 | return err != nil && strings.Contains(err.Error(), s) 91 | } 92 | 93 | for i, tt := range parseMACTests { 94 | out, err := ParseMAC(tt.in) 95 | if !reflect.DeepEqual(out, tt.out) || !match(err, tt.err) { 96 | t.Errorf("ParseMAC(%q) = %v, %v, want %v, %v", tt.in, out, err, tt.out, tt.err) 97 | } 98 | if tt.err == "" { 99 | // Verify that serialization works too, and that it round-trips. 100 | s := out.String() 101 | out2, err := ParseMAC(s) 102 | if err != nil { 103 | t.Errorf("%d. ParseMAC(%q) = %v", i, s, err) 104 | continue 105 | } 106 | if !reflect.DeepEqual(out2, out) { 107 | t.Errorf("%d. ParseMAC(%q) = %v, want %v", i, s, out2, out) 108 | } 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /net.go: -------------------------------------------------------------------------------- 1 | // TINYGO: The following is copied and modified from Go 1.21.4 official implementation. 2 | 3 | // Copyright 2009 The Go Authors. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | 7 | package net 8 | 9 | import ( 10 | "errors" 11 | "io" 12 | "time" 13 | ) 14 | 15 | // Addr represents a network end point address. 16 | // 17 | // The two methods Network and String conventionally return strings 18 | // that can be passed as the arguments to Dial, but the exact form 19 | // and meaning of the strings is up to the implementation. 20 | type Addr interface { 21 | Network() string // name of the network (for example, "tcp", "udp") 22 | String() string // string form of address (for example, "192.0.2.1:25", "[2001:db8::1]:80") 23 | } 24 | 25 | // Conn is a generic stream-oriented network connection. 26 | // 27 | // Multiple goroutines may invoke methods on a Conn simultaneously. 28 | type Conn interface { 29 | // Read reads data from the connection. 30 | // Read can be made to time out and return an error after a fixed 31 | // time limit; see SetDeadline and SetReadDeadline. 32 | Read(b []byte) (n int, err error) 33 | 34 | // Write writes data to the connection. 35 | // Write can be made to time out and return an error after a fixed 36 | // time limit; see SetDeadline and SetWriteDeadline. 37 | Write(b []byte) (n int, err error) 38 | 39 | // Close closes the connection. 40 | // Any blocked Read or Write operations will be unblocked and return errors. 41 | Close() error 42 | 43 | // LocalAddr returns the local network address, if known. 44 | LocalAddr() Addr 45 | 46 | // RemoteAddr returns the remote network address, if known. 47 | RemoteAddr() Addr 48 | 49 | // SetDeadline sets the read and write deadlines associated 50 | // with the connection. It is equivalent to calling both 51 | // SetReadDeadline and SetWriteDeadline. 52 | // 53 | // A deadline is an absolute time after which I/O operations 54 | // fail instead of blocking. The deadline applies to all future 55 | // and pending I/O, not just the immediately following call to 56 | // Read or Write. After a deadline has been exceeded, the 57 | // connection can be refreshed by setting a deadline in the future. 58 | // 59 | // If the deadline is exceeded a call to Read or Write or to other 60 | // I/O methods will return an error that wraps os.ErrDeadlineExceeded. 61 | // This can be tested using errors.Is(err, os.ErrDeadlineExceeded). 62 | // The error's Timeout method will return true, but note that there 63 | // are other possible errors for which the Timeout method will 64 | // return true even if the deadline has not been exceeded. 65 | // 66 | // An idle timeout can be implemented by repeatedly extending 67 | // the deadline after successful Read or Write calls. 68 | // 69 | // A zero value for t means I/O operations will not time out. 70 | SetDeadline(t time.Time) error 71 | 72 | // SetReadDeadline sets the deadline for future Read calls 73 | // and any currently-blocked Read call. 74 | // A zero value for t means Read will not time out. 75 | SetReadDeadline(t time.Time) error 76 | 77 | // SetWriteDeadline sets the deadline for future Write calls 78 | // and any currently-blocked Write call. 79 | // Even if write times out, it may return n > 0, indicating that 80 | // some of the data was successfully written. 81 | // A zero value for t means Write will not time out. 82 | SetWriteDeadline(t time.Time) error 83 | } 84 | 85 | type conn struct { 86 | // TINYGO: no fd defined 87 | } 88 | 89 | // Close closes the connection. 90 | func (c *conn) Close() error { 91 | return errors.New("conn.Close not implemented") 92 | } 93 | 94 | // LocalAddr returns the local network address. 95 | // The Addr returned is shared by all invocations of LocalAddr, so 96 | // do not modify it. 97 | func (c *conn) LocalAddr() Addr { 98 | // TINYGO: not implemented 99 | return nil 100 | } 101 | 102 | // SetDeadline implements the Conn SetDeadline method. 103 | func (c *conn) SetDeadline(t time.Time) error { 104 | return errors.New("conn.SetDeadline not implemented") 105 | } 106 | 107 | // SetReadDeadline implements the Conn SetReadDeadline method. 108 | func (c *conn) SetReadDeadline(t time.Time) error { 109 | return errors.New("conn.SetReadDeadline not implemented") 110 | } 111 | 112 | // SetWriteDeadline implements the Conn SetWriteDeadline method. 113 | func (c *conn) SetWriteDeadline(t time.Time) error { 114 | return errors.New("conn.SetWriteDeadline not implemented") 115 | } 116 | 117 | // PacketConn is a generic packet-oriented network connection. 118 | // 119 | // Multiple goroutines may invoke methods on a PacketConn simultaneously. 120 | type PacketConn interface { 121 | // ReadFrom reads a packet from the connection, 122 | // copying the payload into p. It returns the number of 123 | // bytes copied into p and the return address that 124 | // was on the packet. 125 | // It returns the number of bytes read (0 <= n <= len(p)) 126 | // and any error encountered. Callers should always process 127 | // the n > 0 bytes returned before considering the error err. 128 | // ReadFrom can be made to time out and return an error after a 129 | // fixed time limit; see SetDeadline and SetReadDeadline. 130 | ReadFrom(p []byte) (n int, addr Addr, err error) 131 | 132 | // WriteTo writes a packet with payload p to addr. 133 | // WriteTo can be made to time out and return an Error after a 134 | // fixed time limit; see SetDeadline and SetWriteDeadline. 135 | // On packet-oriented connections, write timeouts are rare. 136 | WriteTo(p []byte, addr Addr) (n int, err error) 137 | 138 | // Close closes the connection. 139 | // Any blocked ReadFrom or WriteTo operations will be unblocked and return errors. 140 | Close() error 141 | 142 | // LocalAddr returns the local network address, if known. 143 | LocalAddr() Addr 144 | 145 | // SetDeadline sets the read and write deadlines associated 146 | // with the connection. It is equivalent to calling both 147 | // SetReadDeadline and SetWriteDeadline. 148 | // 149 | // A deadline is an absolute time after which I/O operations 150 | // fail instead of blocking. The deadline applies to all future 151 | // and pending I/O, not just the immediately following call to 152 | // Read or Write. After a deadline has been exceeded, the 153 | // connection can be refreshed by setting a deadline in the future. 154 | // 155 | // If the deadline is exceeded a call to Read or Write or to other 156 | // I/O methods will return an error that wraps os.ErrDeadlineExceeded. 157 | // This can be tested using errors.Is(err, os.ErrDeadlineExceeded). 158 | // The error's Timeout method will return true, but note that there 159 | // are other possible errors for which the Timeout method will 160 | // return true even if the deadline has not been exceeded. 161 | // 162 | // An idle timeout can be implemented by repeatedly extending 163 | // the deadline after successful ReadFrom or WriteTo calls. 164 | // 165 | // A zero value for t means I/O operations will not time out. 166 | SetDeadline(t time.Time) error 167 | 168 | // SetReadDeadline sets the deadline for future ReadFrom calls 169 | // and any currently-blocked ReadFrom call. 170 | // A zero value for t means ReadFrom will not time out. 171 | SetReadDeadline(t time.Time) error 172 | 173 | // SetWriteDeadline sets the deadline for future WriteTo calls 174 | // and any currently-blocked WriteTo call. 175 | // Even if write times out, it may return n > 0, indicating that 176 | // some of the data was successfully written. 177 | // A zero value for t means WriteTo will not time out. 178 | SetWriteDeadline(t time.Time) error 179 | } 180 | 181 | // A Listener is a generic network listener for stream-oriented protocols. 182 | // 183 | // Multiple goroutines may invoke methods on a Listener simultaneously. 184 | type Listener interface { 185 | // Accept waits for and returns the next connection to the listener. 186 | Accept() (Conn, error) 187 | 188 | // Close closes the listener. 189 | // Any blocked Accept operations will be unblocked and return errors. 190 | Close() error 191 | 192 | // Addr returns the listener's network address. 193 | Addr() Addr 194 | } 195 | 196 | type UnknownNetworkError string 197 | 198 | func (e UnknownNetworkError) Error() string { return "unknown network " + string(e) } 199 | func (e UnknownNetworkError) Timeout() bool { return false } 200 | func (e UnknownNetworkError) Temporary() bool { return false } 201 | 202 | // An Error represents a network error. 203 | type Error interface { 204 | error 205 | Timeout() bool // Is the error a timeout? 206 | 207 | // Deprecated: Temporary errors are not well-defined. 208 | // Most "temporary" errors are timeouts, and the few exceptions are surprising. 209 | // Do not use this method. 210 | Temporary() bool 211 | } 212 | 213 | // OpError is the error type usually returned by functions in the net 214 | // package. It describes the operation, network type, and address of 215 | // an error. 216 | type OpError struct { 217 | // Op is the operation which caused the error, such as 218 | // "read" or "write". 219 | Op string 220 | 221 | // Net is the network type on which this error occurred, 222 | // such as "tcp" or "udp6". 223 | Net string 224 | 225 | // For operations involving a remote network connection, like 226 | // Dial, Read, or Write, Source is the corresponding local 227 | // network address. 228 | Source Addr 229 | 230 | // Addr is the network address for which this error occurred. 231 | // For local operations, like Listen or SetDeadline, Addr is 232 | // the address of the local endpoint being manipulated. 233 | // For operations involving a remote network connection, like 234 | // Dial, Read, or Write, Addr is the remote address of that 235 | // connection. 236 | Addr Addr 237 | 238 | // Err is the error that occurred during the operation. 239 | // The Error method panics if the error is nil. 240 | Err error 241 | } 242 | 243 | func (e *OpError) Unwrap() error { return e.Err } 244 | 245 | func (e *OpError) Error() string { 246 | if e == nil { 247 | return "" 248 | } 249 | s := e.Op 250 | if e.Net != "" { 251 | s += " " + e.Net 252 | } 253 | if e.Source != nil { 254 | s += " " + e.Source.String() 255 | } 256 | if e.Addr != nil { 257 | if e.Source != nil { 258 | s += "->" 259 | } else { 260 | s += " " 261 | } 262 | s += e.Addr.String() 263 | } 264 | s += ": " + e.Err.Error() 265 | return s 266 | } 267 | 268 | type timeout interface { 269 | Timeout() bool 270 | } 271 | 272 | func (e *OpError) Timeout() bool { 273 | t, ok := e.Err.(timeout) 274 | return ok && t.Timeout() 275 | } 276 | 277 | // A ParseError is the error type of literal network address parsers. 278 | type ParseError struct { 279 | // Type is the type of string that was expected, such as 280 | // "IP address", "CIDR address". 281 | Type string 282 | 283 | // Text is the malformed text string. 284 | Text string 285 | } 286 | 287 | func (e *ParseError) Error() string { return "invalid " + e.Type + ": " + e.Text } 288 | 289 | type AddrError struct { 290 | Err string 291 | Addr string 292 | } 293 | 294 | func (e *AddrError) Error() string { 295 | if e == nil { 296 | return "" 297 | } 298 | s := e.Err 299 | if e.Addr != "" { 300 | s = "address " + e.Addr + ": " + s 301 | } 302 | return s 303 | } 304 | 305 | // errNetClosing is the type of the variable ErrNetClosing. 306 | // This is used to implement the net.Error interface. 307 | type errNetClosing struct{} 308 | 309 | // Error returns the error message for ErrNetClosing. 310 | // Keep this string consistent because of issue #4373: 311 | // since historically programs have not been able to detect 312 | // this error, they look for the string. 313 | func (e errNetClosing) Error() string { return "use of closed network connection" } 314 | 315 | func (e errNetClosing) Timeout() bool { return false } 316 | func (e errNetClosing) Temporary() bool { return false } 317 | 318 | // errClosed exists just so that the docs for ErrClosed don't mention 319 | // the internal package poll. 320 | var errClosed = errNetClosing{} 321 | 322 | // ErrClosed is the error returned by an I/O call on a network 323 | // connection that has already been closed, or that is closed by 324 | // another goroutine before the I/O is completed. This may be wrapped 325 | // in another error, and should normally be tested using 326 | // errors.Is(err, net.ErrClosed). 327 | var ErrClosed error = errClosed 328 | 329 | // buffersWriter is the interface implemented by Conns that support a 330 | // "writev"-like batch write optimization. 331 | // writeBuffers should fully consume and write all chunks from the 332 | // provided Buffers, else it should report a non-nil error. 333 | type buffersWriter interface { 334 | writeBuffers(*Buffers) (int64, error) 335 | } 336 | 337 | // Buffers contains zero or more runs of bytes to write. 338 | // 339 | // On certain machines, for certain types of connections, this is 340 | // optimized into an OS-specific batch write operation (such as 341 | // "writev"). 342 | type Buffers [][]byte 343 | 344 | var ( 345 | _ io.WriterTo = (*Buffers)(nil) 346 | _ io.Reader = (*Buffers)(nil) 347 | ) 348 | 349 | // WriteTo writes contents of the buffers to w. 350 | // 351 | // WriteTo implements [io.WriterTo] for [Buffers]. 352 | // 353 | // WriteTo modifies the slice v as well as v[i] for 0 <= i < len(v), 354 | // but does not modify v[i][j] for any i, j. 355 | func (v *Buffers) WriteTo(w io.Writer) (n int64, err error) { 356 | if wv, ok := w.(buffersWriter); ok { 357 | return wv.writeBuffers(v) 358 | } 359 | for _, b := range *v { 360 | nb, err := w.Write(b) 361 | n += int64(nb) 362 | if err != nil { 363 | v.consume(n) 364 | return n, err 365 | } 366 | } 367 | v.consume(n) 368 | return n, nil 369 | } 370 | 371 | // Read from the buffers. 372 | // 373 | // Read implements [io.Reader] for [Buffers]. 374 | // 375 | // Read modifies the slice v as well as v[i] for 0 <= i < len(v), 376 | // but does not modify v[i][j] for any i, j. 377 | func (v *Buffers) Read(p []byte) (n int, err error) { 378 | for len(p) > 0 && len(*v) > 0 { 379 | n0 := copy(p, (*v)[0]) 380 | v.consume(int64(n0)) 381 | p = p[n0:] 382 | n += n0 383 | } 384 | if len(*v) == 0 { 385 | err = io.EOF 386 | } 387 | return 388 | } 389 | 390 | func (v *Buffers) consume(n int64) { 391 | for len(*v) > 0 { 392 | ln0 := int64(len((*v)[0])) 393 | if ln0 > n { 394 | (*v)[0] = (*v)[0][n:] 395 | return 396 | } 397 | n -= ln0 398 | (*v)[0] = nil 399 | *v = (*v)[1:] 400 | } 401 | } 402 | -------------------------------------------------------------------------------- /netdev.go: -------------------------------------------------------------------------------- 1 | // L3/L4 network/transport layer 2 | 3 | package net 4 | 5 | import ( 6 | "errors" 7 | "net/netip" 8 | "time" 9 | ) 10 | 11 | const ( 12 | _AF_INET = 0x2 13 | _SOCK_STREAM = 0x1 14 | _SOCK_DGRAM = 0x2 15 | _SOL_SOCKET = 0x1 16 | _SO_KEEPALIVE = 0x9 17 | _SO_LINGER = 0xd 18 | _SOL_TCP = 0x6 19 | _TCP_KEEPINTVL = 0x5 20 | _IPPROTO_TCP = 0x6 21 | _IPPROTO_UDP = 0x11 22 | // Made up, not a real IP protocol number. This is used to create a 23 | // TLS socket on the device, assuming the device supports mbed TLS. 24 | _IPPROTO_TLS = 0xFE 25 | _F_SETFL = 0x4 26 | ) 27 | 28 | // netdev is the current netdev, set by the application with useNetdev(). 29 | // 30 | // Initialized to a NOP netdev that errors out cleanly in case netdev was not 31 | // explicitly set with useNetdev(). 32 | var netdev netdever = &nopNetdev{} 33 | 34 | // (useNetdev is go:linkname'd from tinygo/drivers package) 35 | func useNetdev(dev netdever) { 36 | netdev = dev 37 | } 38 | 39 | // netdever is TinyGo's OSI L3/L4 network/transport layer interface. Network 40 | // drivers implement the netdever interface, providing a common network L3/L4 41 | // interface to TinyGo's "net" package. net.Conn implementations (TCPConn, 42 | // UDPConn, and TLSConn) use the netdever interface for device I/O access. 43 | // 44 | // A netdever is passed to the "net" package using net.useNetdev(). 45 | // 46 | // Just like a net.Conn, multiple goroutines may invoke methods on a netdever 47 | // simultaneously. 48 | // 49 | // NOTE: The netdever interface is mirrored in drivers/netdev/netdev.go. 50 | // NOTE: If making changes to this interface, mirror the changes in 51 | // NOTE: drivers/netdev/netdev.go, and vice-versa. 52 | 53 | type netdever interface { 54 | 55 | // GetHostByName returns the IP address of either a hostname or IPv4 56 | // address in standard dot notation 57 | GetHostByName(name string) (netip.Addr, error) 58 | 59 | // Addr returns IP address assigned to the interface, either by 60 | // DHCP or statically 61 | Addr() (netip.Addr, error) 62 | 63 | // # Socket Address family/domain argument 64 | // 65 | // Socket address families specifies a communication domain: 66 | // - AF_UNIX, AF_LOCAL(synonyms): Local communication For further information, see unix(7). 67 | // - AF_INET: IPv4 Internet protocols. For further information, see ip(7). 68 | // 69 | // # Socket type argument 70 | // 71 | // Socket types which specifies the communication semantics. 72 | // - SOCK_STREAM: Provides sequenced, reliable, two-way, connection-based 73 | // byte streams. An out-of-band data transmission mechanism may be supported. 74 | // - SOCK_DGRAM: Supports datagrams (connectionless, unreliable messages of 75 | // a fixed maximum length). 76 | // 77 | // The type argument serves a second purpose: in addition to specifying a 78 | // socket type, it may include the bitwise OR of any of the following values, 79 | // to modify the behavior of socket(): 80 | // - SOCK_NONBLOCK: Set the O_NONBLOCK file status flag on the open file description. 81 | // 82 | // # Socket protocol argument 83 | // 84 | // The protocol specifies a particular protocol to be used with the 85 | // socket. Normally only a single protocol exists to support a 86 | // particular socket type within a given protocol family, in which 87 | // case protocol can be specified as 0. However, it is possible 88 | // that many protocols may exist, in which case a particular 89 | // protocol must be specified in this manner. 90 | // 91 | // # Return value 92 | // 93 | // On success, a file descriptor for the new socket is returned. Quoting man pages: 94 | // "On error, -1 is returned, and errno is set to indicate the error." Since 95 | // this is not C we may use a error type native to Go to represent the error 96 | // ocurred which by itself not only notifies of an error but also provides 97 | // information on the error as a human readable string when calling the Error method. 98 | Socket(domain int, stype int, protocol int) (sockfd int, _ error) 99 | Bind(sockfd int, ip netip.AddrPort) error 100 | Connect(sockfd int, host string, ip netip.AddrPort) error 101 | Listen(sockfd int, backlog int) error 102 | Accept(sockfd int) (int, netip.AddrPort, error) 103 | 104 | // # Flags argument on Send and Recv 105 | // 106 | // The flags argument is formed by ORing one or more of the following values: 107 | // - MSG_CMSG_CLOEXEC: Set the close-on-exec flag for the file descriptor. Unix. 108 | // - MSG_DONTWAIT: Enables nonblocking operation. If call would block then returns error. 109 | // - MSG_ERRQUEUE: (see manpage) his flag specifies that queued errors should be received 110 | // from the socket error queue. 111 | // - MSG_OOB: his flag requests receipt of out-of-band data that would not be received in the normal data stream. 112 | // - MSG_PEEK: This flag causes the receive operation to return data from 113 | // the beginning of the receive queue without removing that data from the queue. 114 | // - MSG_TRUNC: Ask for real length of datagram even when it was longer than passed buffer. 115 | // - MSG_WAITALL: This flag requests that the operation block until the full request is satisfied. 116 | 117 | Send(sockfd int, buf []byte, flags int, deadline time.Time) (int, error) 118 | Recv(sockfd int, buf []byte, flags int, deadline time.Time) (int, error) 119 | Close(sockfd int) error 120 | 121 | // SetSockOpt manipulates options for the socket 122 | // referred to by the file descriptor sockfd. Options may exist at 123 | // multiple protocol levels; they are always present at the 124 | // uppermost socket level. 125 | // 126 | // # Level argument 127 | // 128 | // When manipulating socket options, the level at which the option 129 | // resides and the name of the option must be specified. To 130 | // manipulate options at the sockets API level, level is specified 131 | // as SOL_SOCKET. To manipulate options at any other level the 132 | // protocol number of the appropriate protocol controlling the 133 | // option is supplied. For example, to indicate that an option is 134 | // to be interpreted by the TCP protocol, level should be set to the 135 | // protocol number of TCP; see getprotoent(3). 136 | // 137 | // # Option argument 138 | // 139 | // The arguments optval and optlen are used to access option values 140 | // for setsockopt(). For getsockopt() they identify a buffer in 141 | // which the value for the requested option(s) are to be returned. 142 | // In Go we provide developers with an `any` interface to be able 143 | // to pass driver-specific configurations. 144 | SetSockOpt(sockfd int, level int, opt int, value interface{}) error 145 | } 146 | 147 | var ErrNetdevNotSet = errors.New("Netdev not set") 148 | 149 | // nopNetdev is a NOP netdev that errors out any interface calls 150 | type nopNetdev struct { 151 | } 152 | 153 | func (n *nopNetdev) GetHostByName(name string) (netip.Addr, error) { 154 | return netip.Addr{}, ErrNetdevNotSet 155 | } 156 | func (n *nopNetdev) Addr() (netip.Addr, error) { return netip.Addr{}, ErrNetdevNotSet } 157 | func (n *nopNetdev) Socket(domain int, stype int, protocol int) (sockfd int, _ error) { 158 | return -1, ErrNetdevNotSet 159 | } 160 | func (n *nopNetdev) Bind(sockfd int, ip netip.AddrPort) error { return ErrNetdevNotSet } 161 | func (n *nopNetdev) Connect(sockfd int, host string, ip netip.AddrPort) error { return ErrNetdevNotSet } 162 | func (n *nopNetdev) Listen(sockfd int, backlog int) error { return ErrNetdevNotSet } 163 | func (n *nopNetdev) Accept(sockfd int) (int, netip.AddrPort, error) { 164 | return -1, netip.AddrPort{}, ErrNetdevNotSet 165 | } 166 | func (n *nopNetdev) Send(sockfd int, buf []byte, flags int, deadline time.Time) (int, error) { 167 | return -1, ErrNetdevNotSet 168 | } 169 | func (n *nopNetdev) Recv(sockfd int, buf []byte, flags int, deadline time.Time) (int, error) { 170 | return -1, ErrNetdevNotSet 171 | } 172 | func (n *nopNetdev) Close(sockfd int) error { return ErrNetdevNotSet } 173 | func (n *nopNetdev) SetSockOpt(sockfd int, level int, opt int, value interface{}) error { 174 | return ErrNetdevNotSet 175 | } 176 | -------------------------------------------------------------------------------- /parse.go: -------------------------------------------------------------------------------- 1 | // TINYGO: The following is copied from Go 1.21.4 official implementation. 2 | 3 | // Copyright 2009 The Go Authors. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | 7 | // Simple file i/o and string manipulation, to avoid 8 | // depending on strconv and bufio and strings. 9 | 10 | package net 11 | 12 | import ( 13 | "internal/bytealg" 14 | "io" 15 | "os" 16 | "time" 17 | ) 18 | 19 | type file struct { 20 | file *os.File 21 | data []byte 22 | atEOF bool 23 | } 24 | 25 | func (f *file) close() { f.file.Close() } 26 | 27 | func (f *file) getLineFromData() (s string, ok bool) { 28 | data := f.data 29 | i := 0 30 | for i = 0; i < len(data); i++ { 31 | if data[i] == '\n' { 32 | s = string(data[0:i]) 33 | ok = true 34 | // move data 35 | i++ 36 | n := len(data) - i 37 | copy(data[0:], data[i:]) 38 | f.data = data[0:n] 39 | return 40 | } 41 | } 42 | if f.atEOF && len(f.data) > 0 { 43 | // EOF, return all we have 44 | s = string(data) 45 | f.data = f.data[0:0] 46 | ok = true 47 | } 48 | return 49 | } 50 | 51 | func (f *file) readLine() (s string, ok bool) { 52 | if s, ok = f.getLineFromData(); ok { 53 | return 54 | } 55 | if len(f.data) < cap(f.data) { 56 | ln := len(f.data) 57 | n, err := io.ReadFull(f.file, f.data[ln:cap(f.data)]) 58 | if n >= 0 { 59 | f.data = f.data[0 : ln+n] 60 | } 61 | if err == io.EOF || err == io.ErrUnexpectedEOF { 62 | f.atEOF = true 63 | } 64 | } 65 | s, ok = f.getLineFromData() 66 | return 67 | } 68 | 69 | func open(name string) (*file, error) { 70 | fd, err := os.Open(name) 71 | if err != nil { 72 | return nil, err 73 | } 74 | return &file{fd, make([]byte, 0, 64*1024), false}, nil 75 | } 76 | 77 | func stat(name string) (mtime time.Time, size int64, err error) { 78 | st, err := os.Stat(name) 79 | if err != nil { 80 | return time.Time{}, 0, err 81 | } 82 | return st.ModTime(), st.Size(), nil 83 | } 84 | 85 | // Count occurrences in s of any bytes in t. 86 | func countAnyByte(s string, t string) int { 87 | n := 0 88 | for i := 0; i < len(s); i++ { 89 | if bytealg.IndexByteString(t, s[i]) >= 0 { 90 | n++ 91 | } 92 | } 93 | return n 94 | } 95 | 96 | // Split s at any bytes in t. 97 | func splitAtBytes(s string, t string) []string { 98 | a := make([]string, 1+countAnyByte(s, t)) 99 | n := 0 100 | last := 0 101 | for i := 0; i < len(s); i++ { 102 | if bytealg.IndexByteString(t, s[i]) >= 0 { 103 | if last < i { 104 | a[n] = s[last:i] 105 | n++ 106 | } 107 | last = i + 1 108 | } 109 | } 110 | if last < len(s) { 111 | a[n] = s[last:] 112 | n++ 113 | } 114 | return a[0:n] 115 | } 116 | 117 | func getFields(s string) []string { return splitAtBytes(s, " \r\t\n") } 118 | 119 | // Bigger than we need, not too big to worry about overflow 120 | const big = 0xFFFFFF 121 | 122 | // Decimal to integer. 123 | // Returns number, characters consumed, success. 124 | func dtoi(s string) (n int, i int, ok bool) { 125 | n = 0 126 | for i = 0; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ { 127 | n = n*10 + int(s[i]-'0') 128 | if n >= big { 129 | return big, i, false 130 | } 131 | } 132 | if i == 0 { 133 | return 0, 0, false 134 | } 135 | return n, i, true 136 | } 137 | 138 | // Hexadecimal to integer. 139 | // Returns number, characters consumed, success. 140 | func xtoi(s string) (n int, i int, ok bool) { 141 | n = 0 142 | for i = 0; i < len(s); i++ { 143 | if '0' <= s[i] && s[i] <= '9' { 144 | n *= 16 145 | n += int(s[i] - '0') 146 | } else if 'a' <= s[i] && s[i] <= 'f' { 147 | n *= 16 148 | n += int(s[i]-'a') + 10 149 | } else if 'A' <= s[i] && s[i] <= 'F' { 150 | n *= 16 151 | n += int(s[i]-'A') + 10 152 | } else { 153 | break 154 | } 155 | if n >= big { 156 | return 0, i, false 157 | } 158 | } 159 | if i == 0 { 160 | return 0, i, false 161 | } 162 | return n, i, true 163 | } 164 | 165 | // xtoi2 converts the next two hex digits of s into a byte. 166 | // If s is longer than 2 bytes then the third byte must be e. 167 | // If the first two bytes of s are not hex digits or the third byte 168 | // does not match e, false is returned. 169 | func xtoi2(s string, e byte) (byte, bool) { 170 | if len(s) > 2 && s[2] != e { 171 | return 0, false 172 | } 173 | n, ei, ok := xtoi(s[:2]) 174 | return byte(n), ok && ei == 2 175 | } 176 | 177 | // Convert i to a hexadecimal string. Leading zeros are not printed. 178 | func appendHex(dst []byte, i uint32) []byte { 179 | if i == 0 { 180 | return append(dst, '0') 181 | } 182 | for j := 7; j >= 0; j-- { 183 | v := i >> uint(j*4) 184 | if v > 0 { 185 | dst = append(dst, hexDigit[v&0xf]) 186 | } 187 | } 188 | return dst 189 | } 190 | 191 | // Number of occurrences of b in s. 192 | func count(s string, b byte) int { 193 | n := 0 194 | for i := 0; i < len(s); i++ { 195 | if s[i] == b { 196 | n++ 197 | } 198 | } 199 | return n 200 | } 201 | 202 | // Index of rightmost occurrence of b in s. 203 | func last(s string, b byte) int { 204 | i := len(s) 205 | for i--; i >= 0; i-- { 206 | if s[i] == b { 207 | break 208 | } 209 | } 210 | return i 211 | } 212 | 213 | // hasUpperCase tells whether the given string contains at least one upper-case. 214 | func hasUpperCase(s string) bool { 215 | for i := range s { 216 | if 'A' <= s[i] && s[i] <= 'Z' { 217 | return true 218 | } 219 | } 220 | return false 221 | } 222 | 223 | // lowerASCIIBytes makes x ASCII lowercase in-place. 224 | func lowerASCIIBytes(x []byte) { 225 | for i, b := range x { 226 | if 'A' <= b && b <= 'Z' { 227 | x[i] += 'a' - 'A' 228 | } 229 | } 230 | } 231 | 232 | // lowerASCII returns the ASCII lowercase version of b. 233 | func lowerASCII(b byte) byte { 234 | if 'A' <= b && b <= 'Z' { 235 | return b + ('a' - 'A') 236 | } 237 | return b 238 | } 239 | 240 | // trimSpace returns x without any leading or trailing ASCII whitespace. 241 | func trimSpace(x string) string { 242 | for len(x) > 0 && isSpace(x[0]) { 243 | x = x[1:] 244 | } 245 | for len(x) > 0 && isSpace(x[len(x)-1]) { 246 | x = x[:len(x)-1] 247 | } 248 | return x 249 | } 250 | 251 | // isSpace reports whether b is an ASCII space character. 252 | func isSpace(b byte) bool { 253 | return b == ' ' || b == '\t' || b == '\n' || b == '\r' 254 | } 255 | 256 | // removeComment returns line, removing any '#' byte and any following 257 | // bytes. 258 | func removeComment(line string) string { 259 | if i := bytealg.IndexByteString(line, '#'); i != -1 { 260 | return line[:i] 261 | } 262 | return line 263 | } 264 | 265 | // foreachField runs fn on each non-empty run of non-space bytes in x. 266 | // It returns the first non-nil error returned by fn. 267 | func foreachField(x string, fn func(field string) error) error { 268 | x = trimSpace(x) 269 | for len(x) > 0 { 270 | sp := bytealg.IndexByteString(x, ' ') 271 | if sp == -1 { 272 | return fn(x) 273 | } 274 | if field := trimSpace(x[:sp]); len(field) > 0 { 275 | if err := fn(field); err != nil { 276 | return err 277 | } 278 | } 279 | x = trimSpace(x[sp+1:]) 280 | } 281 | return nil 282 | } 283 | 284 | // stringsHasSuffix is strings.HasSuffix. It reports whether s ends in 285 | // suffix. 286 | func stringsHasSuffix(s, suffix string) bool { 287 | return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix 288 | } 289 | 290 | // stringsHasSuffixFold reports whether s ends in suffix, 291 | // ASCII-case-insensitively. 292 | func stringsHasSuffixFold(s, suffix string) bool { 293 | return len(s) >= len(suffix) && stringsEqualFold(s[len(s)-len(suffix):], suffix) 294 | } 295 | 296 | // stringsHasPrefix is strings.HasPrefix. It reports whether s begins with prefix. 297 | func stringsHasPrefix(s, prefix string) bool { 298 | return len(s) >= len(prefix) && s[:len(prefix)] == prefix 299 | } 300 | 301 | // stringsEqualFold is strings.EqualFold, ASCII only. It reports whether s and t 302 | // are equal, ASCII-case-insensitively. 303 | func stringsEqualFold(s, t string) bool { 304 | if len(s) != len(t) { 305 | return false 306 | } 307 | for i := 0; i < len(s); i++ { 308 | if lowerASCII(s[i]) != lowerASCII(t[i]) { 309 | return false 310 | } 311 | } 312 | return true 313 | } 314 | 315 | func readFull(r io.Reader) (all []byte, err error) { 316 | buf := make([]byte, 1024) 317 | for { 318 | n, err := r.Read(buf) 319 | all = append(all, buf[:n]...) 320 | if err == io.EOF { 321 | return all, nil 322 | } 323 | if err != nil { 324 | return nil, err 325 | } 326 | } 327 | } 328 | -------------------------------------------------------------------------------- /pipe.go: -------------------------------------------------------------------------------- 1 | // The following is copied from Go 1.21.4 official implementation. 2 | 3 | // Copyright 2010 The Go Authors. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | 7 | package net 8 | 9 | import ( 10 | "io" 11 | "os" 12 | "sync" 13 | "time" 14 | ) 15 | 16 | // pipeDeadline is an abstraction for handling timeouts. 17 | type pipeDeadline struct { 18 | mu sync.Mutex // Guards timer and cancel 19 | timer *time.Timer 20 | cancel chan struct{} // Must be non-nil 21 | } 22 | 23 | func makePipeDeadline() pipeDeadline { 24 | return pipeDeadline{cancel: make(chan struct{})} 25 | } 26 | 27 | // set sets the point in time when the deadline will time out. 28 | // A timeout event is signaled by closing the channel returned by waiter. 29 | // Once a timeout has occurred, the deadline can be refreshed by specifying a 30 | // t value in the future. 31 | // 32 | // A zero value for t prevents timeout. 33 | func (d *pipeDeadline) set(t time.Time) { 34 | d.mu.Lock() 35 | defer d.mu.Unlock() 36 | 37 | if d.timer != nil && !d.timer.Stop() { 38 | <-d.cancel // Wait for the timer callback to finish and close cancel 39 | } 40 | d.timer = nil 41 | 42 | // Time is zero, then there is no deadline. 43 | closed := isClosedChan(d.cancel) 44 | if t.IsZero() { 45 | if closed { 46 | d.cancel = make(chan struct{}) 47 | } 48 | return 49 | } 50 | 51 | // Time in the future, setup a timer to cancel in the future. 52 | if dur := time.Until(t); dur > 0 { 53 | if closed { 54 | d.cancel = make(chan struct{}) 55 | } 56 | d.timer = time.AfterFunc(dur, func() { 57 | close(d.cancel) 58 | }) 59 | return 60 | } 61 | 62 | // Time in the past, so close immediately. 63 | if !closed { 64 | close(d.cancel) 65 | } 66 | } 67 | 68 | // wait returns a channel that is closed when the deadline is exceeded. 69 | func (d *pipeDeadline) wait() chan struct{} { 70 | d.mu.Lock() 71 | defer d.mu.Unlock() 72 | return d.cancel 73 | } 74 | 75 | func isClosedChan(c <-chan struct{}) bool { 76 | select { 77 | case <-c: 78 | return true 79 | default: 80 | return false 81 | } 82 | } 83 | 84 | type pipeAddr struct{} 85 | 86 | func (pipeAddr) Network() string { return "pipe" } 87 | func (pipeAddr) String() string { return "pipe" } 88 | 89 | type pipe struct { 90 | wrMu sync.Mutex // Serialize Write operations 91 | 92 | // Used by local Read to interact with remote Write. 93 | // Successful receive on rdRx is always followed by send on rdTx. 94 | rdRx <-chan []byte 95 | rdTx chan<- int 96 | 97 | // Used by local Write to interact with remote Read. 98 | // Successful send on wrTx is always followed by receive on wrRx. 99 | wrTx chan<- []byte 100 | wrRx <-chan int 101 | 102 | once sync.Once // Protects closing localDone 103 | localDone chan struct{} 104 | remoteDone <-chan struct{} 105 | 106 | readDeadline pipeDeadline 107 | writeDeadline pipeDeadline 108 | } 109 | 110 | // Pipe creates a synchronous, in-memory, full duplex 111 | // network connection; both ends implement the Conn interface. 112 | // Reads on one end are matched with writes on the other, 113 | // copying data directly between the two; there is no internal 114 | // buffering. 115 | func Pipe() (Conn, Conn) { 116 | cb1 := make(chan []byte) 117 | cb2 := make(chan []byte) 118 | cn1 := make(chan int) 119 | cn2 := make(chan int) 120 | done1 := make(chan struct{}) 121 | done2 := make(chan struct{}) 122 | 123 | p1 := &pipe{ 124 | rdRx: cb1, rdTx: cn1, 125 | wrTx: cb2, wrRx: cn2, 126 | localDone: done1, remoteDone: done2, 127 | readDeadline: makePipeDeadline(), 128 | writeDeadline: makePipeDeadline(), 129 | } 130 | p2 := &pipe{ 131 | rdRx: cb2, rdTx: cn2, 132 | wrTx: cb1, wrRx: cn1, 133 | localDone: done2, remoteDone: done1, 134 | readDeadline: makePipeDeadline(), 135 | writeDeadline: makePipeDeadline(), 136 | } 137 | return p1, p2 138 | } 139 | 140 | func (*pipe) LocalAddr() Addr { return pipeAddr{} } 141 | func (*pipe) RemoteAddr() Addr { return pipeAddr{} } 142 | 143 | func (p *pipe) Read(b []byte) (int, error) { 144 | n, err := p.read(b) 145 | if err != nil && err != io.EOF && err != io.ErrClosedPipe { 146 | err = &OpError{Op: "read", Net: "pipe", Err: err} 147 | } 148 | return n, err 149 | } 150 | 151 | func (p *pipe) read(b []byte) (n int, err error) { 152 | switch { 153 | case isClosedChan(p.localDone): 154 | return 0, io.ErrClosedPipe 155 | case isClosedChan(p.remoteDone): 156 | return 0, io.EOF 157 | case isClosedChan(p.readDeadline.wait()): 158 | return 0, os.ErrDeadlineExceeded 159 | } 160 | 161 | select { 162 | case bw := <-p.rdRx: 163 | nr := copy(b, bw) 164 | p.rdTx <- nr 165 | return nr, nil 166 | case <-p.localDone: 167 | return 0, io.ErrClosedPipe 168 | case <-p.remoteDone: 169 | return 0, io.EOF 170 | case <-p.readDeadline.wait(): 171 | return 0, os.ErrDeadlineExceeded 172 | } 173 | } 174 | 175 | func (p *pipe) Write(b []byte) (int, error) { 176 | n, err := p.write(b) 177 | if err != nil && err != io.ErrClosedPipe { 178 | err = &OpError{Op: "write", Net: "pipe", Err: err} 179 | } 180 | return n, err 181 | } 182 | 183 | func (p *pipe) write(b []byte) (n int, err error) { 184 | switch { 185 | case isClosedChan(p.localDone): 186 | return 0, io.ErrClosedPipe 187 | case isClosedChan(p.remoteDone): 188 | return 0, io.ErrClosedPipe 189 | case isClosedChan(p.writeDeadline.wait()): 190 | return 0, os.ErrDeadlineExceeded 191 | } 192 | 193 | p.wrMu.Lock() // Ensure entirety of b is written together 194 | defer p.wrMu.Unlock() 195 | for once := true; once || len(b) > 0; once = false { 196 | select { 197 | case p.wrTx <- b: 198 | nw := <-p.wrRx 199 | b = b[nw:] 200 | n += nw 201 | case <-p.localDone: 202 | return n, io.ErrClosedPipe 203 | case <-p.remoteDone: 204 | return n, io.ErrClosedPipe 205 | case <-p.writeDeadline.wait(): 206 | return n, os.ErrDeadlineExceeded 207 | } 208 | } 209 | return n, nil 210 | } 211 | 212 | func (p *pipe) SetDeadline(t time.Time) error { 213 | if isClosedChan(p.localDone) || isClosedChan(p.remoteDone) { 214 | return io.ErrClosedPipe 215 | } 216 | p.readDeadline.set(t) 217 | p.writeDeadline.set(t) 218 | return nil 219 | } 220 | 221 | func (p *pipe) SetReadDeadline(t time.Time) error { 222 | if isClosedChan(p.localDone) || isClosedChan(p.remoteDone) { 223 | return io.ErrClosedPipe 224 | } 225 | p.readDeadline.set(t) 226 | return nil 227 | } 228 | 229 | func (p *pipe) SetWriteDeadline(t time.Time) error { 230 | if isClosedChan(p.localDone) || isClosedChan(p.remoteDone) { 231 | return io.ErrClosedPipe 232 | } 233 | p.writeDeadline.set(t) 234 | return nil 235 | } 236 | 237 | func (p *pipe) Close() error { 238 | p.once.Do(func() { close(p.localDone) }) 239 | return nil 240 | } 241 | -------------------------------------------------------------------------------- /tcpsock.go: -------------------------------------------------------------------------------- 1 | // TINYGO: The following is copied and modified from Go 1.21.4 official implementation. 2 | 3 | // Copyright 2009 The Go Authors. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | 7 | package net 8 | 9 | import ( 10 | "errors" 11 | "fmt" 12 | "internal/itoa" 13 | "io" 14 | "net/netip" 15 | "strconv" 16 | "syscall" 17 | "time" 18 | ) 19 | 20 | // TCPAddr represents the address of a TCP end point. 21 | type TCPAddr struct { 22 | IP IP 23 | Port int 24 | Zone string // IPv6 scoped addressing zone 25 | } 26 | 27 | // AddrPort returns the TCPAddr a as a netip.AddrPort. 28 | // 29 | // If a.Port does not fit in a uint16, it's silently truncated. 30 | // 31 | // If a is nil, a zero value is returned. 32 | func (a *TCPAddr) AddrPort() netip.AddrPort { 33 | if a == nil { 34 | return netip.AddrPort{} 35 | } 36 | na, _ := netip.AddrFromSlice(a.IP) 37 | na = na.WithZone(a.Zone) 38 | return netip.AddrPortFrom(na, uint16(a.Port)) 39 | } 40 | 41 | // Network returns the address's network name, "tcp". 42 | func (a *TCPAddr) Network() string { return "tcp" } 43 | 44 | func (a *TCPAddr) String() string { 45 | if a == nil { 46 | return "" 47 | } 48 | ip := ipEmptyString(a.IP) 49 | if a.Zone != "" { 50 | return JoinHostPort(ip+"%"+a.Zone, itoa.Itoa(a.Port)) 51 | } 52 | return JoinHostPort(ip, itoa.Itoa(a.Port)) 53 | } 54 | 55 | func (a *TCPAddr) isWildcard() bool { 56 | if a == nil || a.IP == nil { 57 | return true 58 | } 59 | return a.IP.IsUnspecified() 60 | } 61 | 62 | func (a *TCPAddr) opAddr() Addr { 63 | if a == nil { 64 | return nil 65 | } 66 | return a 67 | } 68 | 69 | // ResolveTCPAddr returns an address of TCP end point. 70 | // 71 | // The network must be a TCP network name. 72 | // 73 | // If the host in the address parameter is not a literal IP address or 74 | // the port is not a literal port number, ResolveTCPAddr resolves the 75 | // address to an address of TCP end point. 76 | // Otherwise, it parses the address as a pair of literal IP address 77 | // and port number. 78 | // The address parameter can use a host name, but this is not 79 | // recommended, because it will return at most one of the host name's 80 | // IP addresses. 81 | // 82 | // See func Dial for a description of the network and address 83 | // parameters. 84 | func ResolveTCPAddr(network, address string) (*TCPAddr, error) { 85 | 86 | switch network { 87 | case "tcp", "tcp4": 88 | default: 89 | return nil, fmt.Errorf("Network '%s' not supported", network) 90 | } 91 | 92 | switch address { 93 | case ":http": 94 | address = ":80" 95 | } 96 | 97 | // TINYGO: Use netdev resolver 98 | 99 | host, sport, err := SplitHostPort(address) 100 | if err != nil { 101 | return nil, err 102 | } 103 | 104 | port, err := strconv.Atoi(sport) 105 | if err != nil { 106 | return nil, fmt.Errorf("Error parsing port '%s' in address: %s", 107 | sport, err) 108 | } 109 | 110 | if host == "" { 111 | return &TCPAddr{Port: port}, nil 112 | } 113 | 114 | ip, err := netdev.GetHostByName(host) 115 | if err != nil { 116 | return nil, fmt.Errorf("Lookup of host name '%s' failed: %s", host, err) 117 | } 118 | 119 | return &TCPAddr{IP: ip.AsSlice(), Port: port}, nil 120 | } 121 | 122 | // TCPAddrFromAddrPort returns addr as a TCPAddr. If addr.IsValid() is false, 123 | // then the returned TCPAddr will contain a nil IP field, indicating an 124 | // address family-agnostic unspecified address. 125 | func TCPAddrFromAddrPort(addr netip.AddrPort) *TCPAddr { 126 | return &TCPAddr{ 127 | IP: addr.Addr().AsSlice(), 128 | Zone: addr.Addr().Zone(), 129 | Port: int(addr.Port()), 130 | } 131 | } 132 | 133 | // TCPConn is an implementation of the Conn interface for TCP network 134 | // connections. 135 | type TCPConn struct { 136 | fd int 137 | net string 138 | laddr *TCPAddr 139 | raddr *TCPAddr 140 | readDeadline time.Time 141 | writeDeadline time.Time 142 | } 143 | 144 | // DialTCP acts like Dial for TCP networks. 145 | // 146 | // The network must be a TCP network name; see func Dial for details. 147 | // 148 | // If laddr is nil, a local address is automatically chosen. 149 | // If the IP field of raddr is nil or an unspecified IP address, the 150 | // local system is assumed. 151 | func DialTCP(network string, laddr, raddr *TCPAddr) (*TCPConn, error) { 152 | 153 | switch network { 154 | case "tcp", "tcp4": 155 | default: 156 | return nil, errors.New("Network not supported: '" + network + "'") 157 | } 158 | 159 | // TINYGO: Use netdev to create TCP socket and connect 160 | 161 | if raddr == nil { 162 | raddr = &TCPAddr{} 163 | } 164 | 165 | if raddr.IP.IsUnspecified() { 166 | return nil, errors.New("Sorry, localhost isn't available on Tinygo") 167 | } else if len(raddr.IP) != 4 { 168 | return nil, errors.New("only ipv4 supported") 169 | } 170 | 171 | fd, err := netdev.Socket(_AF_INET, _SOCK_STREAM, _IPPROTO_TCP) 172 | if err != nil { 173 | return nil, err 174 | } 175 | 176 | rip, _ := netip.AddrFromSlice(raddr.IP) 177 | raddrport := netip.AddrPortFrom(rip, uint16(raddr.Port)) 178 | if err = netdev.Connect(fd, "", raddrport); err != nil { 179 | netdev.Close(fd) 180 | return nil, err 181 | } 182 | 183 | return &TCPConn{ 184 | fd: fd, 185 | net: network, 186 | laddr: laddr, 187 | raddr: raddr, 188 | }, nil 189 | } 190 | 191 | // TINYGO: Use netdev for Conn methods: Read = Recv, Write = Send, etc. 192 | 193 | // SyscallConn returns a raw network connection. 194 | // This implements the syscall.Conn interface. 195 | func (c *TCPConn) SyscallConn() (syscall.RawConn, error) { 196 | return nil, errors.New("SyscallConn not implemented") 197 | } 198 | 199 | func (c *TCPConn) Read(b []byte) (int, error) { 200 | n, err := netdev.Recv(c.fd, b, 0, c.readDeadline) 201 | // Turn the -1 socket error into 0 and let err speak for error 202 | if n < 0 { 203 | n = 0 204 | } 205 | if err != nil && err != io.EOF { 206 | err = &OpError{Op: "read", Net: c.net, Source: c.laddr, Addr: c.raddr, Err: err} 207 | } 208 | return n, err 209 | } 210 | 211 | func (c *TCPConn) Write(b []byte) (int, error) { 212 | n, err := netdev.Send(c.fd, b, 0, c.writeDeadline) 213 | // Turn the -1 socket error into 0 and let err speak for error 214 | if n < 0 { 215 | n = 0 216 | } 217 | if err != nil { 218 | err = &OpError{Op: "write", Net: c.net, Source: c.laddr, Addr: c.raddr, Err: err} 219 | } 220 | return n, err 221 | } 222 | 223 | func (c *TCPConn) Close() error { 224 | return netdev.Close(c.fd) 225 | } 226 | 227 | func (c *TCPConn) LocalAddr() Addr { 228 | return c.laddr 229 | } 230 | 231 | func (c *TCPConn) RemoteAddr() Addr { 232 | return c.raddr 233 | } 234 | 235 | func (c *TCPConn) SetDeadline(t time.Time) error { 236 | c.readDeadline = t 237 | c.writeDeadline = t 238 | return nil 239 | } 240 | 241 | // SetLinger sets the behavior of Close on a connection which still 242 | // has data waiting to be sent or to be acknowledged. 243 | // 244 | // If sec < 0 (the default), the operating system finishes sending the 245 | // data in the background. 246 | // 247 | // If sec == 0, the operating system discards any unsent or 248 | // unacknowledged data. 249 | // 250 | // If sec > 0, the data is sent in the background as with sec < 0. 251 | // On some operating systems including Linux, this may cause Close to block 252 | // until all data has been sent or discarded. 253 | // On some operating systems after sec seconds have elapsed any remaining 254 | // unsent data may be discarded. 255 | func (c *TCPConn) SetLinger(sec int) error { 256 | return netdev.SetSockOpt(c.fd, _SOL_SOCKET, _SO_LINGER, sec) 257 | } 258 | 259 | // SetKeepAlive sets whether the operating system should send 260 | // keep-alive messages on the connection. 261 | func (c *TCPConn) SetKeepAlive(keepalive bool) error { 262 | return netdev.SetSockOpt(c.fd, _SOL_SOCKET, _SO_KEEPALIVE, keepalive) 263 | } 264 | 265 | // SetKeepAlivePeriod sets period between keep-alives. 266 | func (c *TCPConn) SetKeepAlivePeriod(d time.Duration) error { 267 | // Units are 1/2 seconds 268 | return netdev.SetSockOpt(c.fd, _SOL_TCP, _TCP_KEEPINTVL, 2*d.Seconds()) 269 | } 270 | 271 | func (c *TCPConn) SetReadDeadline(t time.Time) error { 272 | c.readDeadline = t 273 | return nil 274 | } 275 | 276 | func (c *TCPConn) SetWriteDeadline(t time.Time) error { 277 | c.writeDeadline = t 278 | return nil 279 | } 280 | 281 | func (c *TCPConn) CloseWrite() error { 282 | return fmt.Errorf("CloseWrite not implemented") 283 | } 284 | 285 | type listener struct { 286 | fd int 287 | laddr *TCPAddr 288 | } 289 | 290 | func (l *listener) Accept() (Conn, error) { 291 | fd, raddr, err := netdev.Accept(l.fd) 292 | if err != nil { 293 | return nil, err 294 | } 295 | 296 | return &TCPConn{ 297 | fd: fd, 298 | net: "tcp", 299 | laddr: l.laddr, 300 | raddr: TCPAddrFromAddrPort(raddr), 301 | }, nil 302 | } 303 | 304 | func (l *listener) Close() error { 305 | return netdev.Close(l.fd) 306 | } 307 | 308 | func (l *listener) Addr() Addr { 309 | return l.laddr 310 | } 311 | 312 | func listenTCP(laddr *TCPAddr) (Listener, error) { 313 | fd, err := netdev.Socket(_AF_INET, _SOCK_STREAM, _IPPROTO_TCP) 314 | if err != nil { 315 | return nil, err 316 | } 317 | 318 | laddrport := laddr.AddrPort() 319 | err = netdev.Bind(fd, laddrport) 320 | if err != nil { 321 | return nil, err 322 | } 323 | 324 | err = netdev.Listen(fd, 5) 325 | if err != nil { 326 | return nil, err 327 | } 328 | 329 | return &listener{fd: fd, laddr: laddr}, nil 330 | } 331 | 332 | // TCPListener is a TCP network listener. Clients should typically 333 | // use variables of type Listener instead of assuming TCP. 334 | type TCPListener struct { 335 | listener 336 | } 337 | -------------------------------------------------------------------------------- /tlssock.go: -------------------------------------------------------------------------------- 1 | // TINYGO: The following is copied and modified from Go 1.21.4 official implementation. 2 | 3 | // Copyright 2010 The Go Authors. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | 7 | // TLS low level connection and record layer 8 | 9 | package net 10 | 11 | import ( 12 | "internal/itoa" 13 | "io" 14 | "net/netip" 15 | "strconv" 16 | "time" 17 | ) 18 | 19 | // TLSAddr represents the address of a TLS end point. 20 | type TLSAddr struct { 21 | Host string 22 | Port int 23 | } 24 | 25 | func (a *TLSAddr) Network() string { return "tls" } 26 | 27 | func (a *TLSAddr) String() string { 28 | if a == nil { 29 | return "" 30 | } 31 | return JoinHostPort(a.Host, itoa.Itoa(a.Port)) 32 | } 33 | 34 | // A TLSConn represents a secured connection. 35 | // It implements the net.Conn interface. 36 | type TLSConn struct { 37 | fd int 38 | net string 39 | laddr *TLSAddr 40 | raddr *TLSAddr 41 | readDeadline time.Time 42 | writeDeadline time.Time 43 | } 44 | 45 | func DialTLS(addr string) (*TLSConn, error) { 46 | 47 | host, sport, err := SplitHostPort(addr) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | port, err := strconv.Atoi(sport) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | if port == 0 { 58 | port = 443 59 | } 60 | 61 | fd, err := netdev.Socket(_AF_INET, _SOCK_STREAM, _IPPROTO_TLS) 62 | if err != nil { 63 | return nil, err 64 | } 65 | addrport := netip.AddrPortFrom(netip.Addr{}, uint16(port)) 66 | if err = netdev.Connect(fd, host, addrport); err != nil { 67 | netdev.Close(fd) 68 | return nil, err 69 | } 70 | 71 | return &TLSConn{ 72 | fd: fd, 73 | net: "tls", 74 | raddr: &TLSAddr{host, port}, 75 | }, nil 76 | } 77 | 78 | func (c *TLSConn) Read(b []byte) (int, error) { 79 | n, err := netdev.Recv(c.fd, b, 0, c.readDeadline) 80 | // Turn the -1 socket error into 0 and let err speak for error 81 | if n < 0 { 82 | n = 0 83 | } 84 | if err != nil && err != io.EOF { 85 | err = &OpError{Op: "read", Net: c.net, Source: c.laddr, Addr: c.raddr, Err: err} 86 | } 87 | return n, err 88 | } 89 | 90 | func (c *TLSConn) Write(b []byte) (int, error) { 91 | n, err := netdev.Send(c.fd, b, 0, c.writeDeadline) 92 | // Turn the -1 socket error into 0 and let err speak for error 93 | if n < 0 { 94 | n = 0 95 | } 96 | if err != nil { 97 | err = &OpError{Op: "write", Net: c.net, Source: c.laddr, Addr: c.raddr, Err: err} 98 | } 99 | return n, err 100 | } 101 | 102 | func (c *TLSConn) Close() error { 103 | return netdev.Close(c.fd) 104 | } 105 | 106 | func (c *TLSConn) LocalAddr() Addr { 107 | return c.laddr 108 | } 109 | 110 | func (c *TLSConn) RemoteAddr() Addr { 111 | return c.raddr 112 | } 113 | 114 | func (c *TLSConn) SetDeadline(t time.Time) error { 115 | c.readDeadline = t 116 | c.writeDeadline = t 117 | return nil 118 | } 119 | 120 | func (c *TLSConn) SetReadDeadline(t time.Time) error { 121 | c.readDeadline = t 122 | return nil 123 | } 124 | 125 | func (c *TLSConn) SetWriteDeadline(t time.Time) error { 126 | c.writeDeadline = t 127 | return nil 128 | } 129 | 130 | // Handshake runs the client or server handshake 131 | // protocol if it has not yet been run. 132 | // 133 | // Most uses of this package need not call Handshake explicitly: the 134 | // first Read or Write will call it automatically. 135 | // 136 | // For control over canceling or setting a timeout on a handshake, use 137 | // HandshakeContext or the Dialer's DialContext method instead. 138 | func (c *TLSConn) Handshake() error { 139 | panic("TLSConn.Handshake() not implemented") 140 | return nil 141 | } 142 | -------------------------------------------------------------------------------- /udpsock.go: -------------------------------------------------------------------------------- 1 | // TINYGO: The following is copied and modified from Go 1.21.4 official implementation. 2 | 3 | // Copyright 2009 The Go Authors. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | 7 | package net 8 | 9 | import ( 10 | "errors" 11 | "fmt" 12 | "internal/itoa" 13 | "io" 14 | "net/netip" 15 | "strconv" 16 | "syscall" 17 | "time" 18 | ) 19 | 20 | // UDPAddr represents the address of a UDP end point. 21 | type UDPAddr struct { 22 | IP IP 23 | Port int 24 | Zone string // IPv6 scoped addressing zone 25 | } 26 | 27 | // AddrPort returns the UDPAddr a as a netip.AddrPort. 28 | // 29 | // If a.Port does not fit in a uint16, it's silently truncated. 30 | // 31 | // If a is nil, a zero value is returned. 32 | func (a *UDPAddr) AddrPort() netip.AddrPort { 33 | if a == nil { 34 | return netip.AddrPort{} 35 | } 36 | na, _ := netip.AddrFromSlice(a.IP) 37 | na = na.WithZone(a.Zone) 38 | return netip.AddrPortFrom(na, uint16(a.Port)) 39 | } 40 | 41 | // Network returns the address's network name, "udp". 42 | func (a *UDPAddr) Network() string { return "udp" } 43 | 44 | func (a *UDPAddr) String() string { 45 | if a == nil { 46 | return "" 47 | } 48 | ip := ipEmptyString(a.IP) 49 | if a.Zone != "" { 50 | return JoinHostPort(ip+"%"+a.Zone, itoa.Itoa(a.Port)) 51 | } 52 | return JoinHostPort(ip, itoa.Itoa(a.Port)) 53 | } 54 | 55 | func (a *UDPAddr) isWildcard() bool { 56 | if a == nil || a.IP == nil { 57 | return true 58 | } 59 | return a.IP.IsUnspecified() 60 | } 61 | 62 | func (a *UDPAddr) opAddr() Addr { 63 | if a == nil { 64 | return nil 65 | } 66 | return a 67 | } 68 | 69 | // ResolveUDPAddr returns an address of UDP end point. 70 | // 71 | // The network must be a UDP network name. 72 | // 73 | // If the host in the address parameter is not a literal IP address or 74 | // the port is not a literal port number, ResolveUDPAddr resolves the 75 | // address to an address of UDP end point. 76 | // Otherwise, it parses the address as a pair of literal IP address 77 | // and port number. 78 | // The address parameter can use a host name, but this is not 79 | // recommended, because it will return at most one of the host name's 80 | // IP addresses. 81 | // 82 | // See func Dial for a description of the network and address 83 | // parameters. 84 | func ResolveUDPAddr(network, address string) (*UDPAddr, error) { 85 | 86 | switch network { 87 | case "udp", "udp4": 88 | default: 89 | return nil, fmt.Errorf("Network '%s' not supported", network) 90 | } 91 | 92 | // TINYGO: Use netdev resolver 93 | 94 | host, sport, err := SplitHostPort(address) 95 | if err != nil { 96 | return nil, err 97 | } 98 | 99 | port, err := strconv.Atoi(sport) 100 | if err != nil { 101 | return nil, fmt.Errorf("Error parsing port '%s' in address: %s", 102 | sport, err) 103 | } 104 | 105 | if host == "" { 106 | return &UDPAddr{Port: port}, nil 107 | } 108 | 109 | ip, err := netdev.GetHostByName(host) 110 | if err != nil { 111 | return nil, fmt.Errorf("Lookup of host name '%s' failed: %s", host, err) 112 | } 113 | 114 | return &UDPAddr{IP: ip.AsSlice(), Port: port}, nil 115 | } 116 | 117 | // UDPConn is the implementation of the Conn and PacketConn interfaces 118 | // for UDP network connections. 119 | type UDPConn struct { 120 | fd int 121 | net string 122 | laddr *UDPAddr 123 | raddr *UDPAddr 124 | readDeadline time.Time 125 | writeDeadline time.Time 126 | } 127 | 128 | // Use IANA RFC 6335 port range 49152–65535 for ephemeral (dynamic) ports 129 | var eport = int32(49151) 130 | 131 | func ephemeralPort() int { 132 | if eport == int32(65535) { 133 | eport = int32(49151) 134 | } else { 135 | eport++ 136 | } 137 | return int(eport) 138 | } 139 | 140 | // DialUDP acts like Dial for UDP networks. 141 | // 142 | // The network must be a UDP network name; see func Dial for details. 143 | // 144 | // If laddr is nil, a local address is automatically chosen. 145 | // If the IP field of raddr is nil or an unspecified IP address, the 146 | // local system is assumed. 147 | func DialUDP(network string, laddr, raddr *UDPAddr) (*UDPConn, error) { 148 | switch network { 149 | case "udp", "udp4": 150 | default: 151 | return nil, fmt.Errorf("Network '%s' not supported", network) 152 | } 153 | 154 | // TINYGO: Use netdev to create UDP socket and connect 155 | 156 | if laddr == nil { 157 | laddr = &UDPAddr{} 158 | } 159 | 160 | if raddr == nil { 161 | raddr = &UDPAddr{} 162 | } 163 | 164 | if raddr.IP.IsUnspecified() { 165 | return nil, fmt.Errorf("Sorry, localhost isn't available on Tinygo") 166 | } 167 | 168 | // If no port was given, grab an ephemeral port 169 | if laddr.Port == 0 { 170 | laddr.Port = ephemeralPort() 171 | } 172 | 173 | fd, err := netdev.Socket(_AF_INET, _SOCK_DGRAM, _IPPROTO_UDP) 174 | if err != nil { 175 | return nil, err 176 | } 177 | lip, _ := netip.AddrFromSlice(laddr.IP) 178 | laddrport := netip.AddrPortFrom(lip, uint16(laddr.Port)) 179 | 180 | // Local bind 181 | err = netdev.Bind(fd, laddrport) 182 | if err != nil { 183 | netdev.Close(fd) 184 | return nil, err 185 | } 186 | 187 | rip, _ := netip.AddrFromSlice(raddr.IP) 188 | raddrport := netip.AddrPortFrom(rip, uint16(raddr.Port)) 189 | // Remote connect 190 | if err = netdev.Connect(fd, "", raddrport); err != nil { 191 | netdev.Close(fd) 192 | return nil, err 193 | } 194 | 195 | return &UDPConn{ 196 | fd: fd, 197 | net: network, 198 | laddr: laddr, 199 | raddr: raddr, 200 | }, nil 201 | } 202 | 203 | // SyscallConn returns a raw network connection. 204 | // This implements the syscall.Conn interface. 205 | func (c *UDPConn) SyscallConn() (syscall.RawConn, error) { 206 | return nil, errors.New("SyscallConn not implemented") 207 | } 208 | 209 | // TINYGO: Use netdev for Conn methods: Read = Recv, Write = Send, etc. 210 | 211 | func (c *UDPConn) Read(b []byte) (int, error) { 212 | n, err := netdev.Recv(c.fd, b, 0, c.readDeadline) 213 | // Turn the -1 socket error into 0 and let err speak for error 214 | if n < 0 { 215 | n = 0 216 | } 217 | if err != nil && err != io.EOF { 218 | err = &OpError{Op: "read", Net: c.net, Source: c.laddr, Addr: c.raddr, Err: err} 219 | } 220 | return n, err 221 | } 222 | 223 | func (c *UDPConn) Write(b []byte) (int, error) { 224 | n, err := netdev.Send(c.fd, b, 0, c.writeDeadline) 225 | // Turn the -1 socket error into 0 and let err speak for error 226 | if n < 0 { 227 | n = 0 228 | } 229 | if err != nil { 230 | err = &OpError{Op: "write", Net: c.net, Source: c.laddr, Addr: c.raddr, Err: err} 231 | } 232 | return n, err 233 | } 234 | 235 | // ReadFrom implements the PacketConn ReadFrom method. 236 | func (c *UDPConn) ReadFrom(b []byte) (int, Addr, error) { 237 | return 0, nil, errors.New("ReadFrom not implemented") 238 | } 239 | 240 | // ReadMsgUDP reads a message from c, copying the payload into b and 241 | // the associated out-of-band data into oob. It returns the number of 242 | // bytes copied into b, the number of bytes copied into oob, the flags 243 | // that were set on the message and the source address of the message. 244 | // 245 | // The packages golang.org/x/net/ipv4 and golang.org/x/net/ipv6 can be 246 | // used to manipulate IP-level socket options in oob. 247 | func (c *UDPConn) ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *UDPAddr, err error) { 248 | err = errors.New("ReadMsgUDP not implemented") 249 | return 250 | } 251 | 252 | // WriteTo implements the PacketConn WriteTo method. 253 | func (c *UDPConn) WriteTo(b []byte, addr Addr) (int, error) { 254 | return 0, errors.New("WriteTo not implemented") 255 | } 256 | 257 | // WriteMsgUDP writes a message to addr via c if c isn't connected, or 258 | // to c's remote address if c is connected (in which case addr must be 259 | // nil). The payload is copied from b and the associated out-of-band 260 | // data is copied from oob. It returns the number of payload and 261 | // out-of-band bytes written. 262 | // 263 | // The packages golang.org/x/net/ipv4 and golang.org/x/net/ipv6 can be 264 | // used to manipulate IP-level socket options in oob. 265 | func (c *UDPConn) WriteMsgUDP(b, oob []byte, addr *UDPAddr) (n, oobn int, err error) { 266 | return 0, 0, errors.New("WriteMsgUDP not implemented") 267 | } 268 | 269 | func (c *UDPConn) Close() error { 270 | return netdev.Close(c.fd) 271 | } 272 | 273 | func (c *UDPConn) LocalAddr() Addr { 274 | return c.laddr 275 | } 276 | 277 | func (c *UDPConn) RemoteAddr() Addr { 278 | return c.raddr 279 | } 280 | 281 | func (c *UDPConn) SetDeadline(t time.Time) error { 282 | c.readDeadline = t 283 | c.writeDeadline = t 284 | return nil 285 | } 286 | 287 | func (c *UDPConn) SetReadDeadline(t time.Time) error { 288 | c.readDeadline = t 289 | return nil 290 | } 291 | 292 | func (c *UDPConn) SetWriteDeadline(t time.Time) error { 293 | c.writeDeadline = t 294 | return nil 295 | } 296 | -------------------------------------------------------------------------------- /unixsock.go: -------------------------------------------------------------------------------- 1 | // TINYGO: The following is copied and modified from Go 1.21.4 official implementation. 2 | 3 | // Copyright 2009 The Go Authors. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | 7 | package net 8 | 9 | // BUG(mikio): On JS, WASIP1 and Plan 9, methods and functions related 10 | // to UnixConn and UnixListener are not implemented. 11 | 12 | // BUG(mikio): On Windows, methods and functions related to UnixConn 13 | // and UnixListener don't work for "unixgram" and "unixpacket". 14 | 15 | // BUG(paralin): On TinyGo, Unix sockets are not implemented. 16 | 17 | // UnixAddr represents the address of a Unix domain socket end point. 18 | type UnixAddr struct { 19 | Name string 20 | Net string 21 | } 22 | 23 | // Network returns the address's network name, "unix", "unixgram" or 24 | // "unixpacket". 25 | func (a *UnixAddr) Network() string { 26 | return a.Net 27 | } 28 | 29 | func (a *UnixAddr) String() string { 30 | if a == nil { 31 | return "" 32 | } 33 | return a.Name 34 | } 35 | 36 | func (a *UnixAddr) isWildcard() bool { 37 | return a == nil || a.Name == "" 38 | } 39 | 40 | func (a *UnixAddr) opAddr() Addr { 41 | if a == nil { 42 | return nil 43 | } 44 | return a 45 | } 46 | 47 | // ResolveUnixAddr returns an address of Unix domain socket end point. 48 | // 49 | // The network must be a Unix network name. 50 | // 51 | // See func [Dial] for a description of the network and address 52 | // parameters. 53 | func ResolveUnixAddr(network, address string) (*UnixAddr, error) { 54 | switch network { 55 | case "unix", "unixgram", "unixpacket": 56 | return &UnixAddr{Name: address, Net: network}, nil 57 | default: 58 | return nil, UnknownNetworkError(network) 59 | } 60 | } 61 | --------------------------------------------------------------------------------