Fix problem with http://server:port not being removed when exact port not specified...
[jgroupdav.git] / src / net / bionicmessage / groupdav / groupDAV.java
blobb64907966022c9244fa2a8550080bd7c5fe18bd9
1 /* BionicMessage.net Java GroupDAV library V1.0
2 * groupDAV.java
4 * Created on February 26, 2006, 1:11 PM
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 * IN THE SOFTWARE.
25 package net.bionicmessage.groupdav;
27 import java.io.*;
28 import java.net.*;
29 import java.nio.ByteBuffer;
30 import net.bionicmessage.extutils.*;
31 import java.util.*;
32 import java.util.logging.*;
33 import javax.xml.parsers.*;
34 import org.xml.sax.InputSource;
35 import org.xml.sax.helpers.DefaultHandler;
36 import javax.net.SocketFactory;
37 import javax.net.ssl.SSLSocketFactory;
38 import org.xml.sax.Attributes;
39 import org.xml.sax.SAXException;
41 /**
42 * A basic GroupDAV implementation
43 * @author <a href="http://bionicmessage.net">Mathew McBride</a>.
45 public class groupDAV {
47 private String u = "";
48 private String p = "";
49 private String host = "";
50 private int po = 2000;
51 private String base64cache = "";
52 private String sdir = "";
53 private static Logger logger =
54 Logger.getLogger(("funambol"));
55 private static ConsoleHandler ch = new ConsoleHandler();
56 DocumentBuilderFactory fdb = DocumentBuilderFactory.newInstance();
57 /* At the moment we only support one set of folders each. */
58 private String vtodoloc = "";
59 private String vcalloc = "";
60 private ArrayList vcallocs = null;
61 private String vaddrloc = "";
62 private SocketFactory ssf = null;
63 private boolean ssl = false;
64 private Socket sock = null;
65 private int offset = 0;
66 private StringBuffer sbuf = null;
67 private Thread th = null;
68 private static final String USER_AGENT = "BionicMessage.net GroupDAV {0.9;Java}";
69 private String tok = "http://";
70 private String origurl = "";
71 private entityFinderHandler storeFinderHandler = null;
72 private entityFinderHandler dirFinderHandler = null;
74 /** For use in situations where something (i.e in the case of
75 * Funambol's authentication) has already given us a HTTP Basic Base64
76 * representation of user:pass
77 * @param url The host and port of the server to connect to, in the
78 * format of http://hostname:port/
79 * @param b64cache A String pre-encoded in Base 64 representing
80 * &quot;Autorization: Basic pass:user&quot;
81 * (Google HTTP Basic authentication for more information)
83 public groupDAV(String url, String b64cache) {
84 origurl = url;
85 if (!origurl.substring(origurl.length() - 1).equals("/")) {
86 origurl = url + "/";
88 if (url.indexOf("https://") != -1) {
89 ssl = true;
90 tok = "https://";
92 url = url.replaceAll(tok, ""); // goodbye http://
93 String hostname = url.split(":")[0];
94 String ports = url.split(":")[1]; // port and url
95 ports = ports.replace("/", "");
96 int port = Integer.valueOf(ports).intValue();
97 host = hostname;
98 po = port;
99 base64cache = "Authorization: Basic " + b64cache;
100 init();
103 /** Constructor using a http://// url, and plain text passwords.
104 * @param url The host and port of the server to connect to, in the
105 * format of http://hostname:port/
106 * @param user The username of the user connecting to the server
107 * @param pass The password of the user connecting to the server
109 public groupDAV(String url, String user, String pass) {
110 origurl = url;
111 if (url.indexOf("https://") != -1) {
112 ssl = true;
113 tok = "https://";
115 url = url.replaceAll(tok, ""); // goodbye http://
116 String hostname = url.split(":")[0];
117 String ports = url.split(":")[1]; // port and url
118 ports = ports.replace("/", "");
119 int port = Integer.valueOf(ports).intValue();
120 host = hostname;
121 po = port;
122 u = user;
123 p = pass;
124 base64cache = "Authorization: Basic " + Base64.encodeBytes(new String(u + ":" + p).getBytes());
125 init();
128 /** Set the logger to output to
129 * @param log The logger instance to use
131 public void setLogger(Logger log) {
132 logger = log;
135 /** Initiate client */
136 public void init() {
137 logger.setLevel(Level.ALL);
138 logger.addHandler(ch);
139 ch.setLevel(Level.ALL);
140 logger.info("GroupDAV client init()");
141 storeFinderHandler = new entityFinderHandler(logger);
142 dirFinderHandler = new entityFinderHandler(logger);
144 ssf = SSLSocketFactory.getDefault();
145 try {
146 sbuf = new StringBuffer();
147 // sock = createSocket();
148 //sock.setKeepAlive(true);
149 // setupReadThread();
150 // th.setDaemon(true);
151 // th.start();
153 } catch (Exception ex) {
154 ex.printStackTrace();
158 /** Send after client initation to discover object stores on the server
159 * @throws Exception When the server fails to supply discovery data */
160 public boolean findStores() throws Exception {
161 byte[] pfind = buildPROPFIND("");
162 String output = sendNonKeepAliveRequest(pfind);
163 int clength = findContentLength(output);
164 if (clength == -1) {
165 logger.warning("No Content-Length in findStores");
167 String xmldata = output.substring(output.length() - clength - 1, output.length());
168 xmldata = xmldata.trim();
169 if (!xmldata.contains("<?xml")) {
170 throw new Exception("No <?xml field in xmldata, dying");
172 logger.finer("Split=" + xmldata);
173 generateEntityList(xmldata, storeFinderHandler);
174 return false;
177 public entityFinderHandler getDirFinderHandler() {
178 return dirFinderHandler;
181 public entityFinderHandler getStoreFinderHandler() {
182 return storeFinderHandler;
185 /** Get a list of store URL's from the server */
186 public Hashtable getURLLocs() {
187 Hashtable locations = new Hashtable();
188 ArrayList list = storeFinderHandler.getObjectList();
189 Hashtable dtypes = storeFinderHandler.getObjectDtypes();
190 Hashtable gtypes = storeFinderHandler.getObjectGtypes();
191 for (int i = 0; i < list.size(); i++) {
192 String url = (String) list.get(0);
193 String davtype = (String) dtypes.get(url);
194 if (davtype.equals("collection")) {
195 String gtype = (String) gtypes.get(url);
196 if (gtype != null) {
197 locations.put(url, gtype);
201 return locations;
204 /** List objects in a store
206 public List listObjects(String url) throws Exception {
207 byte[] pfind = buildPROPFIND(url);
208 String output = sendNonKeepAliveRequest(pfind);
209 int clength = findContentLength(output);
210 if (clength == -1) {
211 logger.warning("We don't have a Content Length field in listObjects");
213 String xmldata = output.toString().split("\r\n\r\n")[1];
214 generateEntityList(xmldata, dirFinderHandler);
215 ArrayList objectList = dirFinderHandler.getObjectList();
216 Hashtable types = dirFinderHandler.getObjectDtypes();
217 ArrayList cleaned = new ArrayList();
218 for (int i = 0; i < objectList.size(); i++) {
219 String lurl = (String) objectList.get(i);
220 String type = (String) types.get(lurl);
221 if (type != null && type.contains("collection")) {
222 /* Don't add this location to the list; it is a collection */
223 } else {
224 cleaned.add(lurl);
227 return cleaned;
230 /** Posts an object */
231 public GroupDAVObject postObject(String subdir, String uid, String contents) throws Exception {
232 uid = uid.trim();
233 String type = "text/calendar";
234 if (contents.indexOf("begin:vcard") != -1 ||
235 contents.indexOf("BEGIN:VCARD") != -1) {
236 type = "text/x-vcard";
238 String[] headers = new String[]{"Content-Type: " + type + "; charset=utf-8",
239 "If-None-Match: *",
240 base64cache
242 byte[] query = buildGroupDAVQuery("PUT", subdir + "new.ics", contents, headers);
243 String q = new String(query);
244 String t = sendNonKeepAliveRequest(query);
245 logger.fine("We got" + t);
246 String firstLine = t.split("\r\n")[0];
247 if (firstLine.indexOf("405") != -1) {
248 throw new Exception("Post error: " + t);
250 GroupDAVObject gobjP = new GroupDAVObject(t, GroupDAVObject.OBJECT_PUT);
251 /* Did the server give us a location? If not, assume the location is exactly
252 * what we gave it */
253 if (gobjP.getLocation() == null) {
254 gobjP.setLocation(origurl + subdir + uid);
255 } else if (gobjP.getLocation() != null) {
256 // String strippedurl = gobjP.getLocation().replace(tok+host+":"+po,"");
257 // gobjP.setLocation(strippedurl);
259 // Did the server give us an eTag with the PUT request or will we have
260 // to fetch the object?
261 if (gobjP.getEtag() == null) {
262 GroupDAVObject gobjG = getObject(gobjP.getLocation());
263 return gobjG;
264 } else {
265 return gobjP;
269 public GroupDAVObject modifyObject(String addr,
270 String contents,
271 String etag) throws Exception {
272 String type = "text/calendar";
273 if (contents.indexOf("begin:vcard") != -1 ||
274 contents.indexOf("BEGIN:VCARD") != -1) {
275 type = "text/x-vcard";
277 // Drop the hostname since we specify it in Host:
278 //addr = addr.replace(origurl, "/");
279 URL objAddr = new URL(addr);
281 String[] header = new String[]{base64cache,
282 "Content-Type: " + type + "; charset=utf-8", "If-Match:" + etag
284 byte[] query = buildGroupDAVQuery("PUT", objAddr.getPath(), contents, header);
285 String q = new String(query);
286 String resp = sendNonKeepAliveRequest(query);
287 logger.finest("We got: " + resp);
288 String firstLine = resp.split("\r\n")[0];
289 if (firstLine.indexOf("405") != -1) {
290 throw new Exception("Modify error:" + resp);
292 GroupDAVObject gobjP = new GroupDAVObject(resp, GroupDAVObject.OBJECT_PUT);
293 /* Did the server give us a location? If not, assume the location is exactly
294 * what we gave it */
295 if (gobjP.getLocation() == null) {
296 gobjP.setLocation(addr);
297 } else if (gobjP.getLocation() != null) {
298 String strippedurl = gobjP.getLocation().replace(tok + host + ":" + po, "");
299 gobjP.setLocation(strippedurl);
301 // Did the server give us an eTag with the PUT request or will we have
302 // to fetch the object?
303 if (gobjP.getEtag() == null) {
304 GroupDAVObject gobjG = getObject(gobjP.getLocation());
305 return gobjG;
306 } else {
307 return gobjP;
311 public GroupDAVObject deleteObject(String addr, String etag) throws Exception {
312 URL netURL = new URL(addr);
313 String[] header = new String[]{base64cache, "If-Match:" + etag};
314 byte[] query = buildGroupDAVQuery("DELETE", netURL.getPath(), null, header);
315 String resp = sendNonKeepAliveRequest(query);
316 logger.finest("We got: " + resp);
317 GroupDAVObject gbo = new GroupDAVObject(resp, GroupDAVObject.OBJECT_KILLED);
318 return gbo;
321 /** Execute a GET command and return a GroupDAVObject */
322 public GroupDAVObject getObject(String url) throws Exception {
323 //url = url.replace(tok + host + ":" + po, "");
324 URL pathURL = new URL(url);
325 String header[] = {base64cache};
326 byte[] send = buildGroupDAVQuery("GET", pathURL.getPath(), null, header);
327 String st = new String(send);
328 String t = sendNonKeepAliveRequest(send);
329 String firstLine = t.split("\r\n")[0];
330 if (firstLine.contains("400")) {
331 throw new Exception("HTTP Exception encountered in getObject: " + firstLine);
333 GroupDAVObject obj = new GroupDAVObject(t, GroupDAVObject.OBJECT_GET);
334 return obj;
337 /** TODO: Do a GroupDAV compliant PROPFIND */
338 public byte[] buildPROPFIND(String subdir) {
339 String gdir = sdir + "/";
340 if (subdir.indexOf(sdir) > -1) {
341 gdir = "";
343 String XMLSTRING = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
344 "<propfind xmlns=\"DAV:\"><allprop/><prop><getetag/></prop></propfind>";
345 // String XMLSTRING = "";
346 String PROPFIND = "PROPFIND " + gdir + subdir + " HTTP/1.1\n" +
347 "Cache-control: no-cache\nPragma: no-cache\nAccept-Language: en\n" +
348 base64cache + "\nContent-Length: " +
349 XMLSTRING.getBytes().length + "\nHost: " + host + ":" + po + "\n" +
350 "Depth: 1\nContent-Type: text/xml;charset=utf-8\nAccept: text/*\n\n" +
351 XMLSTRING;
352 return PROPFIND.getBytes();
355 /* public byte[] buildPROPFINDList(String subdir) {
356 String gdir = sdir + "/";
357 if (subdir.indexOf(sdir) > -1) {
358 gdir = "";
360 String XMLSTRING = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
361 "<propfind xmlns=\"DAV:\">" +
362 "<prop xmlns=\"DAV:\"><allprop xmlns=\"DAV:\"/><getetag xmlns=\"DAV:\"/>" +
363 "</prop></propfind>";
364 String PROPFIND = "PROPFIND " + gdir + subdir + " HTTP/1.1\n" +
365 "Cache-control: no-cache\nPragma: no-cache\nAccept-Language: en\n" +
366 base64cache + "\nContent-Length: " +
367 XMLSTRING.getBytes().length + "\nHost: " + host + ":" + po + "\n" +
368 "Content-Type: text/xml;charset=utf-8\nAccept: text/*\n\n" +
369 XMLSTRING;
370 return PROPFIND.getBytes();
372 } */
374 private void generateEntityList(String xmldata, entityFinderHandler efh) throws Exception {
375 org.apache.xerces.parsers.SAXParser sparser = new org.apache.xerces.parsers.SAXParser();
376 sparser.setFeature("http://xml.org/sax/features/namespaces", true);
377 sparser.setContentHandler(efh);
378 sparser.parse(new InputSource(new StringReader(xmldata)));
381 /** Testing method. Edit source and replace with own
382 * server settings to test client. */
383 public static void main(String args[]) {
384 /* Todo: use GUI to ask for info */
385 String SERVER = "http://your.server.here:80";
386 groupDAV client = new groupDAV(SERVER,
388 "");
389 try {
390 List calobjects = client.listObjects("/groupdav/Calendar/");
391 Map etags = client.getEtags();
392 for (int i = 0; i < calobjects.size(); i++) {
393 String objectu = (String) calobjects.get(i);
394 String eTag = null;
395 if (etags.get(objectu) != null) {
396 eTag = (String) etags.get(objectu);
397 } else {
398 eTag = "not found";
400 GroupDAVObject gobj = client.getObject(objectu);
401 System.err.println(gobj.getContent() + "\n|etag-server=" + eTag);
402 System.err.println("|etag-object=" + gobj.getEtag());
403 String object = gobj.getContent();
406 java.util.Random rd = new java.util.Random();
407 int uid = rd.nextInt(32);
408 String u = "" + uid;
409 String object = "BEGIN:VCALENDAR\n" +
410 "PRODID:-//BM GroupDAV Client//Test Creation Object/EN\n" +
411 "VERSION:2.0\n" +
412 "METHOD:REQUEST\n" +
413 "BEGIN:VEVENT\n" +
414 "SUMMARY:Test object\n" +
415 "LOCATION:In dev slash null\n" +
416 "DESCRIPTION:Please don't fail me! I'm innocent!\n" +
417 "DTSTART:20060408T100059Z\n" +
418 "DTEND:20060408T110059Z\n" +
419 "TRANSP:OPAQUE\n" +
420 "UID:" + u + "\n" +
421 "SEQUENCE:1\n" +
422 "ORGANIZER:MAILTO:matt@comalies\n" +
423 "END:VEVENT\n" +
424 "END:VCALENDAR";
426 // GroupDAVObject gbo = client.postObject(calendarUrl,u,object);
427 //System.out.println("Etag for new object= " + gbo.getEtag());
428 // Test change operation
429 // object.replaceAll("don't","never");
430 // GroupDAVObject gboc = client.modifyObject(gbo.getLocation(),object,gbo.getEtag());
431 // GroupDAVObject gbod = client.deleteObject(gbo.getLocation(),gbo.getEtag());
432 } catch (Exception ex) {
434 ex.printStackTrace();
438 /** Parses the result of PROPFIND */
439 private class entityFinderHandler extends DefaultHandler {
441 Integer element_index;
442 Stack parents = null;
443 Hashtable charBuffers = null;
444 Hashtable element_names = null;
445 Logger lg;
446 String obj_href = ""; // DAV Object href
447 String obj_dtype = ""; // DAV resource type
448 String obj_gtype = ""; // GroupDAV resource type
449 String obj_status = ""; // DAV object status
450 String obj_etag = ""; // DAV Object etag
451 Hashtable object_dtypes = null;
452 Hashtable object_gtypes = null;
453 Hashtable object_etags = null;
454 Hashtable object_statuses = null;
455 ArrayList object_list = null;
457 /** Creates a new instance of entityFinderHandler */
458 public entityFinderHandler(Logger l) {
459 lg = l;
462 public void startDocument() throws SAXException {
463 charBuffers = new Hashtable();
464 element_names = new Hashtable();
465 element_index = -1;
466 parents = new Stack();
467 parents.push(new Integer(element_index));
468 object_dtypes = new Hashtable();
469 object_gtypes = new Hashtable();
470 object_etags = new Hashtable();
471 object_statuses = new Hashtable();
472 object_list = new ArrayList();
475 public void endDocument() throws SAXException {
478 public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
479 ++element_index;
480 int parentNodeIx = ((Integer) parents.peek()).intValue();
481 // this element in turn becomes the parent for subsequent routines
482 Integer key = new Integer(element_index);
483 element_names.put(key, qName);
484 parents.push(key);
485 charBuffers.put(key, new StringBuffer());
486 // etc ...
489 public void endElement(String uri, String localName, String qName) throws SAXException {
490 Integer key = (Integer) parents.pop();
491 StringBuffer charBuffer = (StringBuffer) charBuffers.get(key);
492 if (uri.contains("groupdav.org")) { // GroupDAV namespace
493 if (localName.contains("vevent-collection") ||
494 localName.contains("vtodo-collection") ||
495 localName.contains("vcard-collection")) {
496 obj_gtype = localName;
498 } else if (uri.contains("DAV:")) { // DAV namespace
499 if (localName.contains("collection")) {
500 obj_dtype = localName;
501 } else if (localName.contains("status")) {
502 obj_status = charBuffer.toString();
503 } else if (localName.contains("href")) {
504 obj_href = charBuffer.toString();
505 } else if (localName.contains("getetag")) {
506 obj_etag = charBuffer.toString();
509 if (uri.contains("DAV:") && localName.contains("response")) {
510 // Stuff all the known data about the object into the collections
511 object_list.add(obj_href);
512 object_etags.put(obj_href, obj_etag);
513 object_statuses.put(obj_href, obj_status);
514 object_dtypes.put(obj_href, obj_dtype);
515 object_gtypes.put(obj_href, obj_gtype);
517 /* Reset ready for the next one */
518 if (localName.contains("response")) {
519 obj_href = "";
520 obj_dtype = "";
521 obj_gtype = "";
522 obj_status = "";
523 obj_etag = "";
527 public void characters(char[] ch, int start, int length) throws SAXException {
528 String chars = new String(ch, start, length);
529 if (chars.trim().length() > 0) {
530 Integer parent = (Integer) parents.peek();
531 StringBuffer charBuffer = (StringBuffer) charBuffers.get(parent);
532 charBuffer.append(chars);
536 public ArrayList getObjectList() {
537 return object_list;
540 public Hashtable getObjectEtags() {
541 return object_etags;
544 public Hashtable getObjectStatuses() {
545 return object_statuses;
548 public Hashtable getObjectDtypes() {
549 return object_dtypes;
552 public Hashtable getObjectGtypes() {
553 return object_gtypes;
557 /** Create a socket to the host */
558 private Socket createSocket() throws Exception {
559 Socket st = null;
560 if (ssl) {
561 st = ssf.createSocket(host, po);
562 } else {
563 st = new Socket(host, po);
565 return st;
568 private String readChars() throws Exception {
569 th.sleep(sock.getSoTimeout());
570 String chars = sbuf.toString();
571 // clear buffer
572 sbuf = new StringBuffer();
573 return chars;
576 private int findContentLength(String data) {
577 String[] headers = data.split("\n");
578 for (int i = 0; i < headers.length; i++) {
579 String curHeader = headers[i];
580 String[] line = curHeader.split(":");
581 if (line[0].equalsIgnoreCase("Content-Length")) {
582 Integer it = new Integer(line[1].trim());
583 return it.intValue();
586 return -1;
589 private String sendNonKeepAliveRequest(byte[] by) throws Exception {
590 String dbugstring = new String(by);
591 logger.fine(dbugstring);
592 String loggableString = dbugstring.replace(base64cache, "Authorization: Basic [removed]");
593 logger.finer("We sent:\r\n" + loggableString);
594 // Measure latency
595 long time = new java.util.Date().getTime();
596 Socket st = createSocket();
597 st.getOutputStream().write(by);
598 st.getOutputStream().flush();
599 InputStream sc = st.getInputStream();
600 BufferedInputStream bis = new BufferedInputStream(sc);
602 StringBuffer sb = new StringBuffer();
603 int op = 0;
604 int fouls = 0;
606 // We need to process the message character by chacter instead of byte by byte
607 // or otherwise 2-4 byte characters (many Unicode characters) get stripped away.
608 // To do this, we use the InputStreamReader class to convert byte stream into
609 // an Unicode character stream.
610 BufferedReader charstream = new BufferedReader(new InputStreamReader(bis, "UTF-8"));
612 while (op != -1) {
613 char[] buf = new char[300];
614 op = charstream.read(buf);
615 if (op != 0 && op != -1) {
616 for (int i = 0; i < op; i++) {
617 char c = buf[i];
618 // Strip bytes which would foul an XML parser, i.e SIF
619 // for Funambol upstream. See
620 // http://cse-mjmcl.cse.bris.ac.uk/blog/2007/02/14/1171465494443.html
621 if ((c == 0x9) ||
622 (c == 0xA) ||
623 (c == 0xD) ||
624 ((c >= 0x20) && (c <= 0xD7FF)) ||
625 ((c >= 0xE000) && (c <= 0xFFFD)) ||
626 ((c >= 0x10000) && (c <= 0x10FFFF))) {
627 sb.append(c);
628 } else {
629 fouls++;
635 if (fouls > 0) {
636 logger.fine(fouls + " invalid (XML-wise) characters detected in stream. Is the source/users clean?");
638 charstream.close();
639 bis.close();
640 long closetime = new java.util.Date().getTime();
641 long rtt = closetime - time;
642 logger.finer("We got:\r\n" + sb.toString() + "\r\n....in " + rtt + "ms");
643 return sb.toString();
646 /** Returns a list of HTTP/DAV etags. Object URL's are the key in the Map.
647 * If the DAV PROPFIND did not return etag properties in its result
648 * an attempt to get() a etag will return null as per Map implemementation rules
650 public Map getEtags() {
651 return dirFinderHandler.getObjectEtags();
654 private byte[] buildGroupDAVQuery(String method, String addr, String contents,
655 String[] headers) {
656 String query = method + " " + addr + " HTTP/1.1";
657 for (int i = 0; i <
658 headers.length; i++) {
659 query += "\n" + headers[i];
662 if (contents != null) {
663 query += "\nContent-Length: " + contents.getBytes().length;
664 } else {
665 query += "\nContent-Length: 0";
668 query += "\nUser-Agent: " + USER_AGENT + "\nHost: " + host + ":" + po +
669 "\n\n";
670 // "Host: " + host + "\n\n\n";
671 if (contents != null) {
672 query += contents;
673 } else {
674 query += "\n\n";
677 return query.getBytes();