Annotation of java/classes/org/w3c/jigsaw/servlet/JigsawHttpServletResponse.java, revision 1.47

1.1       abaird      1: // JigsawHttpServletReponse.java
1.47    ! ylafon      2: // $Id: JigsawHttpServletResponse.java,v 1.46 2001/11/12 13:59:00 ylafon Exp $
1.1       abaird      3: // (c) COPYRIGHT MIT and INRIA, 1996.
                      4: // Please first read the full copyright statement in file COPYRIGHT.html
                      5: 
1.10      bmahe       6: package org.w3c.jigsaw.servlet;
1.1       abaird      7: 
1.39      ylafon      8: import java.io.ByteArrayOutputStream;
                      9: import java.io.DataOutputStream;
                     10: import java.io.IOException;
                     11: import java.io.OutputStream;
                     12: import java.io.OutputStreamWriter;
1.40      ylafon     13: import java.io.PipedOutputStream;
1.39      ylafon     14: import java.io.PrintWriter;
                     15: import java.io.UnsupportedEncodingException;
                     16: 
                     17: import javax.servlet.ServletOutputStream;
                     18: 
                     19: import javax.servlet.http.Cookie;
                     20: import javax.servlet.http.HttpServletResponse;
                     21: import javax.servlet.http.HttpSession;
                     22: 
                     23: import java.net.MalformedURLException;
                     24: import java.net.URL;
                     25: 
1.33      bmahe      26: import java.util.Locale;
1.1       abaird     27: 
1.39      ylafon     28: import org.w3c.www.mime.MimeType;
                     29: import org.w3c.www.mime.MimeTypeFormatException;
                     30: import org.w3c.www.mime.Utils;
                     31: 
                     32: import org.w3c.www.http.BasicValue;
                     33: import org.w3c.www.http.HttpAcceptCharsetList;
                     34: import org.w3c.www.http.HttpAcceptEncodingList;
                     35: import org.w3c.www.http.HttpAcceptLanguageList;
                     36: import org.w3c.www.http.HttpAcceptList;
                     37: import org.w3c.www.http.HttpCookie;
                     38: import org.w3c.www.http.HttpCookieList;
                     39: import org.w3c.www.http.HttpEntityMessage;
                     40: import org.w3c.www.http.HttpEntityTagList;
                     41: import org.w3c.www.http.HttpExt;
                     42: import org.w3c.www.http.HttpExtList;
                     43: import org.w3c.www.http.HttpFactory;
                     44: import org.w3c.www.http.HttpMessage;
                     45: import org.w3c.www.http.HttpParamList;
                     46: import org.w3c.www.http.HttpRangeList;
                     47: import org.w3c.www.http.HttpReplyMessage;
                     48: import org.w3c.www.http.HttpRequestMessage;
                     49: import org.w3c.www.http.HttpSetCookie;
                     50: import org.w3c.www.http.HttpSetCookieList;
                     51: import org.w3c.www.http.HttpString;
                     52: import org.w3c.www.http.HttpTokenList;
                     53: import org.w3c.www.http.HttpWarningList;
                     54: 
1.41      ylafon     55: import org.w3c.jigsaw.html.HtmlGenerator;
                     56: 
1.39      ylafon     57: import org.w3c.jigsaw.http.Reply;
                     58: import org.w3c.jigsaw.http.Request;
                     59: 
