1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 package org.modeshape.common.text;
25
26 import java.text.CharacterIterator;
27 import java.text.StringCharacterIterator;
28 import java.util.BitSet;
29 import net.jcip.annotations.Immutable;
30
31
32
33
34
35
36 @Immutable
37 public class UrlEncoder implements TextEncoder, TextDecoder {
38
39
40
41
42
43
44
45
46
47
48
49
50
51 private static final BitSet RFC2396_UNRESERVED_CHARACTERS = new BitSet(256);
52 private static final BitSet RFC2396_UNRESERVED_WITH_SLASH_CHARACTERS;
53
54 public static final char ESCAPE_CHARACTER = '%';
55
56 static {
57 RFC2396_UNRESERVED_CHARACTERS.set('a', 'z' + 1);
58 RFC2396_UNRESERVED_CHARACTERS.set('A', 'Z' + 1);
59 RFC2396_UNRESERVED_CHARACTERS.set('0', '9' + 1);
60 RFC2396_UNRESERVED_CHARACTERS.set('-');
61 RFC2396_UNRESERVED_CHARACTERS.set('_');
62 RFC2396_UNRESERVED_CHARACTERS.set('.');
63 RFC2396_UNRESERVED_CHARACTERS.set('!');
64 RFC2396_UNRESERVED_CHARACTERS.set('~');
65 RFC2396_UNRESERVED_CHARACTERS.set('*');
66 RFC2396_UNRESERVED_CHARACTERS.set('\'');
67 RFC2396_UNRESERVED_CHARACTERS.set('(');
68 RFC2396_UNRESERVED_CHARACTERS.set(')');
69
70 RFC2396_UNRESERVED_WITH_SLASH_CHARACTERS = (BitSet)RFC2396_UNRESERVED_CHARACTERS.clone();
71 RFC2396_UNRESERVED_WITH_SLASH_CHARACTERS.set('/');
72 }
73
74 private boolean slashEncoded = true;
75
76
77
78
79 public String encode( String text ) {
80 if (text == null) return null;
81 if (text.length() == 0) return text;
82 return encode(text, isSlashEncoded() ? RFC2396_UNRESERVED_CHARACTERS : RFC2396_UNRESERVED_WITH_SLASH_CHARACTERS);
83 }
84
85 protected String encode( String text,
86 BitSet safeChars ) {
87 final StringBuilder result = new StringBuilder();
88 final CharacterIterator iter = new StringCharacterIterator(text);
89 for (char c = iter.first(); c != CharacterIterator.DONE; c = iter.next()) {
90 if (safeChars.get(c)) {
91
92 result.append(c);
93 } else {
94
95 result.append(ESCAPE_CHARACTER);
96 result.append(Character.toLowerCase(Character.forDigit(c / 16, 16)));
97 result.append(Character.toLowerCase(Character.forDigit(c % 16, 16)));
98 }
99 }
100 return result.toString();
101 }
102
103
104
105
106 public String decode( String encodedText ) {
107 if (encodedText == null) return null;
108 if (encodedText.length() == 0) return encodedText;
109 final StringBuilder result = new StringBuilder();
110 final CharacterIterator iter = new StringCharacterIterator(encodedText);
111 for (char c = iter.first(); c != CharacterIterator.DONE; c = iter.next()) {
112 if (c == ESCAPE_CHARACTER) {
113 boolean foundEscapedCharacter = false;
114
115 char hexChar1 = iter.next();
116 char hexChar2 = hexChar1 != CharacterIterator.DONE ? iter.next() : CharacterIterator.DONE;
117 if (hexChar2 != CharacterIterator.DONE) {
118
119 int hexNum1 = Character.digit(hexChar1, 16);
120 int hexNum2 = Character.digit(hexChar2, 16);
121 if (hexNum1 > -1 && hexNum2 > -1) {
122 foundEscapedCharacter = true;
123 result.append((char)(hexNum1 * 16 + hexNum2));
124 }
125 }
126 if (!foundEscapedCharacter) {
127 result.append(c);
128 if (hexChar1 != CharacterIterator.DONE) result.append(hexChar1);
129 if (hexChar2 != CharacterIterator.DONE) result.append(hexChar2);
130 }
131 } else {
132 result.append(c);
133 }
134 }
135 return result.toString();
136 }
137
138
139
140
141 public boolean isSlashEncoded() {
142 return this.slashEncoded;
143 }
144
145
146
147
148
149 public UrlEncoder setSlashEncoded( boolean slashEncoded ) {
150 this.slashEncoded = slashEncoded;
151 return this;
152 }
153
154 }