The padding '=' characters at the end are considered optional, but 107 | * if any are present, there must be the correct number of them. 108 | * 109 | * @param str the input String to decode, which is converted to 110 | * bytes using the default charset 111 | * @param flags controls certain features of the decoded output. 112 | * Pass {@code DEFAULT} to decode standard Base64. 113 | * 114 | * @throws IllegalArgumentException if the input contains 115 | * incorrect padding 116 | */ 117 | public static byte[] decode(String str, int flags) { 118 | return decode(str.getBytes(), flags); 119 | } 120 | 121 | /** 122 | * Decode the Base64-encoded data in input and return the data in 123 | * a new byte array. 124 | * 125 | *
The padding '=' characters at the end are considered optional, but 126 | * if any are present, there must be the correct number of them. 127 | * 128 | * @param input the input array to decode 129 | * @param flags controls certain features of the decoded output. 130 | * Pass {@code DEFAULT} to decode standard Base64. 131 | * 132 | * @throws IllegalArgumentException if the input contains 133 | * incorrect padding 134 | */ 135 | public static byte[] decode(byte[] input, int flags) { 136 | return decode(input, 0, input.length, flags); 137 | } 138 | 139 | /** 140 | * Decode the Base64-encoded data in input and return the data in 141 | * a new byte array. 142 | * 143 | *
The padding '=' characters at the end are considered optional, but
144 | * if any are present, there must be the correct number of them.
145 | *
146 | * @param input the data to decode
147 | * @param offset the position within the input array at which to start
148 | * @param len the number of bytes of input to decode
149 | * @param flags controls certain features of the decoded output.
150 | * Pass {@code DEFAULT} to decode standard Base64.
151 | *
152 | * @throws IllegalArgumentException if the input contains
153 | * incorrect padding
154 | */
155 | public static byte[] decode(byte[] input, int offset, int len, int flags) {
156 | // Allocate space for the most data the input could represent.
157 | // (It could contain less if it contains whitespace, etc.)
158 | Decoder decoder = new Decoder(flags, new byte[len*3/4]);
159 |
160 | if (!decoder.process(input, offset, len, true)) {
161 | throw new IllegalArgumentException("bad base-64");
162 | }
163 |
164 | // Maybe we got lucky and allocated exactly enough output space.
165 | if (decoder.op == decoder.output.length) {
166 | return decoder.output;
167 | }
168 |
169 | // Need to shorten the array, so allocate a new one of the
170 | // right size and copy.
171 | byte[] temp = new byte[decoder.op];
172 | System.arraycopy(decoder.output, 0, temp, 0, decoder.op);
173 | return temp;
174 | }
175 |
176 | /* package */ static class Decoder extends Coder {
177 | /**
178 | * Lookup table for turning bytes into their position in the
179 | * Base64 alphabet.
180 | */
181 | private static final int DECODE[] = {
182 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
183 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
184 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
185 | 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1,
186 | -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
187 | 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
188 | -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
189 | 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
190 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
191 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
192 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
193 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
194 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
195 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
196 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
197 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
198 | };
199 |
200 | /**
201 | * Decode lookup table for the "web safe" variant (RFC 3548
202 | * sec. 4) where - and _ replace + and /.
203 | */
204 | private static final int DECODE_WEBSAFE[] = {
205 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
206 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
207 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1,
208 | 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1,
209 | -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
210 | 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63,
211 | -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
212 | 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
213 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
214 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
215 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
216 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
217 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
218 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
219 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
220 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
221 | };
222 |
223 | /** Non-data values in the DECODE arrays. */
224 | private static final int SKIP = -1;
225 | private static final int EQUALS = -2;
226 |
227 | /**
228 | * States 0-3 are reading through the next input tuple.
229 | * State 4 is having read one '=' and expecting exactly
230 | * one more.
231 | * State 5 is expecting no more data or padding characters
232 | * in the input.
233 | * State 6 is the error state; an error has been detected
234 | * in the input and no future input can "fix" it.
235 | */
236 | private int state; // state number (0 to 6)
237 | private int value;
238 |
239 | final private int[] alphabet;
240 |
241 | public Decoder(int flags, byte[] output) {
242 | this.output = output;
243 |
244 | alphabet = ((flags & URL_SAFE) == 0) ? DECODE : DECODE_WEBSAFE;
245 | state = 0;
246 | value = 0;
247 | }
248 |
249 | /**
250 | * @return an overestimate for the number of bytes {@code
251 | * len} bytes could decode to.
252 | */
253 | public int maxOutputSize(int len) {
254 | return len * 3/4 + 10;
255 | }
256 |
257 | /**
258 | * Decode another block of input data.
259 | *
260 | * @return true if the state machine is still healthy. false if
261 | * bad base-64 data has been detected in the input stream.
262 | */
263 | public boolean process(byte[] input, int offset, int len, boolean finish) {
264 | if (this.state == 6) return false;
265 |
266 | int p = offset;
267 | len += offset;
268 |
269 | // Using local variables makes the decoder about 12%
270 | // faster than if we manipulate the member variables in
271 | // the loop. (Even alphabet makes a measurable
272 | // difference, which is somewhat surprising to me since
273 | // the member variable is final.)
274 | int state = this.state;
275 | int value = this.value;
276 | int op = 0;
277 | final byte[] output = this.output;
278 | final int[] alphabet = this.alphabet;
279 |
280 | while (p < len) {
281 | // Try the fast path: we're starting a new tuple and the
282 | // next four bytes of the input stream are all data
283 | // bytes. This corresponds to going through states
284 | // 0-1-2-3-0. We expect to use this method for most of
285 | // the data.
286 | //
287 | // If any of the next four bytes of input are non-data
288 | // (whitespace, etc.), value will end up negative. (All
289 | // the non-data values in decode are small negative
290 | // numbers, so shifting any of them up and or'ing them
291 | // together will result in a value with its top bit set.)
292 | //
293 | // You can remove this whole block and the output should
294 | // be the same, just slower.
295 | if (state == 0) {
296 | while (p+4 <= len &&
297 | (value = ((alphabet[input[p] & 0xff] << 18) |
298 | (alphabet[input[p+1] & 0xff] << 12) |
299 | (alphabet[input[p+2] & 0xff] << 6) |
300 | (alphabet[input[p+3] & 0xff]))) >= 0) {
301 | output[op+2] = (byte) value;
302 | output[op+1] = (byte) (value >> 8);
303 | output[op] = (byte) (value >> 16);
304 | op += 3;
305 | p += 4;
306 | }
307 | if (p >= len) break;
308 | }
309 |
310 | // The fast path isn't available -- either we've read a
311 | // partial tuple, or the next four input bytes aren't all
312 | // data, or whatever. Fall back to the slower state
313 | // machine implementation.
314 |
315 | int d = alphabet[input[p++] & 0xff];
316 |
317 | switch (state) {
318 | case 0:
319 | if (d >= 0) {
320 | value = d;
321 | ++state;
322 | } else if (d != SKIP) {
323 | this.state = 6;
324 | return false;
325 | }
326 | break;
327 |
328 | case 1:
329 | if (d >= 0) {
330 | value = (value << 6) | d;
331 | ++state;
332 | } else if (d != SKIP) {
333 | this.state = 6;
334 | return false;
335 | }
336 | break;
337 |
338 | case 2:
339 | if (d >= 0) {
340 | value = (value << 6) | d;
341 | ++state;
342 | } else if (d == EQUALS) {
343 | // Emit the last (partial) output tuple;
344 | // expect exactly one more padding character.
345 | output[op++] = (byte) (value >> 4);
346 | state = 4;
347 | } else if (d != SKIP) {
348 | this.state = 6;
349 | return false;
350 | }
351 | break;
352 |
353 | case 3:
354 | if (d >= 0) {
355 | // Emit the output triple and return to state 0.
356 | value = (value << 6) | d;
357 | output[op+2] = (byte) value;
358 | output[op+1] = (byte) (value >> 8);
359 | output[op] = (byte) (value >> 16);
360 | op += 3;
361 | state = 0;
362 | } else if (d == EQUALS) {
363 | // Emit the last (partial) output tuple;
364 | // expect no further data or padding characters.
365 | output[op+1] = (byte) (value >> 2);
366 | output[op] = (byte) (value >> 10);
367 | op += 2;
368 | state = 5;
369 | } else if (d != SKIP) {
370 | this.state = 6;
371 | return false;
372 | }
373 | break;
374 |
375 | case 4:
376 | if (d == EQUALS) {
377 | ++state;
378 | } else if (d != SKIP) {
379 | this.state = 6;
380 | return false;
381 | }
382 | break;
383 |
384 | case 5:
385 | if (d != SKIP) {
386 | this.state = 6;
387 | return false;
388 | }
389 | break;
390 | }
391 | }
392 |
393 | if (!finish) {
394 | // We're out of input, but a future call could provide
395 | // more.
396 | this.state = state;
397 | this.value = value;
398 | this.op = op;
399 | return true;
400 | }
401 |
402 | // Done reading input. Now figure out where we are left in
403 | // the state machine and finish up.
404 |
405 | switch (state) {
406 | case 0:
407 | // Output length is a multiple of three. Fine.
408 | break;
409 | case 1:
410 | // Read one extra input byte, which isn't enough to
411 | // make another output byte. Illegal.
412 | this.state = 6;
413 | return false;
414 | case 2:
415 | // Read two extra input bytes, enough to emit 1 more
416 | // output byte. Fine.
417 | output[op++] = (byte) (value >> 4);
418 | break;
419 | case 3:
420 | // Read three extra input bytes, enough to emit 2 more
421 | // output bytes. Fine.
422 | output[op++] = (byte) (value >> 10);
423 | output[op++] = (byte) (value >> 2);
424 | break;
425 | case 4:
426 | // Read one padding '=' when we expected 2. Illegal.
427 | this.state = 6;
428 | return false;
429 | case 5:
430 | // Read all the padding '='s we expected and no more.
431 | // Fine.
432 | break;
433 | }
434 |
435 | this.state = state;
436 | this.op = op;
437 | return true;
438 | }
439 | }
440 |
441 | // --------------------------------------------------------
442 | // encoding
443 | // --------------------------------------------------------
444 |
445 | /**
446 | * Base64-encode the given data and return a newly allocated
447 | * String with the result.
448 | *
449 | * @param input the data to encode
450 | * @param flags controls certain features of the encoded output.
451 | * Passing {@code DEFAULT} results in output that
452 | * adheres to RFC 2045.
453 | */
454 | public static String encodeToString(byte[] input, int flags) {
455 | try {
456 | return new String(encode(input, flags), "US-ASCII");
457 | } catch (UnsupportedEncodingException e) {
458 | // US-ASCII is guaranteed to be available.
459 | throw new AssertionError(e);
460 | }
461 | }
462 |
463 | /**
464 | * Base64-encode the given data and return a newly allocated
465 | * String with the result.
466 | *
467 | * @param input the data to encode
468 | * @param offset the position within the input array at which to
469 | * start
470 | * @param len the number of bytes of input to encode
471 | * @param flags controls certain features of the encoded output.
472 | * Passing {@code DEFAULT} results in output that
473 | * adheres to RFC 2045.
474 | */
475 | public static String encodeToString(byte[] input, int offset, int len, int flags) {
476 | try {
477 | return new String(encode(input, offset, len, flags), "US-ASCII");
478 | } catch (UnsupportedEncodingException e) {
479 | // US-ASCII is guaranteed to be available.
480 | throw new AssertionError(e);
481 | }
482 | }
483 |
484 | /**
485 | * Base64-encode the given data and return a newly allocated
486 | * byte[] with the result.
487 | *
488 | * @param input the data to encode
489 | * @param flags controls certain features of the encoded output.
490 | * Passing {@code DEFAULT} results in output that
491 | * adheres to RFC 2045.
492 | */
493 | public static byte[] encode(byte[] input, int flags) {
494 | return encode(input, 0, input.length, flags);
495 | }
496 |
497 | /**
498 | * Base64-encode the given data and return a newly allocated
499 | * byte[] with the result.
500 | *
501 | * @param input the data to encode
502 | * @param offset the position within the input array at which to
503 | * start
504 | * @param len the number of bytes of input to encode
505 | * @param flags controls certain features of the encoded output.
506 | * Passing {@code DEFAULT} results in output that
507 | * adheres to RFC 2045.
508 | */
509 | public static byte[] encode(byte[] input, int offset, int len, int flags) {
510 | Encoder encoder = new Encoder(flags, null);
511 |
512 | // Compute the exact length of the array we will produce.
513 | int output_len = len / 3 * 4;
514 |
515 | // Account for the tail of the data and the padding bytes, if any.
516 | if (encoder.do_padding) {
517 | if (len % 3 > 0) {
518 | output_len += 4;
519 | }
520 | } else {
521 | switch (len % 3) {
522 | case 0: break;
523 | case 1: output_len += 2; break;
524 | case 2: output_len += 3; break;
525 | }
526 | }
527 |
528 | // Account for the newlines, if any.
529 | if (encoder.do_newline && len > 0) {
530 | output_len += (((len-1) / (3 * Encoder.LINE_GROUPS)) + 1) *
531 | (encoder.do_cr ? 2 : 1);
532 | }
533 |
534 | encoder.output = new byte[output_len];
535 | encoder.process(input, offset, len, true);
536 |
537 | assert encoder.op == output_len;
538 |
539 | return encoder.output;
540 | }
541 |
542 | /* package */ static class Encoder extends Coder {
543 | /**
544 | * Emit a new line every this many output tuples. Corresponds to
545 | * a 76-character line length (the maximum allowable according to
546 | * RFC 2045).
547 | */
548 | public static final int LINE_GROUPS = 19;
549 |
550 | /**
551 | * Lookup table for turning Base64 alphabet positions (6 bits)
552 | * into output bytes.
553 | */
554 | private static final byte ENCODE[] = {
555 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
556 | 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
557 | 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
558 | 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/',
559 | };
560 |
561 | /**
562 | * Lookup table for turning Base64 alphabet positions (6 bits)
563 | * into output bytes.
564 | */
565 | private static final byte ENCODE_WEBSAFE[] = {
566 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
567 | 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
568 | 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
569 | 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_',
570 | };
571 |
572 | final private byte[] tail;
573 | /* package */ int tailLen;
574 | private int count;
575 |
576 | final public boolean do_padding;
577 | final public boolean do_newline;
578 | final public boolean do_cr;
579 | final private byte[] alphabet;
580 |
581 | public Encoder(int flags, byte[] output) {
582 | this.output = output;
583 |
584 | do_padding = (flags & NO_PADDING) == 0;
585 | do_newline = (flags & NO_WRAP) == 0;
586 | do_cr = (flags & CRLF) != 0;
587 | alphabet = ((flags & URL_SAFE) == 0) ? ENCODE : ENCODE_WEBSAFE;
588 |
589 | tail = new byte[2];
590 | tailLen = 0;
591 |
592 | count = do_newline ? LINE_GROUPS : -1;
593 | }
594 |
595 | /**
596 | * @return an overestimate for the number of bytes {@code
597 | * len} bytes could encode to.
598 | */
599 | public int maxOutputSize(int len) {
600 | return len * 8/5 + 10;
601 | }
602 |
603 | public boolean process(byte[] input, int offset, int len, boolean finish) {
604 | // Using local variables makes the encoder about 9% faster.
605 | final byte[] alphabet = this.alphabet;
606 | final byte[] output = this.output;
607 | int op = 0;
608 | int count = this.count;
609 |
610 | int p = offset;
611 | len += offset;
612 | int v = -1;
613 |
614 | // First we need to concatenate the tail of the previous call
615 | // with any input bytes available now and see if we can empty
616 | // the tail.
617 |
618 | switch (tailLen) {
619 | case 0:
620 | // There was no tail.
621 | break;
622 |
623 | case 1:
624 | if (p+2 <= len) {
625 | // A 1-byte tail with at least 2 bytes of
626 | // input available now.
627 | v = ((tail[0] & 0xff) << 16) |
628 | ((input[p++] & 0xff) << 8) |
629 | (input[p++] & 0xff);
630 | tailLen = 0;
631 | };
632 | break;
633 |
634 | case 2:
635 | if (p+1 <= len) {
636 | // A 2-byte tail with at least 1 byte of input.
637 | v = ((tail[0] & 0xff) << 16) |
638 | ((tail[1] & 0xff) << 8) |
639 | (input[p++] & 0xff);
640 | tailLen = 0;
641 | }
642 | break;
643 | }
644 |
645 | if (v != -1) {
646 | output[op++] = alphabet[(v >> 18) & 0x3f];
647 | output[op++] = alphabet[(v >> 12) & 0x3f];
648 | output[op++] = alphabet[(v >> 6) & 0x3f];
649 | output[op++] = alphabet[v & 0x3f];
650 | if (--count == 0) {
651 | if (do_cr) output[op++] = '\r';
652 | output[op++] = '\n';
653 | count = LINE_GROUPS;
654 | }
655 | }
656 |
657 | // At this point either there is no tail, or there are fewer
658 | // than 3 bytes of input available.
659 |
660 | // The main loop, turning 3 input bytes into 4 output bytes on
661 | // each iteration.
662 | while (p+3 <= len) {
663 | v = ((input[p] & 0xff) << 16) |
664 | ((input[p+1] & 0xff) << 8) |
665 | (input[p+2] & 0xff);
666 | output[op] = alphabet[(v >> 18) & 0x3f];
667 | output[op+1] = alphabet[(v >> 12) & 0x3f];
668 | output[op+2] = alphabet[(v >> 6) & 0x3f];
669 | output[op+3] = alphabet[v & 0x3f];
670 | p += 3;
671 | op += 4;
672 | if (--count == 0) {
673 | if (do_cr) output[op++] = '\r';
674 | output[op++] = '\n';
675 | count = LINE_GROUPS;
676 | }
677 | }
678 |
679 | if (finish) {
680 | // Finish up the tail of the input. Note that we need to
681 | // consume any bytes in tail before any bytes
682 | // remaining in input; there should be at most two bytes
683 | // total.
684 |
685 | if (p-tailLen == len-1) {
686 | int t = 0;
687 | v = ((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 4;
688 | tailLen -= t;
689 | output[op++] = alphabet[(v >> 6) & 0x3f];
690 | output[op++] = alphabet[v & 0x3f];
691 | if (do_padding) {
692 | output[op++] = '=';
693 | output[op++] = '=';
694 | }
695 | if (do_newline) {
696 | if (do_cr) output[op++] = '\r';
697 | output[op++] = '\n';
698 | }
699 | } else if (p-tailLen == len-2) {
700 | int t = 0;
701 | v = (((tailLen > 1 ? tail[t++] : input[p++]) & 0xff) << 10) |
702 | (((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 2);
703 | tailLen -= t;
704 | output[op++] = alphabet[(v >> 12) & 0x3f];
705 | output[op++] = alphabet[(v >> 6) & 0x3f];
706 | output[op++] = alphabet[v & 0x3f];
707 | if (do_padding) {
708 | output[op++] = '=';
709 | }
710 | if (do_newline) {
711 | if (do_cr) output[op++] = '\r';
712 | output[op++] = '\n';
713 | }
714 | } else if (do_newline && op > 0 && count != LINE_GROUPS) {
715 | if (do_cr) output[op++] = '\r';
716 | output[op++] = '\n';
717 | }
718 |
719 | assert tailLen == 0;
720 | assert p == len;
721 | } else {
722 | // Save the leftovers in tail to be consumed on the next
723 | // call to encodeInternal.
724 |
725 | if (p == len-1) {
726 | tail[tailLen++] = input[p];
727 | } else if (p == len-2) {
728 | tail[tailLen++] = input[p];
729 | tail[tailLen++] = input[p+1];
730 | }
731 | }
732 |
733 | this.op = op;
734 | this.count = count;
735 |
736 | return true;
737 | }
738 | }
739 |
740 | private Base64() { } // don't instantiate
741 | }
742 |
--------------------------------------------------------------------------------
/src/com/google/tools/Client.java:
--------------------------------------------------------------------------------
1 | package com.google.tools;
2 |
3 | import java.io.ByteArrayOutputStream;
4 | import java.io.IOException;
5 | import java.io.InputStream;
6 | import java.io.OutputStream;
7 | import java.net.HttpURLConnection;
8 | import java.net.ProtocolException;
9 | import java.util.zip.GZIPInputStream;
10 | import java.util.zip.GZIPOutputStream;
11 |
12 | public abstract class Client {
13 | public static final String KEY_HTTP_USER_AGENT = "userAgent";
14 |
15 | protected static final String REQUEST_CONTENT_TYPE_FIELD = "Content-Type";
16 | protected static final String REQUEST_CONTENT_ENCODING_FIELD = "Content-Encoding";
17 | protected static final String REQUEST_USER_AGENT_FIELD = "User-Agent";
18 | protected static final String REQUEST_ACCEPT_FIELD = "Accept";
19 | protected static final String REQUEST_ACCEPT_ENCODING_FIELD = "Accept-Encoding";
20 |
21 | public static boolean DEBUG = false;
22 | public static boolean DEBUG_ERROR = true;
23 | public static boolean DEBUG_HEADER = false;
24 |
25 | protected String requestContentType = "application/x-www-form-urlencoded; charset=UTF-8";
26 | protected String requestMethod = "POST";
27 |
28 | protected boolean isError(final HttpURLConnection connection) {
29 | try {
30 | if (connection.getResponseCode() != 200) {
31 | if (DEBUG_ERROR) {
32 | System.out
33 | .println("Error: " + connection.getResponseCode() + " " + connection.getResponseMessage());
34 | }
35 | return true;
36 | }
37 | } catch (final IOException e) {
38 | return true;
39 | }
40 | return false;
41 | }
42 |
43 | protected void prepareConnection(final HttpURLConnection connection, final boolean gzip) {
44 | try {
45 | connection.setRequestMethod(requestMethod);
46 | } catch (final ProtocolException e) {
47 | if (DEBUG_ERROR) {
48 | System.out.println("Could not enable POST-Request");
49 | }
50 | throw new RuntimeException("Could not enable POST-Request", e);
51 | }
52 | connection.setRequestProperty(REQUEST_CONTENT_TYPE_FIELD, requestContentType);
53 | //connection.setRequestProperty(REQUEST_ACCEPT_ENCODING_FIELD, gzip ? "gzip" : "identity");
54 | if (gzip) {
55 | connection.setRequestProperty(REQUEST_CONTENT_ENCODING_FIELD, "gzip");
56 | }
57 | connection.setUseCaches(false);
58 | connection.setDoInput(true);
59 | connection.setDoOutput(true);
60 | }
61 |
62 | protected byte[] readData(final HttpURLConnection connection, final boolean gunzip) {
63 | return readData(connection, isError(connection), gunzip);
64 | }
65 |
66 | protected byte[] readData(final HttpURLConnection connection, final boolean error, final boolean gunzip) {
67 | try {
68 | InputStream is = null;
69 | if (error) {
70 | is = connection.getErrorStream();
71 | } else {
72 | is = connection.getInputStream();
73 | if (gunzip) {
74 | is = new GZIPInputStream(is);
75 | }
76 | }
77 | if (is == null) {
78 | if (DEBUG) {
79 | System.out.println("InputStream is null ?!");
80 | }
81 | }
82 | return readStreamToEnd(is);
83 | } catch (final Exception e) {
84 | if (DEBUG_ERROR) {
85 | System.out.println("Could not read data!");
86 | }
87 | throw new RuntimeException("Could not read data", e);
88 | }
89 | }
90 |
91 | protected void setUserAgent(HttpURLConnection connection, RequestContext info) {
92 | connection.setRequestProperty(REQUEST_USER_AGENT_FIELD, info.getString(KEY_HTTP_USER_AGENT));
93 | }
94 |
95 | protected void setUserAgent(HttpURLConnection connection, String userAgent) {
96 | connection.setRequestProperty(REQUEST_USER_AGENT_FIELD, userAgent);
97 | }
98 |
99 | protected byte[] readStreamToEnd(final InputStream is) throws IOException {
100 | final ByteArrayOutputStream bos = new ByteArrayOutputStream();
101 | if (is != null) {
102 | final byte[] buff = new byte[1024];
103 | while (true) {
104 | final int nb = is.read(buff);
105 | if (nb < 0) {
106 | break;
107 | }
108 | bos.write(buff, 0, nb);
109 | }
110 | is.close();
111 | }
112 | return bos.toByteArray();
113 | }
114 |
115 | protected void beforeRequest(final HttpURLConnection connection) {
116 | if (DEBUG_HEADER) {
117 | for (String key : connection.getRequestProperties().keySet()) {
118 | System.out.println("Header | " + key + ": " + connection.getRequestProperty(key));
119 | }
120 | }
121 | }
122 |
123 | protected void writeData(final HttpURLConnection connection, final byte[] bytes, final boolean gzip) {
124 | beforeRequest(connection);
125 | try {
126 | OutputStream os = connection.getOutputStream();
127 | if (gzip) {
128 | os = new GZIPOutputStream(os);
129 | }
130 | /*
131 | * final DataOutputStream stream = new DataOutputStream(os);
132 | * stream.writeBytes(string);
133 | */
134 | os.write(bytes);
135 | os.flush();
136 | os.close();
137 | } catch (final IOException e) {
138 | if (DEBUG_ERROR) {
139 | System.out.println("Could not send data!");
140 | e.printStackTrace();
141 | }
142 | throw new RuntimeException("Could not send data", e);
143 | }
144 | }
145 |
146 | protected void writeData(final HttpURLConnection connection, final String string, final boolean gzip) {
147 | if (DEBUG) {
148 | System.out.println("Sending" + (gzip ? "(gzipped)" : "") + ": " + string);
149 | }
150 | writeData(connection, string.getBytes(), gzip);
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/src/com/google/tools/RequestContext.java:
--------------------------------------------------------------------------------
1 | package com.google.tools;
2 |
3 | import com.google.android.AndroidContext;
4 |
5 | import java.util.*;
6 | import java.util.regex.Pattern;
7 |
8 | public class RequestContext {
9 | public static final Pattern FALSE_PATTERN = Pattern.compile("^(0|false|f|off|no|n)$", Pattern.CASE_INSENSITIVE);
10 | public static final Pattern TRUE_PATTERN = Pattern.compile("^(1|true|t|on|yes|y)$", Pattern.CASE_INSENSITIVE);
11 | private final HashMap