001: // HttpReplyMessage.java
002: // $Id: HttpReplyMessage.java,v 1.35 2003/02/24 12:31:19 ylafon Exp $
003: // (c) COPYRIGHT MIT and INRIA, 1996.
004: // Please first read the full copyright statement in file COPYRIGHT.html
005:
006: package org.w3c.www.http;
007:
008: import java.io.IOException;
009: import java.io.OutputStream;
010:
011: import java.net.URL;
012:
013: import org.w3c.www.mime.MimeParser;
014:
015: public class HttpReplyMessage extends HttpEntityMessage {
016: // HTTP Reply message well-known headers
017: public static int H_ACCEPT_RANGES = 50;
018: public static int H_AGE = 51;
019: public static int H_LOCATION = 52;
020: public static int H_PROXY_AUTHENTICATE = 53;
021: public static int H_PUBLIC = 54;
022: public static int H_RETRY_AFTER = 55;
023: public static int H_SERVER = 56;
024: public static int H_VARY = 57;
025: public static int H_WARNING = 58;
026: public static int H_WWW_AUTHENTICATE = 59;
027: public static int H_AUTHENTICATION_INFO = 60;
028:
029: static {
030: registerHeader("Accept-Ranges",
031: "org.w3c.www.http.HttpTokenList", H_ACCEPT_RANGES);
032: registerHeader("Age", "org.w3c.www.http.HttpInteger", H_AGE);
033: registerHeader("Location", "org.w3c.www.http.HttpString",
034: H_LOCATION);
035: registerHeader("Proxy-Authenticate",
036: "org.w3c.www.http.HttpChallenge", H_PROXY_AUTHENTICATE);
037: registerHeader("Public", "org.w3c.www.http.HttpTokenList",
038: H_PUBLIC);
039: // String as it can be a number or a date...
040: registerHeader("Retry-After", "org.w3c.www.http.HttpString",
041: H_RETRY_AFTER);
042: registerHeader("Server", "org.w3c.www.http.HttpString",
043: H_SERVER);
044: registerHeader("Trailer", "org.w3c.www.http.HttpTokenList",
045: H_TRAILER);
046: registerHeader("Vary", "org.w3c.www.http.HttpCaseTokenList",
047: H_VARY);
048: registerHeader("Warning", "org.w3c.www.http.HttpWarningList",
049: H_WARNING);
050: registerHeader("WWW-Authenticate",
051: "org.w3c.www.http.HttpChallenge", H_WWW_AUTHENTICATE);
052: registerHeader("Authentication-Info",
053: "org.w3c.www.http.HttpParamList", H_AUTHENTICATION_INFO);
054: }
055:
056: /**
057: * The status associated with this reply.
058: */
059: protected int status = -1;
060: /**
061: * The reason phrase.
062: */
063: protected String reason = null;
064:
065: /**
066: * Emit the status line before emiting the actual reply headers.
067: * @param out The output stream to emit the reply to.
068: */
069:
070: protected void startEmit(OutputStream out, int what)
071: throws IOException {
072: if ((what & EMIT_HEADERS) != EMIT_HEADERS)
073: return;
074: if (major < 1)
075: return;
076: HttpBuffer buf = new HttpBuffer(64);
077: buf.append(HTTP.byteArrayVersion);
078: buf.append(' ');
079: buf.appendInt(status);
080: buf.append(' ');
081: buf.append((reason != null) ? reason : "Unknown Status Code");
082: buf.append('\r');
083: buf.append('\n');
084: buf.emit(out);
085: }
086:
087: public void dump(OutputStream out) {
088: // Dump the reply status line first, and then the headers
089: try {
090: startEmit(out, EMIT_HEADERS);
091: } catch (Exception ex) {
092: }
093: super .dump(out);
094: }
095:
096: /**
097: * MimeHeaderHolder implementation - Begining of reply parsing.
098: * If we can determine that this reply version number is less then
099: * 1.0, then we skip the header parsing by returning <strong>true</strong>
100: * to the MIME parser.
101: * <p>Otherwise, we parse the status line, and return <strong>false
102: * </strong> to make the MIME parser continue.
103: * @return A boolean <strong>true</strong> if the MIME parser should stop
104: * parsing, <strong>false</strong> otherwise.
105: * @exception IOException If some IO error occured while reading the
106: * stream.
107: * @exception HttpParserException if parsing failed.
108: */
109: public boolean notifyBeginParsing(MimeParser parser)
110: throws HttpParserException, IOException {
111: if (major <= 0)
112: return true;
113: // Append the whole reply line in some buffer:
114: HttpBuffer buf = new HttpBuffer();
115: int ch = parser.read();
116: int len = 0;
117:
118: // Skip leading CRLF, a present to Netscape
119: while ((ch == '\r') || (ch == '\n'))
120: ch = parser.read();
121: loop: while (true) {
122: switch (ch) {
123: case -1:
124: throw new HttpParserException("End Of File");
125: case '\r':
126: if ((ch = parser.read()) != '\n')
127: parser.unread(ch);
128: break loop;
129: case '\n':
130: break loop;
131: default:
132: buf.append(ch);
133: len++;
134: }
135: // That's a guard against very poor HTTP
136: if (len > 16 * 1024)
137: throw new HttpParserException("Invalid HTTP");
138: ch = parser.read();
139: }
140: // Parse the bufer into HTTP version and status code
141: byte line[] = buf.getByteCopy();
142: ParseState ps = new ParseState();
143: ps.ioff = 0;
144: ps.bufend = line.length;
145: ps.separator = (byte) ' ';
146:
147: if (HttpParser.nextItem(line, ps) < 0) {
148: // invalid, it should be an HTTP/0.9 reply...
149: // FIXME check this...
150: this .major = 0;
151: this .minor = 9;
152: this .status = 200;
153: return true;
154: // throw new RuntimeException("Bad reply: invalid status line ["
155: // + new String(line, 0, 0, line.length)
156: // + "]");
157: }
158: // Parse the reply version:
159: if ((line.length >= 4) && line[4] == (byte) '/') {
160: // A present to broken NCSA servers around
161: ParseState item = new ParseState();
162: item.ioff = ps.start + 5;
163: item.bufend = ps.end;
164: this .major = (short) HttpParser.parseInt(line, item);
165: item.prepare();
166: item.ioff++;
167: this .minor = (short) HttpParser.parseInt(line, item);
168: } else {
169: this .major = 1;
170: this .minor = 0;
171: }
172: // Parse the status code:
173: ps.prepare();
174: this .status = HttpParser.parseInt(line, ps);
175: // The rest of the sentence if the reason phrase:
176: ps.prepare();
177: HttpParser.skipSpaces(line, ps);
178: this .reason = new String(line, 0, ps.ioff, line.length
179: - ps.ioff);
180: return false;
181: }
182:
183: /**
184: * Get the standard HTTP reason phrase for the given status code.
185: * @param status The given status code.
186: * @return A String giving the standard reason phrase, or
187: * <strong>null</strong> if the status doesn't match any knowned error.
188: */
189:
190: public String getStandardReason(int status) {
191: int category = status / 100;
192: int catcode = status % 100;
193: switch (category) {
194: case 1:
195: if ((catcode >= 0) && (catcode < msg_100.length))
196: return HTTP.msg_100[catcode];
197: break;
198: case 2:
199: if ((catcode >= 0) && (catcode < msg_200.length))
200: return HTTP.msg_200[catcode];
201: break;
202: case 3:
203: if ((catcode >= 0) && (catcode < msg_300.length))
204: return HTTP.msg_300[catcode];
205: break;
206: case 4:
207: if ((catcode >= 0) && (catcode < msg_400.length))
208: return HTTP.msg_400[catcode];
209: break;
210: case 5:
211: if ((catcode >= 0) && (catcode < msg_500.length))
212: return HTTP.msg_500[catcode];
213: break;
214: }
215: return null;
216: }
217:
218: /**
219: * Get this reply status code.
220: * @return An integer, giving the reply status code.
221: */
222:
223: public int getStatus() {
224: return status;
225: }
226:
227: /**
228: * Set this reply status code.
229: * This will also set the reply reason, to the default HTTP/1.1 reason
230: * phrase.
231: * @param status The status code for this reply.
232: */
233:
234: public void setStatus(int status) {
235: if ((status != this .status) || (reason == null))
236: this .reason = getStandardReason(status);
237: this .status = status;
238: }
239:
240: /**
241: * Get the reason phrase for this reply.
242: * @return A String encoded reason phrase.
243: */
244:
245: public String getReason() {
246: return reason;
247: }
248:
249: /**
250: * Set the reason phrase of this reply.
251: * @param reason The reason phrase for this reply.
252: */
253:
254: public void setReason(String reason) {
255: this .reason = reason;
256: }
257:
258: /**
259: * Get the <code>private</code> directive of the cache control header.
260: * @return A list of fields (potentially empty) encoded as an array of
261: * String (with 0 length if empty), or <strong>null</strong> if
262: * undefined.
263: */
264:
265: public String[] getPrivate() {
266: HttpCacheControl cc = getCacheControl();
267: return (cc == null) ? null : cc.getPrivate();
268: }
269:
270: /**
271: * Check the <code>public</code> directive of the cache control header.
272: * @return A boolean <strong>true</strong> if set, <strong>false</strong>
273: * otherwise.
274: */
275:
276: public boolean checkPublic() {
277: HttpCacheControl cc = getCacheControl();
278: return (cc == null) ? false : cc.checkPublic();
279: }
280:
281: /**
282: * Set the <code>public</code> cache control directive.
283: * @param onoff Set it on or off.
284: */
285:
286: public void setPublic(boolean onoff) {
287: HttpCacheControl cc = getCacheControl();
288: if (onoff && (cc == null))
289: setCacheControl(cc = new HttpCacheControl(true));
290: else
291: return;
292: cc.setPublic(onoff);
293: }
294:
295: /**
296: * Check the <code>proxy-revalidate</code> directive of the cache control
297: * header.
298: * @return A boolean <strong>true</strong> if set, <strong>false</strong>
299: * otherwise.
300: */
301:
302: public boolean checkProxyRevalidate() {
303: HttpCacheControl cc = getCacheControl();
304: return (cc == null) ? false : cc.checkProxyRevalidate();
305: }
306:
307: /**
308: * Set the <code>proxy-revalidate</code> cache control directive.
309: * @param onoff Set it on or off.
310: */
311:
312: public void setProxyRevalidate(boolean onoff) {
313: HttpCacheControl cc = getCacheControl();
314: if (onoff && (cc == null))
315: setCacheControl(cc = new HttpCacheControl(true));
316: else
317: return;
318: cc.setProxyRevalidate(onoff);
319: }
320:
321: /**
322: * Check the <code>must-revalidate</code> directive of the cache control
323: * header.
324: * @return A boolean <strong>true</strong> if set, <strong>false</strong>
325: * otherwise.
326: */
327:
328: public boolean checkMustRevalidate() {
329: HttpCacheControl cc = getCacheControl();
330: return (cc == null) ? false : cc.checkMustRevalidate();
331: }
332:
333: /**
334: * Set the <code>must-revalidate</code> cache control directive.
335: * @param onoff Set it on or off.
336: */
337:
338: public void setMustRevalidate(boolean onoff) {
339: HttpCacheControl cc = getCacheControl();
340: if (onoff && (cc == null))
341: setCacheControl(cc = new HttpCacheControl(true));
342: else
343: return;
344: cc.setMustRevalidate(onoff);
345: }
346:
347: /**
348: * Get the list of accepted ranges.
349: * @return The list of units in which range requests are accepted, encoded
350: * as an array of String, or <strong>null</strong> if undefined.
351: */
352:
353: public String[] getAcceptRanges() {
354: HeaderValue value = getHeaderValue(H_ACCEPT_RANGES);
355: return (value != null) ? (String[]) value.getValue() : null;
356: }
357:
358: /**
359: * Set the list of units in which range requests are accepted.
360: * @param units The list of units, encoded as a String array, or
361: * <strong>null</strong> to reset the value.
362: */
363:
364: public void setAcceptRanges(String units[]) {
365: setHeaderValue(H_ACCEPT_RANGES, ((units == null) ? null
366: : new HttpTokenList(units)));
367: }
368:
369: /**
370: * Get the age of the attached entity.
371: * @return An integer giving the age as a number of seconds, or
372: * <strong>-1</strong> if undefined.
373: */
374:
375: public int getAge() {
376: HeaderValue value = getHeaderValue(H_AGE);
377: try {
378: return (value != null) ? ((Integer) value.getValue())
379: .intValue() : -1;
380: } catch (HttpInvalidValueException hpx) {
381: if (hpx.getMessage().indexOf("overflow") != -1) {
382: return Integer.MAX_VALUE;
383: }
384: }
385: return -1;
386: }
387:
388: /**
389: * Set the age of the attached entity.
390: * @param age The age of the attached entity as a number of seconds,
391: * or <strong>null</strong> to reset the value.
392: */
393:
394: public void setAge(int age) {
395: setHeaderValue(H_AGE, ((age == -1) ? null : new HttpInteger(
396: true, age)));
397: }
398:
399: /**
400: * Get the location of the reply.
401: * The location header field keeps track of where to relocate clients
402: * if needed.
403: * @return The location encoded as a String.
404: */
405:
406: public String getLocation() {
407: HeaderValue value = getHeaderValue(H_LOCATION);
408: return (value != null) ? (String) value.getValue() : null;
409: }
410:
411: /**
412: * Set the location value of the reply.
413: * @param location The location an URL instance, or <strong>null</strong>
414: * to reset the value.
415: */
416:
417: public void setLocation(URL location) {
418: setHeaderValue(H_LOCATION, ((location == null) ? null
419: : new HttpString(true, location.toExternalForm())));
420: }
421:
422: /**
423: * Set the location value of the reply.
424: * @param location The location a String, or <strong>null</strong> to
425: * reset the value.
426: */
427:
428: public void setLocation(String location) {
429: setHeaderValue(H_LOCATION, ((location == null) ? null
430: : new HttpString(true, location)));
431: }
432:
433: /**
434: * Get the proxy authentication challenge from this reply.
435: * @return An instance of HttpChallenge, or <strong>null</strong>
436: * if undefined.
437: */
438:
439: public HttpChallenge getProxyAuthenticate() {
440: HeaderValue value = getHeaderValue(H_PROXY_AUTHENTICATE);
441: return (value == null) ? null : (HttpChallenge) value
442: .getValue();
443: }
444:
445: /**
446: * Set thye proxy authentication challenge on this reply.
447: * @param challenge The challenge to set, or <strong>null</strong>
448: * to reset the value.
449: */
450:
451: public void setProxyAuthenticate(HttpChallenge challenge) {
452: setHeaderValue(H_PROXY_AUTHENTICATE, challenge);
453: }
454:
455: /**
456: * Get the list of publicly allowed methods on queried resource.
457: * @return The list of methods, encoded as a String array, or <strong>
458: * null</strong> if undefined.
459: */
460:
461: public String[] getPublic() {
462: HeaderValue value = getHeaderValue(H_PUBLIC);
463: return (value != null) ? (String[]) value.getValue() : null;
464: }
465:
466: /**
467: * Set the list of allowed method on queried resource.
468: * @param mth The list of public methods, encoded as a String array, or
469: * <strong>null</strong> to reset the value.
470: */
471:
472: public void setPublic(String mth[]) {
473: setHeaderValue(H_PUBLIC, ((mth == null) ? null
474: : new HttpTokenList(mth)));
475: }
476:
477: /**
478: * Get the description of the server that generated this reply.
479: * @return A String giving the description, or <strong>null</strong>
480: * if undefined.
481: */
482:
483: public String getServer() {
484: HeaderValue value = getHeaderValue(H_SERVER);
485: return (value != null) ? (String) value.getValue() : null;
486: }
487:
488: /**
489: * Set the description of the server.
490: * @param server The String decribing the server, or <strong>null</strong>
491: * to reset the value.
492: */
493:
494: public void setServer(String server) {
495: setHeaderValue(H_SERVER, ((server == null) ? null
496: : new HttpString(true, server)));
497: }
498:
499: /**
500: * set the retry after as a delay
501: * @param an integer representing the delay, in seconds
502: */
503:
504: public void setRetryAfter(int delay) {
505: setHeaderValue(H_RETRY_AFTER, ((delay == -1) ? null
506: : new HttpString(true,
507: ((new Integer(delay)).toString()))));
508: }
509:
510: /**
511: * set the retry after as a date
512: * @param long the date as a long
513: */
514:
515: public void setRetryAfter(long date) {
516: if (date == -1) {
517: setHeaderValue(H_RETRY_AFTER, null);
518: } else {
519: HttpDate d = new HttpDate(true, date);
520: setHeaderValue(H_RETRY_AFTER, new HttpString(true, d
521: .toExternalForm()));
522: }
523: }
524:
525: /**
526: * get the RetryAfter as a date
527: * @return A long giving the date as the number of milliseconds since the
528: * Java epoch, or <strong>-1</strong> if undefined.
529: */
530:
531: // FIXME Implementation
532: /**
533: * Get the vary header value.
534: * @return A list of field-names on which the negotiated resource vary,
535: * or a list containing only <code>*</code> (if varies on all headers),
536: * or <strong>null</strong> if undefined.
537: */
538:
539: public String[] getVary() {
540: HeaderValue value = getHeaderValue(H_VARY);
541: return (value != null) ? (String[]) value.getValue() : null;
542: }
543:
544: /**
545: * Set the vary header value.
546: * @param varies The list of headers on which this resource varies, or
547: * <strong>null</strong> to reset the value.
548: */
549:
550: public void setVary(String varies[]) {
551: setHeaderValue(H_VARY, ((varies == null) ? null
552: : new HttpCaseTokenList(varies)));
553: }
554:
555: /**
556: * Get the list of warnings attached to this reply.
557: * @return An array of HttpWarning, or <strong>null</strong> if
558: * undefined.
559: */
560:
561: public HttpWarning[] getWarning() {
562: HeaderValue value = getHeaderValue(H_WARNING);
563: return (value != null) ? (HttpWarning[]) value.getValue()
564: : null;
565: }
566:
567: /**
568: * Set the warning list attached to this reply.
569: * @param warnings An array of warnings to attach to the given reply,
570: * or <strong>null</strong> to reset the value.
571: */
572:
573: public void setWarning(HttpWarning warnings[]) {
574: setHeaderValue(H_WARNING, ((warnings == null) ? null
575: : new HttpWarningList(warnings)));
576: }
577:
578: /**
579: * Add a warning to this reply message.
580: * @param warning The warning to add.
581: */
582:
583: public void addWarning(HttpWarning warning) {
584: HttpWarningList wl = (HttpWarningList) getHeaderValue(H_WARNING);
585: if (wl == null) {
586: wl = new HttpWarningList(warning);
587: setHeaderValue(H_WARNING, wl);
588: } else {
589: wl.addWarning(warning);
590: }
591: }
592:
593: /**
594: * Get the challenge attached to this reply.
595: * @return An instance of HttpChallenge, or <strong>null</strong> if
596: * undefined.
597: */
598:
599: public HttpChallenge getWWWAuthenticate() {
600: HeaderValue value = getHeaderValue(H_WWW_AUTHENTICATE);
601: return (value != null) ? (HttpChallenge) value.getValue()
602: : null;
603: }
604:
605: /**
606: * Attach a challenge to this reply.
607: * @param challenge The challenge to be attached to the reply, or
608: * <strong>null</strong> to reset the value.
609: */
610:
611: public void setWWWAuthenticate(HttpChallenge challenge) {
612: setHeaderValue(H_WWW_AUTHENTICATE, challenge);
613: }
614:
615: /**
616: * Get the Authentication Info (see digest auth) attached to this reply.
617: * @return An instance of HttpParamList, or <strong>null</strong> if
618: * undefined.
619: */
620:
621: public HttpParamList getAuthenticationInfo() {
622: HeaderValue value = getHeaderValue(H_AUTHENTICATION_INFO);
623: return (value != null) ? (HttpParamList) value.getValue()
624: : null;
625: }
626:
627: /**
628: * Attach Authentication Info to this reply.
629: * @param plist, the parameter list to be attached to the reply, or
630: * <strong>null</strong> to reset the value.
631: */
632:
633: public void setAuthenticationInfo(HttpParamList plist) {
634: setHeaderValue(H_AUTHENTICATION_INFO, plist);
635: }
636:
637: /**
638: * Add the given parameter/value pair to the Authentication Info
639: * header
640: * @param String the name of the value
641: * @param String the value
642: */
643:
644: public void addAuthenticationInfo(String name, String value) {
645: HttpParamList hpl = getAuthenticationInfo();
646: if (hpl == null) {
647: setAuthenticationInfo(hpl = new HttpParamList(true));
648: }
649: hpl.setParameter(name, value);
650: }
651:
652: public HttpReplyMessage(MimeParser parser) {
653: super (parser);
654: }
655:
656: public HttpReplyMessage() {
657: super();
658: }
659:
660: }
|