]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/hcl/hcl/strconv/quote.go
Initial transfer of provider code
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / hcl / hcl / strconv / quote.go
1 package strconv
2
3 import (
4 "errors"
5 "unicode/utf8"
6 )
7
8 // ErrSyntax indicates that a value does not have the right syntax for the target type.
9 var ErrSyntax = errors.New("invalid syntax")
10
11 // Unquote interprets s as a single-quoted, double-quoted,
12 // or backquoted Go string literal, returning the string value
13 // that s quotes. (If s is single-quoted, it would be a Go
14 // character literal; Unquote returns the corresponding
15 // one-character string.)
16 func Unquote(s string) (t string, err error) {
17 n := len(s)
18 if n < 2 {
19 return "", ErrSyntax
20 }
21 quote := s[0]
22 if quote != s[n-1] {
23 return "", ErrSyntax
24 }
25 s = s[1 : n-1]
26
27 if quote != '"' {
28 return "", ErrSyntax
29 }
30 if !contains(s, '$') && !contains(s, '{') && contains(s, '\n') {
31 return "", ErrSyntax
32 }
33
34 // Is it trivial? Avoid allocation.
35 if !contains(s, '\\') && !contains(s, quote) && !contains(s, '$') {
36 switch quote {
37 case '"':
38 return s, nil
39 case '\'':
40 r, size := utf8.DecodeRuneInString(s)
41 if size == len(s) && (r != utf8.RuneError || size != 1) {
42 return s, nil
43 }
44 }
45 }
46
47 var runeTmp [utf8.UTFMax]byte
48 buf := make([]byte, 0, 3*len(s)/2) // Try to avoid more allocations.
49 for len(s) > 0 {
50 // If we're starting a '${}' then let it through un-unquoted.
51 // Specifically: we don't unquote any characters within the `${}`
52 // section.
53 if s[0] == '$' && len(s) > 1 && s[1] == '{' {
54 buf = append(buf, '$', '{')
55 s = s[2:]
56
57 // Continue reading until we find the closing brace, copying as-is
58 braces := 1
59 for len(s) > 0 && braces > 0 {
60 r, size := utf8.DecodeRuneInString(s)
61 if r == utf8.RuneError {
62 return "", ErrSyntax
63 }
64
65 s = s[size:]
66
67 n := utf8.EncodeRune(runeTmp[:], r)
68 buf = append(buf, runeTmp[:n]...)
69
70 switch r {
71 case '{':
72 braces++
73 case '}':
74 braces--
75 }
76 }
77 if braces != 0 {
78 return "", ErrSyntax
79 }
80 if len(s) == 0 {
81 // If there's no string left, we're done!
82 break
83 } else {
84 // If there's more left, we need to pop back up to the top of the loop
85 // in case there's another interpolation in this string.
86 continue
87 }
88 }
89
90 if s[0] == '\n' {
91 return "", ErrSyntax
92 }
93
94 c, multibyte, ss, err := unquoteChar(s, quote)
95 if err != nil {
96 return "", err
97 }
98 s = ss
99 if c < utf8.RuneSelf || !multibyte {
100 buf = append(buf, byte(c))
101 } else {
102 n := utf8.EncodeRune(runeTmp[:], c)
103 buf = append(buf, runeTmp[:n]...)
104 }
105 if quote == '\'' && len(s) != 0 {
106 // single-quoted must be single character
107 return "", ErrSyntax
108 }
109 }
110 return string(buf), nil
111 }
112
113 // contains reports whether the string contains the byte c.
114 func contains(s string, c byte) bool {
115 for i := 0; i < len(s); i++ {
116 if s[i] == c {
117 return true
118 }
119 }
120 return false
121 }
122
123 func unhex(b byte) (v rune, ok bool) {
124 c := rune(b)
125 switch {
126 case '0' <= c && c <= '9':
127 return c - '0', true
128 case 'a' <= c && c <= 'f':
129 return c - 'a' + 10, true
130 case 'A' <= c && c <= 'F':
131 return c - 'A' + 10, true
132 }
133 return
134 }
135
136 func unquoteChar(s string, quote byte) (value rune, multibyte bool, tail string, err error) {
137 // easy cases
138 switch c := s[0]; {
139 case c == quote && (quote == '\'' || quote == '"'):
140 err = ErrSyntax
141 return
142 case c >= utf8.RuneSelf:
143 r, size := utf8.DecodeRuneInString(s)
144 return r, true, s[size:], nil
145 case c != '\\':
146 return rune(s[0]), false, s[1:], nil
147 }
148
149 // hard case: c is backslash
150 if len(s) <= 1 {
151 err = ErrSyntax
152 return
153 }
154 c := s[1]
155 s = s[2:]
156
157 switch c {
158 case 'a':
159 value = '\a'
160 case 'b':
161 value = '\b'
162 case 'f':
163 value = '\f'
164 case 'n':
165 value = '\n'
166 case 'r':
167 value = '\r'
168 case 't':
169 value = '\t'
170 case 'v':
171 value = '\v'
172 case 'x', 'u', 'U':
173 n := 0
174 switch c {
175 case 'x':
176 n = 2
177 case 'u':
178 n = 4
179 case 'U':
180 n = 8
181 }
182 var v rune
183 if len(s) < n {
184 err = ErrSyntax
185 return
186 }
187 for j := 0; j < n; j++ {
188 x, ok := unhex(s[j])
189 if !ok {
190 err = ErrSyntax
191 return
192 }
193 v = v<<4 | x
194 }
195 s = s[n:]
196 if c == 'x' {
197 // single-byte string, possibly not UTF-8
198 value = v
199 break
200 }
201 if v > utf8.MaxRune {
202 err = ErrSyntax
203 return
204 }
205 value = v
206 multibyte = true
207 case '0', '1', '2', '3', '4', '5', '6', '7':
208 v := rune(c) - '0'
209 if len(s) < 2 {
210 err = ErrSyntax
211 return
212 }
213 for j := 0; j < 2; j++ { // one digit already; two more
214 x := rune(s[j]) - '0'
215 if x < 0 || x > 7 {
216 err = ErrSyntax
217 return
218 }
219 v = (v << 3) | x
220 }
221 s = s[2:]
222 if v > 255 {
223 err = ErrSyntax
224 return
225 }
226 value = v
227 case '\\':
228 value = '\\'
229 case '\'', '"':
230 if c != quote {
231 err = ErrSyntax
232 return
233 }
234 value = rune(c)
235 default:
236 err = ErrSyntax
237 return
238 }
239 tail = s
240 return
241 }