/* * File : HTTPRequest.java * Created : 13-feb-2003 15:19 * By : fbusquets * * JClic - Authoring and playing system for educational activities * * Copyright (C) 2000 - 2005 Francesc Busquets & Departament * d'Educacio de la Generalitat de Catalunya * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details (see the LICENSE file). */ package edu.xtec.jclic.report; import edu.xtec.servlet.RequestProcessor; import edu.xtec.util.Html; import edu.xtec.util.StrUtils; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.Socket; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.StringTokenizer; /** * This class provides methods and encapsulates the data involved in a transaction * using the HTTP protocol. It provides basic methods to accept an incoming conenction * throught an open {@link java.net.Socket}, read the cookies and parameters passed * by the client, read the data passed into the request header and leave a prepared * {@link java.io.OutputStream} and a {@link java.io.PrintWriter} for sending back * the response to the client. * @author Francesc Busquets (fbusquets@xtec.cat) * @version 13.09.17 */ public class HTTPRequest { public Socket socket; public BufferedReader in; public OutputStream os; public PrintWriter pw; public Map<String, Object> cookies=new HashMap<String, Object>(); public Map<String, Object> params=new HashMap<String, Object>(); public String method; public String urlBase; public String protocol; public boolean commited; public String firstLine; public InputStream inputStream; ResponseHead head=new ResponseHead(); public static final int OK=200, BAD_REQUEST=400, MOVED_PERM=301, FOUND=302, NOT_FOUND=404, SERVER_ERROR=500; public static final String MIME_HTML="text/html"; /** Creates a new instance of HTTPRequest */ public HTTPRequest(Socket socket) throws Exception { this.socket=socket; buildStreams(); //buildStreamsDebug(System.out); int cl=-1; try{ firstLine = in.readLine(); if(firstLine==null){ while (in.ready()) in.readLine(); return; } StringTokenizer st=new StringTokenizer(firstLine, " "); method=st.nextToken(); String s=st.nextToken(); int i=s.indexOf('?'); if(i<0) urlBase=s; else{ urlBase=s.substring(0, i); processParamsLine(s.substring(i+1), params, '&', '=', true); } protocol=st.nextToken(); if(protocol==null || !protocol.equals("HTTP/1.0") && !protocol.equals("HTTP/1.1")){ error(BAD_REQUEST, null); throw new Exception("Bad request!"); } }catch(Exception ex){ error(BAD_REQUEST, null); throw ex; } while (true) { String line = StrUtils.nullableString(in.readLine()); if(line==null) break; else if(line.toLowerCase().startsWith("cookie:")){ int k=line.indexOf(' '); if(k>0) processParamsLine(line.substring(k+1), cookies, ';', '=', false); } else if(line.toLowerCase().startsWith("content-length:")){ cl=Integer.parseInt(line.substring(16)); } } if(cl>=0){ char[] buf=new char[cl]; int k=in.read(buf); // Corrected a bug that added trailing zeroes to the resulting line // Now the "copyValueOf" takes care of the number of readed "chars", // not the estimated length in bytes // String line=String.copyValueOf(buf); String line=String.copyValueOf(buf, 0, k); inputStream=new java.io.ByteArrayInputStream(line.getBytes()); if(!line.startsWith("<")) processParamsLine(line, params, '&', '=', true); } else{ while (in.ready()) { String line = StrUtils.nullableString(in.readLine()); if (line==null) break; processParamsLine(line, params, '&', '=', true); } } } private void buildStreams() throws java.io.IOException{ in = new BufferedReader(new InputStreamReader(socket.getInputStream())); os = new BufferedOutputStream(socket.getOutputStream()); pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), RequestProcessor.CHARSET)); } private void buildStreamsDebug(final java.io.PrintStream log) throws java.io.IOException{ in = new BufferedReader(new InputStreamReader(socket.getInputStream())){ @Override public String readLine() throws java.io.IOException{ String result=super.readLine(); if(result!=null){ log.println("< "+result); } return result; } @Override public int read(char buf[]) throws java.io.IOException{ int result=super.read(buf); if(result>0) log.println("< "+new String(buf, 0, result)); return result; } }; os = new BufferedOutputStream(socket.getOutputStream()){ @Override public synchronized void write(byte b[], int off, int len) throws java.io.IOException{ if(b!=null){ log.println("> "+new String(b, off, len)); } super.write(b, off, len); } }; pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), RequestProcessor.CHARSET)){ @Override public void write(String s, int off, int len ){ log.println("> "+s); super.write(s, off, len); } }; } public static void processParamsLine(String txt, Map<String, Object> map, char sep, char equalSign, boolean arrays){ if(txt!=null && txt.length()>0 && map!=null){ StringTokenizer st=new StringTokenizer(txt, String.valueOf(sep)); while(st.hasMoreTokens()){ String s=st.nextToken().trim(); String key, value=null; int k=s.indexOf(equalSign); if(k>0){ key=Html.decode(s.substring(0, k).replace('+', ' ')); if(k<s.length()-1) value=Html.decode(s.substring(k+1).replace('+', ' ')); } else key=Html.decode(s.replace('+', ' ')); if(arrays){ String[] vArray=(String[])map.get(key); if(vArray==null){ vArray=new String[]{value}; } else{ String[] v2=new String[vArray.length+1]; int i=0; for(; i<vArray.length; i++) v2[i]=vArray[i]; v2[i]=value; vArray=v2; } map.put(key, vArray); } else{ map.put(key, value); } } } } public final void error(int code, String msg) throws Exception{ String err; switch(code){ case BAD_REQUEST: err="Bad Request"; break; case MOVED_PERM: err="Moved permanently"; break; case NOT_FOUND: err="File not found"; break; case SERVER_ERROR: err="Server error"; break; default: err="Undefined error"; } head.code=code; head.title=err; head.write(); StringBuilder sb=new StringBuilder(100); sb.append("<B>").append(err).append("</B>\n<BR> <BR>\n"); if(msg!=null) sb.append(msg); minimalPage("ERROR: "+err, sb.substring(0)); } public void redirect(String url) throws Exception{ head.contentType=null; head.code=MOVED_PERM; head.title="Moved permanently"; StringBuilder sb=new StringBuilder(100); sb.append("http://"); String serverAddress=socket.getLocalAddress().getHostAddress(); if(serverAddress==null || "0.0.0.0".equals(serverAddress)) serverAddress=java.net.InetAddress.getLocalHost().getHostAddress(); sb.append(serverAddress); sb.append(":").append(socket.getLocalPort()).append("/").append(url); String fullUrl=sb.substring(0); sb.setLength(0); sb.append("Location: ").append(fullUrl); head.extra=sb.substring(0); head.write(); sb.setLength(0); sb.append("redirected to: <A HREF=\"").append(fullUrl).append("\">").append(fullUrl).append("</A>"); minimalPage("redirect", sb.substring(0)); } public class ResponseHead{ public String contentType; public int code, contentLength; public String title, extra; public boolean cache; public boolean commited; public ResponseHead(){ contentType=MIME_HTML; code=OK; cache=true; commited=false; contentLength=-1; } public void write(){ StringBuilder sb=new StringBuilder(200); sb.append("HTTP/1.0 "); sb.append(code).append(" ").append(title==null ? "OK" : title); pw.println(sb.substring(0)); if(extra!=null) pw.println(extra); if(contentType!=null){ sb.setLength(0); pw.println(sb.append("Content-Type: ").append(contentType).substring(0)); } sb.setLength(0); pw.println(sb.append("Date: ").append(RequestProcessor.httpDate(new java.util.Date())).substring(0)); pw.println("Server: JClicHttpServer 1.0"); if (!cache){ pw.println("Pragma: no-cache");//HTTP/1.0 pw.println("Cache-Control: no-cache");//HTTP/1.1 pw.println("Expires: 0"); } if(cookies.size()>0){ sb.setLength(0); sb.append("Set-Cookie: "); Iterator it=cookies.keySet().iterator(); boolean first=true; while(it.hasNext()){ String key=(String)it.next(); if(!first) sb.append(";"); else first=false; sb.append(key).append("=").append(cookies.get(key)); } pw.println(sb.substring(0)); } if(contentLength>0){ sb.setLength(0); pw.println(sb.append("Content-Length: ").append(contentLength).substring(0)); } pw.println(""); pw.flush(); commited=true; } } public void minimalPage(String title, String text) throws Exception{ StringBuilder sb=new StringBuilder(1024); sb.append("<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n"); sb.append("<HTTP>\n<HEAD>\n"); if(title!=null) sb.append("<TITLE>").append(title).append("</TITLE>\n"); sb.append("</HEAD>\n<BODY>\n"); sb.append("<H2>").append(title).append("</H2>\n"); sb.append(text); sb.append("\n</BODY>\n</HTML>"); pw.println(sb.substring(0)); os.flush(); commited=true; } }