~james-page/ubuntu/oneiric/jenkins-winstone/827651

« back to all changes in this revision

Viewing changes to src/java/winstone/WinstoneResponse.java

  • Committer: Bazaar Package Importer
  • Author(s): James Page
  • Date: 2011-06-29 12:16:17 UTC
  • Revision ID: james.westby@ubuntu.com-20110629121617-vd3ha6lp4nqvxkbr
Tags: upstream-0.9.10-jenkins-25+dfsg
ImportĀ upstreamĀ versionĀ 0.9.10-jenkins-25+dfsg

Show diffs side-by-side

added added

removed removed

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