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}