pkg/net: added package
Support:
- various IP types
- FQDNs
Also supports:
- use of single-param builtins as types
(consistent with curried builtins).
- fixed bug in interpretation of top
- allow unexported functions to be used by builtins
Change-Id: I3568f2383510a904e00d4cf941c753efd03679ce
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2664
Reviewed-by: Marcel van Lohuizen <mpvl@google.com>
diff --git a/pkg/net/doc.go b/pkg/net/doc.go
new file mode 100644
index 0000000..1e7a237
--- /dev/null
+++ b/pkg/net/doc.go
@@ -0,0 +1,27 @@
+// Copyright 2019 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package net provides net-related type definitions.
+//
+// The IP-related defintions can be represented as either a string or a list of
+// byte values. To allow one format over an other these types can be further
+// constraint using string or [...]. For instance,
+//
+// // multicast defines a multicast IP address in string form.
+// multicast: net.MulticastIP & string
+//
+// // unicast defines a global unicast IP address in list form.
+// unicast: net.GlobalUnicastIP & [...]
+//
+package net
diff --git a/pkg/net/host.go b/pkg/net/host.go
new file mode 100644
index 0000000..a690fac
--- /dev/null
+++ b/pkg/net/host.go
@@ -0,0 +1,105 @@
+// Copyright 2019 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package net
+
+import (
+ "fmt"
+ "net"
+ "strconv"
+ "unicode/utf8"
+
+ "cuelang.org/go/cue"
+ "golang.org/x/net/idna"
+)
+
+var idnaProfile = idna.New(
+ idna.ValidateLabels(true),
+ idna.VerifyDNSLength(true),
+ idna.StrictDomainName(true),
+)
+
+// SplitHostPort splits a network address of the form "host:port",
+// "host%zone:port", "[host]:port" or "[host%zone]:port" into host or host%zone
+// and port.
+//
+// A literal IPv6 address in hostport must be enclosed in square brackets, as in
+// "[::1]:80", "[::1%lo0]:80".
+func SplitHostPort(s string) (hostport []string, err error) {
+ host, port, err := net.SplitHostPort(s)
+ if err != nil {
+ return nil, err
+ }
+ return []string{host, port}, nil
+}
+
+// JoinHostPort combines host and port into a network address of the
+// form "host:port". If host contains a colon, as found in literal
+// IPv6 addresses, then JoinHostPort returns "[host]:port".
+//
+// See func Dial for a description of the host and port parameters.
+func JoinHostPort(host, port cue.Value) (string, error) {
+ var err error
+ hostStr := ""
+ switch host.Kind() {
+ case cue.ListKind:
+ ipdata := netGetIP(host)
+ if len(ipdata) != 4 && len(ipdata) != 16 {
+ err = fmt.Errorf("invalid host %q", host)
+ }
+ hostStr = ipdata.String()
+ case cue.BytesKind:
+ var b []byte
+ b, err = host.Bytes()
+ hostStr = string(b)
+ default:
+ hostStr, err = host.String()
+ }
+ if err != nil {
+ return "", err
+ }
+
+ portStr := ""
+ switch port.Kind() {
+ case cue.StringKind:
+ portStr, err = port.String()
+ case cue.BytesKind:
+ var b []byte
+ b, err = port.Bytes()
+ portStr = string(b)
+ default:
+ var i int64
+ i, err = port.Int64()
+ portStr = strconv.Itoa(int(i))
+ }
+ if err != nil {
+ return "", err
+ }
+
+ return net.JoinHostPort(hostStr, portStr), nil
+}
+
+// FQDN reports whether is is a valid fully qualified domain name.
+//
+// FQDN allows only ASCII characters as prescribed by RFC 1034 (A-Z, a-z, 0-9
+// and the hyphen).
+func FQDN(s string) bool {
+ for i := 0; i < len(s); i++ {
+ if s[i] >= utf8.RuneSelf {
+ return false
+ }
+ }
+ _, err := idnaProfile.ToASCII(s)
+ return err == nil
+}
diff --git a/pkg/net/ip.go b/pkg/net/ip.go
new file mode 100644
index 0000000..6b658e3
--- /dev/null
+++ b/pkg/net/ip.go
@@ -0,0 +1,193 @@
+// Copyright 2019 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package net defines net-related types.
+package net
+
+import (
+ "fmt"
+ "net"
+
+ "cuelang.org/go/cue"
+)
+
+// IP address lengths (bytes).
+const (
+ IPv4len = 4
+ IPv6len = 16
+)
+
+func netGetIP(ip cue.Value) (goip net.IP) {
+ switch ip.Kind() {
+ case cue.StringKind:
+ s, err := ip.String()
+ if err != nil {
+ return nil
+ }
+ goip := net.ParseIP(s)
+ if goip == nil {
+ return nil
+ }
+ return goip
+
+ case cue.BytesKind:
+ b, err := ip.Bytes()
+ if err != nil {
+ return nil
+ }
+ goip := net.ParseIP(string(b))
+ if goip == nil {
+ return nil
+ }
+ return goip
+
+ case cue.ListKind:
+ iter, err := ip.List()
+ if err != nil {
+ return nil
+ }
+ for iter.Next() {
+ v, err := iter.Value().Int64()
+ if err != nil {
+ return nil
+ }
+ if v < 0 || 255 < v {
+ return nil
+ }
+ goip = append(goip, byte(v))
+ }
+ return goip
+
+ default:
+ // TODO: return canonical invalid type.
+ return nil
+ }
+}
+
+// ParseIP parses s as an IP address, returning the result.
+// The string s can be in dotted decimal ("192.0.2.1")
+// or IPv6 ("2001:db8::68") form.
+// If s is not a valid textual representation of an IP address,
+// ParseIP returns nil.
+func ParseIP(s string) ([]uint, error) {
+ goip := net.ParseIP(s)
+ if goip == nil {
+ return nil, fmt.Errorf("invalid IP address %q", s)
+ }
+ return netToList(goip), nil
+}
+
+func netToList(ip net.IP) []uint {
+ a := make([]uint, len(ip))
+ for i, p := range ip {
+ a[i] = uint(p)
+ }
+ return a
+}
+
+// IPv4 reports whether s is a valid IPv4 address.
+//
+// The address may be a string or list of bytes.
+func IPv4(ip cue.Value) bool {
+ // TODO: convert to native CUE.
+ return netGetIP(ip).To4() != nil
+}
+
+// IP reports whether s is a valid IPv4 or IPv6 address.
+//
+// The address may be a string or list of bytes.
+func IP(ip cue.Value) bool {
+ // TODO: convert to native CUE.
+ return netGetIP(ip) != nil
+}
+
+// LoopbackIP reports whether ip is a loopback address.
+func LoopbackIP(ip cue.Value) bool {
+ return netGetIP(ip).IsLoopback()
+}
+
+// MulticastIP reports whether ip is a multicast address.
+func MulticastIP(ip cue.Value) bool {
+ return netGetIP(ip).IsMulticast()
+}
+
+// InterfaceLocalMulticastIP reports whether ip is an interface-local multicast
+// address.
+func InterfaceLocalMulticastIP(ip cue.Value) bool {
+ return netGetIP(ip).IsInterfaceLocalMulticast()
+}
+
+// LinkLocalMulticast reports whether ip is a link-local multicast address.
+func LinkLocalMulticastIP(ip cue.Value) bool {
+ return netGetIP(ip).IsLinkLocalMulticast()
+}
+
+// LinkLocalUnicastIP reports whether ip is a link-local unicast address.
+func LinkLocalUnicastIP(ip cue.Value) bool {
+ return netGetIP(ip).IsLinkLocalUnicast()
+}
+
+// GlobalUnicastIP reports whether ip is a global unicast address.
+//
+// The identification of global unicast addresses uses address type
+// identification as defined in RFC 1122, RFC 4632 and RFC 4291 with the
+// exception of IPv4 directed broadcast addresses. It returns true even if ip is
+// in IPv4 private address space or local IPv6 unicast address space.
+func GlobalUnicastIP(ip cue.Value) bool {
+ return netGetIP(ip).IsGlobalUnicast()
+}
+
+// UnspecifiedIP reports whether ip is an unspecified address, either the IPv4
+// address "0.0.0.0" or the IPv6 address "::".
+func UnspecifiedIP(ip cue.Value) bool {
+ return netGetIP(ip).IsUnspecified()
+}
+
+// ToIP4 converts a given IP address, which may be a string or a list, to its
+// 4-byte representation.
+func ToIP4(ip cue.Value) ([]uint, error) {
+ ipdata := netGetIP(ip)
+ if ipdata == nil {
+ return nil, fmt.Errorf("invalid IP %q", ip)
+ }
+ ipv4 := ipdata.To4()
+ if ipv4 == nil {
+ return nil, fmt.Errorf("cannot convert %q to IPv4", ipdata)
+ }
+ return netToList(ipv4), nil
+}
+
+// ToIP16 converts a given IP address, which may be a string or a list, to its
+// 16-byte representation.
+func ToIP16(ip cue.Value) ([]uint, error) {
+ ipdata := netGetIP(ip)
+ if ipdata == nil {
+ return nil, fmt.Errorf("invalid IP %q", ip)
+ }
+ return netToList(ipdata), nil
+}
+
+// IPString returns the string form of the IP address ip. It returns one of 4 forms:
+//
+// - "<nil>", if ip has length 0
+// - dotted decimal ("192.0.2.1"), if ip is an IPv4 or IP4-mapped IPv6 address
+// - IPv6 ("2001:db8::1"), if ip is a valid IPv6 address
+// - the hexadecimal form of ip, without punctuation, if no other cases apply
+func IPString(ip cue.Value) (string, error) {
+ ipdata := netGetIP(ip)
+ if ipdata == nil {
+ return "", fmt.Errorf("invalid IP %q", ip)
+ }
+ return ipdata.String(), nil
+}