1.33      bmahe      60: import org.w3c.www.http.HeaderValue;
1.2       abaird     61: 
                     62: /**
1.30      bmahe      63:  * @author Alexandre Rafalovitch <alex@access.com.au>
                     64:  * @author Anselm Baird-Smith <abaird@w3.org>
                     65:  * @author Benoît Mahé (bmahe@w3.org)
                     66:  * @author Roland Mainz (Roland.Mainz@informatik.med.uni-giessen.de)
1.2       abaird     67:  */
1.1       abaird     68: 
1.29      bmahe      69: public class JigsawHttpServletResponse implements HttpServletResponse {
                     70: 
1.33      bmahe      71:     public final static String CHARSET_PARAMETER = "charset";
                     72: 
                     73:     public final static int DEFAULT_BUFFER_SIZE   = 8 * 1024; // 8KB buffer
                     74:     public final static int MIN_BUFFER_SIZE       = 4 * 1024; // 4KB buffer
                     75: 
1.31      ylafon     76:     private final static int STATE_INITIAL        = 0;
                     77:     private final static int STATE_HEADERS_DONE   = 1;
                     78:     private final static int STATE_ALL_DONE       = 2;
1.34      bmahe      79: 
1.11      bmahe      80:     private final static int STREAM_STATE_INITIAL = 0;
1.31      ylafon     81:     private final static int STREAM_WRITER_USED   = 1;
                     82:     private final static int OUTPUT_STREAM_USED   = 2;
1.11      bmahe      83: 
                     84:     private int stream_state = STREAM_STATE_INITIAL;
1.20      bmahe      85: 
                     86:     private JigsawServletOutputStream output = null;
                     87:     private PrintWriter               writer = null;
1.39      ylafon     88: 
1.30      bmahe      89:     private MimeTypeFormatException setContentTypeException = null;
                     90: 
                     91:     // servlet has set a fixed content length or not, 
                     92:     // and cut (see flushStream) any data which are too much here...
                     93:     private final static int CALC_CONTENT_LENGTH = -1; 
                     94:     private              int fixedContentLength  = CALC_CONTENT_LENGTH;
                     95: 
1.33      bmahe      96:     // Our Locale
                     97:     protected Locale locale;
                     98: 
1.29      bmahe      99:     /**
                    100:      * Our temp stream.
                    101:      */
                    102:     protected ByteArrayOutputStream out = null;
1.40      ylafon    103:     protected PipedOutputStream pout = null;
1.29      bmahe     104: 
1.14      bmahe     105:     protected JigsawHttpServletRequest jrequest = null;
                    106: 
1.33      bmahe     107:     protected int buffer_size;
                    108: 
1.14      bmahe     109:     protected void setServletRequest(JigsawHttpServletRequest jrequest) {
                    110:        this.jrequest = jrequest;
                    111:     }
                    112: 
1.7       bmahe     113:     public static final 
1.29      bmahe     114:        String INCLUDED = "org.w3c.jigsaw.servlet.included";
1.40      ylafon    115:     public static final 
                    116:        String STREAM = "org.w3c.jigsaw.servlet.stream";
                    117:     public static final 
                    118:        String MONITOR = "org.w3c.jigsaw.servlet.monitor";
1.28      bmahe     119: 
1.1       abaird    120:     int   state = STATE_INITIAL;
                    121:     Reply reply = null;
                    122:     Request request = null;
1.39      ylafon    123: 
1.18      bmahe     124:     /**
                    125:      * Sets the content length for this response. 
                    126:      * @param len - the content length 
                    127:      */
1.5       abaird    128:     public void setContentLength(int i) {
1.30      bmahe     129:        fixedContentLength = i;
1.1       abaird    130:        reply.setContentLength(i);
                    131:     }
1.18      bmahe     132: 
                    133:     /**
                    134:      * Sets the content type for this response. This type may later be 
                    135:      * implicitly modified by addition of properties such as the MIME
                    136:      * charset=<value> if the service finds it necessary, and the appropriate
                    137:      * media type property has not been set.
                    138:      * <p>This response property may only be assigned one time. If a writer 
                    139:      * is to be used to write a text response, this method must be
                    140:      * called before the method getWriter. If an output stream will be used 
                    141:      * to write a response, this method must be called before the
                    142:      * output stream is used to write response data. 
                    143:      * @param spec - the content's MIME type 
1.27      ylafon    144:      * @see JigsawHttpServletResponse#getOutputStream
                    145:      * @see JigsawHttpServletResponse#getWriter
1.18      bmahe     146:      */    
1.5       abaird    147:     public void setContentType(String spec) {
1.1       abaird    148:        try {
                    149:            MimeType type= new MimeType(spec);
                    150:            reply.setContentType(type);
1.30      bmahe     151:            setContentTypeException = null;
1.1       abaird    152:        } catch(MimeTypeFormatException ex) {
1.30      bmahe     153:            //store exception
                    154:            setContentTypeException = ex;
1.1       abaird    155:        }
                    156:     }
1.5       abaird    157: 
1.28      bmahe     158:     protected boolean isStreamObtained() {
                    159:        return (stream_state != STREAM_STATE_INITIAL);
                    160:     }
                    161: 
1.29      bmahe     162:     protected Reply getReply() {
                    163:        return reply;
                    164:     }
                    165: 
1.18      bmahe     166:     /**
                    167:      * Returns an output stream for writing binary response data.
1.24      bmahe     168:      * @return A ServletOutputStream
1.18      bmahe     169:      * @exception IOException if an I/O exception has occurred 
                    170:      * @exception IllegalStateException if getWriter has been called on this 
                    171:      * same request. 
1.27      ylafon    172:      * @see JigsawHttpServletResponse#getWriter
1.18      bmahe     173:      */    
1.5       abaird    174:     public synchronized ServletOutputStream getOutputStream()
                    175:        throws IOException
                    176:     {
1.12      bmahe     177:        if (stream_state == STREAM_WRITER_USED)
                    178:            throw new IllegalStateException("Writer used");
                    179:        stream_state = OUTPUT_STREAM_USED;
1.34      bmahe     180:        return getJigsawOutputStream(false);
1.11      bmahe     181:     }
                    182: 
1.34      bmahe     183:     protected ServletOutputStream getJigsawOutputStream(boolean writerUsed)
1.11      bmahe     184:        throws IOException
                    185:     {
1.12      bmahe     186:        if ( output != null )
                    187:            return output;
1.30      bmahe     188: 
                    189:        // any exception during setContentType ?    
                    190:        if( setContentTypeException != null ) {
                    191:            // "wrap" the exception from setContentType in an IOException
1.33      bmahe     192:            throw new IOException("Illegal Content Type: "+
1.30      bmahe     193:                                  setContentTypeException.toString());
                    194:        }
                    195: 
                    196:        if( request.hasState(INCLUDED) ) {
                    197:            out = new ByteArrayOutputStream();
                    198:            output = new JigsawServletOutputStream(this, 
1.33      bmahe     199:                                                   new DataOutputStream(out),
1.34      bmahe     200:                                                   buffer_size,
                    201:                                                   writerUsed);
1.30      bmahe     202:        } else {
1.40      ylafon    203:            if (reply.hasState(STREAM)) {
1.45      ylafon    204:                try {
                    205:                    pout = (PipedOutputStream) reply.getState(STREAM);
                    206:                    DataOutputStream dos = new DataOutputStream(pout);
                    207:                    output = new JigsawServletOutputStream(this, 
                    208:                                                           dos,
                    209:                                                           buffer_size,
                    210:                                                           writerUsed);
                    211:                } catch (ClassCastException ex) {
                    212:                    // it is null, no OutputStream -> redirect done
                    213:                    // we will eat this anyway
                    214:                    output = new JigsawServletOutputStream(this, 
                    215:                                                       reply, 
1.40      ylafon    216:                                                       buffer_size,
                    217:                                                       writerUsed);
1.45      ylafon    218:                }
1.40      ylafon    219:            } else {
                    220:                output = new JigsawServletOutputStream(this, 
                    221:                                                       reply, 
                    222:                                                       buffer_size,
                    223:                                                       writerUsed);
1.43      ylafon    224:            }
                    225:            // release the monitor waiting for the end of the reply setup
                    226:            if (reply.hasState(STREAM)) {
                    227:                reply.setState(STREAM, null);
                    228:            }
                    229:            Object o = reply.getState(MONITOR);
                    230:            if (o != null) {
                    231:                synchronized (o) {
                    232:                    o.notifyAll();
                    233:                }
1.40      ylafon    234:            }
1.30      bmahe     235:        }
1.5       abaird    236:        return output;
1.1       abaird    237:     }
1.29      bmahe     238: 
1.18      bmahe     239:     /**
                    240:      * Sets the status code and message for this response. If the field had
                    241:      * already been set, the new value overwrites the previous one. The message
                    242:      * is sent as the body of an HTML page, which is returned to the user to
                    243:      * describe the problem. The page is sent with a default HTML header; the
                    244:      * message is enclosed in simple body tags (<body></body>).
                    245:      * @param i - the status code 
                    246:      * @param reason - the status message
1.28      bmahe     247:      * @deprecated since jsdk2.1
1.18      bmahe     248:      */
1.5       abaird    249:     public void setStatus(int i, String reason) {
1.1       abaird    250:        reply.setStatus(i);
                    251:        reply.setReason(reason);
                    252:     }
1.39      ylafon    253: 
1.18      bmahe     254:     /**
                    255:      * Sets the status code for this response. This method is used to set the
                    256:      * return status code when there is no error (for example, for the status
                    257:      * codes SC_OK or SC_MOVED_TEMPORARILY). If there is an error, the 
                    258:      * sendError method should be used instead.
                    259:      * @param i - the status code 
1.27      ylafon    260:      * @see JigsawHttpServletResponse#sendError
1.18      bmahe     261:      */
1.5       abaird    262:     public void setStatus(int i) {
1.1       abaird    263:        setStatus(i, reply.getStandardReason(i));
                    264:     }
1.39      ylafon    265: 
1.18      bmahe     266:     /**
                    267:      * Adds a field to the response header with the given name and value. If
                    268:      * the field had already been set, the new value overwrites the previous
                    269:      * one. The containsHeader method can be used to test for the presence of a
                    270:      * header before setting its value.
                    271:      * @param name - the name of the header field 
                    272:      * @param value - the header field's value 
1.27      ylafon    273:      * @see JigsawHttpServletResponse#containsHeader
1.18      bmahe     274:      */
1.5       abaird    275:     public void setHeader(String name, String value) {
1.1       abaird    276:        reply.setValue(name, value);
                    277:     }
1.39      ylafon    278: 
1.18      bmahe     279:     /**
                    280:      * Adds a field to the response header with the given name and integer
                    281:      * value. If the field had already been set, the new value overwrites the
                    282:      * previous one. The containsHeader method can be used to test for the
                    283:      * presence of a header before setting its value.
                    284:      * @param name - the name of the header field 
                    285:      * @param value - the header field's integer value 
1.27      ylafon    286:      * @see JigsawHttpServletResponse#containsHeader
1.18      bmahe     287:      */
1.5       abaird    288:     public void setIntHeader(String name, int value) {
1.1       abaird    289:        setHeader(name, String.valueOf(value));
                    290:     }
1.39      ylafon    291: 
1.18      bmahe     292:     /**
                    293:      * Adds a field to the response header with the given name and date-valued
                    294:      * field. The date is specified in terms of milliseconds since the epoch. 
                    295:      * If the date field had already been set, the new value overwrites the
                    296:      * previous one. The containsHeader method can be used to test for the
                    297:      * presence of a header before setting its value.
                    298:      * @param name - the name of the header field 
                    299:      * @param value - the header field's date value 
1.27      ylafon    300:      * @see JigsawHttpServletResponse#containsHeader 
1.18      bmahe     301:      */
1.5       abaird    302:     public void setDateHeader(String name, long date) {
1.32      bmahe     303:        setHeader(name, HttpFactory.makeDate(date).toExternalForm());
1.1       abaird    304:     }
1.39      ylafon    305: 
1.5       abaird    306:     public void unsetHeader(String name) {
1.1       abaird    307:        setHeader(name, null);
                    308:     }
1.39      ylafon    309: 
1.18      bmahe     310:     /**
                    311:      * Sends an error response to the client using the specified status code
                    312:      * and descriptive message. If setStatus has previously been called, it is
                    313:      * reset to the error status code. The message is sent as the body of an
                    314:      * HTML page, which is returned to the user to describe the problem. The
                    315:      * page is sent with a default HTML header; the message is enclosed in
                    316:      * simple body tags (<body></body>).
                    317:      * @param sc - the status code 
                    318:      * @param msg - the detail message 
                    319:      * @exception IOException If an I/O error has occurred.
                    320:      */
1.5       abaird    321:     public void sendError(int i, String msg) 
                    322:        throws IOException
1.1       abaird    323:     {
1.47    ! ylafon    324:        if (isStreamObtained()) {
        !           325:            throw new IOException("Reply already started in servlet");
        !           326:        }
1.1       abaird    327:        setStatus(i);
                    328:        reply.setContent(msg);
                    329:        state = STATE_ALL_DONE;
1.44      ylafon    330:        reply.setState(STREAM, new Object());
1.1       abaird    331:     }
1.18      bmahe     332: 
                    333:     /**
                    334:      * Sends an error response to the client using the specified status 
                    335:      * code and a default message. 
                    336:      * @param sc - the status code 
                    337:      * @exception IOException If an I/O error has occurred.
                    338:      */
1.1       abaird    339:     public void sendError(int i)
                    340:         throws IOException
                    341:     {
1.47    ! ylafon    342:        if (isStreamObtained()) {
        !           343:            throw new IOException("Reply already started in servlet");
        !           344:        }
1.1       abaird    345:        setStatus(i);
                    346:        reply.setContent(reply.getStandardReason(i));
                    347:        state = STATE_ALL_DONE;
1.44      ylafon    348:        reply.setState(STREAM, new Object());
1.1       abaird    349:     }
1.39      ylafon    350: 
1.18      bmahe     351:     /**
                    352:      * Sends a temporary redirect response to the client using the specified
                    353:      *  redirect location URL. The URL must be absolute (for example, 
                    354:      * https://hostname/path/file.html). Relative URLs are not permitted here. 
                    355:      * @param url - the redirect location URL 
                    356:      * @exception IOException If an I/O error has occurred. 
                    357:      */    
1.1       abaird    358:     public void sendRedirect(String url)
                    359:         throws IOException
                    360:     {
1.4       abaird    361:        URL loc = null;
1.47    ! ylafon    362:        if (isStreamObtained()) {
        !           363:            throw new IOException("Reply already started in servlet");
        !           364:        }
1.4       abaird    365:        try {
1.37      bmahe     366:            String requri = jrequest.getRequestURI();
                    367:            URL    requrl = request.getURL();
                    368:            loc = new URL(requrl.getProtocol(), 
                    369:                          requrl.getHost(), 
                    370:                          requrl.getPort(),
                    371:                          requri);
                    372:            loc = new URL(loc, url);
1.38      bmahe     373: //  Removed (netscape doesn't know SEE OTHER! sig)
                    374: //         if (jrequest.getMethod().equalsIgnoreCase("POST") &&
                    375: //             jrequest.getProtocol().equals("HTTP/1.1")) {
                    376: //             setStatus(SC_SEE_OTHER);
                    377: //         } else {
1.35      bmahe     378:                setStatus(SC_MOVED_TEMPORARILY);
1.38      bmahe     379: //         }
1.4       abaird    380:            reply.setLocation(loc);
1.41      ylafon    381:            HtmlGenerator g = new HtmlGenerator("Moved");
1.46      ylafon    382:            g.append("<P>This resource has moved, click on the link if your"
1.41      ylafon    383:                     + " browser doesn't support automatic redirection<BR>"+
                    384:                     "<A HREF=\""+loc.toExternalForm()+"\">"+
                    385:                     loc.toExternalForm()+"</A>");
                    386:            reply.setStream(g);
1.4       abaird    387:            state = STATE_ALL_DONE;
1.44      ylafon    388:            reply.setState(STREAM, new Object());
1.4       abaird    389:        } catch (Exception ex) {
                    390:            ex.printStackTrace();
                    391:        }
1.1       abaird    392:     }
1.6       bmahe     393: 
1.18      bmahe     394:     /**
                    395:      * Checks whether the response message header has a field with the
                    396:      * specified name. 
                    397:      * @param name - the header field name 
                    398:      * @return true if the response message header has a field with the 
                    399:      * specified name; false otherwise
                    400:      */
1.12      bmahe     401:     public boolean containsHeader(String header) {
                    402:        return reply.hasHeader(header);
                    403:     }
                    404: 
1.18      bmahe     405:     /**
                    406:      * Adds the specified cookie to the response. It can be called multiple 
                    407:      * times to set more than one cookie. 
                    408:      * @param cookie - the Cookie to return to the client 
                    409:      */
1.12      bmahe     410:     public void addCookie(Cookie cookie) {
1.13      bmahe     411:        HttpSetCookieList clist = reply.getSetCookie();
                    412:        if (clist == null) {
                    413:            HttpSetCookie cookies [] = new HttpSetCookie[1];
                    414:            cookies[0] = convertCookie(cookie);
                    415:            clist = new HttpSetCookieList(cookies);
                    416:        } else {
                    417:            clist.addSetCookie(convertCookie(cookie));
                    418:        }
                    419:        reply.setSetCookie(clist);
                    420:     }
1.12      bmahe     421: 
1.31      ylafon    422:     private HttpSetCookie convertCookie(Cookie cookie) {
1.13      bmahe     423:        HttpSetCookie scookie = new HttpSetCookie(true, 
                    424:                                                  cookie.getName(),
                    425:                                                  cookie.getValue());
                    426:        scookie.setComment(cookie.getComment());
                    427:        scookie.setDomain(cookie.getDomain());
                    428:        scookie.setMaxAge(cookie.getMaxAge());
                    429:        scookie.setPath(cookie.getPath());
                    430:        scookie.setSecurity(cookie.getSecure());
                    431:        scookie.setVersion(cookie.getVersion());
                    432:        return scookie;
1.12      bmahe     433:     }
                    434: 
1.18      bmahe     435:     /**
                    436:      * Encodes the specified URL for use in the sendRedirect method or, if 
                    437:      * encoding is not needed, returns the URL unchanged. The implementation 
                    438:      * of this method should include the logic to determine whether the 
                    439:      * session ID needs to be encoded in the URL.
                    440:      * Because the rules for making this determination differ from those used
                    441:      * to decide whether to encode a normal link, this method is seperate from
                    442:      * the encodeUrl method.
                    443:      * <p>All URLs sent to the HttpServletResponse.sendRedirect method should
                    444:      * be run through this method. Otherwise, URL rewriting canont be used 
                    445:      * with browsers which do not support cookies. 
                    446:      * @param url - the url to be encoded. 
                    447:      * @return the encoded URL if encoding is needed; the unchanged URL 
                    448:      * otherwise. 
1.28      bmahe     449:      * @deprecated since jsdk2.1
1.27      ylafon    450:      * @see JigsawHttpServletResponse#sendRedirect
                    451:      * @see JigsawHttpServletResponse#encodeUrl
1.18      bmahe     452:      */
1.12      bmahe     453:     public String encodeRedirectUrl(String url) {
1.19      bmahe     454:        try {
                    455:            URL redirect = new URL(url);
                    456:            URL requested = new URL(jrequest.getRequestURI());
                    457:            if ( redirect.getHost().equals(requested.getHost()) &&
                    458:                 redirect.getPort() == requested.getPort())
                    459:                return encodeUrl(url);
                    460:        } catch (MalformedURLException ex) {
                    461:            //error so return url.
                    462:            return url;
                    463:        }
                    464:        return url;
1.12      bmahe     465:     }
                    466: 
1.28      bmahe     467:         /**
                    468:      * Encodes the specified URL for use in the sendRedirect method or, if 
                    469:      * encoding is not needed, returns the URL unchanged. The implementation 
                    470:      * of this method should include the logic to determine whether the 
                    471:      * session ID needs to be encoded in the URL.
                    472:      * Because the rules for making this determination differ from those used
                    473:      * to decide whether to encode a normal link, this method is seperate from
                    474:      * the encodeUrl method.
                    475:      * <p>All URLs sent to the HttpServletResponse.sendRedirect method should
                    476:      * be run through this method. Otherwise, URL rewriting canont be used 
                    477:      * with browsers which do not support cookies. 
                    478:      * @param url - the url to be encoded. 
                    479:      * @return the encoded URL if encoding is needed; the unchanged URL 
                    480:      * otherwise. 
                    481:      * @see JigsawHttpServletResponse#sendRedirect
                    482:      * @see JigsawHttpServletResponse#encodeUrl
                    483:      */
                    484:     public String encodeRedirectURL(String url) {
                    485:        return encodeRedirectUrl(url);
                    486:     }
                    487: 
1.18      bmahe     488:     /**
                    489:      * Encodes the specified URL by including the session ID in it, or, if 
                    490:      * encoding is not needed, returns the URL unchanged. The implementation of
                    491:      * this method should include the logic to determine whether the session ID
                    492:      * needs to be encoded in the URL. For example, if the browser supports
                    493:      * cookies, or session tracking is turned off, URL encoding is unnecessary.
                    494:      * <p>All URLs emitted by a Servlet should be run through this method. 
                    495:      * Otherwise, URL rewriting cannot be used with browsers which do not 
                    496:      * support cookies.
                    497:      * @param url - the url to be encoded. 
                    498:      * @return the encoded URL if encoding is needed; the unchanged URL 
                    499:      * otherwise. 
1.28      bmahe     500:      * @deprecated since jsdk2.1
1.18      bmahe     501:      */
1.12      bmahe     502:     public String encodeUrl(String url) {
1.15      bmahe     503:        if (! jrequest.isRequestedSessionIdFromCookie()) {
1.25      bmahe     504:            url = url + ((url.indexOf("?") != -1) ? "&" : "?")+
                    505:                jrequest.getCookieName()+"="+
1.15      bmahe     506:                jrequest.getSession(true).getId();
                    507:        }
1.12      bmahe     508:        return url;
1.28      bmahe     509:     }
                    510: 
                    511:     /**
                    512:      * Encodes the specified URL by including the session ID in it, or, if 
                    513:      * encoding is not needed, returns the URL unchanged. The implementation of
                    514:      * this method should include the logic to determine whether the session ID
                    515:      * needs to be encoded in the URL. For example, if the browser supports
                    516:      * cookies, or session tracking is turned off, URL encoding is unnecessary.
                    517:      * <p>All URLs emitted by a Servlet should be run through this method. 
                    518:      * Otherwise, URL rewriting cannot be used with browsers which do not 
                    519:      * support cookies.
                    520:      * @param url - the url to be encoded. 
                    521:      * @return the encoded URL if encoding is needed; the unchanged URL 
                    522:      * otherwise. 
                    523:      */
                    524:     public String encodeURL(String url) {
                    525:        return encodeUrl(url);
1.12      bmahe     526:     }
                    527: 
                    528:     /**
                    529:      * Return the Charset parameter of content type
                    530:      * @return A String instance
                    531:      */
                    532:     public String getCharacterEncoding() {
                    533:        org.w3c.www.mime.MimeType type = reply.getContentType();
1.33      bmahe     534:        if ((type != null) && (type.hasParameter(CHARSET_PARAMETER))) {
                    535:            return type.getParameterValue(CHARSET_PARAMETER);
1.12      bmahe     536:        }
1.30      bmahe     537:        return System.getProperty("file.encoding");
1.12      bmahe     538:     }
                    539: 
1.18      bmahe     540:     /**
                    541:      * Returns a print writer for writing formatted text responses. 
                    542:      * The MIME type of the response will be modified, if necessary, to
                    543:      * reflect the character encoding used, through the charset=... property. 
                    544:      * This means that the content type must be set before calling this 
                    545:      * method. 
                    546:      * @exception UnsupportedEncodingException if no such encoding can be 
                    547:      * provided 
                    548:      * @exception IllegalStateException if getOutputStream has been called 
                    549:      * on this same request.
                    550:      * @exception IOException on other errors. 
1.27      ylafon    551:      * @see JigsawHttpServletResponse#getOutputStream
                    552:      * @see JigsawHttpServletResponse#setContentType 
1.18      bmahe     553:      */    
1.12      bmahe     554:     public synchronized PrintWriter getWriter() 
                    555:        throws IOException, UnsupportedEncodingException
                    556:     {
                    557:        if (stream_state == OUTPUT_STREAM_USED)
                    558:            throw new IllegalStateException("Output stream used");
                    559:        stream_state = STREAM_WRITER_USED;
1.39      ylafon    560: 
1.12      bmahe     561:        if (writer == null) {
                    562:            writer = new PrintWriter(
1.34      bmahe     563:                         new OutputStreamWriter(getJigsawOutputStream(true), 
1.29      bmahe     564:                                                getCharacterEncoding()));
1.12      bmahe     565:        }
                    566:        return writer;
1.20      bmahe     567:     }
                    568: 
1.31      ylafon    569:     /**
                    570:      * Flush the output stream.
                    571:      * @param close Close the stream if true.
                    572:      * @exception IOException if an IO error occurs.
                    573:      */
1.30      bmahe     574:     protected synchronized void flushStream(boolean close) 
                    575:        throws IOException
                    576:     {
1.34      bmahe     577:        if (state == STATE_ALL_DONE) {
1.42      bmahe     578:            return;
1.34      bmahe     579:        }
1.30      bmahe     580:        int writeLength;
1.29      bmahe     581: 
1.30      bmahe     582:        if (stream_state == OUTPUT_STREAM_USED) {
                    583:            output.flush();
                    584:        } else if (stream_state == STREAM_WRITER_USED) {
1.36      bmahe     585:            writer.flush();
1.40      ylafon    586:            if (close) {
                    587:                output.realFlush();
                    588:            } else {
                    589:                output.flush();
                    590:            }
1.36      bmahe     591:        } else {
                    592:            // force flush even if no stream are openned
                    593:            getWriter();
1.30      bmahe     594:            writer.flush();
1.34      bmahe     595:            output.realFlush();
1.30      bmahe     596:        }
1.29      bmahe     597: 
1.30      bmahe     598:        if (request.hasState(INCLUDED)) {
                    599:            if (out == null)
                    600:                return;
                    601: 
                    602:            if( fixedContentLength != CALC_CONTENT_LENGTH ) {
                    603:                writeLength = (out.size() < fixedContentLength) 
                    604:                    ? (out.size())
                    605:                    : (fixedContentLength);
1.29      bmahe     606:            } else {
1.30      bmahe     607:                writeLength = out.size();
1.29      bmahe     608:            }
1.30      bmahe     609:            reply.setContentLength(writeLength);
                    610:            
                    611:            OutputStream rout = reply.getOutputStream(false);
                    612:            byte content[] = out.toByteArray();
                    613:            if (close)
                    614:                out.close();
                    615:            else 
                    616:                out.reset();
                    617:            rout.write(content);
                    618:            rout.flush();
1.40      ylafon    619:        } else {
                    620:            if ((pout != null) && close) {
                    621:                pout.flush();
                    622:                pout.close();
                    623:            }
1.29      bmahe     624:        }
1.13      bmahe     625:     }
                    626: 
1.33      bmahe     627:     // 2.2
                    628: 
                    629:     /**
                    630:      * Sets the preferred buffer size for the body of the response.  
                    631:      * The servlet container will use a buffer at least as large as 
                    632:      * the size requested.  The actual buffer size used can be found
                    633:      * using <code>getBufferSize</code>.
                    634:      *
                    635:      * <p>A larger buffer allows more content to be written before anything is
                    636:      * actually sent, thus providing the servlet with more time to set
                    637:      * appropriate status codes and headers.  A smaller buffer decreases 
                    638:      * server memory load and allows the client to start receiving data more
                    639:      * quickly.
                    640:      *
                    641:      * <p>This method must be called before any response body content is
                    642:      * written; if content has been written, this method throws an 
                    643:      * <code>IllegalStateException</code>.
                    644:      * @param size the preferred buffer size
                    645:      * @exception IllegalStateException        if this method is called after
                    646:      * content has been written
                    647:      * @see #getBufferSize
                    648:      * @see #flushBuffer
                    649:      * @see #isCommitted
                    650:      * @see #reset
                    651:      */
                    652:     public void setBufferSize(int size) {
                    653:        if (stream_state != STREAM_STATE_INITIAL) {
                    654:            throw new IllegalStateException("Stream already initialized");
                    655:        }
1.34      bmahe     656:        buffer_size = size < MIN_BUFFER_SIZE ? MIN_BUFFER_SIZE : size;
1.33      bmahe     657:     }
                    658: 
                    659:     /**
                    660:      * Returns the actual buffer size used for the response.  If no buffering
                    661:      * is used, this method returns 0.
                    662:      * @return the actual buffer size used
                    663:      * @see #setBufferSize
                    664:      * @see #flushBuffer
                    665:      * @see #isCommitted
                    666:      * @see #reset
                    667:      */
                    668:     public int getBufferSize() {
                    669:        return buffer_size;
                    670:     }
                    671: 
                    672:     /**
                    673:      * Forces any content in the buffer to be written to the client.  A call
                    674:      * to this method automatically commits the response, meaning the status 
                    675:      * code and headers will be written.
                    676:      * @see #setBufferSize
                    677:      * @see #getBufferSize
                    678:      * @see #isCommitted
                    679:      * @see #reset
                    680:      */
                    681:     public void flushBuffer() 
                    682:        throws IOException
                    683:     {
                    684:        if (output != null) {
1.34      bmahe     685:            if (stream_state == STREAM_WRITER_USED) {
                    686:                writer.flush();
                    687:            }
1.33      bmahe     688:            output.flush();
                    689:        }
                    690:     }
                    691: 
                    692:     /**
                    693:      * Returns a boolean indicating if the response has been
                    694:      * committed.  A commited response has already had its status 
                    695:      * code and headers written.
                    696:      * @return a boolean indicating if the response has been
                    697:      * committed
                    698:      * @see #setBufferSize
                    699:      * @see #getBufferSize
                    700:      * @see #flushBuffer
                    701:      * @see #reset
                    702:      */
                    703:     public boolean isCommitted() {
                    704:        if (output != null) {
                    705:            return output.isCommitted();
                    706:        } else {
                    707:            return false;
                    708:        }
                    709:     }
                    710: 
                    711:     /**
                    712:      * Clears any data that exists in the buffer as well as the status code and
                    713:      * headers.  If the response has been committed, this method throws an 
                    714:      * <code>IllegalStateException</code>.
                    715:      * @exception IllegalStateException  if the response has already been
                    716:      *                                   committed
                    717:      * @see #setBufferSize
                    718:      * @see #getBufferSize
                    719:      * @see #flushBuffer
                    720:      * @see #isCommitted
                    721:      */
                    722:     public void reset() {
                    723:        if (output != null) {
1.34      bmahe     724:            if (stream_state == STREAM_WRITER_USED) {
                    725:                writer.flush();
                    726:            }
1.33      bmahe     727:            output.reset();
                    728:        }
                    729:     }
                    730: 
                    731:     /**
                    732:      * Sets the locale of the response, setting the headers (including the
                    733:      * Content-Type's charset) as appropriate.  This method should be called
                    734:      * before a call to {@link #getWriter}.  By default, the response locale
                    735:      * is the default locale for the server.
                    736:      * @param loc  the locale of the response
                    737:      * @see #getLocale
                    738:      */
                    739:     public void setLocale(Locale locale) {
                    740:        if (locale == null) {
                    741:            return;
                    742:        }
                    743:        this.locale = locale;
                    744: 
                    745:        // content language
                    746:        String lang = locale.getLanguage();
                    747:        if (lang.length() > 0) {
                    748:            String array[] = new String[1];
                    749:            array[0] = lang;
                    750:            reply.setContentLanguage(array);
                    751:        }
                    752:        
                    753:        // content type (charset)
                    754:        String charset = org.w3c.www.mime.Utils.getCharset(locale);
                    755:        if (charset != null) {
                    756:            MimeType contentType = reply.getContentType();
                    757:            // override charset
                    758:            contentType.setParameter(CHARSET_PARAMETER, charset);
                    759:        }
                    760:     }
                    761: 
                    762:     /**
                    763:      * Returns the locale assigned to the response.
                    764:      * @see #setLocale
                    765:      */
                    766:     public Locale getLocale() {
                    767:        return locale;
                    768:     }
                    769: 
                    770:     public void addHeader(String name, String value) {
                    771:        String      lname = name.toLowerCase();
                    772:        HeaderValue hvalue = reply.getHeaderValue(lname);
                    773:        //
                    774:        // Horrible, Shame on us, I hate that
                    775:        //
                    776:        if (hvalue == null) {
                    777:            setHeader(name, value);
                    778:        } else if (hvalue instanceof HttpAcceptCharsetList) {
                    779:            HttpAcceptCharsetList acl = (HttpAcceptCharsetList) hvalue;
                    780:            acl.addCharset(HttpFactory.parseAcceptCharset(value));
                    781:        } else if (hvalue instanceof HttpAcceptEncodingList) {
                    782:            HttpAcceptEncodingList ael = (HttpAcceptEncodingList) hvalue;
                    783:            ael.addEncoding(HttpFactory.parseAcceptEncoding(value));
                    784:        } else if (hvalue instanceof HttpAcceptLanguageList) {
                    785:            HttpAcceptLanguageList all = (HttpAcceptLanguageList) hvalue;
                    786:            all.addLanguage(HttpFactory.parseAcceptLanguage(value));
                    787:        } else if (hvalue instanceof HttpAcceptList) {
                    788:            HttpAcceptList al = (HttpAcceptList) hvalue;
                    789:            al.addAccept(HttpFactory.parseAccept(value));
                    790:        } else if (hvalue instanceof HttpEntityTagList) {
                    791:            HttpEntityTagList etl = (HttpEntityTagList) hvalue;
                    792:            etl.addTag(HttpFactory.parseETag(value));
                    793:        } else if (hvalue instanceof HttpExtList) {
                    794:            HttpExtList el = (HttpExtList) hvalue;
                    795:            el.addHttpExt(new HttpExt(value, false));
                    796:        } else if (hvalue instanceof HttpCookieList) {
                    797:            // shouldn't be used, but who knows?
                    798:            HttpCookieList cl  = (HttpCookieList) hvalue;
                    799:            HttpCookieList ncl = HttpFactory.parseCookieList(value);
                    800:            HttpCookie scookies[] = ncl.getCookies();
                    801:            for (int i = 0 ; i < scookies.length ; i++) {
                    802:                HttpCookie cookie = scookies[i];
                    803:                cl.addCookie(cookie.getName(), cookie.getValue());
                    804:            }
                    805:        } else if (hvalue instanceof HttpParamList) {
                    806:            int idx = value.indexOf('=');
                    807:            if (idx != -1) {
                    808:                String pname  = value.substring(0, idx);
                    809:                String pvalue = value.substring(idx+1);
                    810:                HttpParamList pl = (HttpParamList) hvalue;
                    811:                pl.setParameter(pname, pvalue);
                    812:            }
                    813:        } else if (hvalue instanceof HttpRangeList) {
                    814:            HttpRangeList rl = (HttpRangeList) hvalue;
                    815:            rl.addRange(HttpFactory.parseRange(value));
                    816:        } else if (hvalue instanceof HttpSetCookieList) {
                    817:            HttpSetCookieList scl = (HttpSetCookieList) hvalue;
                    818:            HttpSetCookieList nscl = HttpFactory.parseSetCookieList(value);
                    819:            HttpSetCookie scookies[] = nscl.getSetCookies();
                    820:            for (int i = 0 ; i < scookies.length ; i++) {
                    821:                scl.addSetCookie(scookies[i]);
                    822:            }
                    823:        } else if (hvalue instanceof HttpTokenList) {
                    824:            ((HttpTokenList) hvalue).addToken(value, true);
                    825:        } else if (hvalue instanceof HttpWarningList) {
                    826:            HttpWarningList wl = (HttpWarningList) hvalue;
                    827:            wl.addWarning(HttpFactory.parseWarning(value));
                    828:        } else if (hvalue instanceof HttpString) {
                    829:            // this is the default type for unkown header
                    830:            // we don't know what it is, so just append
                    831:            HttpString s = (HttpString) hvalue;
                    832:            String string = (String) s.getValue();
                    833:            s.setValue(string+", "+value);
                    834:        } else {
                    835:            // not compliant with HTTP/1.1, override
                    836:            setHeader(name, value);
                    837:        }
                    838:     }
                    839: 
                    840:     public void addDateHeader(String name, long date) {
                    841:        addHeader(name, HttpFactory.makeDate(date).toExternalForm());
                    842:     }
                    843: 
                    844:     public void addIntHeader(String name, int value) {
                    845:        addHeader(name, String.valueOf(value));
                    846:     }
                    847: 
1.13      bmahe     848:     JigsawHttpServletResponse(Request request, Reply reply) {
1.33      bmahe     849:        this.request     = request;
                    850:        this.reply       = reply;
                    851:        this.buffer_size = DEFAULT_BUFFER_SIZE;
1.11      bmahe     852:     }
1.6       bmahe     853: 
1.1       abaird    854: }

Webmaster