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/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
+}