001: /*
002: * Copyright 2005-2007 Noelios Consulting.
003: *
004: * The contents of this file are subject to the terms of the Common Development
005: * and Distribution License (the "License"). You may not use this file except in
006: * compliance with the License.
007: *
008: * You can obtain a copy of the license at
009: * http://www.opensource.org/licenses/cddl1.txt See the License for the specific
010: * language governing permissions and limitations under the License.
011: *
012: * When distributing Covered Code, include this CDDL HEADER in each file and
013: * include the License file at http://www.opensource.org/licenses/cddl1.txt If
014: * applicable, add the following below this CDDL HEADER, with the fields
015: * enclosed by brackets "[]" replaced with your own identifying information:
016: * Portions Copyright [yyyy] [name of copyright owner]
017: */
018:
019: package com.noelios.restlet.http;
020:
021: import java.io.IOException;
022: import java.io.InputStream;
023: import java.io.OutputStream;
024:
025: import org.restlet.data.CharacterSet;
026: import org.restlet.data.Parameter;
027: import org.restlet.data.Reference;
028:
029: /**
030: * HTTP-style header manipulation utilities.
031: *
032: * @author Jerome Louvel (contact@noelios.com)
033: */
034: public class HttpUtils {
035: /**
036: * Appends a source string as an HTTP comment.
037: *
038: * @param source
039: * The source string to format.
040: * @param destination
041: * The appendable destination.
042: * @throws IOException
043: */
044: public Appendable appendComment(CharSequence source,
045: Appendable destination) throws IOException {
046: destination.append('(');
047:
048: char c;
049: for (int i = 0; i < source.length(); i++) {
050: c = source.charAt(i);
051:
052: if (c == '(') {
053: destination.append("\\(");
054: } else if (c == ')') {
055: destination.append("\\)");
056: } else if (c == '\\') {
057: destination.append("\\\\");
058: } else {
059: destination.append(c);
060: }
061: }
062:
063: destination.append(')');
064: return destination;
065: }
066:
067: /**
068: * Creates a parameter.
069: *
070: * @param name
071: * The parameter name buffer.
072: * @param value
073: * The parameter value buffer (can be null).
074: * @return The created parameter.
075: * @throws IOException
076: */
077: public static Parameter createParameter(CharSequence name,
078: CharSequence value) throws IOException {
079: if (value != null) {
080: return new Parameter(name.toString(), value.toString());
081: } else {
082: return new Parameter(name.toString(), null);
083: }
084: }
085:
086: /**
087: * Appends a source string as an HTTP quoted string.
088: *
089: * @param source
090: * The unquoted source string.
091: * @param destination
092: * The destination to append to.
093: * @throws IOException
094: */
095: public static Appendable appendQuote(CharSequence source,
096: Appendable destination) throws IOException {
097: destination.append('"');
098:
099: char c;
100: for (int i = 0; i < source.length(); i++) {
101: c = source.charAt(i);
102:
103: if (c == '"') {
104: destination.append("\\\"");
105: } else if (c == '\\') {
106: destination.append("\\\\");
107: } else {
108: destination.append(c);
109: }
110: }
111:
112: destination.append('"');
113: return destination;
114: }
115:
116: /**
117: * Appends a source string as an URI encoded string.
118: *
119: * @param source
120: * The source string to format.
121: * @param destination
122: * The appendable destination.
123: * @param characterSet
124: * The supported character encoding.
125: * @throws IOException
126: */
127: public static Appendable appendUriEncoded(CharSequence source,
128: Appendable destination, CharacterSet characterSet)
129: throws IOException {
130: destination.append(Reference.encode(source.toString(),
131: characterSet));
132: return destination;
133: }
134:
135: /**
136: * Formats a product description.
137: *
138: * @param nameToken
139: * The product name token.
140: * @param versionToken
141: * The product version token.
142: * @param destination
143: * The appendable destination;
144: * @throws IOException
145: */
146: public static void formatProduct(CharSequence nameToken,
147: CharSequence versionToken, Appendable destination)
148: throws IOException {
149: if (!isToken(nameToken)) {
150: throw new IllegalArgumentException(
151: "Invalid product name detected. Only token characters are allowed.");
152: } else {
153: destination.append(nameToken);
154:
155: if (versionToken != null) {
156: if (!isToken(versionToken)) {
157: throw new IllegalArgumentException(
158: "Invalid product version detected. Only token characters are allowed.");
159: } else {
160: destination.append('/').append(versionToken);
161: }
162: }
163: }
164: }
165:
166: /**
167: * Indicates if the given character is in ASCII range.
168: *
169: * @param character
170: * The character to test.
171: * @return True if the given character is in ASCII range.
172: */
173: public static boolean isAsciiChar(int character) {
174: return (character >= 0) && (character <= 127);
175: }
176:
177: /**
178: * Indicates if the given character is upper case (A-Z).
179: *
180: * @param character
181: * The character to test.
182: * @return True if the given character is upper case (A-Z).
183: */
184: public static boolean isUpperCase(int character) {
185: return (character >= 'A') && (character <= 'Z');
186: }
187:
188: /**
189: * Indicates if the given character is lower case (a-z).
190: *
191: * @param character
192: * The character to test.
193: * @return True if the given character is lower case (a-z).
194: */
195: public static boolean isLowerCase(int character) {
196: return (character >= 'a') && (character <= 'z');
197: }
198:
199: /**
200: * Indicates if the given character is alphabetical (a-z or A-Z).
201: *
202: * @param character
203: * The character to test.
204: * @return True if the given character is alphabetical (a-z or A-Z).
205: */
206: public static boolean isAlpha(int character) {
207: return isUpperCase(character) || isLowerCase(character);
208: }
209:
210: /**
211: * Indicates if the given character is a digit (0-9).
212: *
213: * @param character
214: * The character to test.
215: * @return True if the given character is a digit (0-9).
216: */
217: public static boolean isDigit(int character) {
218: return (character >= '0') && (character <= '9');
219: }
220:
221: /**
222: * Indicates if the given character is a control character.
223: *
224: * @param character
225: * The character to test.
226: * @return True if the given character is a control character.
227: */
228: public static boolean isControlChar(int character) {
229: return ((character >= 0) && (character <= 31))
230: || (character == 127);
231: }
232:
233: /**
234: * Indicates if the given character is a carriage return.
235: *
236: * @param character
237: * The character to test.
238: * @return True if the given character is a carriage return.
239: */
240: public static boolean isCarriageReturn(int character) {
241: return (character == 13);
242: }
243:
244: /**
245: * Indicates if the given character is a line feed.
246: *
247: * @param character
248: * The character to test.
249: * @return True if the given character is a line feed.
250: */
251: public static boolean isLineFeed(int character) {
252: return (character == 10);
253: }
254:
255: /**
256: * Indicates if the given character is a space.
257: *
258: * @param character
259: * The character to test.
260: * @return True if the given character is a space.
261: */
262: public static boolean isSpace(int character) {
263: return (character == 32);
264: }
265:
266: /**
267: * Indicates if the given character is an horizontal tab.
268: *
269: * @param character
270: * The character to test.
271: * @return True if the given character is an horizontal tab.
272: */
273: public static boolean isHorizontalTab(int character) {
274: return (character == 9);
275: }
276:
277: /**
278: * Indicates if the given character is a double quote.
279: *
280: * @param character
281: * The character to test.
282: * @return True if the given character is a double quote.
283: */
284: public static boolean isDoubleQuote(int character) {
285: return (character == 34);
286: }
287:
288: /**
289: * Indicates if the given character is textual (ASCII and not a control
290: * character).
291: *
292: * @param character
293: * The character to test.
294: * @return True if the given character is textual (ASCII and not a control
295: * character).
296: */
297: public static boolean isText(int character) {
298: return isAsciiChar(character) && !isControlChar(character);
299: }
300:
301: /**
302: * Indicates if the given character is a separator.
303: *
304: * @param character
305: * The character to test.
306: * @return True if the given character is a separator.
307: */
308: public static boolean isSeparator(int character) {
309: switch (character) {
310: case '(':
311: case ')':
312: case '<':
313: case '>':
314: case '@':
315: case ',':
316: case ';':
317: case ':':
318: case '\\':
319: case '"':
320: case '/':
321: case '[':
322: case ']':
323: case '?':
324: case '=':
325: case '{':
326: case '}':
327: case ' ':
328: case '\t':
329: return true;
330:
331: default:
332: return false;
333: }
334: }
335:
336: /**
337: * Indicates if the given character is a token character (text and not a
338: * separator).
339: *
340: * @param character
341: * The character to test.
342: * @return True if the given character is a token character (text and not a
343: * separator).
344: */
345: public static boolean isTokenChar(int character) {
346: return isText(character) && !isSeparator(character);
347: }
348:
349: /**
350: * Indicates if the token is valid.<br/> Only contains valid token
351: * characters.
352: *
353: * @param token
354: * The token to check
355: * @return True if the token is valid.
356: */
357: public static boolean isToken(CharSequence token) {
358: for (int i = 0; i < token.length(); i++) {
359: if (!isTokenChar(token.charAt(i)))
360: return false;
361: }
362:
363: return true;
364: }
365:
366: /**
367: * Read a header. Return null if the last header was already read.
368: *
369: * @param is
370: * The message input stream.
371: * @param sb
372: * The string builder to reuse.
373: * @return The header read or null.
374: * @throws IOException
375: */
376: public static Parameter readHeader(InputStream is, StringBuilder sb)
377: throws IOException {
378: Parameter result = null;
379:
380: // Detect the end of headers
381: int next = is.read();
382: if (HttpUtils.isCarriageReturn(next)) {
383: next = is.read();
384: if (!HttpUtils.isLineFeed(next)) {
385: throw new IOException(
386: "Invalid end of headers. Line feed missing after the carriage return.");
387: }
388: } else {
389: result = new Parameter();
390:
391: // Parse the header name
392: while ((next != -1) && (next != ':')) {
393: sb.append((char) next);
394: next = is.read();
395: }
396:
397: if (next == -1) {
398: throw new IOException(
399: "Unable to parse the header name. End of stream reached too early.");
400: } else {
401: result.setName(sb.toString());
402: sb.delete(0, sb.length());
403:
404: next = is.read();
405: while (HttpUtils.isSpace(next)) {
406: // Skip any separator space between colon and header value
407: next = is.read();
408: }
409:
410: // Parse the header value
411: while ((next != -1)
412: && (!HttpUtils.isCarriageReturn(next))) {
413: sb.append((char) next);
414: next = is.read();
415: }
416:
417: if (next == -1) {
418: throw new IOException(
419: "Unable to parse the header value. End of stream reached too early.");
420: } else {
421: next = is.read();
422:
423: if (HttpUtils.isLineFeed(next)) {
424: result.setValue(sb.toString());
425: sb.delete(0, sb.length());
426: } else {
427: throw new IOException(
428: "Unable to parse the HTTP header value. The carriage return must be followed by a line feed.");
429: }
430: }
431:
432: }
433: }
434:
435: return result;
436: }
437:
438: /**
439: * Writes a header line.
440: *
441: * @param header
442: * The header to write.
443: * @param os
444: * The output stream.
445: * @throws IOException
446: */
447: public static void writeHeader(Parameter header, OutputStream os)
448: throws IOException {
449: os.write(header.getName().getBytes());
450: os.write(':');
451: os.write(' ');
452: os.write(header.getValue().getBytes());
453: os.write(13); // CR
454: os.write(10); // LF
455: }
456: }
|