001: /*
002: * Copyright 2003-2006 Rick Knowles <winstone-devel at lists sourceforge net>
003: * Distributed under the terms of either:
004: * - the common development and distribution license (CDDL), v1.0; or
005: * - the GNU Lesser General Public License, v2.1 or later
006: */
007: package winstone;
008:
009: import java.io.IOException;
010: import java.io.PrintWriter;
011: import java.io.UnsupportedEncodingException;
012: import java.io.Writer;
013: import java.text.DateFormat;
014: import java.text.SimpleDateFormat;
015: import java.util.ArrayList;
016: import java.util.Date;
017: import java.util.Iterator;
018: import java.util.List;
019: import java.util.Locale;
020: import java.util.Map;
021: import java.util.StringTokenizer;
022: import java.util.TimeZone;
023:
024: import javax.servlet.ServletOutputStream;
025: import javax.servlet.http.Cookie;
026: import javax.servlet.http.HttpServletResponse;
027:
028: /**
029: * Response for servlet
030: *
031: * @author <a href="mailto:rick_knowles@hotmail.com">Rick Knowles</a>
032: * @version $Id: WinstoneResponse.java,v 1.28 2005/04/19 07:33:41 rickknowles
033: * Exp $
034: */
035: public class WinstoneResponse implements HttpServletResponse {
036: private static final DateFormat HTTP_DF = new SimpleDateFormat(
037: "EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
038: private static final DateFormat VERSION0_DF = new SimpleDateFormat(
039: "EEE, dd-MMM-yy HH:mm:ss z", Locale.US);
040: static {
041: HTTP_DF.setTimeZone(TimeZone.getTimeZone("GMT"));
042: VERSION0_DF.setTimeZone(TimeZone.getTimeZone("GMT"));
043: }
044:
045: static final String CONTENT_LENGTH_HEADER = "Content-Length";
046: static final String CONTENT_TYPE_HEADER = "Content-Type";
047:
048: // Response header constants
049: private static final String CONTENT_LANGUAGE_HEADER = "Content-Language";
050: private static final String KEEP_ALIVE_HEADER = "Connection";
051: private static final String KEEP_ALIVE_OPEN = "Keep-Alive";
052: private static final String KEEP_ALIVE_CLOSE = "Close";
053: private static final String DATE_HEADER = "Date";
054: private static final String LOCATION_HEADER = "Location";
055: private static final String OUT_COOKIE_HEADER1 = "Set-Cookie";
056: private static final String X_POWERED_BY_HEADER = "X-Powered-By";
057: private static final String X_POWERED_BY_HEADER_VALUE = Launcher.RESOURCES
058: .getString("PoweredByHeader");
059:
060: private int statusCode;
061: private WinstoneRequest req;
062: private WebAppConfiguration webAppConfig;
063: private WinstoneOutputStream outputStream;
064: private PrintWriter outputWriter;
065:
066: private List headers;
067: private String explicitEncoding;
068: private String implicitEncoding;
069: private List cookies;
070:
071: private Locale locale;
072: private String protocol;
073: private String reqKeepAliveHeader;
074: private Integer errorStatusCode;
075:
076: /**
077: * Constructor
078: */
079: public WinstoneResponse() {
080:
081: this .headers = new ArrayList();
082: this .cookies = new ArrayList();
083:
084: this .statusCode = SC_OK;
085: this .locale = null; //Locale.getDefault();
086: this .explicitEncoding = null;
087: this .protocol = null;
088: this .reqKeepAliveHeader = null;
089: }
090:
091: /**
092: * Resets the request to be reused
093: */
094: public void cleanUp() {
095: this .req = null;
096: this .webAppConfig = null;
097: this .outputStream = null;
098: this .outputWriter = null;
099: this .headers.clear();
100: this .cookies.clear();
101: this .protocol = null;
102: this .reqKeepAliveHeader = null;
103:
104: this .statusCode = SC_OK;
105: this .errorStatusCode = null;
106: this .locale = null; //Locale.getDefault();
107: this .explicitEncoding = null;
108: this .implicitEncoding = null;
109: }
110:
111: private String getEncodingFromLocale(Locale loc) {
112: String localeString = loc.getLanguage() + "_"
113: + loc.getCountry();
114: Map encMap = this .webAppConfig.getLocaleEncodingMap();
115: Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
116: "WinstoneResponse.LookForLocaleEncoding", new String[] {
117: localeString, encMap + "" });
118:
119: String fullMatch = (String) encMap.get(localeString);
120: if (fullMatch != null) {
121: Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
122: "WinstoneResponse.FoundLocaleEncoding", fullMatch);
123: return fullMatch;
124: } else {
125: localeString = loc.getLanguage();
126: Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
127: "WinstoneResponse.LookForLocaleEncoding",
128: new String[] { localeString, encMap + "" });
129: String match = (String) encMap.get(localeString);
130: if (match != null) {
131: Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
132: "WinstoneResponse.FoundLocaleEncoding", match);
133: }
134: return match;
135: }
136: }
137:
138: public void setErrorStatusCode(int statusCode) {
139: this .errorStatusCode = new Integer(statusCode);
140: this .statusCode = statusCode;
141: }
142:
143: public WinstoneOutputStream getWinstoneOutputStream() {
144: return this .outputStream;
145: }
146:
147: public void setOutputStream(WinstoneOutputStream outData) {
148: this .outputStream = outData;
149: }
150:
151: public void setWebAppConfig(WebAppConfiguration webAppConfig) {
152: this .webAppConfig = webAppConfig;
153: }
154:
155: public String getProtocol() {
156: return this .protocol;
157: }
158:
159: public void setProtocol(String protocol) {
160: this .protocol = protocol;
161: }
162:
163: public void extractRequestKeepAliveHeader(WinstoneRequest req) {
164: this .reqKeepAliveHeader = req.getHeader(KEEP_ALIVE_HEADER);
165: }
166:
167: public List getHeaders() {
168: return this .headers;
169: }
170:
171: public List getCookies() {
172: return this .cookies;
173: }
174:
175: public WinstoneRequest getRequest() {
176: return this .req;
177: }
178:
179: public void setRequest(WinstoneRequest req) {
180: this .req = req;
181: }
182:
183: public void startIncludeBuffer() {
184: this .outputStream.startIncludeBuffer();
185: }
186:
187: public void finishIncludeBuffer() throws IOException {
188: if (isIncluding()) {
189: if (this .outputWriter != null) {
190: this .outputWriter.flush();
191: }
192: this .outputStream.finishIncludeBuffer();
193: }
194: }
195:
196: public void clearIncludeStackForForward() throws IOException {
197: this .outputStream.clearIncludeStackForForward();
198: }
199:
200: protected static String getCharsetFromContentTypeHeader(
201: String type, StringBuffer remainder) {
202: if (type == null) {
203: return null;
204: }
205: // Parse type to set encoding if needed
206: StringTokenizer st = new StringTokenizer(type, ";");
207: String localEncoding = null;
208: while (st.hasMoreTokens()) {
209: String clause = st.nextToken().trim();
210: if (clause.startsWith("charset="))
211: localEncoding = clause.substring(8);
212: else {
213: if (remainder.length() > 0) {
214: remainder.append(";");
215: }
216: remainder.append(clause);
217: }
218: }
219: if ((localEncoding == null) || !localEncoding.startsWith("\"")
220: || !localEncoding.endsWith("\"")) {
221: return localEncoding;
222: } else {
223: return localEncoding.substring(1,
224: localEncoding.length() - 1);
225: }
226: }
227:
228: /**
229: * This ensures the bare minimum correct http headers are present
230: */
231: public void validateHeaders() {
232: // Need this block for WebDAV support. "Connection:close" header is ignored
233: String lengthHeader = getHeader(CONTENT_LENGTH_HEADER);
234: if ((lengthHeader == null) && (this .statusCode >= 300)) {
235: int bodyBytes = this .outputStream.getOutputStreamLength();
236: if (getBufferSize() > bodyBytes) {
237: Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
238: "WinstoneResponse.ForcingContentLength", ""
239: + bodyBytes);
240: forceHeader(CONTENT_LENGTH_HEADER, "" + bodyBytes);
241: lengthHeader = getHeader(CONTENT_LENGTH_HEADER);
242: }
243: }
244:
245: forceHeader(KEEP_ALIVE_HEADER,
246: !closeAfterRequest() ? KEEP_ALIVE_OPEN
247: : KEEP_ALIVE_CLOSE);
248: String contentType = getHeader(CONTENT_TYPE_HEADER);
249: if (this .statusCode != SC_MOVED_TEMPORARILY) {
250: if (contentType == null) {
251: // Bypass normal encoding
252: forceHeader(CONTENT_TYPE_HEADER, "text/html;charset="
253: + getCharacterEncoding());
254: } else if (contentType.startsWith("text/")) {
255: // replace charset in content
256: StringBuffer remainder = new StringBuffer();
257: getCharsetFromContentTypeHeader(contentType, remainder);
258: forceHeader(CONTENT_TYPE_HEADER, remainder.toString()
259: + ";charset=" + getCharacterEncoding());
260: }
261: }
262: if (getHeader(DATE_HEADER) == null) {
263: forceHeader(DATE_HEADER, formatHeaderDate(new Date()));
264: }
265: if (getHeader(X_POWERED_BY_HEADER) == null) {
266: forceHeader(X_POWERED_BY_HEADER, X_POWERED_BY_HEADER_VALUE);
267: }
268: if (this .locale != null) {
269: String lang = this .locale.getLanguage();
270: if ((this .locale.getCountry() != null)
271: && !this .locale.getCountry().equals("")) {
272: lang = lang + "-" + this .locale.getCountry();
273: }
274: forceHeader(CONTENT_LANGUAGE_HEADER, lang);
275: }
276:
277: // If we don't have a webappConfig, exit here, cause we definitely don't
278: // have a session
279: if (req.getWebAppConfig() == null) {
280: return;
281: }
282: // Write out the new session cookie if it's present
283: HostConfiguration hostConfig = req.getHostGroup()
284: .getHostByName(req.getServerName());
285: for (Iterator i = req.getCurrentSessionIds().keySet()
286: .iterator(); i.hasNext();) {
287: String prefix = (String) i.next();
288: String sessionId = (String) req.getCurrentSessionIds().get(
289: prefix);
290: WebAppConfiguration ownerContext = hostConfig
291: .getWebAppByURI(prefix);
292: if (ownerContext != null) {
293: WinstoneSession session = ownerContext.getSessionById(
294: sessionId, true);
295: if ((session != null) && session.isNew()) {
296: session.setIsNew(false);
297: Cookie cookie = new Cookie(
298: WinstoneSession.SESSION_COOKIE_NAME,
299: session.getId());
300: cookie.setMaxAge(-1);
301: cookie.setSecure(req.isSecure());
302: cookie.setVersion(0); //req.isSecure() ? 1 : 0);
303: cookie.setPath(req.getWebAppConfig()
304: .getContextPath().equals("") ? "/" : req
305: .getWebAppConfig().getContextPath());
306: this .cookies.add(cookie); // don't call addCookie because we might be including
307: }
308: }
309: }
310:
311: // Look for expired sessions: ie ones where the requested and current ids are different
312: for (Iterator i = req.getRequestedSessionIds().keySet()
313: .iterator(); i.hasNext();) {
314: String prefix = (String) i.next();
315: String sessionId = (String) req.getRequestedSessionIds()
316: .get(prefix);
317: if (!req.getCurrentSessionIds().containsKey(prefix)) {
318: Cookie cookie = new Cookie(
319: WinstoneSession.SESSION_COOKIE_NAME, sessionId);
320: cookie.setMaxAge(0); // explicitly expire this cookie
321: cookie.setSecure(req.isSecure());
322: cookie.setVersion(0); //req.isSecure() ? 1 : 0);
323: cookie.setPath(prefix.equals("") ? "/" : prefix);
324: this .cookies.add(cookie); // don't call addCookie because we might be including
325: }
326: }
327:
328: Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
329: "WinstoneResponse.HeadersPreCommit", this .headers + "");
330: }
331:
332: /**
333: * Writes out the http header for a single cookie
334: */
335: public String writeCookie(Cookie cookie) throws IOException {
336:
337: Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
338: "WinstoneResponse.WritingCookie", cookie + "");
339: StringBuffer out = new StringBuffer();
340:
341: // Set-Cookie or Set-Cookie2
342: if (cookie.getVersion() >= 1)
343: out.append(OUT_COOKIE_HEADER1).append(": "); // TCK doesn't like set-cookie2
344: else
345: out.append(OUT_COOKIE_HEADER1).append(": ");
346:
347: // name/value pair
348: if (cookie.getVersion() == 0)
349: out.append(cookie.getName()).append("=").append(
350: cookie.getValue());
351: else {
352: out.append(cookie.getName()).append("=");
353: quote(cookie.getValue(), out);
354: }
355:
356: if (cookie.getVersion() >= 1) {
357: out.append("; Version=1");
358: if (cookie.getDomain() != null) {
359: out.append("; Domain=");
360: quote(cookie.getDomain(), out);
361: }
362: if (cookie.getSecure())
363: out.append("; Secure");
364:
365: if (cookie.getMaxAge() >= 0)
366: out.append("; Max-Age=").append(cookie.getMaxAge());
367: else
368: out.append("; Discard");
369: if (cookie.getPath() != null) {
370: out.append("; Path=");
371: quote(cookie.getPath(), out);
372: }
373: } else {
374: if (cookie.getDomain() != null) {
375: out.append("; Domain=");
376: out.append(cookie.getDomain());
377: }
378: if (cookie.getMaxAge() > 0) {
379: long expiryMS = System.currentTimeMillis()
380: + (1000 * (long) cookie.getMaxAge());
381: String expiryDate = null;
382: synchronized (VERSION0_DF) {
383: expiryDate = VERSION0_DF.format(new Date(expiryMS));
384: }
385: out.append("; Expires=").append(expiryDate);
386: } else if (cookie.getMaxAge() == 0) {
387: String expiryDate = null;
388: synchronized (VERSION0_DF) {
389: expiryDate = VERSION0_DF.format(new Date(5000));
390: }
391: out.append("; Expires=").append(expiryDate);
392: }
393: if (cookie.getPath() != null)
394: out.append("; Path=").append(cookie.getPath());
395: if (cookie.getSecure())
396: out.append("; Secure");
397: }
398: return out.toString();
399: }
400:
401: private static String formatHeaderDate(Date dateIn) {
402: String date = null;
403: synchronized (HTTP_DF) {
404: date = HTTP_DF.format(dateIn);
405: }
406: return date;
407: }
408:
409: /**
410: * Quotes the necessary strings in a cookie header. The quoting is only
411: * applied if the string contains special characters.
412: */
413: protected static void quote(String value, StringBuffer out) {
414: if (value.startsWith("\"") && value.endsWith("\"")) {
415: out.append(value);
416: } else {
417: boolean containsSpecial = false;
418: for (int n = 0; n < value.length(); n++) {
419: char this Char = value.charAt(n);
420: if ((this Char < 32) || (this Char >= 127)
421: || (specialCharacters.indexOf(this Char) != -1)) {
422: containsSpecial = true;
423: break;
424: }
425: }
426: if (containsSpecial)
427: out.append('"').append(value).append('"');
428: else
429: out.append(value);
430: }
431: }
432:
433: private static final String specialCharacters = "()<>@,;:\\\"/[]?={} \t";
434:
435: /**
436: * Based on request/response headers and the protocol, determine whether or
437: * not this connection should operate in keep-alive mode.
438: */
439: public boolean closeAfterRequest() {
440: String inKeepAliveHeader = this .reqKeepAliveHeader;
441: String outKeepAliveHeader = getHeader(KEEP_ALIVE_HEADER);
442: boolean hasContentLength = (getHeader(CONTENT_LENGTH_HEADER) != null);
443: if (this .protocol.startsWith("HTTP/0"))
444: return true;
445: else if ((inKeepAliveHeader == null)
446: && (outKeepAliveHeader == null))
447: return this .protocol.equals("HTTP/1.0") ? true
448: : !hasContentLength;
449: else if (outKeepAliveHeader != null)
450: return outKeepAliveHeader
451: .equalsIgnoreCase(KEEP_ALIVE_CLOSE)
452: || !hasContentLength;
453: else if (inKeepAliveHeader != null)
454: return inKeepAliveHeader.equalsIgnoreCase(KEEP_ALIVE_CLOSE)
455: || !hasContentLength;
456: else
457: return false;
458: }
459:
460: // ServletResponse interface methods
461: public void flushBuffer() throws IOException {
462: if (this .outputWriter != null) {
463: this .outputWriter.flush();
464: }
465: this .outputStream.flush();
466: }
467:
468: public void setBufferSize(int size) {
469: this .outputStream.setBufferSize(size);
470: }
471:
472: public int getBufferSize() {
473: return this .outputStream.getBufferSize();
474: }
475:
476: public String getCharacterEncoding() {
477: String enc = getCurrentEncoding();
478: return (enc == null ? "ISO-8859-1" : enc);
479: }
480:
481: public void setCharacterEncoding(String encoding) {
482: if ((this .outputWriter == null) && !isCommitted()) {
483: Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
484: "WinstoneResponse.SettingEncoding", encoding);
485: this .explicitEncoding = encoding;
486: correctContentTypeHeaderEncoding(encoding);
487: }
488: }
489:
490: private void correctContentTypeHeaderEncoding(String encoding) {
491: String contentType = getContentType();
492: if (contentType != null) {
493: StringBuffer remainderHeader = new StringBuffer();
494: getCharsetFromContentTypeHeader(contentType,
495: remainderHeader);
496: if (remainderHeader.length() != 0) {
497: forceHeader(CONTENT_TYPE_HEADER, remainderHeader
498: + ";charset=" + encoding);
499: }
500: }
501: }
502:
503: public String getContentType() {
504: return getHeader(CONTENT_TYPE_HEADER);
505: }
506:
507: public void setContentType(String type) {
508: setHeader(CONTENT_TYPE_HEADER, type);
509: }
510:
511: public Locale getLocale() {
512: return this .locale == null ? Locale.getDefault() : this .locale;
513: }
514:
515: private boolean isIncluding() {
516: return this .outputStream.isIncluding();
517: }
518:
519: public void setLocale(Locale loc) {
520: if (isIncluding()) {
521: return;
522: } else if (isCommitted()) {
523: Logger.log(Logger.WARNING, Launcher.RESOURCES,
524: "WinstoneResponse.SetLocaleTooLate");
525: } else {
526: if ((this .outputWriter == null)
527: && (this .explicitEncoding == null)) {
528: String localeEncoding = getEncodingFromLocale(loc);
529: if (localeEncoding != null) {
530: this .implicitEncoding = localeEncoding;
531: correctContentTypeHeaderEncoding(localeEncoding);
532: }
533: }
534: this .locale = loc;
535: }
536: }
537:
538: public ServletOutputStream getOutputStream() throws IOException {
539: Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
540: "WinstoneResponse.GetOutputStream");
541: return this .outputStream;
542: }
543:
544: public PrintWriter getWriter() throws IOException {
545: Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
546: "WinstoneResponse.GetWriter");
547: if (this .outputWriter != null)
548: return this .outputWriter;
549: else {
550: this .outputWriter = new WinstoneResponseWriter(
551: this .outputStream, this );
552: return this .outputWriter;
553: }
554: }
555:
556: public boolean isCommitted() {
557: return this .outputStream.isCommitted();
558: }
559:
560: public void reset() {
561: if (!isIncluding()) {
562: resetBuffer();
563: this .statusCode = SC_OK;
564: this .headers.clear();
565: this .cookies.clear();
566: }
567: }
568:
569: public void resetBuffer() {
570: if (!isIncluding()) {
571: if (isCommitted())
572: throw new IllegalStateException(
573: Launcher.RESOURCES
574: .getString("WinstoneResponse.ResponseCommitted"));
575:
576: // Disregard any output temporarily while we flush
577: this .outputStream.setDisregardMode(true);
578:
579: if (this .outputWriter != null) {
580: this .outputWriter.flush();
581: }
582:
583: this .outputStream.setDisregardMode(false);
584: this .outputStream.reset();
585: }
586: }
587:
588: public void setContentLength(int len) {
589: setIntHeader(CONTENT_LENGTH_HEADER, len);
590: }
591:
592: // HttpServletResponse interface methods
593: public void addCookie(Cookie cookie) {
594: if (!isIncluding()) {
595: this .cookies.add(cookie);
596: }
597: }
598:
599: public boolean containsHeader(String name) {
600: for (int n = 0; n < this .headers.size(); n++)
601: if (((String) this .headers.get(n)).startsWith(name))
602: return true;
603: return false;
604: }
605:
606: public void addDateHeader(String name, long date) {
607: addHeader(name, formatHeaderDate(new Date(date)));
608: } // df.format(new Date(date)));}
609:
610: public void addIntHeader(String name, int value) {
611: addHeader(name, "" + value);
612: }
613:
614: public void addHeader(String name, String value) {
615: if (isIncluding()) {
616: Logger.log(Logger.DEBUG, Launcher.RESOURCES,
617: "WinstoneResponse.HeaderInInclude", new String[] {
618: name, value });
619: } else if (isCommitted()) {
620: Logger.log(Logger.DEBUG, Launcher.RESOURCES,
621: "WinstoneResponse.HeaderAfterCommitted",
622: new String[] { name, value });
623: } else if (value != null) {
624: if (name.equals(CONTENT_TYPE_HEADER)) {
625: StringBuffer remainderHeader = new StringBuffer();
626: String headerEncoding = getCharsetFromContentTypeHeader(
627: value, remainderHeader);
628: if (this .outputWriter != null) {
629: value = remainderHeader + ";charset="
630: + getCharacterEncoding();
631: } else if (headerEncoding != null) {
632: this .explicitEncoding = headerEncoding;
633: }
634: }
635: this .headers.add(name + ": " + value);
636: }
637: }
638:
639: public void setDateHeader(String name, long date) {
640: setHeader(name, formatHeaderDate(new Date(date)));
641: }
642:
643: public void setIntHeader(String name, int value) {
644: setHeader(name, "" + value);
645: }
646:
647: public void setHeader(String name, String value) {
648: if (isIncluding()) {
649: Logger.log(Logger.DEBUG, Launcher.RESOURCES,
650: "WinstoneResponse.HeaderInInclude", new String[] {
651: name, value });
652: } else if (isCommitted()) {
653: Logger.log(Logger.DEBUG, Launcher.RESOURCES,
654: "WinstoneResponse.HeaderAfterCommitted",
655: new String[] { name, value });
656: } else {
657: boolean found = false;
658: for (int n = 0; (n < this .headers.size()); n++) {
659: String header = (String) this .headers.get(n);
660: if (header.startsWith(name + ": ")) {
661: if (found) {
662: this .headers.remove(n);
663: continue;
664: }
665: if (name.equals(CONTENT_TYPE_HEADER)) {
666: if (value != null) {
667: StringBuffer remainderHeader = new StringBuffer();
668: String headerEncoding = getCharsetFromContentTypeHeader(
669: value, remainderHeader);
670: if (this .outputWriter != null) {
671: value = remainderHeader + ";charset="
672: + getCharacterEncoding();
673: } else if (headerEncoding != null) {
674: this .explicitEncoding = headerEncoding;
675: }
676: }
677: }
678:
679: if (value != null) {
680: this .headers.set(n, name + ": " + value);
681: } else {
682: this .headers.remove(n);
683: }
684: found = true;
685: }
686: }
687: if (!found) {
688: addHeader(name, value);
689: }
690: }
691: }
692:
693: private void forceHeader(String name, String value) {
694: boolean found = false;
695: for (int n = 0; (n < this .headers.size()); n++) {
696: String header = (String) this .headers.get(n);
697: if (header.startsWith(name + ": ")) {
698: found = true;
699: this .headers.set(n, name + ": " + value);
700: }
701: }
702: if (!found) {
703: this .headers.add(name + ": " + value);
704: }
705: }
706:
707: private String getCurrentEncoding() {
708: if (this .explicitEncoding != null) {
709: return this .explicitEncoding;
710: } else if (this .implicitEncoding != null) {
711: return this .implicitEncoding;
712: } else if ((this .req != null)
713: && (this .req.getCharacterEncoding() != null)) {
714: try {
715: "0".getBytes(this .req.getCharacterEncoding());
716: return this .req.getCharacterEncoding();
717: } catch (UnsupportedEncodingException err) {
718: return null;
719: }
720: } else {
721: return null;
722: }
723: }
724:
725: public String getHeader(String name) {
726: for (int n = 0; n < this .headers.size(); n++) {
727: String header = (String) this .headers.get(n);
728: if (header.startsWith(name + ": "))
729: return header.substring(name.length() + 2);
730: }
731: return null;
732: }
733:
734: public String encodeRedirectURL(String url) {
735: return url;
736: }
737:
738: public String encodeURL(String url) {
739: return url;
740: }
741:
742: public int getStatus() {
743: return this .statusCode;
744: }
745:
746: public Integer getErrorStatusCode() {
747: return this .errorStatusCode;
748: }
749:
750: public void setStatus(int sc) {
751: if (!isIncluding() && (this .errorStatusCode == null)) {
752: // if (!isIncluding()) {
753: this .statusCode = sc;
754: // if (this.errorStatusCode != null) {
755: // this.errorStatusCode = new Integer(sc);
756: // }
757: }
758: }
759:
760: public void sendRedirect(String location) throws IOException {
761: if (isIncluding()) {
762: Logger.log(Logger.ERROR, Launcher.RESOURCES,
763: "IncludeResponse.Redirect", location);
764: return;
765: } else if (isCommitted()) {
766: throw new IllegalStateException(Launcher.RESOURCES
767: .getString("WinstoneOutputStream.AlreadyCommitted"));
768: }
769: resetBuffer();
770:
771: // Build location
772: StringBuffer fullLocation = new StringBuffer();
773: if (location.startsWith("http://")
774: || location.startsWith("https://")) {
775: fullLocation.append(location);
776: } else {
777: if (location.trim().equals(".")) {
778: location = "";
779: }
780:
781: fullLocation.append(this .req.getScheme()).append("://");
782: fullLocation.append(this .req.getServerName());
783: if (!((this .req.getServerPort() == 80) && this .req
784: .getScheme().equals("http"))
785: && !((this .req.getServerPort() == 443) && this .req
786: .getScheme().equals("https")))
787: fullLocation.append(':').append(
788: this .req.getServerPort());
789: if (location.startsWith("/")) {
790: fullLocation.append(location);
791: } else {
792: fullLocation.append(this .req.getRequestURI());
793: int questionPos = fullLocation.toString().indexOf("?");
794: if (questionPos != -1) {
795: fullLocation.delete(questionPos, fullLocation
796: .length());
797: }
798: fullLocation.delete(fullLocation.toString()
799: .lastIndexOf("/") + 1, fullLocation.length());
800: fullLocation.append(location);
801: }
802: }
803: if (this .req != null) {
804: this .req.discardRequestBody();
805: }
806: this .statusCode = HttpServletResponse.SC_MOVED_TEMPORARILY;
807: setHeader(LOCATION_HEADER, fullLocation.toString());
808: setContentLength(0);
809: getWriter().flush();
810: }
811:
812: public void sendError(int sc) throws IOException {
813: sendError(sc, null);
814: }
815:
816: public void sendError(int sc, String msg) throws IOException {
817: if (isIncluding()) {
818: Logger.log(Logger.ERROR, Launcher.RESOURCES,
819: "IncludeResponse.Error", new String[] { "" + sc,
820: msg });
821: return;
822: }
823:
824: Logger.log(Logger.DEBUG, Launcher.RESOURCES,
825: "WinstoneResponse.SendingError", new String[] {
826: "" + sc, msg });
827:
828: if ((this .webAppConfig != null) && (this .req != null)) {
829:
830: RequestDispatcher rd = this .webAppConfig
831: .getErrorDispatcherByCode(sc, msg, null);
832: if (rd != null) {
833: try {
834: rd.forward(this .req, this );
835: return;
836: } catch (IllegalStateException err) {
837: throw err;
838: } catch (IOException err) {
839: throw err;
840: } catch (Throwable err) {
841: Logger
842: .log(
843: Logger.WARNING,
844: Launcher.RESOURCES,
845: "WinstoneResponse.ErrorInErrorPage",
846: new String[] { rd.getName(),
847: sc + "" }, err);
848: return;
849: }
850: }
851: }
852: // If we are here there was no webapp and/or no request object, so
853: // show the default error page
854: if (this .errorStatusCode == null) {
855: this .statusCode = sc;
856: }
857: String output = Launcher.RESOURCES.getString(
858: "WinstoneResponse.ErrorPage", new String[] { sc + "",
859: (msg == null ? "" : msg), "",
860: Launcher.RESOURCES.getString("ServerVersion"),
861: "" + new Date() });
862: setContentLength(output.getBytes(getCharacterEncoding()).length);
863: Writer out = getWriter();
864: out.write(output);
865: out.flush();
866: }
867:
868: /**
869: * @deprecated
870: */
871: public String encodeRedirectUrl(String url) {
872: return encodeRedirectURL(url);
873: }
874:
875: /**
876: * @deprecated
877: */
878: public String encodeUrl(String url) {
879: return encodeURL(url);
880: }
881:
882: /**
883: * @deprecated
884: */
885: public void setStatus(int sc, String sm) {
886: setStatus(sc);
887: }
888: }
|