escape.go

  1// Copyright (C) 2016 Kohei YOSHIDA. All rights reserved.
  2//
  3// This program is free software; you can redistribute it and/or
  4// modify it under the terms of The BSD 3-Clause License
  5// that can be found in the LICENSE file.
  6
  7package uritemplate
  8
  9import (
 10	"strings"
 11	"unicode"
 12	"unicode/utf8"
 13)
 14
 15var (
 16	hex = []byte("0123456789ABCDEF")
 17	// reserved   = gen-delims / sub-delims
 18	// gen-delims =  ":" / "/" / "?" / "#" / "[" / "]" / "@"
 19	// sub-delims =  "!" / "$" / "&" / "’" / "(" / ")"
 20	//            /  "*" / "+" / "," / ";" / "="
 21	rangeReserved = &unicode.RangeTable{
 22		R16: []unicode.Range16{
 23			{Lo: 0x21, Hi: 0x21, Stride: 1}, // '!'
 24			{Lo: 0x23, Hi: 0x24, Stride: 1}, // '#' - '$'
 25			{Lo: 0x26, Hi: 0x2C, Stride: 1}, // '&' - ','
 26			{Lo: 0x2F, Hi: 0x2F, Stride: 1}, // '/'
 27			{Lo: 0x3A, Hi: 0x3B, Stride: 1}, // ':' - ';'
 28			{Lo: 0x3D, Hi: 0x3D, Stride: 1}, // '='
 29			{Lo: 0x3F, Hi: 0x40, Stride: 1}, // '?' - '@'
 30			{Lo: 0x5B, Hi: 0x5B, Stride: 1}, // '['
 31			{Lo: 0x5D, Hi: 0x5D, Stride: 1}, // ']'
 32		},
 33		LatinOffset: 9,
 34	}
 35	reReserved = `\x21\x23\x24\x26-\x2c\x2f\x3a\x3b\x3d\x3f\x40\x5b\x5d`
 36	// ALPHA      = %x41-5A / %x61-7A
 37	// DIGIT      = %x30-39
 38	// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
 39	rangeUnreserved = &unicode.RangeTable{
 40		R16: []unicode.Range16{
 41			{Lo: 0x2D, Hi: 0x2E, Stride: 1}, // '-' - '.'
 42			{Lo: 0x30, Hi: 0x39, Stride: 1}, // '0' - '9'
 43			{Lo: 0x41, Hi: 0x5A, Stride: 1}, // 'A' - 'Z'
 44			{Lo: 0x5F, Hi: 0x5F, Stride: 1}, // '_'
 45			{Lo: 0x61, Hi: 0x7A, Stride: 1}, // 'a' - 'z'
 46			{Lo: 0x7E, Hi: 0x7E, Stride: 1}, // '~'
 47		},
 48	}
 49	reUnreserved = `\x2d\x2e\x30-\x39\x41-\x5a\x5f\x61-\x7a\x7e`
 50)
 51
 52type runeClass uint8
 53
 54const (
 55	runeClassU runeClass = 1 << iota
 56	runeClassR
 57	runeClassPctE
 58	runeClassLast
 59
 60	runeClassUR = runeClassU | runeClassR
 61)
 62
 63var runeClassNames = []string{
 64	"U",
 65	"R",
 66	"pct-encoded",
 67}
 68
 69func (rc runeClass) String() string {
 70	ret := make([]string, 0, len(runeClassNames))
 71	for i, j := 0, runeClass(1); j < runeClassLast; j <<= 1 {
 72		if rc&j == j {
 73			ret = append(ret, runeClassNames[i])
 74		}
 75		i++
 76	}
 77	return strings.Join(ret, "+")
 78}
 79
 80func pctEncode(w *strings.Builder, r rune) {
 81	if s := r >> 24 & 0xff; s > 0 {
 82		w.Write([]byte{'%', hex[s/16], hex[s%16]})
 83	}
 84	if s := r >> 16 & 0xff; s > 0 {
 85		w.Write([]byte{'%', hex[s/16], hex[s%16]})
 86	}
 87	if s := r >> 8 & 0xff; s > 0 {
 88		w.Write([]byte{'%', hex[s/16], hex[s%16]})
 89	}
 90	if s := r & 0xff; s > 0 {
 91		w.Write([]byte{'%', hex[s/16], hex[s%16]})
 92	}
 93}
 94
 95func unhex(c byte) byte {
 96	switch {
 97	case '0' <= c && c <= '9':
 98		return c - '0'
 99	case 'a' <= c && c <= 'f':
100		return c - 'a' + 10
101	case 'A' <= c && c <= 'F':
102		return c - 'A' + 10
103	}
104	return 0
105}
106
107func ishex(c byte) bool {
108	switch {
109	case '0' <= c && c <= '9':
110		return true
111	case 'a' <= c && c <= 'f':
112		return true
113	case 'A' <= c && c <= 'F':
114		return true
115	default:
116		return false
117	}
118}
119
120func pctDecode(s string) string {
121	size := len(s)
122	for i := 0; i < len(s); {
123		switch s[i] {
124		case '%':
125			size -= 2
126			i += 3
127		default:
128			i++
129		}
130	}
131	if size == len(s) {
132		return s
133	}
134
135	buf := make([]byte, size)
136	j := 0
137	for i := 0; i < len(s); {
138		switch c := s[i]; c {
139		case '%':
140			buf[j] = unhex(s[i+1])<<4 | unhex(s[i+2])
141			i += 3
142			j++
143		default:
144			buf[j] = c
145			i++
146			j++
147		}
148	}
149	return string(buf)
150}
151
152type escapeFunc func(*strings.Builder, string) error
153
154func escapeLiteral(w *strings.Builder, v string) error {
155	w.WriteString(v)
156	return nil
157}
158
159func escapeExceptU(w *strings.Builder, v string) error {
160	for i := 0; i < len(v); {
161		r, size := utf8.DecodeRuneInString(v[i:])
162		if r == utf8.RuneError {
163			return errorf(i, "invalid encoding")
164		}
165		if unicode.Is(rangeUnreserved, r) {
166			w.WriteRune(r)
167		} else {
168			pctEncode(w, r)
169		}
170		i += size
171	}
172	return nil
173}
174
175func escapeExceptUR(w *strings.Builder, v string) error {
176	for i := 0; i < len(v); {
177		r, size := utf8.DecodeRuneInString(v[i:])
178		if r == utf8.RuneError {
179			return errorf(i, "invalid encoding")
180		}
181		// TODO(yosida95): is pct-encoded triplets allowed here?
182		if unicode.In(r, rangeUnreserved, rangeReserved) {
183			w.WriteRune(r)
184		} else {
185			pctEncode(w, r)
186		}
187		i += size
188	}
189	return nil
190}