1 /*
2  * Copyright (C) 2014 jsonwebtoken.io
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package io.jsonwebtoken.impl;
17
18 public class Base64UrlCodec extends AbstractTextCodec {
19
20     @Override
21     public String encode(byte[] data) {
22         String base64Text = TextCodec.BASE64.encode(data);
23         byte[] bytes = base64Text.getBytes(US_ASCII);
24
25         //base64url encoding doesn't use padding chars:
26         bytes = removePadding(bytes);
27
28         //replace URL-unfriendly Base64 chars to url-friendly ones:
29         for (int i = 0; i < bytes.length; i++) {
30             if (bytes[i] == '+') {
31                 bytes[i] = '-';
32             } else if (bytes[i] == '/') {
33                 bytes[i] = '_';
34             }
35         }
36
37         return new String(bytes, US_ASCII);
38     }
39
40     protected byte[] removePadding(byte[] bytes) {
41
42         byte[] result = bytes;
43
44         int paddingCount = 0;
45         for (int i = bytes.length - 1; i > 0; i--) {
46             if (bytes[i] == '=') {
47                 paddingCount++;
48             } else {
49                 break;
50             }
51         }
52         if (paddingCount > 0) {
53             result = new byte[bytes.length - paddingCount];
54             System.arraycopy(bytes, 0, result, 0, bytes.length - paddingCount);
55         }
56
57         return result;
58     }
59
60     @Override
61     public byte[] decode(String encoded) {
62         char[] chars = encoded.toCharArray(); //always ASCII - one char == 1 byte
63
64         //Base64 requires padding to be in place before decoding, so add it if necessary:
65         chars = ensurePadding(chars);
66
67         //Replace url-friendly chars back to normal Base64 chars:
68         for (int i = 0; i < chars.length; i++) {
69             if (chars[i] == '-') {
70                 chars[i] = '+';
71             } else if (chars[i] == '_') {
72                 chars[i] = '/';
73             }
74         }
75
76         String base64Text = new String(chars);
77
78         return TextCodec.BASE64.decode(base64Text);
79     }
80
81     protected char[] ensurePadding(char[] chars) {
82
83         char[] result = chars; //assume argument in case no padding is necessary
84
85         int paddingCount = 0;
86
87         //fix for https://github.com/jwtk/jjwt/issues/31
88         int remainder = chars.length % 4;
89         if (remainder == 2 || remainder == 3) {
90             paddingCount = 4 - remainder;
91         }
92
93         if (paddingCount > 0) {
94             result = new char[chars.length + paddingCount];
95             System.arraycopy(chars, 0, result, 0, chars.length);
96             for (int i = 0; i < paddingCount; i++) {
97                 result[chars.length + i] = '=';
98             }
99         }
100
101         return result;
102     }
103
104 }
105