/* * Copyright � 2008, 2010, Oracle and/or its affiliates. All rights reserved */ package com.sun.lwuit.browser; import com.sun.lwuit.html.DocumentInfo; import com.sun.lwuit.html.DocumentRequestHandler; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Enumeration; import java.util.Hashtable; import java.util.Vector; import javax.microedition.io.Connector; import javax.microedition.io.HttpConnection; /** * An implementation of DocumentRequestHandler that handles fetching HTML documents both from HTTP and from the JAR. * This request handler takes care of cookies, redirects and handles both GET and POST requests * * @author Ofir Leitner */ public class HttpRequestHandler implements DocumentRequestHandler { //Hashtable connections = new Hashtable(); /** * A hastable containing all cookies - the table keys are domain names, while the value is another hashtbale containing a pair of cookie name and value. */ static Hashtable cookies = Storage.getCookies(); /** * A hastable containing all history - the table keys are domain names, while the value is a vector containing the visited links. */ static Hashtable visitedLinks = Storage.getHistory(); /** * If true will cache HTML pages, this also means that they will be buffered and read fully and only then passed to HTMLComponent - this can have memory implications. * Also note that for the cached HTMLs to be written Storage.RMS_ENABLED[TYPE_CACHE] should be true */ static boolean CACHE_HTML=false; /** * If true will cache images, this also means that they will be buffered and read fully and only then passed to HTMLComponent - this can have memory implications. * Also note that for the cached images to be written Storage.RMS_ENABLED[TYPE_CACHE] should be true */ static boolean CACHE_IMAGES=true; /** * If true will cache CSS files, this also means that they will be buffered and read fully and only then passed to HTMLComponent - this can have memory implications. * Also note that for the cached CSS files to be written Storage.RMS_ENABLED[TYPE_CACHE] should be true */ static boolean CACHE_CSS=false; /** * Returns the domain string we use to identify visited link. * Note that this may be different than the domain name returned by HttpConnection.getHost * * @param url The link URL * @return The link's domain */ static String getDomainForLinks(String url) { String domain=null; if (url.startsWith("file:")) { return "localhost"; // Just a common name to store local files under } int index=-1; if (url.startsWith("http://")) { index=7; } else if (url.startsWith("https://")) { index=8; } if (index!=-1) { domain=url.substring(index); index=domain.indexOf('/'); if (index!=-1) { domain=domain.substring(0,index); } } return domain; } /** * {@inheritDoc} */ public InputStream resourceRequested(DocumentInfo docInfo) { InputStream is=null; String url=docInfo.getUrl(); String linkDomain=getDomainForLinks(url); // Visited links if (docInfo.getExpectedContentType()==DocumentInfo.TYPE_HTML) { // Only mark base documents as visited links if (linkDomain!=null) { Vector hostVisitedLinks=(Vector)visitedLinks.get(linkDomain); if (hostVisitedLinks==null) { hostVisitedLinks=new Vector(); visitedLinks.put(linkDomain,hostVisitedLinks); } if (!hostVisitedLinks.contains(url)) { hostVisitedLinks.addElement(url); Storage.addHistory(linkDomain, url); } } else { System.out.println("Link domain null for "+url); } } String params=docInfo.getParams(); if ((!docInfo.isPostRequest()) && (params !=null) && (!params.equals(""))) { url=url+"?"+params; } // See if page/image is in the cache // caching will be used only if there are no parameters and no cookies (Since if they are this is probably dynamic content) boolean useCache=false; if (((docInfo.getExpectedContentType()==DocumentInfo.TYPE_HTML) && (CACHE_HTML) && ((params==null) || (params.equals(""))) && (!cookiesExistForDomain(linkDomain) )) || ((docInfo.getExpectedContentType()==DocumentInfo.TYPE_IMAGE) && (CACHE_IMAGES)) || ((docInfo.getExpectedContentType()==DocumentInfo.TYPE_CSS) && (CACHE_CSS))) { useCache=true; InputStream imageIS=Storage.getResourcefromCache(url); if (imageIS!=null) { return imageIS; } } // Handle the file protocol if (url.startsWith("file://")) { return getFileStream(docInfo); } try { HttpConnection hc = (HttpConnection)Connector.open(url); String encoding=null; if (docInfo.isPostRequest()) { encoding="application/x-www-form-urlencoded"; } if (!docInfo.getEncoding().equals(DocumentInfo.ENCODING_ISO)) { encoding=docInfo.getEncoding(); } //hc.setRequestProperty("Accept_Language","en-US"); //String domain=hc.getHost(); // sub.domain.com / sub.domain.co.il String domain=linkDomain; // will return one of the following formats: sub.domain.com / sub.domain.co.il sendCookies(domain, hc); domain=domain.substring(domain.indexOf('.')); // .domain.com / .domain.co.il if (domain.indexOf('.',1)!=-1) { // Make sure that we didn't get just .com - TODO - however note that if the domain was domain.co.il - it can be here .co.il sendCookies(domain, hc); } if (encoding!=null) { hc.setRequestProperty("Content-Type", encoding); } if (docInfo.isPostRequest()) { hc.setRequestMethod(HttpConnection.POST); if (params==null) { params=""; } byte[] paramBuf=params.getBytes(); hc.setRequestProperty("Content-Length", ""+paramBuf.length); OutputStream os=hc.openOutputStream(); os.write(paramBuf); os.close(); //os.flush(); // flush is said to be problematic in some devices, uncomment if it is necessary for your device } String contentTypeStr=hc.getHeaderField("content-type"); if (contentTypeStr!=null) { contentTypeStr=contentTypeStr.toLowerCase(); if (docInfo.getExpectedContentType()==DocumentInfo.TYPE_HTML) { //We perform these checks only for text (i.e. main page), for images/css we just send what the server sends and "hope for the best" if (contentTypeStr!=null) { if ((contentTypeStr.startsWith("text/")) || (contentTypeStr.startsWith("application/xhtml")) || (contentTypeStr.startsWith("application/vnd.wap"))) { docInfo.setExpectedContentType(DocumentInfo.TYPE_HTML); } else if (contentTypeStr.startsWith("image/")) { docInfo.setExpectedContentType(DocumentInfo.TYPE_IMAGE); hc.close(); return getStream("<img src=\""+url+"\">",null); } else { hc.close(); return getStream("Content type "+contentTypeStr+" is not supported.","Error"); } } } if ((docInfo.getExpectedContentType()==DocumentInfo.TYPE_HTML) || (docInfo.getExpectedContentType()==DocumentInfo.TYPE_CSS)) { // Charset is relevant for HTML and CSS only int charsetIndex = contentTypeStr.indexOf("charset="); if (charsetIndex!=-1) { String charset=contentTypeStr.substring(charsetIndex+8); docInfo.setEncoding(charset.trim()); // if ((charset.startsWith("utf-8")) || (charset.startsWith("utf8"))) { //startwith to allow trailing white spaces // docInfo.setEncoding(DocumentInfo.ENCODING_UTF8); // } } } } int i=0; while (hc.getHeaderFieldKey(i)!=null) { if (hc.getHeaderFieldKey(i).equalsIgnoreCase("set-cookie")) { addCookie(hc.getHeaderField(i), url); } i++; } int response=hc.getResponseCode(); if (response/100==3) { // 30x code is redirect String newURL=hc.getHeaderField("Location"); if (newURL!=null) { hc.close(); docInfo.setUrl(newURL); if ((response==302) || (response==303)) { // The "302 Found" and "303 See Other" change the request method to GET docInfo.setPostRequest(false); docInfo.setParams(null); //reset params } return resourceRequested(docInfo); } } is = hc.openInputStream(); if (useCache) { byte[] buf=getBuffer(is); Storage.addResourceToCache(url, buf,false); ByteArrayInputStream bais=new ByteArrayInputStream(buf); is.close(); hc.close(); //all the data is in the buffer return bais; } } catch (SecurityException e) { return getStream("Network access was disallowed for this session. Only local and cached pages can be viewed.<br><br> To browse external sites please exit the application and when asked for network access allow it.", "Security error"); } catch (IOException e) { System.out.println("HttpRequestHandler->IOException: "+e.getMessage()); return getStream("The page could not be loaded due to an I/O error.", "Error"); } catch (IllegalArgumentException e) { // For malformed URL System.out.println("HttpRequestHandler->IllegalArgumentException: "+e.getMessage()); return getStream("The reuqested URL is not valid.", "Malformed URL"); } return is; } /** * Checks if there are cookies stored on the client for the specified domain * * @param domain The domain to check for cookies * @return true if cookies for the specified domain exists, false otherwise */ private boolean cookiesExistForDomain(String domain) { Object obj=cookies.get(domain); //System.out.println("Cookies for domain "+domain+": "+obj); if (obj==null) { int index=domain.indexOf('.'); if (index!=-1) { domain=domain.substring(index); // .domain.com / .domain.co.il if (domain.indexOf('.',1)!=-1) { // Make sure that we didn't get just .com - TODO - however note that if the domain was domain.co.il - it can be here .co.il obj=cookies.get(domain); //System.out.println("Cookies for domain "+domain+": "+obj); } } } return (obj!=null); } /** * Sends the avaiable cookies for the given domain * * @param domain The cookies domain * @param hc The HTTPConnection * @throws IOException */ private void sendCookies(String domain,HttpConnection hc) throws IOException { //System.out.println("Sending cookies for "+domain); Hashtable hostCookies=(Hashtable)cookies.get(domain); String cookieStr=""; if (hostCookies!=null) { for (Enumeration e=hostCookies.keys();e.hasMoreElements();) { String name = (String)e.nextElement(); String value = (String)hostCookies.get(name); String cookie=name+"="+value; if (cookieStr.length()!=0) { cookieStr+="; "; } cookieStr+=cookie; } } if (cookieStr.length()!=0) { //System.out.println("Cookies for domain "+domain+": "+cookieStr); hc.setRequestProperty("cookie", cookieStr); } } /** * Returns an Inputstream of the specified HTML text * * @param htmlText The text to get the stream from * @param title The page's title * @return an Inputstream of the specified HTML text */ private InputStream getStream(String htmlText,String title) { String titleStr=""; if (title!=null) { titleStr="<head><title>"+title+"</title></head>"; } htmlText="<html>"+titleStr+"<body>"+htmlText+"</body></html>"; ByteArrayInputStream bais = new ByteArrayInputStream(htmlText.getBytes()); return bais; } /** * Adds the given cookie to the cookie collection * * @param setCookie The cookie to add * @param hc The HttpConnection */ private void addCookie(String setCookie,String url/*HttpConnection hc*/) { //System.out.println("Adding cookie: "+setCookie); String urlDomain=getDomainForLinks(url); // Determine cookie domain String domain=null; int index=setCookie.indexOf("domain="); if (index!=-1) { domain=setCookie.substring(index+7); index=domain.indexOf(';'); if (index!=-1) { domain=domain.substring(0, index); } if (!urlDomain.endsWith(domain)) { //if (!hc.getHost().endsWith(domain)) { System.out.println("Warning: Cookie tried to set to another domain"); domain=null; } } if (domain==null) { domain=urlDomain; //domain=hc.getHost(); } // Check cookie expiry boolean save=false; index=setCookie.indexOf("expires="); if (index!=-1) { // Cookies without the expires= property are valid only for the current session and as such are not saved to RMS String expire=setCookie.substring(index+8); index=expire.indexOf(';'); if (index!=-1) { expire=expire.substring(0, index); } save=true; } // Get cookie name and value index=setCookie.indexOf(';'); if (index!=-1) { setCookie=setCookie.substring(0, index); } index=setCookie.indexOf('='); String name=setCookie; String value=""; if (index!=-1) { name=setCookie.substring(0, index); value=setCookie.substring(index+1); } Hashtable hostCookies=(Hashtable)cookies.get(domain); if (hostCookies==null) { hostCookies=new Hashtable(); cookies.put(domain,hostCookies); } hostCookies.put(name,value); if (save) { // Note that we save all cookies with expiry specified, while not checking the specific expiry date Storage.addCookie(domain, name, value); } } /** * This method is used when the requested document is a file in the JAR * * @param url The URL of the file * @return An InputStream of the specified file */ private InputStream getFileStream(DocumentInfo docInfo) { String url=docInfo.getUrl(); // If a from was submitted on a local file, just display the parameters if ((docInfo.getParams()!=null) && (!docInfo.getParams().equals(""))) { String method="GET"; if (docInfo.isPostRequest()) { method="POST"; } String params=docInfo.getParams(); String newParams=""; if (params!=null) { for(int i=0;i<params.length();i++) { char c=params.charAt(i); if (c=='&') { newParams+=", "; } else { newParams+=c; } } } return getStream("<h2>Form submitted locally.</h2><b>Method:</b> "+method+"<br><br><b>Parameters:</b><br>"+newParams+"<hr><a href=\""+docInfo.getUrl()+"\">Continue to local URL</a>","Form Results"); } url=url.substring(7); // Cut the file:// int hash=url.indexOf('#'); //trim anchors if (hash!=-1) { url=url.substring(0,hash); } int param=url.indexOf('?'); //trim parameters, not relvant for files if (param!=-1) { url=url.substring(0, param); } // Use the following commented segment for loading HTML files saved with the UTF8 header added by some utils - 0xEF, 0xBB, 0xBF // This is a simple code to skip automatically 3 chars on a certain file suffix (.htm isntead of .html) // A better solution is to detect these bytes, but that requires buffering of the stream (to "unread" if these are not the right chars) /* if (url.endsWith(".htm")) { System.out.println("Notepad UTF - Skipping 3 chars"); docInfo.setEncoding(DocumentInfo.ENCODING_UTF8); // If the UTF8 encoding string doesn't work on your device, try the following instead of the line above: //docInfo.setEncoding("UTF-8"); InputStream is= getClass().getResourceAsStream(url); try { is.read(); is.read(); is.read(); return is; } catch (IOException ex) { ex.printStackTrace(); } } */ return getClass().getResourceAsStream(url); } /** * Reads an inputstream completely and places it into a buffer * * @param is The InputStream to read * @return A buffer containing the stream's contents * @throws IOException */ static byte[] getBuffer(InputStream is) throws IOException { int chunk = 50000; byte[] buf = new byte[chunk]; int i=0; int b = is.read(); while (b!=-1) { if (i>=buf.length) { byte[] tempbuf=new byte[buf.length+chunk]; for (int j=0;j<buf.length;j++) { tempbuf[j]=buf[j]; } buf=tempbuf; } buf[i]=(byte)b; i++; b = is.read(); } byte[] tempbuf=new byte[i]; for (int j=0;j<tempbuf.length;j++) { tempbuf[j]=buf[j]; } buf=tempbuf; return buf; } }