1
18
19 package io.undertow.util;
20
21 import java.io.IOException;
22 import java.io.ObjectInputStream;
23 import java.io.OutputStream;
24 import java.io.Serializable;
25 import java.lang.reflect.Field;
26 import java.nio.ByteBuffer;
27
28 import static java.lang.Integer.signum;
29 import static java.lang.System.arraycopy;
30 import static java.util.Arrays.copyOfRange;
31
32 import io.undertow.UndertowMessages;
33
34
39 public final class HttpString implements Comparable<HttpString>, Serializable {
40 private static final long serialVersionUID = 1L;
41
42 private final byte[] bytes;
43 private final transient int hashCode;
44
48 private final int orderInt;
49 private transient String string;
50
51 private static final Field hashCodeField;
52
53 static {
54 try {
55 hashCodeField = HttpString.class.getDeclaredField("hashCode");
56 hashCodeField.setAccessible(true);
57 } catch (NoSuchFieldException e) {
58 throw new NoSuchFieldError(e.getMessage());
59 }
60 }
61
62
65 public static final HttpString EMPTY = new HttpString("");
66
67
72 public HttpString(final byte[] bytes) {
73 this(bytes.clone(), null);
74 }
75
76
83 public HttpString(final byte[] bytes, int offset, int length) {
84 this(copyOfRange(bytes, offset, offset + length), null);
85 }
86
87
92 public HttpString(final ByteBuffer buffer) {
93 this(take(buffer), null);
94 }
95
96
102 public HttpString(final String string) {
103 this(string, 0);
104 }
105
106 HttpString(final String string, int orderInt) {
107 this.orderInt = orderInt;
108 final int len = string.length();
109 final byte[] bytes = new byte[len];
110 for (int i = 0; i < len; i++) {
111 char c = string.charAt(i);
112 if (c > 0xff) {
113 throw new IllegalArgumentException("Invalid string contents " + string);
114 }
115 bytes[i] = (byte) c;
116 }
117 this.bytes = bytes;
118 this.hashCode = calcHashCode(bytes);
119 this.string = string;
120 checkForNewlines();
121 }
122
123 private void checkForNewlines() {
124 for(byte b : bytes) {
125 if(b == '\r' || b == '\n') {
126 throw UndertowMessages.MESSAGES.newlineNotSupportedInHttpString(string);
127 }
128 }
129 }
130
131 private HttpString(final byte[] bytes, final String string) {
132 this.bytes = bytes;
133 this.hashCode = calcHashCode(bytes);
134 this.string = string;
135 this.orderInt = 0;
136 checkForNewlines();
137 }
138
139
146 public static HttpString tryFromString(String string) {
147 HttpString cached = Headers.fromCache(string);
148 if(cached != null) {
149 return cached;
150 }
151 final int len = string.length();
152 final byte[] bytes = new byte[len];
153 for (int i = 0; i < len; i++) {
154 char c = string.charAt(i);
155 if (c > 0xff) {
156 return null;
157 }
158 bytes[i] = (byte) c;
159 }
160 return new HttpString(bytes, string);
161 }
162
163
168 public int length() {
169 return bytes.length;
170 }
171
172
177 public byte byteAt(int idx) {
178 return bytes[idx];
179 }
180
181
189 public void copyTo(int srcOffs, byte[] dst, int offs, int len) {
190 arraycopy(bytes, srcOffs, dst, offs, len);
191 }
192
193
200 public void copyTo(byte[] dst, int offs, int len) {
201 copyTo(0, dst, offs, len);
202 }
203
204
210 public void copyTo(byte[] dst, int offs) {
211 copyTo(dst, offs, bytes.length);
212 }
213
214
219 public void appendTo(ByteBuffer buffer) {
220 buffer.put(bytes);
221 }
222
223
229 public void writeTo(OutputStream output) throws IOException {
230 output.write(bytes);
231 }
232
233 private static byte[] take(final ByteBuffer buffer) {
234 if (buffer.hasArray()) {
235
236 try {
237 return copyOfRange(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining());
238 } finally {
239 buffer.position(buffer.limit());
240 }
241 } else {
242 final byte[] bytes = new byte[buffer.remaining()];
243 buffer.get(bytes);
244 return bytes;
245 }
246 }
247
248
254 public int compareTo(final HttpString other) {
255 if(orderInt != 0 && other.orderInt != 0) {
256 return signum(orderInt - other.orderInt);
257 }
258 final int len = Math.min(bytes.length, other.bytes.length);
259 int res;
260 for (int i = 0; i < len; i++) {
261 res = signum(higher(bytes[i]) - higher(other.bytes[i]));
262 if (res != 0) return res;
263 }
264
265 return signum(bytes.length - other.bytes.length);
266 }
267
268
273 @Override
274 public int hashCode() {
275 return hashCode;
276 }
277
278
284 @Override
285 public boolean equals(final Object other) {
286 if(other == this) {
287 return true;
288 }
289 if(!(other instanceof HttpString)) {
290 return false;
291 }
292 HttpString otherString = (HttpString) other;
293 if(orderInt > 0 && otherString.orderInt > 0) {
294
295 return false;
296 }
297 return bytesAreEqual(bytes, otherString.bytes);
298 }
299
300
306 public boolean equals(final HttpString other) {
307 return other == this || other != null && bytesAreEqual(bytes, other.bytes);
308 }
309
310 private static int calcHashCode(final byte[] bytes) {
311 int hc = 17;
312 for (byte b : bytes) {
313 hc = (hc << 4) + hc + higher(b);
314 }
315 return hc;
316 }
317
318 private static int higher(byte b) {
319 return b & (b >= 'a' && b <= 'z' ? 0xDF : 0xFF);
320 }
321
322 private static boolean bytesAreEqual(final byte[] a, final byte[] b) {
323 return a.length == b.length && bytesAreEquivalent(a, b);
324 }
325
326 private static boolean bytesAreEquivalent(final byte[] a, final byte[] b) {
327 assert a.length == b.length;
328 final int len = a.length;
329 for (int i = 0; i < len; i++) {
330 if (higher(a[i]) != higher(b[i])) {
331 return false;
332 }
333 }
334 return true;
335 }
336
337
342 @Override
343 @SuppressWarnings("deprecation")
344 public String toString() {
345 if (string == null) {
346 string = new String(bytes, 0);
347 }
348 return string;
349 }
350
351 private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
352 ois.defaultReadObject();
353 try {
354 hashCodeField.setInt(this, calcHashCode(bytes));
355 } catch (IllegalAccessException e) {
356 throw new IllegalAccessError(e.getMessage());
357 }
358 }
359
360 static int hashCodeOf(String headerName) {
361 int hc = 17;
362
363 for (int i = 0; i < headerName.length(); ++i) {
364 hc = (hc << 4) + hc + higher((byte) headerName.charAt(i));
365 }
366 return hc;
367 }
368
369 public boolean equalToString(String headerName) {
370 if(headerName.length() != bytes.length) {
371 return false;
372 }
373
374 final int len = bytes.length;
375 for (int i = 0; i < len; i++) {
376 if (higher(bytes[i]) != higher((byte)headerName.charAt(i))) {
377 return false;
378 }
379 }
380 return true;
381 }
382 }
383