/* Copyright 2005-2006 by data2c.com
Authors:
Wolfgang S. Kechel - wolfgang.kechel@data2c.com
Jörn Marcks - joern.marcks@data2c.com
Wolfgang S. Kechel, Jörn Marcks
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package org.hecl.net;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.Hashtable;
//#ifdef j2me
import javax.microedition.io.Connector;
import javax.microedition.io.HttpConnection;
//#if cldc > 1.0
import javax.microedition.io.HttpsConnection;
//#endif
//#else
import java.net.HttpURLConnection;
import java.net.URL;
import javax.net.ssl.HttpsURLConnection;
//#endif
public class HttpRequest extends Thread {
static public final short SETUP = 0;
static public final short CONNECTED = 1;
static public final short ERROR = 2;
static public final short TIMEOUT = 3;
static public final short OK = 4;
//#ifdef j2me
public static final int HTTP_UNAUTHORIZED = HttpConnection.HTTP_UNAUTHORIZED;
public static final int HTTP_FORBIDDEN = HttpConnection.HTTP_FORBIDDEN;
//#else
public static final int HTTP_UNAUTHORIZED = HttpURLConnection.HTTP_UNAUTHORIZED;
public static final int HTTP_FORBIDDEN = HttpURLConnection.HTTP_FORBIDDEN;
public static final String GET = "GET";
public static final String POST ="POST";
public static final String HEAD = "HEAD";
public static final String OPTIONS = "OPTIONS";
public static final String PUT = "PUT";
public static final String DELETE = "DELETE";
public static final String TRACE = "TRACE";
//#endif
public HttpRequest(String url, QueryParam[] params,
boolean validate, Hashtable headerfields) {
qparams = params;
setup(url,validate,headerfields);
}
public HttpRequest(String url, String queryData,
boolean validate, Hashtable headerfields) {
qdata = queryData;
setup(url,validate,headerfields);
}
private void setup(String url, boolean validate, Hashtable headerfields) {
urlstr = url;
if(headerfields != null) {
Enumeration e = headerfields.keys();
while(e.hasMoreElements()) {
String key = (String)e.nextElement();
requestFields.put(key,(String)headerfields.get(key));
}
}
if(qdata != null || qparams != null) {
requestMethod =
//#ifdef j2me
HttpConnection.POST
//#else
POST
//#endif
;
} else {
if(validate)
requestMethod =
//#ifdef j2me
HttpConnection.HEAD
//#else
HEAD
//#endif
;
}
}
// Add a header field with key and value when sending a request
// Must be called before the request.
public void addRequestField(String key, String value) {
requestFields.put(key, value);
}
// Get the value of a key after the connection is closed.
// Must be called after the request.
public String getResponseFieldValue(String key) {
return (String)responseFields.get(key);
}
public Enumeration getResponseFieldNames() {
return responseFields.keys();
}
public String getURL() {
return urlstr;
}
public static String hexdump(byte[] buf) {
StringBuffer sb = new StringBuffer();
for(int i=0; i<buf.length; ++i) {
byte b = buf[i];
sb.append(Integer.toHexString((b&0xf0)>>4));
sb.append(Integer.toHexString(b&0x0f));
sb.append(' ');
if(i!=0 && ((i+1)%8) == 0)
sb.append(' ');
if(i != 0 && ((i+1)%16) == 0)
sb.append('\n');
}
if(sb.charAt(sb.length()-1) != '\n')
sb.append('\n');
return new String(sb);
}
public synchronized void run() {
MyHttpConn co = null;
rc = -1;
error = null;
inData = null;
try {
co = new MyHttpConn(urlstr);
}
catch (IOException e) {
status = ERROR;
error = e;
e.printStackTrace();
System.err.println("HttpRequest.preparation error");
return;
}
try {
if(DEBUGRC)
System.err.println("Connecting...");
co.connect(requestMethod,requestFields,qdata,qparams);
status = CONNECTED;
if(DEBUGRC)
System.err.println("Connected");
rc = co.getResponseCode();
if(DEBUGRC)
System.err.println("rc="+rc);
if(rc == -1) {
status = ERROR;
return;
}
responseFields = co.readHeader();
inData = co.readBody();
status = OK;
// If no charset is given, use the default (see below).
String charset = DEFCHARSET;
String ct = (String)responseFields.get(CONTENTTYPE);
String coding = (String)responseFields.get(CONTENTENCODING);
if(ct == null) {
// should be present, but who nows...
ct = "text/plain";
responseFields.put(CONTENTTYPE,ct);
}
if((ct != null && !ct.toLowerCase().startsWith("text"))
|| (coding != null &&
(coding.indexOf("gzip") >= 0 || coding.indexOf("compress") >= 0))
) {
// binary transfer
responseFields.put("binary","1");
} else {
// textual transfer
responseFields.put("binary","0");
if(ct != null) {
int begin = ct.toLowerCase().indexOf("charset=");
if (begin >= 0) {
// In a midlet, an empty encoding string would result
// in an UnsupportedEncodingException when creating a
// string object.
begin += 8; // # of chars in 'charset='
int end = ct.indexOf(';', begin);
if (end == -1) {
end = ct.length();
}
charset = ct.substring(begin, end);
//System.err.println("charset in reply="+charset);
}
}
}
//System.err.println("charset now="+charset +", isocharset="+isISOCharset(charset));
// charset is now detected, create a string holding the result.
if(charset == DEFCHARSET || isISOCharset(charset)) {
//System.err.println("internal ISO 8859 decode");
body = bytesToString(inData,0,inData.length);
//System.err.println("ISO 8859 decode done");
charset = DEFCHARSET;
responseFields.put("charset",charset);
} else {
for(int i=0; i<3; ++i) {
switch(i) {
case 0:
break;
case 1:
charset = charset.toLowerCase();
break;
case 2:
charset = charset.toUpperCase();
break;
}
responseFields.put("charset",charset);
try {
//System.err.println("trying to decode with charset="+charset);
body = new String(inData,charset);
//System.err.println("decode success");
break;
}
catch(Exception e2) {
body = "xxx-encoding-failed-xxx\n"+e2.getMessage();
}
}
//System.err.println("decode bytelen="+inData.length);
//System.err.println("decode strlen="+body.length());
}
}
catch (Exception e) {
status = ERROR;
error = e;
e.printStackTrace();
}
if(DEBUGBODY)
System.err.println(getBody());
if(co != null) {
co.close();
}
// no longer needed
inData = null;
}
public static byte[] asISOBytes(String s) {
byte[] buf = new byte[s.length()];
for(int i=0; i<s.length(); ++i) {
char ch = s.charAt(i);
buf[i] = (byte)ch;
}
return buf;
}
public static String bytesToString(byte[] buf) {
return bytesToString(buf,0,buf.length);
}
public static String bytesToString(byte[] buf,int start,int n) {
return bytesToStringBuffer(buf,start,n).toString();
}
public static StringBuffer bytesToStringBuffer(byte[] buf) {
return bytesToStringBuffer(buf,0,buf.length);
}
public static StringBuffer bytesToStringBuffer(byte[] buf,int start,int n) {
StringBuffer sb = new StringBuffer(buf.length);
//System.err.println("old data:");
//System.err.println(hexdump(buf));
for(int i=start; n>0; ++i, --n) {
sb.append((char)buf[i]);
}
//System.err.println("new data:");
//System.err.println(hexdump(asISOBytes(sb.toString())));
return sb;
}
public int getStatus() {
return status;
}
public Exception getException() {
return error;
}
public String getBody() {
return body;
}
//#ifdef notdef
public byte[] getBytes() {
return inData;
}
//#endif
public int getRC() {
return rc;
}
public static String getStatusText(int status) {
switch(status) {
case SETUP:
return "setup";
case CONNECTED:
return "connected";
case ERROR:
return "error";
case TIMEOUT:
return "timeout";
case OK:
return "ok";
default:
return "unknown";
}
}
public static byte[] IRIencode(String str) {
int strlen = str.length();
int utflen = 0;
char[] charr = new char[strlen];
int c, count = 0;
str.getChars(0, strlen, charr, 0);
for (int i = 0; i < strlen; i++) {
c = charr[i];
if ((c >= 0x0001) && (c <= 0x007F)) {
utflen++;
} else if (c > 0x07FF) {
utflen += 3;
} else {
utflen += 2;
}
}
byte[] bytearr = new byte[utflen];
for (int i = 0; i < strlen; i++) {
c = charr[i];
if ((c >= 0x0001) && (c <= 0x007F)) {
bytearr[count++] = (byte)c;
} else if (c > 0x07FF) {
bytearr[count++] = (byte)(0xE0 | ((c >> 12) & 0x0F));
bytearr[count++] = (byte)(0x80 | ((c >> 6) & 0x3F));
bytearr[count++] = (byte)(0x80 | ((c >> 0) & 0x3F));
} else {
bytearr[count++] = (byte)(0xC0 | ((c >> 6) & 0x1F));
bytearr[count++] = (byte)(0x80 | ((c >> 0) & 0x3F));
}
}
return bytearr;
}
public static String urlencode(byte[] s,int start, int n) {
StringBuffer b = new StringBuffer();
for(int i = start; n>0; ++i, --n) {
b.append(urlencodemap[s[i] & 0xff]);
}
return b.toString();
}
public static String urlencode(byte[] s) {
return urlencode(s,0,s.length);
}
public static String urlencode(String[] elems) {
if(elems == null || elems.length == 0)
return null;
StringBuffer b = new StringBuffer();
if(elems != null) {
for(int i = 0; i < elems.length; ++i) {
if(i > 0) {
b.append((i % 2) != 0 ? '=' : '&');
}
b.append(urlencode(IRIencode(elems[i])));
}
}
return b.toString();
}
private boolean isISOCharset(String charset) {
String lower = charset.toLowerCase();
for(int i=0; i<ISOALIASES.length; ++i) {
if(lower.equals(ISOALIASES[i]))
return true;
}
return false;
}
private String urlstr = null;
private byte[] inData = null;
private String body = "";
private String qdata = null;
private QueryParam[] qparams = null;
private String requestMethod =
//#ifdef j2me
HttpConnection.GET
//#else
GET
//#endif
;
private int rc = -1;
private short status = SETUP;
Exception error = null;
private Hashtable requestFields = new Hashtable();
private Hashtable responseFields = new Hashtable();
private static String[] urlencodemap = new String[256];
private static String validUrlChars =
"-_.!~*'()\"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
private static final char[] hexchars = (new String("0123456789ABCDEF")).toCharArray();
public static boolean DEBUGURL = false;
public static boolean DEBUGRC = false;
public static boolean DEBUGBODY = false;
public static String DEFCHARSET = "ISO8859-1";
public static final String CONTENTTYPE = "content-type";
public static final String CONTENTENCODING = "content-encoding";
static private final String ISOALIASES[]= {
"iso-8859-1","iso8859-1","iso8859_1","iso_8859_1","iso-8859_1","iso_8859-1"
};
static {
char[] cbuf = new char[3];
for(int i = 0; i < 256; i++) {
char ch = (char)i;
int idx = validUrlChars.indexOf(ch);
if(idx >= 0) {
urlencodemap[i] = validUrlChars.substring(idx, idx + 1);
} else {
// !!! Do not use
// urlencodemap[i] = "%" + Integer.toHexString(i);
// since it does not print leading 0s
cbuf[0] = '%';
cbuf[1] = hexchars[(i&0xf0)>>4];
cbuf[2] = hexchars[i&0x0f];
urlencodemap[i] = new String(cbuf);
}
}
urlencodemap[' '] = "+";
//urlencodemap['\n'] = "%0D%0A";
}
}
class MyHttpConn {
MyHttpConn(String url) throws IOException {
is = null;
os = null;
conn = null;
//#if javaversion >= 1.5 || cldc > 1.0
secure = url.toLowerCase().startsWith("https");
//#endif
if(HttpRequest.DEBUGURL)
System.err.println("url="+url);
//#ifdef j2me
//#if cldc > 1.0
conn = secure ?
(HttpsConnection)
Connector.open(url/*,Connector.READ_WRITE, true*/) :
(HttpConnection)
Connector.open(url/*,Connector.READ_WRITE, true*/);
//#else
conn = (HttpConnection)
Connector.open(url/*,Connector.READ_WRITE, true*/);
//#endif
//#else
URL myurl = new URL(url);
conn = secure ? (HttpsURLConnection) myurl.openConnection() :
(HttpURLConnection)myurl.openConnection();
//#endif
}
void connect(String rm, Hashtable rfields,
String qdata,QueryParam[] qparams)
throws IOException {
conn.setRequestMethod(rm);
// Set the request fields.
Enumeration e = rfields.keys();
//System.err.println("--- REQUEST -------------------------------------");
while (e.hasMoreElements()) {
String key = (String)e.nextElement();
//System.err.println("key: " + key + ", value: " + (String)rfields.get(key));
conn.setRequestProperty(key, (String)rfields.get(key));
}
//#ifndef j2me
if (qdata != null || qparams != null) {
conn.setDoOutput(true);
}
// Only JDK: Calling connect will open the connection
conn.connect();
//#endif
if (qdata != null || qparams != null) {
//#ifdef j2me
os = conn.openOutputStream();
//#else
os = conn.getOutputStream();
//#endif
if(qdata != null) {
//System.err.println("writing " + qdata.getBytes(DEFCHARSET).length + " bytes");
os.write(qdata.getBytes(/*DEFCHARSET*/));
} else if (qparams != null) {
for(int i=0; i<qparams.length; ++i) {
//System.err.print("qparams["+i+"]: ");
//qparams[i].printon(System.err).println("");
if(i != 0)
os.write('&');
qparams[i].send(os);
}
}
os.flush();
os.close();
os = null;
}
}
int getResponseCode() {
try {
// Only MIDP:
// Getting the response code will open the connection,
// send the request, and read the HTTP response headers.
// The headers are stored until requested.
return conn.getResponseCode();
}
catch(IOException e) {
e.printStackTrace();
}
return -1;
}
Hashtable readHeader() {
Hashtable tab = new Hashtable();
// Getting the response fields.
int idx = 0;
// Some implementations may treat the 0th header field as special,
// i.e. as the status line returned by the HTTP server.
// In this case, getHeaderField(0) returns the status line,
// but getHeaderFieldKey(0) returns null.
// For now, it is not clear if this happens on midlets as well.
//#ifndef j2me
if (conn.getHeaderFieldKey(0) == null) {
++idx;
}
//#endif
//System.err.println("--- RESPONSE HEADER-----------------");
String key = "";
while (key != null) {
//#ifdef j2me
try {
//#endif
key = conn.getHeaderFieldKey(idx++);
if (key != null) {
tab.put(key.toLowerCase(), conn.getHeaderField(key));
//System.err.println("key: " + key + ", value: " + conn.getHeaderField(key));
}
//#ifdef j2me
}
catch (IOException shouldnothappen) {
shouldnothappen.printStackTrace();
}
//#endif
}
return tab;
}
byte[] readBody() throws IOException {
int len = 0;
byte[] buf = new byte[0];
//#ifdef j2me
is = conn.openInputStream();
len = (int)conn.getLength();
//#else
is = conn.getInputStream();
len = conn.getContentLength();
//#endif
int bytesread = 0;
int actual = 0;
if (len >= 0) {
buf = new byte[len];
while ((bytesread != len) && (actual != -1)) {
actual = is.read(buf, bytesread, len - bytesread);
bytesread += actual;
}
} else {
buf = new byte[512];
do {
if(bytesread == buf.length) {
byte[] newbuf = new byte[buf.length+512];
System.arraycopy(buf,0,newbuf,0,bytesread);
buf = newbuf;
}
actual = is.read(buf, bytesread, buf.length - bytesread);
if(actual > 0)
bytesread += actual;
} while(actual > 0);
}
if(bytesread != buf.length) {
byte[] tmp = new byte[buf.length];
System.arraycopy(buf,0,tmp,0,bytesread);
buf = tmp;
}
return buf;
}
void close() {
if (os != null) {
try {os.close();}
catch (IOException e) {}
}
if (is != null) {
try {is.close();}
catch (IOException e) {}
}
if (conn != null) {
//#ifdef j2me
try {
//#endif
//#ifndef j2me
conn.disconnect();
//#else
conn.close();
//#endif
//#ifdef j2me
}
catch (IOException e) {}
//#endif
}
}
//#ifdef j2me
HttpConnection conn;
//#else
HttpURLConnection conn;
//#endif
boolean secure = false;
InputStream is;
OutputStream os;
}