001: /*
002: * @(#)Util.java 0.3-2 18/06/1999
003: *
004: * This file is part of the HTTPClient package
005: * Copyright (C) 1996-1999 Ronald Tschalär
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this library; if not, write to the Free
019: * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
020: * MA 02111-1307, USA
021: *
022: * For questions, suggestions, bug-reports, enhancement-requests etc.
023: * I may be contacted at:
024: *
025: * ronald@innovation.ch
026: *
027: */
028:
029: package HTTPClient;
030:
031: import java.net.URL;
032: import java.util.Date;
033: import java.util.BitSet;
034: import java.util.Vector;
035: import java.util.Hashtable;
036: import java.util.SimpleTimeZone;
037: import java.util.StringTokenizer;
038: import java.text.SimpleDateFormat;
039: import java.util.Locale;
040:
041: /**
042: * This class holds various utility methods.
043: *
044: * @version 0.3-2 18/06/1999
045: * @author Ronald Tschalär
046: */
047:
048: public class Util {
049: private static final BitSet Separators = new BitSet(128);
050: private static final BitSet TokenChar = new BitSet(128);
051: private static final BitSet UnsafeChar = new BitSet(128);
052: private static SimpleDateFormat http_format;
053:
054: static {
055: // rfc-2068 tspecial
056: Separators.set('(');
057: Separators.set(')');
058: Separators.set('<');
059: Separators.set('>');
060: Separators.set('@');
061: Separators.set(',');
062: Separators.set(';');
063: Separators.set(':');
064: Separators.set('\\');
065: Separators.set('"');
066: Separators.set('/');
067: Separators.set('[');
068: Separators.set(']');
069: Separators.set('?');
070: Separators.set('=');
071: Separators.set('{');
072: Separators.set('}');
073: Separators.set(' ');
074: Separators.set('\t');
075:
076: // rfc-2068 token
077: for (int ch = 32; ch < 127; ch++)
078: TokenChar.set(ch);
079: TokenChar.xor(Separators);
080:
081: // rfc-1738 unsafe characters, including CTL and SP, and excluding
082: // "#" and "%"
083: for (int ch = 0; ch < 32; ch++)
084: UnsafeChar.set(ch);
085: UnsafeChar.set(' ');
086: UnsafeChar.set('<');
087: UnsafeChar.set('>');
088: UnsafeChar.set('"');
089: UnsafeChar.set('{');
090: UnsafeChar.set('}');
091: UnsafeChar.set('|');
092: UnsafeChar.set('\\');
093: UnsafeChar.set('^');
094: UnsafeChar.set('~');
095: UnsafeChar.set('[');
096: UnsafeChar.set(']');
097: UnsafeChar.set('`');
098: UnsafeChar.set(127);
099:
100: // rfc-1123 date format (restricted to GMT, as per rfc-2068)
101: /* This initialization has been moved to httpDate() because it
102: * takes an awfully long time and is often not needed
103: *
104: http_format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'",
105: Locale.US);
106: http_format.setTimeZone(new SimpleTimeZone(0, "GMT"));
107: */
108: }
109:
110: // Constructors
111:
112: /**
113: * This class isn't meant to be instantiated.
114: */
115: private Util() {
116: }
117:
118: // Methods
119:
120: // this doesn't work!!! Aaaaarrgghhh!
121: final static Object[] resizeArray(Object[] src, int new_size) {
122: Object tmp[] = new Object[new_size];
123: System.arraycopy(src, 0, tmp, 0,
124: (src.length < new_size ? src.length : new_size));
125: return tmp;
126: }
127:
128: final static NVPair[] resizeArray(NVPair[] src, int new_size) {
129: NVPair tmp[] = new NVPair[new_size];
130: System.arraycopy(src, 0, tmp, 0,
131: (src.length < new_size ? src.length : new_size));
132: return tmp;
133: }
134:
135: final static AuthorizationInfo[] resizeArray(
136: AuthorizationInfo[] src, int new_size) {
137: AuthorizationInfo tmp[] = new AuthorizationInfo[new_size];
138: System.arraycopy(src, 0, tmp, 0,
139: (src.length < new_size ? src.length : new_size));
140: return tmp;
141: }
142:
143: final static Cookie[] resizeArray(Cookie[] src, int new_size) {
144: Cookie tmp[] = new Cookie[new_size];
145: System.arraycopy(src, 0, tmp, 0,
146: (src.length < new_size ? src.length : new_size));
147: return tmp;
148: }
149:
150: final static String[] resizeArray(String[] src, int new_size) {
151: String tmp[] = new String[new_size];
152: System.arraycopy(src, 0, tmp, 0,
153: (src.length < new_size ? src.length : new_size));
154: return tmp;
155: }
156:
157: final static boolean[] resizeArray(boolean[] src, int new_size) {
158: boolean tmp[] = new boolean[new_size];
159: System.arraycopy(src, 0, tmp, 0,
160: (src.length < new_size ? src.length : new_size));
161: return tmp;
162: }
163:
164: final static byte[] resizeArray(byte[] src, int new_size) {
165: byte tmp[] = new byte[new_size];
166: System.arraycopy(src, 0, tmp, 0,
167: (src.length < new_size ? src.length : new_size));
168: return tmp;
169: }
170:
171: final static char[] resizeArray(char[] src, int new_size) {
172: char tmp[] = new char[new_size];
173: System.arraycopy(src, 0, tmp, 0,
174: (src.length < new_size ? src.length : new_size));
175: return tmp;
176: }
177:
178: final static int[] resizeArray(int[] src, int new_size) {
179: int tmp[] = new int[new_size];
180: System.arraycopy(src, 0, tmp, 0,
181: (src.length < new_size ? src.length : new_size));
182: return tmp;
183: }
184:
185: /**
186: * Split a property into an array of Strings, using "|" as the
187: * separator.
188: */
189: static String[] splitProperty(String prop) {
190: if (prop == null)
191: return new String[0];
192:
193: StringTokenizer tok = new StringTokenizer(prop, "|");
194: String[] list = new String[tok.countTokens()];
195: for (int idx = 0; idx < list.length; idx++)
196: list[idx] = tok.nextToken().trim();
197:
198: return list;
199: }
200:
201: /**
202: * Helper method for context lists used by modules. Returns the
203: * list associated with the context if it exists; otherwise it creates
204: * a new list and adds it to the context list.
205: *
206: * @param cntxt_list the list of lists indexed by context
207: * @param cntxt the context
208: */
209: final static Hashtable getList(Hashtable cntxt_list, Object cntxt) {
210: Hashtable list = (Hashtable) cntxt_list.get(cntxt);
211: if (list == null) {
212: synchronized (cntxt_list) // only synch if necessary
213: {
214: list = (Hashtable) cntxt_list.get(cntxt);
215: if (list == null) // verify nobody else beat us to it
216: {
217: list = new Hashtable();
218: cntxt_list.put(cntxt, list);
219: }
220: }
221: }
222:
223: return list;
224: }
225:
226: /**
227: * Creates an array of distances to speed up the search in findStr().
228: * The returned array should be passed as the second argument to
229: * findStr().
230: *
231: * @param search the search string (same as the first argument to
232: * findStr()).
233: * @return an array of distances (to be passed as the second argument to
234: * findStr()).
235: */
236: final static int[] compile_search(byte[] search) {
237: int[] cmp = { 0, 1, 0, 1, 0, 1 };
238: int end;
239:
240: for (int idx = 0; idx < search.length; idx++) {
241: for (end = idx + 1; end < search.length; end++) {
242: if (search[idx] == search[end])
243: break;
244: }
245: if (end < search.length) {
246: if ((end - idx) > cmp[1]) {
247: cmp[4] = cmp[2];
248: cmp[5] = cmp[3];
249: cmp[2] = cmp[0];
250: cmp[3] = cmp[1];
251: cmp[0] = idx;
252: cmp[1] = end - idx;
253: } else if ((end - idx) > cmp[3]) {
254: cmp[4] = cmp[2];
255: cmp[5] = cmp[3];
256: cmp[2] = idx;
257: cmp[3] = end - idx;
258: } else if ((end - idx) > cmp[3]) {
259: cmp[4] = idx;
260: cmp[5] = end - idx;
261: }
262: }
263: }
264:
265: cmp[1] += cmp[0];
266: cmp[3] += cmp[2];
267: cmp[5] += cmp[4];
268: return cmp;
269: }
270:
271: /**
272: * Search for a string. Use compile_search() to first generate the second
273: * argument. This uses a Knuth-Morris-Pratt like algorithm.
274: *
275: * @param search the string to search for.
276: * @param cmp the the array returned by compile_search.
277: * @param str the string in which to look for <var>search</var>.
278: * @param beg the position at which to start the search in
279: * <var>str</var>.
280: * @param end the position at which to end the search in <var>str</var>,
281: * noninclusive.
282: * @return the position in <var>str</var> where <var>search</var> was
283: * found, or -1 if not found.
284: */
285: final static int findStr(byte[] search, int[] cmp, byte[] str,
286: int beg, int end) {
287: int c1f = cmp[0], c1l = cmp[1], d1 = c1l - c1f, c2f = cmp[2], c2l = cmp[3], d2 = c2l
288: - c2f, c3f = cmp[4], c3l = cmp[5], d3 = c3l - c3f;
289:
290: Find: while (beg + search.length <= end) {
291: if (search[c1l] == str[beg + c1l]) {
292: /* This is correct, but Visual J++ can't cope with it...
293: Comp: if (search[c1f] == str[beg+c1f])
294: {
295: for (int idx=0; idx<search.length; idx++)
296: if (search[idx] != str[beg+idx]) break Comp;
297:
298: break Find; // we found it
299: }
300: * so here is the replacement: */
301: if (search[c1f] == str[beg + c1f]) {
302: boolean same = true;
303:
304: for (int idx = 0; idx < search.length; idx++)
305: if (search[idx] != str[beg + idx]) {
306: same = false;
307: break;
308: }
309:
310: if (same)
311: break Find; // we found it
312: }
313:
314: beg += d1;
315: } else if (search[c2l] == str[beg + c2l])
316: beg += d2;
317: else if (search[c3l] == str[beg + c3l])
318: beg += d3;
319: else
320: beg++;
321: }
322:
323: if (beg + search.length > end)
324: return -1;
325: else
326: return beg;
327: }
328:
329: /**
330: * Replace quoted characters by their unquoted version. Quoted characters
331: * are characters preceded by a slash. E.g. "\c" would be replaced by "c".
332: * This is used in parsing http headers where quoted-characters are
333: * allowed in quoted-strings and often used to quote the quote character
334: * <">.
335: *
336: * @param str the string do dequote
337: * @return the string do with all quoted characters replaced by their
338: * true value.
339: */
340: public final static String dequoteString(String str) {
341: if (str.indexOf('\\') == -1)
342: return str;
343:
344: char[] buf = str.toCharArray();
345: int pos = 0, num_deq = 0;
346: while (pos < buf.length) {
347: if (buf[pos] == '\\' && pos + 1 < buf.length) {
348: System.arraycopy(buf, pos + 1, buf, pos, buf.length
349: - pos - 1);
350: num_deq++;
351: }
352: pos++;
353: }
354:
355: return new String(buf, 0, buf.length - num_deq);
356: }
357:
358: /**
359: * Replace given characters by their quoted version. Quoted characters
360: * are characters preceded by a slash. E.g. "c" would be replaced by "\c".
361: * This is used in generating http headers where certain characters need
362: * to be quoted, such as the quote character <">.
363: *
364: * @param str the string do quote
365: * @param qlist the list of characters to quote
366: * @return the string do with all characters replaced by their
367: * quoted version.
368: */
369: public final static String quoteString(String str, String qlist) {
370: char[] list = qlist.toCharArray();
371: int idx;
372: for (idx = 0; idx < list.length; idx++)
373: if (str.indexOf(list[idx]) != -1)
374: break;
375: if (idx == list.length)
376: return str;
377:
378: int len = str.length();
379: char[] buf = new char[len * 2];
380: str.getChars(0, len, buf, 0);
381: int pos = 0;
382: while (pos < len) {
383: if (qlist.indexOf(buf[pos], 0) != -1) {
384: if (len == buf.length)
385: buf = Util.resizeArray(buf, len + str.length());
386:
387: System.arraycopy(buf, pos, buf, pos + 1, len - pos);
388: len++;
389: buf[pos++] = '\\';
390: }
391: pos++;
392: }
393:
394: return new String(buf, 0, len);
395: }
396:
397: /**
398: * This parses the value part of a header. All quoted strings are
399: * dequoted.
400: *
401: * @see #parseHeader(java.lang.String, boolean)
402: * @param header the value part of the header.
403: * @return a Vector containing all the elements; each entry is an
404: * instance of <var>HttpHeaderElement</var>.
405: * @exception ParseException if the syntax rules are violated.
406: */
407: public final static Vector parseHeader(String header)
408: throws ParseException {
409: return parseHeader(header, true);
410: }
411:
412: /**
413: * This parses the value part of a header. The result is a Vector of
414: * HttpHeaderElement's. The syntax the header must conform to is:
415: *
416: * <PRE>
417: * header = [ element ] *( "," [ element ] )
418: * element = name [ "=" [ value ] ] *( ";" [ param ] )
419: * param = name [ "=" [ value ] ]
420: *
421: * name = token
422: * value = ( token | quoted-string )
423: *
424: * token = 1*<any char except "=", ",", ";", <"> and
425: * white space>
426: * quoted-string = <"> *( text | quoted-char ) <">
427: * text = any char except <">
428: * quoted-char = "\" char
429: * </PRE>
430: *
431: * Any amount of white space is allowed between any part of the header,
432: * element or param and is ignored. A missing value in any element or
433: * param will be stored as the empty string; if the "=" is also missing
434: * <var>null</var> will be stored instead.
435: *
436: * @param header the value part of the header.
437: * @param dequote if true all quoted strings are dequoted.
438: * @return a Vector containing all the elements; each entry is an
439: * instance of <var>HttpHeaderElement</var>.
440: * @exception ParseException if the above syntax rules are violated.
441: * @see HTTPClient.HttpHeaderElement
442: */
443: public final static Vector parseHeader(String header,
444: boolean dequote) throws ParseException {
445: if (header == null)
446: return null;
447: char[] buf = header.toCharArray();
448: Vector elems = new Vector();
449: boolean first = true;
450: int beg = -1, end = 0, len = buf.length, abeg[] = new int[1];
451: String elem_name, elem_value;
452:
453: elements: while (true) {
454: if (!first) // find required ","
455: {
456: beg = skipSpace(buf, end);
457: if (beg == len)
458: break;
459: if (buf[beg] != ',')
460: throw new ParseException("Bad header format: '"
461: + header + "'\nExpected \",\" at position "
462: + beg);
463: }
464: first = false;
465:
466: beg = skipSpace(buf, beg + 1);
467: if (beg == len)
468: break elements;
469: if (buf[beg] == ',') // skip empty elements
470: {
471: end = beg;
472: continue elements;
473: }
474:
475: if (buf[beg] == '=' || buf[beg] == ';' || buf[beg] == '"')
476: throw new ParseException("Bad header format: '"
477: + header + "'\nEmpty element name at position "
478: + beg);
479:
480: end = beg + 1; // extract element name
481: while (end < len && !Character.isSpace(buf[end])
482: && buf[end] != '=' && buf[end] != ','
483: && buf[end] != ';')
484: end++;
485: elem_name = new String(buf, beg, end - beg);
486:
487: beg = skipSpace(buf, end);
488: if (beg < len && buf[beg] == '=') // element value
489: {
490: abeg[0] = beg + 1;
491: elem_value = parseValue(buf, abeg, header, dequote);
492: end = abeg[0];
493: } else {
494: elem_value = null;
495: end = beg;
496: }
497:
498: NVPair[] params = new NVPair[0];
499: params: while (true) {
500: String param_name, param_value;
501:
502: beg = skipSpace(buf, end); // expect ";"
503: if (beg == len || buf[beg] != ';')
504: break params;
505:
506: beg = skipSpace(buf, beg + 1);
507: if (beg == len || buf[beg] == ',') {
508: end = beg;
509: break params;
510: }
511: if (buf[beg] == ';') // skip empty parameters
512: {
513: end = beg;
514: continue params;
515: }
516:
517: if (buf[beg] == '=' || buf[beg] == '"')
518: throw new ParseException("Bad header format: '"
519: + header
520: + "'\nEmpty parameter name at position "
521: + beg);
522:
523: end = beg + 1; // extract param name
524: while (end < len && !Character.isSpace(buf[end])
525: && buf[end] != '=' && buf[end] != ','
526: && buf[end] != ';')
527: end++;
528: param_name = new String(buf, beg, end - beg);
529:
530: beg = skipSpace(buf, end);
531: if (beg < len && buf[beg] == '=') // element value
532: {
533: abeg[0] = beg + 1;
534: param_value = parseValue(buf, abeg, header, dequote);
535: end = abeg[0];
536: } else {
537: param_value = null;
538: end = beg;
539: }
540:
541: params = Util.resizeArray(params, params.length + 1);
542: params[params.length - 1] = new NVPair(param_name,
543: param_value);
544: }
545:
546: elems.addElement(new HttpHeaderElement(elem_name,
547: elem_value, params));
548: }
549:
550: return elems;
551: }
552:
553: /**
554: * Parse the value part. Accepts either token or quoted string.
555: */
556: private static String parseValue(char[] buf, int[] abeg,
557: String header, boolean dequote) throws ParseException {
558: int beg = abeg[0], end = beg, len = buf.length;
559: String value;
560:
561: beg = skipSpace(buf, beg);
562:
563: if (beg < len && buf[beg] == '"') // it's a quoted-string
564: {
565: beg++;
566: end = beg;
567: char[] deq_buf = null;
568: int deq_pos = 0, lst_pos = beg;
569:
570: while (end < len && buf[end] != '"') {
571: if (buf[end] == '\\') {
572: if (dequote) // dequote char
573: {
574: if (deq_buf == null)
575: deq_buf = new char[buf.length];
576: System.arraycopy(buf, lst_pos, deq_buf,
577: deq_pos, end - lst_pos);
578: deq_pos += end - lst_pos;
579: lst_pos = ++end;
580: } else
581: end++; // skip quoted char
582: }
583:
584: end++;
585: }
586: if (end == len)
587: throw new ParseException("Bad header format: '"
588: + header + "'\nClosing <\"> for quoted-string"
589: + " starting at position " + (beg - 1)
590: + " not found");
591: if (deq_buf != null) {
592: System.arraycopy(buf, lst_pos, deq_buf, deq_pos, end
593: - lst_pos);
594: deq_pos += end - lst_pos;
595: value = new String(deq_buf, 0, deq_pos);
596: } else
597: value = new String(buf, beg, end - beg);
598: end++;
599: } else // it's a simple token value
600: {
601: end = beg;
602: while (end < len && !Character.isSpace(buf[end])
603: && buf[end] != ',' && buf[end] != ';')
604: end++;
605:
606: value = new String(buf, beg, end - beg);
607: }
608:
609: abeg[0] = end;
610: return value;
611: }
612:
613: /**
614: * Determines if the given header contains a certain token. The header
615: * must conform to the rules outlined in parseHeader().
616: *
617: * @see #parseHeader(java.lang.String)
618: * @param header the header value.
619: * @param token the token to find; the match is case-insensitive.
620: * @return true if the token is present, false otherwise.
621: * @exception ParseException if this is thrown parseHeader().
622: */
623: public final static boolean hasToken(String header, String token)
624: throws ParseException {
625: if (header == null)
626: return false;
627: else
628: return parseHeader(header).contains(
629: new HttpHeaderElement(token));
630: }
631:
632: /**
633: * Get the HttpHeaderElement with the name <var>name</var>.
634: *
635: * @param header a vector of HttpHeaderElement's, such as is returned
636: * from <code>parseHeader()</code>
637: * @param name the name of element to retrieve; matching is
638: * case-insensitive
639: * @return the request element, or null if none found.
640: * @see #parseHeader(java.lang.String)
641: */
642: public final static HttpHeaderElement getElement(Vector header,
643: String name) {
644: int idx = header.indexOf(new HttpHeaderElement(name));
645: if (idx == -1)
646: return null;
647: else
648: return (HttpHeaderElement) header.elementAt(idx);
649: }
650:
651: /**
652: * retrieves the value associated with the parameter <var>param</var> in
653: * a given header string. It parses the header using
654: * <code>parseHeader()</code> and then searches the first element for the
655: * given parameter. This is used especially in headers like
656: * 'Content-type' and 'Content-Disposition'.
657: *
658: * <P>quoted characters ("\x") in a quoted string are dequoted.
659: *
660: * @see #parseHeader(java.lang.String)
661: * @param param the parameter name
662: * @param hdr the header value
663: * @return the value for this parameter, or null if not found.
664: * @exception ParseException if the above syntax rules are violated.
665: */
666: public final static String getParameter(String param, String hdr)
667: throws ParseException {
668: NVPair[] params = ((HttpHeaderElement) parseHeader(hdr)
669: .firstElement()).getParams();
670:
671: for (int idx = 0; idx < params.length; idx++) {
672: if (params[idx].getName().equalsIgnoreCase(param))
673: return params[idx].getValue();
674: }
675:
676: return null;
677: }
678:
679: /**
680: * Assembles a Vector of HttpHeaderElements into a full header string.
681: * The individual header elements are seperated by a ", ".
682: *
683: * @param the parsed header
684: * @return a string containing the assembled header
685: */
686: public final static String assembleHeader(Vector pheader) {
687: StringBuffer hdr = new StringBuffer(200);
688: int len = pheader.size();
689:
690: for (int idx = 0; idx < len; idx++) {
691: ((HttpHeaderElement) pheader.elementAt(idx)).appendTo(hdr);
692: hdr.append(", ");
693: }
694: hdr.setLength(hdr.length() - 2);
695:
696: return hdr.toString();
697: }
698:
699: /**
700: * returns the position of the first non-space character in a char array
701: * starting a position pos.
702: *
703: * @param str the char array
704: * @param pos the position to start looking
705: * @return the position of the first non-space character
706: */
707: final static int skipSpace(char[] str, int pos) {
708: int len = str.length;
709: while (pos < len && Character.isSpace(str[pos]))
710: pos++;
711: return pos;
712: }
713:
714: /**
715: * returns the position of the first space character in a char array
716: * starting a position pos.
717: *
718: * @param str the char array
719: * @param pos the position to start looking
720: * @return the position of the first space character, or the length of
721: * the string if not found
722: */
723: final static int findSpace(char[] str, int pos) {
724: int len = str.length;
725: while (pos < len && !Character.isSpace(str[pos]))
726: pos++;
727: return pos;
728: }
729:
730: /**
731: * returns the position of the first non-token character in a char array
732: * starting a position pos.
733: *
734: * @param str the char array
735: * @param pos the position to start looking
736: * @return the position of the first non-token character, or the length
737: * of the string if not found
738: */
739: final static int skipToken(char[] str, int pos) {
740: int len = str.length;
741: while (pos < len && TokenChar.get(str[pos]))
742: pos++;
743: return pos;
744: }
745:
746: /**
747: * Does the string need to be quoted when sent in a header? I.e. does
748: * it contain non-token characters?
749: *
750: * @param str the string
751: * @return true if it needs quoting (i.e. it contains non-token chars)
752: */
753: final static boolean needsQuoting(String str) {
754: int len = str.length(), pos = 0;
755:
756: while (pos < len && TokenChar.get(str.charAt(pos)))
757: pos++;
758: return (pos < len);
759: }
760:
761: /**
762: * Compares two http urls for equality. This exists because the method
763: * <code>java.net.URL.sameFile()</code> is broken (an explicit port 80
764: * doesn't compare equal to an implicit port, and it doesn't take
765: * escapes into account).
766: *
767: * <P>Two http urls are considered equal if they have the same protocol
768: * (case-insensitive match), the same host (case-insensitive), the
769: * same port and the same file (after decoding escaped characters).
770: *
771: * @param url1 the first url
772: * @param url1 the second url
773: * @return true if <var>url1</var> and <var>url2</var> compare equal
774: */
775: public final static boolean sameHttpURL(URL url1, URL url2) {
776: if (!url1.getProtocol().equalsIgnoreCase(url2.getProtocol()))
777: return false;
778:
779: if (!url1.getHost().equalsIgnoreCase(url2.getHost()))
780: return false;
781:
782: int port1 = url1.getPort(), port2 = url2.getPort();
783: if (port1 == -1)
784: port1 = URI.defaultPort(url1.getProtocol());
785: if (port2 == -1)
786: port2 = URI.defaultPort(url1.getProtocol());
787: if (port1 != port2)
788: return false;
789:
790: try {
791: return URI.unescape(url1.getFile()).equals(
792: URI.unescape(url2.getFile()));
793: } catch (ParseException pe) {
794: return url1.getFile().equals(url2.getFile());
795: }
796: }
797:
798: /**
799: * Return the default port used by a given protocol.
800: *
801: * @param protocol the protocol
802: * @return the port number, or 0 if unknown
803: * @deprecated use URI.defaultPort() instead
804: * @see HTTPClient.URI#defaultPort(java.lang.String)
805: */
806: public final static int defaultPort(String protocol) {
807: return URI.defaultPort(protocol);
808: }
809:
810: /**
811: * This returns a string containing the date and time in <var>date</var>
812: * formatted according to a subset of RFC-1123. The format is defined in
813: * the HTTP/1.0 spec (RFC-1945), section 3.3, and the HTTP/1.1 spec
814: * (RFC-2068), section 3.3.1. Note that Date.toGMTString() is close, but
815: * is missing the weekday and supresses the leading zero if the day is
816: * less than the 10th. Instead we use the SimpleDateFormat class.
817: *
818: * <P>Some versions of JDK 1.1.x are bugged in that their GMT uses
819: * daylight savings time... Therefore we use our own timezone
820: * definitions.
821: *
822: * @param date the date and time to be converted
823: * @return a string containg the date and time as used in http
824: */
825: public final static String httpDate(Date date) {
826: if (http_format == null) {
827: synchronized (HTTPClient.Util.class) {
828: if (http_format == null) {
829: http_format = new SimpleDateFormat(
830: "EEE, dd MMM yyyy HH:mm:ss 'GMT'",
831: Locale.US);
832: http_format
833: .setTimeZone(new SimpleTimeZone(0, "GMT"));
834: }
835: }
836: }
837:
838: return http_format.format(date);
839: }
840:
841: /**
842: * Escape unsafe characters in a path.
843: *
844: * @param path the original path
845: * @return the path with all unsafe characters escaped
846: */
847: final static String escapeUnsafeChars(String path) {
848: int len = path.length();
849: char[] buf = new char[3 * len];
850:
851: int dst = 0;
852: for (int src = 0; src < len; src++) {
853: char ch = path.charAt(src);
854: if (ch >= 128 || UnsafeChar.get(ch)) {
855: buf[dst++] = '%';
856: buf[dst++] = hex_map[(ch & 0xf0) >>> 4];
857: buf[dst++] = hex_map[ch & 0x0f];
858: } else
859: buf[dst++] = ch;
860: }
861:
862: if (dst > len)
863: return new String(buf, 0, dst);
864: else
865: return path;
866: }
867:
868: static final char[] hex_map = { '0', '1', '2', '3', '4', '5', '6',
869: '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
870:
871: /**
872: * Extract the path from an http resource.
873: *
874: * <P>The "resource" part of an HTTP URI can contain a number of parts,
875: * some of which are not always of interest. These methods here will
876: * extract the various parts, assuming the following syntanx (taken from
877: * RFC-2068):
878: *
879: * <PRE>
880: * resource = [ "/" ] [ path ] [ ";" params ] [ "?" query ] [ "#" fragment ]
881: * </PRE>
882: *
883: * @param the resource to split
884: * @return the path, including any leading "/"
885: * @see #getParams
886: * @see #getQuery
887: * @see #getFragment
888: */
889: public final static String getPath(String resource) {
890: int p, end = resource.length();
891: if ((p = resource.indexOf('#')) != -1) // find fragment
892: end = p;
893: if ((p = resource.indexOf('?')) != -1 && p < end) // find query
894: end = p;
895: if ((p = resource.indexOf(';')) != -1 && p < end) // find params
896: end = p;
897: return resource.substring(0, end);
898: }
899:
900: /**
901: * Extract the params part from an http resource.
902: *
903: * @param the resource to split
904: * @return the params, or null if there are none
905: * @see #getPath
906: */
907: public final static String getParams(String resource) {
908: int beg, f, q;
909: if ((beg = resource.indexOf(';')) == -1) // find params
910: return null;
911: if ((f = resource.indexOf('#')) != -1 && f < beg) // find fragment
912: return null;
913: if ((q = resource.indexOf('?')) != -1 && q < beg) // find query
914: return null;
915: if (q == -1 && f == -1)
916: return resource.substring(beg + 1);
917: if (f == -1 || (q != -1 && q < f))
918: return resource.substring(beg + 1, q);
919: else
920: return resource.substring(beg + 1, f);
921: }
922:
923: /**
924: * Extract the query string from an http resource.
925: *
926: * @param the resource to split
927: * @return the query, or null if there was none
928: * @see #getPath
929: */
930: public final static String getQuery(String resource) {
931: int beg, f;
932: if ((beg = resource.indexOf('?')) == -1) // find query
933: return null;
934: if ((f = resource.indexOf('#')) != -1 && f < beg) // find fragment
935: return null; // '?' is in fragment
936: if (f == -1)
937: return resource.substring(beg + 1); // no fragment
938: else
939: return resource.substring(beg + 1, f); // strip fragment
940: }
941:
942: /**
943: * Extract the fragment part from an http resource.
944: *
945: * @param the resource to split
946: * @return the fragment, or null if there was none
947: * @see #getPath
948: */
949: public final static String getFragment(String resource) {
950: int beg;
951: if ((beg = resource.indexOf('#')) == -1) // find fragment
952: return null;
953: else
954: return resource.substring(beg + 1);
955: }
956: }
|