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