...

Source file src/github.com/qri-io/jsonschema/keywords_optional.go

Documentation: github.com/qri-io/jsonschema

     1  package jsonschema
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net"
     7  	"net/mail"
     8  	"net/url"
     9  	"regexp"
    10  	"strconv"
    11  	"strings"
    12  	"time"
    13  
    14  	jptr "github.com/qri-io/jsonpointer"
    15  )
    16  
    17  const (
    18  	hostname       string = `^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$`
    19  	unescapedTilda        = `\~[^01]`
    20  	endingTilda           = `\~$`
    21  	schemePrefix          = `^[^\:]+\:`
    22  	uriTemplate           = `\{[^\{\}\\]*\}`
    23  )
    24  
    25  var (
    26  	// emailPattern           = regexp.MustCompile(email)
    27  	hostnamePattern        = regexp.MustCompile(hostname)
    28  	unescaptedTildaPattern = regexp.MustCompile(unescapedTilda)
    29  	endingTildaPattern     = regexp.MustCompile(endingTilda)
    30  	schemePrefixPattern    = regexp.MustCompile(schemePrefix)
    31  	uriTemplatePattern     = regexp.MustCompile(uriTemplate)
    32  
    33  	disallowedIdnChars = map[string]bool{"\u0020": true, "\u002D": true, "\u00A2": true, "\u00A3": true, "\u00A4": true, "\u00A5": true, "\u034F": true, "\u0640": true, "\u07FA": true, "\u180B": true, "\u180C": true, "\u180D": true, "\u200B": true, "\u2060": true, "\u2104": true, "\u2108": true, "\u2114": true, "\u2117": true, "\u2118": true, "\u211E": true, "\u211F": true, "\u2123": true, "\u2125": true, "\u2282": true, "\u2283": true, "\u2284": true, "\u2285": true, "\u2286": true, "\u2287": true, "\u2288": true, "\u2616": true, "\u2617": true, "\u2619": true, "\u262F": true, "\u2638": true, "\u266C": true, "\u266D": true, "\u266F": true, "\u2752": true, "\u2756": true, "\u2758": true, "\u275E": true, "\u2761": true, "\u2775": true, "\u2794": true, "\u2798": true, "\u27AF": true, "\u27B1": true, "\u27BE": true, "\u3004": true, "\u3012": true, "\u3013": true, "\u3020": true, "\u302E": true, "\u302F": true, "\u3031": true, "\u3032": true, "\u3035": true, "\u303B": true, "\u3164": true, "\uFFA0": true}
    34  )
    35  
    36  // Format defines the format JSON Schema keyword
    37  type Format string
    38  
    39  // NewFormat allocates a new Format keyword
    40  func NewFormat() Keyword {
    41  	return new(Format)
    42  }
    43  
    44  // Register implements the Keyword interface for Format
    45  func (f *Format) Register(uri string, registry *SchemaRegistry) {}
    46  
    47  // Resolve implements the Keyword interface for Format
    48  func (f *Format) Resolve(pointer jptr.Pointer, uri string) *Schema {
    49  	return nil
    50  }
    51  
    52  // ValidateKeyword implements the Keyword interface for Format
    53  func (f Format) ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{}) {
    54  	schemaDebug("[Format] Validating")
    55  	var err error
    56  	if str, ok := data.(string); ok {
    57  		switch f {
    58  		case "date-time":
    59  			err = isValidDateTime(str)
    60  		case "date":
    61  			err = isValidDate(str)
    62  		case "email":
    63  			err = isValidEmail(str)
    64  		case "hostname":
    65  			err = isValidHostname(str)
    66  		case "idn-email":
    67  			err = isValidIDNEmail(str)
    68  		case "idn-hostname":
    69  			err = isValidIDNHostname(str)
    70  		case "ipv4":
    71  			err = isValidIPv4(str)
    72  		case "ipv6":
    73  			err = isValidIPv6(str)
    74  		case "iri-reference":
    75  			err = isValidIriRef(str)
    76  		case "iri":
    77  			err = isValidIri(str)
    78  		case "json-pointer":
    79  			err = isValidJSONPointer(str)
    80  		case "regex":
    81  			err = isValidRegex(str)
    82  		case "relative-json-pointer":
    83  			err = isValidRelJSONPointer(str)
    84  		case "time":
    85  			err = isValidTime(str)
    86  		case "uri-reference":
    87  			err = isValidURIRef(str)
    88  		case "uri-template":
    89  			err = isValidURITemplate(str)
    90  		case "uri":
    91  			err = isValidURI(str)
    92  		default:
    93  			err = nil
    94  		}
    95  		if err != nil {
    96  			currentState.AddError(data, fmt.Sprintf("invalid %s: %s", f, err.Error()))
    97  		}
    98  	}
    99  }
   100  
   101  // A string instance is valid against "date-time" if it is a valid
   102  // representation according to the "date-time" production derived
   103  // from RFC 3339, section 5.6 [RFC3339]
   104  // https://tools.ietf.org/html/rfc3339#section-5.6
   105  func isValidDateTime(dateTime string) error {
   106  	if _, err := time.Parse(time.RFC3339, strings.ToUpper(dateTime)); err != nil {
   107  		return fmt.Errorf("date-time incorrectly Formatted: %s", err.Error())
   108  	}
   109  	return nil
   110  }
   111  
   112  // A string instance is valid against "date" if it is a valid
   113  // representation according to the "full-date" production derived
   114  // from RFC 3339, section 5.6 [RFC3339]
   115  // https://tools.ietf.org/html/rfc3339#section-5.6
   116  func isValidDate(date string) error {
   117  	arbitraryTime := "T08:30:06.283185Z"
   118  	dateTime := fmt.Sprintf("%s%s", date, arbitraryTime)
   119  	return isValidDateTime(dateTime)
   120  }
   121  
   122  // A string instance is valid against "email" if it is a valid
   123  // representation as defined by RFC 5322, section 3.4.1 [RFC5322].
   124  // https://tools.ietf.org/html/rfc5322#section-3.4.1
   125  func isValidEmail(email string) error {
   126  	// if !emailPattern.MatchString(email) {
   127  	// 	return fmt.Errorf("invalid email Format")
   128  	// }
   129  	if _, err := mail.ParseAddress(email); err != nil {
   130  		return fmt.Errorf("email address incorrectly Formatted: %s", err.Error())
   131  	}
   132  	return nil
   133  }
   134  
   135  // A string instance is valid against "hostname" if it is a valid
   136  // representation as defined by RFC 1034, section 3.1 [RFC1034],
   137  // including host names produced using the Punycode algorithm
   138  // specified in RFC 5891, section 4.4 [RFC5891].
   139  // https://tools.ietf.org/html/rfc1034#section-3.1
   140  // https://tools.ietf.org/html/rfc5891#section-4.4
   141  func isValidHostname(hostname string) error {
   142  	if !hostnamePattern.MatchString(hostname) || len(hostname) > 255 {
   143  		return fmt.Errorf("invalid hostname string")
   144  	}
   145  	return nil
   146  }
   147  
   148  // A string instance is valid against "idn-email" if it is a valid
   149  // representation as defined by RFC 6531 [RFC6531]
   150  // https://tools.ietf.org/html/rfc6531
   151  func isValidIDNEmail(idnEmail string) error {
   152  	if _, err := mail.ParseAddress(idnEmail); err != nil {
   153  		return fmt.Errorf("email address incorrectly Formatted: %s", err.Error())
   154  	}
   155  	return nil
   156  }
   157  
   158  // A string instance is valid against "hostname" if it is a valid
   159  // representation as defined by either RFC 1034 as for hostname, or
   160  // an internationalized hostname as defined by RFC 5890, section
   161  // 2.3.2.3 [RFC5890].
   162  // https://tools.ietf.org/html/rfc1034
   163  // https://tools.ietf.org/html/rfc5890#section-2.3.2.3
   164  // https://pdfs.semanticscholar.org/9275/6bcecb29d3dc407e23a997b256be6ff4149d.pdf
   165  func isValidIDNHostname(idnHostname string) error {
   166  	if len(idnHostname) > 255 {
   167  		return fmt.Errorf("invalid idn hostname string")
   168  	}
   169  	for _, r := range idnHostname {
   170  		s := string(r)
   171  		if disallowedIdnChars[s] {
   172  			return fmt.Errorf("invalid hostname: contains illegal character %#U", r)
   173  		}
   174  	}
   175  	return nil
   176  }
   177  
   178  // A string instance is valid against "ipv4" if it is a valid
   179  // representation of an IPv4 address according to the "dotted-quad"
   180  // ABNF syntax as defined in RFC 2673, section 3.2 [RFC2673].
   181  // https://tools.ietf.org/html/rfc2673#section-3.2
   182  func isValidIPv4(ipv4 string) error {
   183  	parsedIP := net.ParseIP(ipv4)
   184  	hasDots := strings.Contains(ipv4, ".")
   185  	if !hasDots || parsedIP == nil {
   186  		return fmt.Errorf("invalid IPv4 address")
   187  	}
   188  	return nil
   189  }
   190  
   191  // A string instance is valid against "ipv6" if it is a valid
   192  // representation of an IPv6 address as defined in RFC 4291, section
   193  // 2.2 [RFC4291].
   194  // https://tools.ietf.org/html/rfc4291#section-2.2
   195  func isValidIPv6(ipv6 string) error {
   196  	parsedIP := net.ParseIP(ipv6)
   197  	hasColons := strings.Contains(ipv6, ":")
   198  	if !hasColons || parsedIP == nil {
   199  		return fmt.Errorf("invalid IPv4 address")
   200  	}
   201  	return nil
   202  }
   203  
   204  // A string instance is a valid against "iri-reference" if it is a
   205  // valid IRI Reference (either an IRI or a relative-reference),
   206  // according to [RFC3987].
   207  // https://tools.ietf.org/html/rfc3987
   208  func isValidIriRef(iriRef string) error {
   209  	return isValidURIRef(iriRef)
   210  }
   211  
   212  // A string instance is a valid against "iri" if it is a valid IRI,
   213  // according to [RFC3987].
   214  // https://tools.ietf.org/html/rfc3987
   215  func isValidIri(iri string) error {
   216  	return isValidURI(iri)
   217  }
   218  
   219  // A string instance is a valid against "json-pointer" if it is a
   220  // valid JSON string representation of a JSON Pointer, according to
   221  // RFC 6901, section 5 [RFC6901].
   222  // https://tools.ietf.org/html/rfc6901#section-5
   223  func isValidJSONPointer(jsonPointer string) error {
   224  	if len(jsonPointer) == 0 {
   225  		return nil
   226  	}
   227  	if jsonPointer[0] != '/' {
   228  		return fmt.Errorf("non-empty references must begin with a '/' character")
   229  	}
   230  	str := jsonPointer[1:]
   231  	if unescaptedTildaPattern.MatchString(str) {
   232  		return fmt.Errorf("unescaped tilda error")
   233  	}
   234  	if endingTildaPattern.MatchString(str) {
   235  		return fmt.Errorf("unescaped tilda error")
   236  	}
   237  	return nil
   238  }
   239  
   240  // A string instance is a valid against "regex" if it is a valid
   241  // regular expression according to the ECMA 262 [ecma262] regular
   242  // expression dialect. Implementations that validate Formats MUST
   243  // accept at least the subset of ECMA 262 defined in the Regular
   244  // Expressions [regexInterop] section of this specification, and
   245  // SHOULD accept all valid ECMA 262 expressions.
   246  // http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf
   247  // http://json-schema.org/latest/jsoxn-schema-validation.html#regexInterop
   248  // https://tools.ietf.org/html/rfc7159
   249  func isValidRegex(regex string) error {
   250  	if _, err := regexp.Compile(regex); err != nil {
   251  		return fmt.Errorf("invalid regex expression")
   252  	}
   253  	return nil
   254  }
   255  
   256  // A string instance is a valid against "relative-json-pointer" if it
   257  // is a valid Relative JSON Pointer [relative-json-pointer].
   258  // https://tools.ietf.org/html/draft-handrews-relative-json-pointer-00
   259  func isValidRelJSONPointer(relJSONPointer string) error {
   260  	parts := strings.Split(relJSONPointer, "/")
   261  	if len(parts) == 1 {
   262  		parts = strings.Split(relJSONPointer, "#")
   263  	}
   264  	if i, err := strconv.Atoi(parts[0]); err != nil || i < 0 {
   265  		return fmt.Errorf("RJP must begin with positive integer")
   266  	}
   267  	//skip over first part
   268  	str := relJSONPointer[len(parts[0]):]
   269  	if len(str) > 0 && str[0] == '#' {
   270  		return nil
   271  	}
   272  	return isValidJSONPointer(str)
   273  }
   274  
   275  // A string instance is valid against "time" if it is a valid
   276  // representation according to the "full-time" production derived
   277  // from RFC 3339, section 5.6 [RFC3339]
   278  // https://tools.ietf.org/html/rfc3339#section-5.6
   279  func isValidTime(time string) error {
   280  	arbitraryDate := "1963-06-19"
   281  	dateTime := fmt.Sprintf("%sT%s", arbitraryDate, time)
   282  	return isValidDateTime(dateTime)
   283  	return nil
   284  }
   285  
   286  // A string instance is a valid against "uri-reference" if it is a
   287  // valid URI Reference (either a URI or a relative-reference),
   288  // according to [RFC3986].
   289  // https://tools.ietf.org/html/rfc3986
   290  func isValidURIRef(uriRef string) error {
   291  	if _, err := url.Parse(uriRef); err != nil {
   292  		return fmt.Errorf("uri incorrectly Formatted: %s", err.Error())
   293  	}
   294  	if strings.Contains(uriRef, "\\") {
   295  		return fmt.Errorf("invalid uri")
   296  	}
   297  	return nil
   298  }
   299  
   300  // A string instance is a valid against "uri-template" if it is a
   301  // valid URI Template (of any level), according to [RFC6570]. Note
   302  // that URI Templates may be used for IRIs; there is no separate IRI
   303  // Template specification.
   304  // https://tools.ietf.org/html/rfc6570
   305  func isValidURITemplate(uriTemplate string) error {
   306  	arbitraryValue := "aaa"
   307  	uriRef := uriTemplatePattern.ReplaceAllString(uriTemplate, arbitraryValue)
   308  	if strings.Contains(uriRef, "{") || strings.Contains(uriRef, "}") {
   309  		return fmt.Errorf("invalid uri template")
   310  	}
   311  	return isValidURIRef(uriRef)
   312  }
   313  
   314  // A string instance is a valid against "uri" if it is a valid URI,
   315  // according to [RFC3986].
   316  // https://tools.ietf.org/html/rfc3986
   317  func isValidURI(uri string) error {
   318  	if _, err := url.Parse(uri); err != nil {
   319  		return fmt.Errorf("uri incorrectly Formatted: %s", err.Error())
   320  	}
   321  	if !schemePrefixPattern.MatchString(uri) {
   322  		return fmt.Errorf("uri missing scheme prefix")
   323  	}
   324  	return nil
   325  }
   326  

View as plain text