1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44 package org.eclipse.jgit.util;
45
46 import static java.nio.charset.StandardCharsets.UTF_8;
47
48 import java.util.Arrays;
49
50 import org.eclipse.jgit.lib.Constants;
51
52
53
54
55 public abstract class QuotedString {
56
57 public static final GitPathStyle GIT_PATH = new GitPathStyle();
58
59
60
61
62
63
64
65
66 public static final BourneStyle BOURNE = new BourneStyle();
67
68
69 public static final BourneUserPathStyle BOURNE_USER_PATH = new BourneUserPathStyle();
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86 public abstract String quote(String in);
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104 public String dequote(String in) {
105 final byte[] b = Constants.encode(in);
106 return dequote(b, 0, b.length);
107 }
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131 public abstract String dequote(byte[] in, int offset, int end);
132
133
134
135
136
137
138
139
140 public static class BourneStyle extends QuotedString {
141 @Override
142 public String quote(String in) {
143 final StringBuilder r = new StringBuilder();
144 r.append('\'');
145 int start = 0, i = 0;
146 for (; i < in.length(); i++) {
147 switch (in.charAt(i)) {
148 case '\'':
149 case '!':
150 r.append(in, start, i);
151 r.append('\'');
152 r.append('\\');
153 r.append(in.charAt(i));
154 r.append('\'');
155 start = i + 1;
156 break;
157 }
158 }
159 r.append(in, start, i);
160 r.append('\'');
161 return r.toString();
162 }
163
164 @Override
165 public String dequote(byte[] in, int ip, int ie) {
166 boolean inquote = false;
167 final byte[] r = new byte[ie - ip];
168 int rPtr = 0;
169 while (ip < ie) {
170 final byte b = in[ip++];
171 switch (b) {
172 case '\'':
173 inquote = !inquote;
174 continue;
175 case '\\':
176 if (inquote || ip == ie)
177 r[rPtr++] = b;
178 else
179 r[rPtr++] = in[ip++];
180 continue;
181 default:
182 r[rPtr++] = b;
183 continue;
184 }
185 }
186 return RawParseUtils.decode(UTF_8, r, 0, rPtr);
187 }
188 }
189
190
191 public static class BourneUserPathStyle extends BourneStyle {
192 @Override
193 public String quote(String in) {
194 if (in.matches("^~[A-Za-z0-9_-]+$")) {
195
196
197
198 return in + "/";
199 }
200
201 if (in.matches("^~[A-Za-z0-9_-]*/.*$")) {
202
203
204
205 final int i = in.indexOf('/') + 1;
206 if (i == in.length())
207 return in;
208 return in.substring(0, i) + super.quote(in.substring(i));
209 }
210
211 return super.quote(in);
212 }
213 }
214
215
216 public static final class GitPathStyle extends QuotedString {
217 private static final byte[] quote;
218 static {
219 quote = new byte[128];
220 Arrays.fill(quote, (byte) -1);
221
222 for (int i = '0'; i <= '9'; i++)
223 quote[i] = 0;
224 for (int i = 'a'; i <= 'z'; i++)
225 quote[i] = 0;
226 for (int i = 'A'; i <= 'Z'; i++)
227 quote[i] = 0;
228 quote[' '] = 0;
229 quote['$'] = 0;
230 quote['%'] = 0;
231 quote['&'] = 0;
232 quote['*'] = 0;
233 quote['+'] = 0;
234 quote[','] = 0;
235 quote['-'] = 0;
236 quote['.'] = 0;
237 quote['/'] = 0;
238 quote[':'] = 0;
239 quote[';'] = 0;
240 quote['='] = 0;
241 quote['?'] = 0;
242 quote['@'] = 0;
243 quote['_'] = 0;
244 quote['^'] = 0;
245 quote['|'] = 0;
246 quote['~'] = 0;
247
248 quote['\u0007'] = 'a';
249 quote['\b'] = 'b';
250 quote['\f'] = 'f';
251 quote['\n'] = 'n';
252 quote['\r'] = 'r';
253 quote['\t'] = 't';
254 quote['\u000B'] = 'v';
255 quote['\\'] = '\\';
256 quote['"'] = '"';
257 }
258
259 @Override
260 public String quote(String instr) {
261 if (instr.length() == 0)
262 return "\"\"";
263 boolean reuse = true;
264 final byte[] in = Constants.encode(instr);
265 final StringBuilder r = new StringBuilder(2 + in.length);
266 r.append('"');
267 for (int i = 0; i < in.length; i++) {
268 final int c = in[i] & 0xff;
269 if (c < quote.length) {
270 final byte style = quote[c];
271 if (style == 0) {
272 r.append((char) c);
273 continue;
274 }
275 if (style > 0) {
276 reuse = false;
277 r.append('\\');
278 r.append((char) style);
279 continue;
280 }
281 }
282
283 reuse = false;
284 r.append('\\');
285 r.append((char) (((c >> 6) & 03) + '0'));
286 r.append((char) (((c >> 3) & 07) + '0'));
287 r.append((char) (((c >> 0) & 07) + '0'));
288 }
289 if (reuse)
290 return instr;
291 r.append('"');
292 return r.toString();
293 }
294
295 @Override
296 public String dequote(byte[] in, int inPtr, int inEnd) {
297 if (2 <= inEnd - inPtr && in[inPtr] == '"' && in[inEnd - 1] == '"')
298 return dq(in, inPtr + 1, inEnd - 1);
299 return RawParseUtils.decode(UTF_8, in, inPtr, inEnd);
300 }
301
302 private static String dq(byte[] in, int inPtr, int inEnd) {
303 final byte[] r = new byte[inEnd - inPtr];
304 int rPtr = 0;
305 while (inPtr < inEnd) {
306 final byte b = in[inPtr++];
307 if (b != '\\') {
308 r[rPtr++] = b;
309 continue;
310 }
311
312 if (inPtr == inEnd) {
313
314
315 r[rPtr++] = '\\';
316 break;
317 }
318
319 switch (in[inPtr++]) {
320 case 'a':
321 r[rPtr++] = 0x07 ;
322 continue;
323 case 'b':
324 r[rPtr++] = '\b';
325 continue;
326 case 'f':
327 r[rPtr++] = '\f';
328 continue;
329 case 'n':
330 r[rPtr++] = '\n';
331 continue;
332 case 'r':
333 r[rPtr++] = '\r';
334 continue;
335 case 't':
336 r[rPtr++] = '\t';
337 continue;
338 case 'v':
339 r[rPtr++] = 0x0B;
340 continue;
341
342 case '\\':
343 case '"':
344 r[rPtr++] = in[inPtr - 1];
345 continue;
346
347 case '0':
348 case '1':
349 case '2':
350 case '3': {
351 int cp = in[inPtr - 1] - '0';
352 for (int n = 1; n < 3 && inPtr < inEnd; n++) {
353 final byte c = in[inPtr];
354 if ('0' <= c && c <= '7') {
355 cp <<= 3;
356 cp |= c - '0';
357 inPtr++;
358 } else {
359 break;
360 }
361 }
362 r[rPtr++] = (byte) cp;
363 continue;
364 }
365
366 default:
367
368
369 r[rPtr++] = '\\';
370 r[rPtr++] = in[inPtr - 1];
371 continue;
372 }
373 }
374
375 return RawParseUtils.decode(UTF_8, r, 0, rPtr);
376 }
377
378 private GitPathStyle() {
379
380 }
381 }
382 }