Source code: com/tripi/asp/Response.java
1 /**
2 * ArrowHead ASP Server
3 * This is a source file for the ArrowHead ASP Server - an 100% Java
4 * VBScript interpreter and ASP server.
5 *
6 * For more information, see http://www.tripi.com/arrowhead
7 *
8 * Copyright (C) 2002 Terence Haddock
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 *
24 */
25 package com.tripi.asp;
26
27 import javax.servlet.http.HttpServletResponse;
28 import java.io.*;
29 import java.util.Calendar;
30 import java.util.Date;
31 import java.util.Enumeration;
32 import java.util.GregorianCalendar;
33 import java.util.Hashtable;
34 import java.util.TimeZone;
35 import java.util.Vector;
36 import java.text.DateFormat;
37 import java.text.SimpleDateFormat;
38 import org.apache.log4j.Category;
39
40 /**
41 * ASP "Response" object. This object handles communication from ASP
42 * back to the client. Implementation state:
43 * <ul>
44 * <li><b>Cookies</b> - Implemented.
45 * <li><b>Buffer</b> - Implemented
46 * <li><b>CacheControl</b> - Implemented
47 * <li><b>Charset</b> - Implemented
48 * <li><b>ContentType</b> - Implemented
49 * <li><b>Expires</b> - Implementes, <b>NOTE</b> Expires is not updated
50 * properly when ExpiresAbsolute is used.
51 * <li><b>ExpiresAbsolute</b> - Fully implemented.
52 * <li><b>IsClientConnected</b> - <b>TODO</b> Not implemented.
53 * <li><b>PICS</b> - <b>TODO</b> Not implemented.
54 * <li><b>Status</b> - Fully implemented.
55 * <li><b>AddHeader</b> - Fully supported.
56 * <li><b>AppendToLog</b> - <b>TODO</b> Not implemented.
57 * <li><b>BinaryWrite</b> - Fully supported.
58 * <li><b>Clear</b> - Implemented.
59 * <li><b>End</b> - Implemented.
60 * <li><b>Flush</b> - Implemented.
61 * <li><b>Redirect</b> - Implemented.
62 * <li><b>Write</b> - Implemented.
63 * </ul>
64 *
65 * @author Terence Haddock
66 * @version 0.9
67 */
68 public class Response
69 {
70 /** Log4j debugging object */
71 Category DBG = Category.getInstance(Response.class);
72
73 /** JSDK servlet response object */
74 HttpServletResponse response;
75
76 /**
77 * Buffer for when Buffer = TRUE. When buffering is enabled,
78 * stringBuf != null, and when buffering is disabled, stringBuf == null.
79 */
80 StringBuffer stringBuf = new StringBuffer();
81
82 /**
83 * Have the headers been sent?
84 */
85 boolean sentHeaders = false;
86
87 /**
88 * Cookie jar to hold cookies to be sent to the client.
89 */
90 public CookieJar Cookies = new CookieJar();
91
92 /**
93 * Cache-control setting
94 */
95 public String cacheControl = "private";
96
97 /**
98 * Content type setting
99 */
100 public String contentType = "text/html";
101
102 /**
103 * Charset setting
104 */
105 public String charset = "";
106
107 /**
108 * Current expiry string. Prepended with an underscore to keep it
109 * from being accessed within ASP.
110 */
111 AspDate _expiryDate = null;
112
113 /**
114 * Current expiry time, in minutes.
115 */
116 int _expiryMinutes = 0;
117
118 /**
119 * Constructor.
120 * @param response JSDK response object.
121 */
122 public Response(HttpServletResponse response)
123 {
124 this.response = response;
125 }
126
127 /**
128 * This unsupported ASP-accessible function obtains the HTTP response
129 * object that this Response object points to.
130 * @return HttpServletResponse object
131 */
132 public HttpServletResponse getHttpServletResponse()
133 {
134 return response;
135 }
136
137 /**
138 * ASP-accessible function to output text to the client.
139 * @param str Text to output to the client.
140 * @throws IOException on output error.
141 */
142 public void Write(String str) throws IOException, AspException
143 {
144 if (stringBuf != null) {
145 stringBuf.append(str);
146 } else {
147 if (!sentHeaders) sendHeaders();
148 OutputStream os = response.getOutputStream();
149 os.write(str.getBytes());
150 os.flush();
151 }
152 }
153
154 /**
155 * ASP-accessible function to output any buffered text to the client.
156 * @throws IOException on output error.
157 * @throws AspException on ASP error.
158 */
159 public void Flush() throws IOException, AspException
160 {
161 if (stringBuf == null)
162 throw new AspException("Flush() called when " +
163 "buffer = FALSE");
164 if (!sentHeaders) sendHeaders();
165 response.getOutputStream().write(stringBuf.toString().getBytes());
166 stringBuf = new StringBuffer();
167 }
168
169 /**
170 * ASP-accessible function to redirect the user to a different URL.
171 * @param redir URL to redirect to, should be fully-qualified
172 * URL.
173 * @throws IOException on output error
174 * @throws AspException always throws AspExitScriptException
175 */
176 public void Redirect(String redir) throws IOException, AspException
177 {
178 if (!sentHeaders) sendHeaders();
179 response.setHeader("Location", redir);
180 response.sendError(302);
181 stringBuf = null;
182 throw new AspExitScriptException();
183 }
184
185 /**
186 * This ASP-accessible function acts as the ASP field 'buffer', which
187 * enables or disables buffering.
188 * @param bBuffer enable, or disable buffer
189 * @throws IOException on error.
190 */
191 public void Buffer(boolean bBuffer) throws IOException, AspException
192 {
193 if (bBuffer)
194 {
195 if (sentHeaders)
196 {
197 throw new AspException("Headers already sent, buffering " +
198 "cannot be turned on after the headers have been sent.");
199 } else if (stringBuf == null) {
200 stringBuf = new StringBuffer();
201 }
202 } else {
203 if (stringBuf != null) {
204 if (stringBuf.length() == 0) stringBuf = null;
205 else {
206 throw new AspException("Buffering cannot be turned off " +
207 "after it has been turned on.");
208 }
209 }
210 }
211 }
212
213 /**
214 * This ASP-accessible function obtains the buffer status
215 * @return <b>true</b> if buffering is on, <b>false</b> if not.
216 */
217 public boolean Buffer()
218 {
219 return stringBuf != null;
220 }
221
222 /**
223 * The ASP-accessible Clear function clears any buffered text.
224 */
225 public void Clear() throws AspException
226 {
227 if (stringBuf == null)
228 throw new AspException("Flush() called when " +
229 "buffer = FALSE");
230 stringBuf = new StringBuffer();
231 }
232
233 /**
234 * The ASP-accessible End function ends the current ASP script
235 * immediately.
236 * @throws AspException always throws AspExitScriptException.
237 */
238 public void End() throws AspException
239 {
240 throw new AspExitScriptException();
241 }
242
243 /**
244 * This ASP-accessible function sets the expiry time to the specified
245 * number of minutes from the current date/time.
246 * @param minutes Number of minutes
247 */
248 public void Expires(int minutes) throws AspException
249 {
250 if (sentHeaders) throw new AspException("Headers Sent");
251 Calendar cal = new GregorianCalendar();
252 Date date = new Date(System.currentTimeMillis());
253 cal.setTime(date);
254 cal.add(cal.MINUTE, minutes);
255 _expiryDate = new AspDate(cal.getTime());
256 _expiryMinutes = minutes;
257 }
258
259 /**
260 * This ASP-accessible function obtains the number of minutes the
261 * expiry time is set to.
262 * @return Number of minutes
263 */
264 public int Expires()
265 {
266 return _expiryMinutes;
267 }
268
269 /**
270 * This ASP-accessible function sets the expiry time to the specified
271 * absolute date.
272 * @param date Date to set expiry to
273 */
274 public void ExpiresAbsolute(AspDate date) throws AspException
275 {
276 if (sentHeaders) throw new AspException("Headers Sent");
277 _expiryDate = date;
278 /* Get the current date in seconds */
279 long currentTime = System.currentTimeMillis() / 1000;
280 /* Get the expiry date in seconds */
281 long expiryTime = date.toDate().getTime() / 1000;
282 /* Obtain the different in seconds between the two times */
283 long diffSec = (expiryTime - currentTime);
284 /* Set the difference in minutes to the expiry setting */
285 _expiryMinutes = (int)(diffSec / 60);
286 }
287
288 /**
289 * This ASP-accessible function obtains the number of minutes the
290 * expiry time is set to.
291 * @return Number of minutes
292 */
293 public AspDate ExpiresAbsolute()
294 {
295 return _expiryDate;
296 }
297
298 /**
299 * This ASP-accessible BinaryWrite function writes binary data to the
300 * output stream. This function assumes it's being called with
301 * an array of Chars.
302 * @param an ArrayNode object
303 */
304 public void BinaryWrite(byte arr[]) throws AspException, IOException
305 {
306 /* XXX This ignores Response.Buffer */
307 Flush();
308 OutputStream os = response.getOutputStream();
309 os.write(arr);
310 os.flush();
311 }
312
313 /**
314 * Internal function to send the headers.
315 */
316 synchronized void sendHeaders() throws AspException
317 {
318 if (sentHeaders) return;
319 if (DBG.isDebugEnabled()) DBG.debug("Sending headers");
320 sentHeaders = true;
321 String contentType = this.contentType;
322 if (this.charset != null && this.charset.length() > 0) {
323 contentType = contentType + "; Charset=" + this.charset;
324 }
325 response.setContentType(contentType);
326 if (cacheControl == null) cacheControl = "private";
327 response.setHeader("Cache-control", cacheControl);
328 Cookies.sendCookies();
329 if (_expiryDate != null)
330 {
331 Calendar cal = _expiryDate.toCalendar();
332 if (!_expiryDate.hasTime())
333 {
334 /* If the time is not set, assume the expiry is at midnight
335 of the current date. */
336 cal.set(cal.HOUR_OF_DAY, 24);
337 cal.set(cal.MINUTE, 0);
338 cal.set(cal.SECOND, 0);
339 cal.set(cal.MILLISECOND, 0);
340 }
341 /* You can get the time in millisecond directly from cal
342 in JDK 1.4 */
343 Date date = cal.getTime();
344 response.setDateHeader("Expires", date.getTime());
345 }
346 }
347
348 /**
349 * ASP-accessible function to set the status for this request.
350 * @param status New status value
351 */
352 public void Status(int value)
353 {
354 response.setStatus(value);
355 }
356
357 /**
358 * ASP-accessible function to add a header to the response stream.
359 * This function will not overwrite a header, it will add a new one
360 * to the response stream.
361 * @param name Header name
362 * @param value Header value
363 */
364 public void AddHeader(String name, String value)
365 {
366 response.addHeader(name, value);
367 }
368
369 /**
370 * Cookie Jar class
371 */
372 public class CookieJar implements SimpleMap
373 {
374 /**
375 * AspCollection containing the cookie crumbs.
376 */
377 AspCollection cookieCrumbs = new AspCollection();
378
379 /**
380 * Constructor, initally creates an empty cookie jar.
381 */
382 public CookieJar()
383 {
384 }
385
386 /**
387 * Obtains a cookie from the jar. This class always returns
388 * a cookie crumb.
389 * @param key Key of cookie, will be converted to string and is
390 * case insensitive.
391 * @return CookieCrumb object representing a cookie.
392 */
393 public synchronized Object get(Object key) throws AspException
394 {
395 String strKey = Types.coerceToString(key);
396 CookieCrumb crumb;
397
398 if (cookieCrumbs.containsKey(strKey))
399 {
400 if (DBG.isDebugEnabled()) DBG.debug("existing(" + strKey + ")");
401 crumb = (CookieCrumb)cookieCrumbs.get(strKey);
402 } else {
403 if (DBG.isDebugEnabled())
404 {
405 DBG.debug("new(" + strKey + ")");
406 DBG.debug("This: " + this);
407 }
408 crumb = new CookieCrumb(strKey);
409 cookieCrumbs.put(strKey, crumb);
410 }
411 return crumb;
412 }
413
414 /**
415 * Stores a cookie value in the jar.
416 * @param key Key of cookie, will be converted to a string and is
417 * case insensitive.
418 * @param obj Value to store in the cookie, will be converted
419 * to a string.
420 */
421 public synchronized void put(Object key, Object value)
422 throws AspException
423 {
424 CookieCrumb crumb = (CookieCrumb)get(key);
425 crumb.setValue(value);
426 }
427
428 /**
429 * Enumerates the elements in this jar.
430 * @return Enumeration of elements in this jar.
431 */
432 public Enumeration getKeys() throws AspException
433 {
434 return cookieCrumbs.getKeys();
435 }
436
437 /**
438 * Send all of the cookies stored in the jar.
439 */
440 void sendCookies() throws AspException
441 {
442 if (DBG.isDebugEnabled())
443 {
444 DBG.debug("Sending cookies");
445 DBG.debug("cookieCrumbs: " + cookieCrumbs);
446 DBG.debug("This: " + this);
447 }
448 Enumeration values = cookieCrumbs.elements();
449 while (values.hasMoreElements())
450 {
451 CookieCrumb crumb = (CookieCrumb)values.nextElement();
452 if (DBG.isDebugEnabled()) DBG.debug("Sending cookie:" + crumb);
453 if (crumb.name != null) {
454 StringBuffer cookieBuf = new StringBuffer();
455 cookieBuf.append(crumb.name);
456 if (crumb.fullValue != null)
457 {
458 cookieBuf.append('=');
459 cookieBuf.append(crumb.fullValue);
460 }
461 if (crumb.expires != null)
462 {
463 DateFormat df = new SimpleDateFormat(
464 "EEE, dd-MMM-yyyy hh:mm:ss z");
465 final TimeZone gmtTime = TimeZone.getTimeZone("GMT");
466 df.setTimeZone(gmtTime);
467 String text = df.format(crumb.expires.toDate());
468 cookieBuf.append("; expires=");
469 cookieBuf.append(text);
470 }
471 cookieBuf.append("; path=");
472 cookieBuf.append(crumb.path);
473 if (crumb.secure)
474 {
475 cookieBuf.append("; secure");
476 }
477 response.addHeader("Set-Cookie", cookieBuf.toString());
478 }
479 }
480 }
481
482 /**
483 * CookieCrumb class to hold cookie information.
484 */
485 public class CookieCrumb implements SimpleReference, SimpleMap
486 {
487 /**
488 * Cookie name
489 */
490 String name = null;
491
492 /**
493 * Cookie base value
494 */
495 String value = null;
496
497 /**
498 * Values of the keys
499 */
500 AspCollection keys = new AspCollection();
501
502 /**
503 * Expires value.
504 */
505 AspDate expires = null;
506
507 /**
508 * Cookie path
509 */
510 String path = "/";
511
512 /**
513 * Secure cookie?
514 */
515 boolean secure = false;
516
517 /**
518 * Cookie full value
519 */
520 String fullValue = null;
521
522 /**
523 * Constructor, setting a value.
524 * @param name cookie name
525 */
526 public CookieCrumb(String name)
527 {
528 this.name = name;
529 }
530
531 /**
532 * Sets the date this cookie expires.
533 * @param Expiry expiration date.
534 * @throws AspException
535 */
536 public void Expires(AspDate date) throws AspException
537 {
538 this.expires = date;
539 updateCookie();
540 }
541
542 /**
543 * Sets the cookie path
544 * @param Path cookie path
545 * @throws AspException
546 */
547 public void Path(String path) throws AspException
548 {
549 this.path = path;
550 updateCookie();
551 }
552
553 /**
554 * Set the secure flag
555 * @param secure The secure flag
556 * @throws AspException
557 */
558 public void Secure(boolean secure) throws AspException
559 {
560 this.secure = secure;
561 updateCookie();
562 }
563
564 /**
565 * Reference write function. Overwrites the current cookie value.
566 * @param value new value
567 * @throws AspException on error
568 * @see SimpleReference#setValue
569 */
570 public void setValue(Object value) throws AspException
571 {
572 if (sentHeaders) {
573 throw new AspException("Headers Sent");
574 }
575 String str = Types.coerceToString(value);
576 this.value = str;
577 updateCookie();
578 }
579
580 /**
581 * Reference read function.
582 * @return value current value
583 * @throws AspException on error
584 * @see SimpleReference#getValue
585 */
586 public Object getValue() throws AspException
587 {
588 throw new AspException("Not implemented");
589 }
590
591 /**
592 * Does this set of cookies have keys?
593 */
594 public boolean HasKeys() throws AspException
595 {
596 return keys.size() > 0;
597 }
598
599 /**
600 * Gets the value in a SimpleMap context.
601 * @param index Index of value to get
602 * @return value of cookie key
603 */
604 public Object get(Object index) throws AspException
605 {
606 throw new AspNotImplementedException();
607 }
608
609 /**
610 * Sets the value in a SimpleMap context.
611 * @param index Index of value to get
612 * @param value New value
613 */
614 public void put(Object index, Object value) throws AspException
615 {
616 String indStr = Types.coerceToString(index);
617 String valStr = Types.coerceToString(value);
618 keys.put(indStr, valStr);
619 updateCookie();
620 }
621
622 /**
623 * Enumerates all of the keys in this cookie.
624 * @return enumeration of the keys in this cookie.
625 */
626 public Enumeration getKeys() throws AspException
627 {
628 return keys.elements();
629 }
630
631 /**
632 * Update the value of the cookie.
633 */
634 private void updateCookie() throws AspException
635 {
636 boolean first = true;
637 StringBuffer buf = new StringBuffer();
638 if (value != null) {
639 buf.append(Server.URLEncode(value));
640 first = false;
641 }
642 for (Enumeration e = keys.keys(); e.hasMoreElements();)
643 {
644 String key = (String)e.nextElement();
645 String val = (String)keys.get(key);
646
647 if (!first)
648 {
649 buf.insert(0, '&');
650 } else {
651 first = false;
652 }
653 buf.insert(0, Server.URLEncode(val));
654 buf.insert(0, '=');
655 buf.insert(0, Server.URLEncode(key));
656 }
657 fullValue = buf.toString();
658 }
659 }
660 }
661 }