View Javadoc
1   //
2   // $Id: FileUploadThreadHTTP.java 488 2008-07-06 20:21:43Z etienne_sf $
3   //
4   // jupload - A file upload applet.
5   // Copyright 2007 The JUpload Team
6   //
7   // Created: 2007-03-07
8   // Creator: etienne_sf
9   // Last modified: $Date: 2008-07-06 22:21:43 +0200 (dim., 06 juil. 2008) $
10  //
11  // This program is free software; you can redistribute it and/or modify it under
12  // the terms of the GNU General Public License as published by the Free Software
13  // Foundation; either version 2 of the License, or (at your option) any later
14  // version. This program is distributed in the hope that it will be useful, but
15  // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16  // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17  // details. You should have received a copy of the GNU General Public License
18  // along with this program; if not, write to the Free Software Foundation, Inc.,
19  // 675 Mass Ave, Cambridge, MA 02139, USA.
20  
21  package wjhk.jupload2.upload.helper;
22  
23  import java.io.BufferedOutputStream;
24  import java.io.DataOutputStream;
25  import java.io.IOException;
26  import java.io.OutputStream;
27  import java.io.PushbackInputStream;
28  import java.net.Proxy;
29  import java.net.ProxySelector;
30  import java.net.Socket;
31  import java.net.URISyntaxException;
32  import java.net.URL;
33  import java.security.KeyManagementException;
34  import java.security.KeyStoreException;
35  import java.security.NoSuchAlgorithmException;
36  import java.security.UnrecoverableKeyException;
37  import java.security.cert.CertificateException;
38  
39  import wjhk.jupload2.exception.JUploadException;
40  import wjhk.jupload2.exception.JUploadIOException;
41  import wjhk.jupload2.policies.UploadPolicy;
42  
43  /**
44   * This class contains utilities to delegate network manipulation. It hides the management for the current upload policy
45   * connection parameters.<BR>
46   * This class goes through the following states, stored in the private connectionStatus attribute: <DIR> <LI>
47   * STATUS_NOT_INITIALIZED: default status when the instance in created. Only available action:
48   * {@link #initRequest(URL, String, boolean, boolean)} <LI>STATUS_BEFORE_SERVER_CONNECTION: the instance is initialized,
49   * and the caller may begin writing the request to this HttpConnectionHelper. All data written to it, will be stored in
50   * a {@link ByteArrayEncoderHTTP}. The connection switches to this status when the
51   * {@link #initRequest(URL, String, boolean, boolean)} is called. <LI>STATUS_WRITING_REQUEST: The network connection to
52   * the server is now opened. The content of the ByteArrayEncoderHTTP has been sent to the server. All subsequent calls
53   * to write methods will directly write on the socket to the server. The {@link #sendRequest()} method changes the
54   * connection to this status. <LI>STATUS_READING_RESPONSE: The request to the server has been totally written. No more
55   * calls to the write methods are allowed. The {@link #readHttpResponse()} is responsible to put the
56   * HttpConnectionHelper to this status. <LI>STATUS_CONNECTION_CLOSED: The response has been read. All getters can be
57   * called, to get information about the server response. The only other method allowed is the
58   * {@link #initRequest(URL, String, boolean, boolean)}, to start a new request to the server. Using the same
59   * HttpConnectionHelper allows to use the same network connection, when the allowHttpPersistent applet parameter is
60   * used. </DIR>
61   * 
62   * @author etienne_sf
63   */
64  public class HTTPConnectionHelper extends OutputStream {
65  
66      // ////////////////////////////////////////////////////////////////////////////////////
67      // /////////////////// PRIVATE CONSTANTS
68      // ////////////////////////////////////////////////////////////////////////////////////
69      /**
70       * Indicates that the connection has not been initialized. The only authorized action in this state is a call to
71       * {@link #initRequest(URL, String, boolean, boolean)}.
72       */
73      final static private int STATUS_NOT_INITIALIZED = 0;
74  
75      /**
76       * Indicates that the network connection to the server has not been opened. All data sent to the
77       * HttpConnectionHelper with write methods are sent to the current ByteArrayEncoder.
78       */
79      final static private int STATUS_BEFORE_SERVER_CONNECTION = 1;
80  
81      /**
82       * Indicates that the network connection to the server is opened, but the request has not been totally sent to the
83       * server. All data sent to the HttpConnectionHelper with write methods is sent to the network connection, that is:
84       * to the current OutputStream. <BR>
85       * That is: the ByteArrayEncoder is now read only (closed).
86       */
87      final static private int STATUS_WRITING_REQUEST = 2;
88  
89      /**
90       * Indicates that the network connection to the server is opened, but the request has not been totally sent to the
91       * server. All data sent to the HttpConnectionHelper with write methods is sent to the network connection, that is:
92       * to the current OutputStream. <BR>
93       * That is: the ByteArrayEncoder is now read only (closed).
94       */
95      final static private int STATUS_READING_RESPONSE = 3;
96  
97      /**
98       * Indicates that the network connection to the server is now closed, that is: we've written the request, read the
99       * response, and free the server connection. If the keepAlive parameter is used, the connection may remain opened
100      * for the next request. <BR>
101      * No more action may be done on this connection helper, out of reading data, until the application do a call to
102      * {@link #initRequest(URL, String, boolean, boolean)}.
103      */
104     final static private int STATUS_CONNECTION_CLOSED = 4;
105 
106     // ////////////////////////////////////////////////////////////////////////////////////
107     // /////////////////// ATTRIBUTE USED TO CONTROL THE OUTPUT TO THE SERVER
108     // ////////////////////////////////////////////////////////////////////////////////////
109     /**
110      * http boundary, for the posting multipart post.
111      */
112     private String boundary = calculateRandomBoundary();
113 
114     /**
115      * Is chunk upload on for this request ?
116      */
117     private boolean bChunkEnabled;
118 
119     /**
120      * Is it the last chunk ? If yes, we'll try to keep the connection open, according to the current applet
121      * configuration.
122      */
123     private boolean bLastChunk;
124 
125     /**
126      * The encoder that will contain the HTTP request.
127      */
128     private ByteArrayEncoder byteArrayEncoder = null;
129 
130     /**
131      * Indicates where data sent to appendXxx method should be added. It must be one of the SENDING_XXX private strings.
132      */
133     private int connectionStatus = STATUS_NOT_INITIALIZED;
134 
135     /**
136      * Contains the HTTP reader. All data coming from the server response are read from it. If this attribute is null,
137      * it means that the server response has not been read.
138      */
139     private HTTPInputStreamReader httpInputStreamReader = null;
140 
141     /**
142      * This stream allows the applet to get the server response. It is opened and closed as the {@link #outputStream}.
143      */
144     private PushbackInputStream inputStream = null;
145 
146     /**
147      * The HTTP method: POST, GET, HEAD...
148      */
149     private String method = null;
150 
151     /**
152      * This stream is open by {@link #sendRequest()}. It is closed by the {@link #readHttpResponse()} method.
153      * 
154      * @see #sendRequest()
155      * @see #readHttpResponse()
156      * @see #getOutputStream()
157      */
158     private DataOutputStream outputStream = null;
159 
160     /**
161      * The current proxy, if any
162      */
163     private Proxy proxy = null;
164 
165     /**
166      * The network socket where the bytes should be written.
167      */
168     private Socket socket = null;
169 
170     /**
171      * The current upload policy
172      */
173     private UploadPolicy uploadPolicy = null;
174 
175     /**
176      * The current URL
177      */
178     private URL url = null;
179 
180     /**
181      * Should we use a proxy
182      */
183     private boolean useProxy;
184 
185     /**
186      * Is SSL mode on ?
187      */
188     private boolean useSSL;
189 
190     // ////////////////////////////////////////////////////////////////////////////////////
191     // /////////////////////// PUBLIC METHODS
192     // ////////////////////////////////////////////////////////////////////////////////////
193 
194     /**
195      * The standard constructor for this class.
196      * 
197      * @param uploadPolicy The current upload policy.
198      */
199     public HTTPConnectionHelper(UploadPolicy uploadPolicy) {
200         this.uploadPolicy = uploadPolicy;
201     }
202 
203     /**
204      * The standard constructor for this class.
205      * 
206      * @param url The target URL
207      * @param method The HTTP method (POST, GET, HEAD...)
208      * @param bChunkEnabled Indicates if chunkUpload is enabled for this query. Put false, if non chunked request or if
209      *            it is not relevant.
210      * @param bLastChunk Indicates whether this chunk is the last one. Put true, if non chunked request or if it is not
211      *            relevant.
212      * @param uploadPolicy The current upload policy.
213      * @throws JUploadIOException
214      */
215     public HTTPConnectionHelper(URL url, String method, boolean bChunkEnabled, boolean bLastChunk,
216             UploadPolicy uploadPolicy) throws JUploadIOException {
217         this.uploadPolicy = uploadPolicy;
218         initRequest(url, method, bChunkEnabled, bLastChunk);
219     }
220 
221     /**
222      * The standard constructor for this class.
223      * 
224      * @param url The target URL
225      * @param bChunkEnabled Indicates if chunkUpload is enabled for this query. Put false, if non chunked request or if
226      *            it is not relevant.
227      * @param method The HTTP method (POST, GET, HEAD...)
228      * @param bLastChunk Indicates whether this chunk is the last one. Put true, if non chunked request or if it is not
229      *            relevant.
230      * @throws JUploadIOException
231      */
232     public synchronized void initRequest(URL url, String method, boolean bChunkEnabled, boolean bLastChunk)
233             throws JUploadIOException {
234         // This method expects that the connection has not been initialized yet,
235         // or that the previous request is finished.
236         if (this.connectionStatus != STATUS_NOT_INITIALIZED && this.connectionStatus != STATUS_CONNECTION_CLOSED) {
237             throw new JUploadIOException("Bad status of the HttpConnectionHelper in initRequest: " + getStatusLabel());
238         }
239 
240         // Clean any current request.
241         if (isKeepAlive()) {
242             dispose();
243         }
244 
245         // Load the new parameters.
246         this.url = url;
247         this.method = method;
248         this.bChunkEnabled = bChunkEnabled;
249         this.bLastChunk = bLastChunk;
250         // We will write to the local ByteArrayEncoder, until a connection to
251         // the server is opened.
252         initByteArrayEncoder();
253 
254         // Ok, the HttpConnectionHelper is now ready to get write commands.
255         this.connectionStatus = STATUS_BEFORE_SERVER_CONNECTION;
256     }
257 
258     /**
259      * Return the current {@link ByteArrayEncoder}. If it was not created, it is initialized.
260      * 
261      * @return The current {@link ByteArrayEncoder}, null if called before the first initialization.
262      * @throws JUploadIOException
263      * @see #initRequest(URL, String, boolean, boolean)
264      */
265     public ByteArrayEncoder getByteArrayEncoder() throws JUploadIOException {
266         return this.byteArrayEncoder;
267     }
268 
269     /**
270      * @return Returns the boundary for this HTTP request.
271      */
272     public String getBoundary() {
273         return this.boundary;
274     }
275 
276     /**
277      * Closes the byteArrayEncoder, create the socket (or not, depending on the current uploadPolicy, and upload
278      * history), send the request, and create the InputStream to read the server response.
279      * 
280      * @throws JUploadIOException
281      */
282     public synchronized void sendRequest() throws JUploadIOException {
283         // This method expects that the connection is writing data to the
284         // server.
285         if (this.connectionStatus != STATUS_BEFORE_SERVER_CONNECTION) {
286             throw new JUploadIOException("Bad status of the HttpConnectionHelper in sendRequest: " + getStatusLabel());
287         }
288 
289         try {
290             // We've finished with the current encoder.
291             if (!this.byteArrayEncoder.isClosed()) {
292                 this.byteArrayEncoder.close();
293             }
294 
295             // Let's clear any field that could have been read in a previous
296             // step:
297             this.httpInputStreamReader = null;
298 
299             // Only connect, if sock is null!!
300             // ... or if we don't persist HTTP connections (patch for IIS, based
301             // on Marc Reidy's patch)
302             if (this.socket == null || !this.uploadPolicy.getAllowHttpPersistent()) {
303                 this.socket = new HttpConnect(this.uploadPolicy).connect(this.url, this.proxy);
304                 this.outputStream = new DataOutputStream(new BufferedOutputStream(this.socket.getOutputStream()));
305                 this.inputStream = new PushbackInputStream(this.socket.getInputStream(), 1);
306             }
307 
308             // Send http request to server
309             this.outputStream.write(this.byteArrayEncoder.getEncodedByteArray());
310 
311             // The request has been sent. The current ByteArrayEncoder is now
312             // useless. A new one is to be created for the next request.
313             this.connectionStatus = STATUS_WRITING_REQUEST;
314 
315         } catch (IOException e) {
316             throw new JUploadIOException("Unable to open socket", e);
317         } catch (KeyManagementException e) {
318             throw new JUploadIOException("Unable to open socket", e);
319         } catch (UnrecoverableKeyException e) {
320             throw new JUploadIOException("Unable to open socket", e);
321         } catch (NoSuchAlgorithmException e) {
322             throw new JUploadIOException("Unable to open socket", e);
323         } catch (KeyStoreException e) {
324             throw new JUploadIOException("Unable to open socket", e);
325         } catch (CertificateException e) {
326             throw new JUploadIOException("Unable to open socket", e);
327         } catch (IllegalArgumentException e) {
328             throw new JUploadIOException("Unable to open socket", e);
329         }
330 
331     }
332 
333     /**
334      * Releases all reserved resources.
335      * 
336      * @throws JUploadIOException
337      */
338     public synchronized void dispose() throws JUploadIOException {
339         // A try to properly clean the connection to the server.
340         try {
341             // Let's shutdown the output, to free the server connection. Only
342             // valid for non https connections.
343             if (this.socket != null && !this.useSSL && !this.socket.isOutputShutdown()) {
344                 this.socket.shutdownOutput();
345             }
346         } catch (IOException e) {
347             throw new JUploadIOException(e);
348         }
349         try {
350             if (this.socket != null && !this.useSSL && !this.socket.isInputShutdown()) {
351                 this.socket.shutdownInput();
352             }
353         } catch (IOException e) {
354             throw new JUploadIOException(e);
355         }
356 
357         try {
358             if (this.outputStream != null) {
359                 this.outputStream.close();
360             }
361         } catch (IOException e) {
362             throw new JUploadIOException(e);
363         } finally {
364             this.outputStream = null;
365         }
366 
367         try {
368             if (this.inputStream != null) {
369                 this.inputStream.close();
370             }
371         } catch (IOException e) {
372             throw new JUploadIOException(e);
373         } finally {
374             this.inputStream = null;
375         }
376 
377         try {
378             if (this.socket != null) {
379                 if (!this.socket.isClosed()) {
380                     this.socket.close();
381                 }
382             }
383         } catch (IOException e) {
384             throw new JUploadIOException(e);
385         } finally {
386             this.socket = null;
387         }
388     }
389 
390     /**
391      * Return the current socket. If the byteArrayEncoder is not closed: close it, and send the request to the server.
392      * 
393      * @return public Socket getSocket() { }
394      */
395 
396     /**
397      * get the output stream, where HTTP data can be written.
398      * 
399      * @return The current output stream to the server, where things can be written, event after the socket is open, if
400      *         the byteArrayEncoder did not contain the full request.
401      */
402     public OutputStream getOutputStream() {
403         return this;
404     }
405 
406     /**
407      * get the input stream, where HTTP server response can be read.
408      * 
409      * @return The current input stream of the socket.
410      */
411     public PushbackInputStream getInputStream() {
412         return this.inputStream;
413     }
414 
415     /**
416      * Get the HTTP method (HEAD, POST, GET...)
417      * 
418      * @return The HTTP method
419      */
420     public String getMethod() {
421         return this.method;
422     }
423 
424     /**
425      * Get the last response body.
426      * 
427      * @return The full response body, that is: the HTTP body of the server response.
428      */
429     public String getResponseBody() {
430         return this.httpInputStreamReader.getResponseBody();
431     }
432 
433     /**
434      * Get the headers of the HTTP response.
435      * 
436      * @return The HTTP headers.
437      */
438     public String getResponseHeaders() {
439         return this.httpInputStreamReader.getResponseHeaders();
440     }
441 
442     /**
443      * Get the last response message.
444      * 
445      * @return the response message, like "200 OK"
446      */
447     public String getResponseMsg() {
448         return this.httpInputStreamReader.getResponseMsg();
449     }
450 
451     /**
452      * Get the label describing the current state of this connection helper.
453      * 
454      * @return A text describing briefly the current connection status.
455      */
456     public synchronized String getStatusLabel() {
457         switch (this.connectionStatus) {
458             case STATUS_NOT_INITIALIZED:
459                 return "Not initialized";
460             case STATUS_BEFORE_SERVER_CONNECTION:
461                 return "Before server connection";
462             case STATUS_WRITING_REQUEST:
463                 return "Writing request to the network";
464             case STATUS_READING_RESPONSE:
465                 return "Reading server response";
466             case STATUS_CONNECTION_CLOSED:
467                 return "Connection closed";
468         }
469         return "Unknown status in HTTPConnectionHelper.getStatusLabel()";
470     }
471 
472     /**
473      * Get the current socket.
474      * 
475      * @return return the current Socket, opened toward the server.
476      */
477     Socket getSocket() {
478         return this.socket;
479     }
480 
481     /**
482      * Append bytes to the current query. The bytes will be written to the current ByteArrayEncoder if the the
483      * connection to the server is not open, or directly to the server if the connection is opened.
484      * 
485      * @param b The byte to send to the server.
486      * @return Returns the current HttpConnectionHelper, to allow coding like StringBuffers: a.append(b).append(c);
487      * @throws JUploadIOException
488      */
489     public synchronized HTTPConnectionHelper append(int b) throws JUploadIOException {
490         if (this.connectionStatus == STATUS_BEFORE_SERVER_CONNECTION) {
491             this.byteArrayEncoder.append(b);
492         } else if (this.connectionStatus == STATUS_WRITING_REQUEST) {
493             try {
494                 this.outputStream.write(b);
495             } catch (IOException e) {
496                 throw new JUploadIOException(e.getClass().getName() + " while writing to httpDataOut", e);
497             }
498         } else {
499             throw new JUploadIOException("Wrong status in HTTPConnectionHelper.write() [" + getStatusLabel() + "]");
500         }
501         return this;
502     }
503 
504     /**
505      * Append bytes to the current query. The bytes will be written to the current ByteArrayEncoder if the the
506      * connection to the server is not open, or directly to the server if the connection is opened.
507      * 
508      * @param bytes The bytes to send to the server.
509      * @return Returns the current HttpConnectionHelper, to allow coding like StringBuffers: a.append(b).append(c);
510      * @throws JUploadIOException
511      */
512     public synchronized HTTPConnectionHelper append(byte[] bytes) throws JUploadIOException {
513         return this.append(bytes, 0, bytes.length);
514     }
515 
516     /**
517      * Append bytes to the current query. The bytes will be written to the current ByteArrayEncoder if the the
518      * connection to the server is not open, or directly to the server if the connection is opened.
519      * 
520      * @param bytes The bytes to send to the server.
521      * @param off The first byte to send
522      * @param len Number of bytes to send.
523      * @return Returns the current HttpConnectionHelper, to allow coding like StringBuffers: a.append(b).append(c);
524      * @throws JUploadIOException
525      */
526     public synchronized HTTPConnectionHelper append(byte[] bytes, int off, int len) throws JUploadIOException {
527 
528         if (this.connectionStatus == STATUS_BEFORE_SERVER_CONNECTION) {
529             this.byteArrayEncoder.append(bytes);
530         } else if (this.connectionStatus == STATUS_WRITING_REQUEST) {
531             try {
532                 this.outputStream.write(bytes, off, len);
533             } catch (IOException e) {
534                 throw new JUploadIOException(e.getClass().getName() + " while writing to httpDataOut", e);
535             }
536         } else {
537             throw new JUploadIOException("Wrong status in HTTPConnectionHelper.write() [" + getStatusLabel() + "]");
538         }
539 
540         if (this.uploadPolicy.getDebugLevel() > 100) {
541             this.uploadPolicy.displayDebug("[HTTPConnectionHelper append(byte[],int,int)] ("
542                     + len
543                     + " bytes appended to "
544                     + (this.connectionStatus == STATUS_BEFORE_SERVER_CONNECTION ? " current ByteArrayEncoder"
545                             : " socket") + ")", 101);
546         }
547 
548         return this;
549     }
550 
551     /**
552      * write a string to the current HTTP request.
553      * 
554      * @param str The string to write
555      * @return The current HTTPConnectionHelper
556      * @throws JUploadIOException If any problem occurs during the writing operation.
557      * @see #append(byte[])
558      */
559     public synchronized HTTPConnectionHelper append(String str) throws JUploadIOException {
560         this.uploadPolicy.displayDebug("[HTTPConnectionHelper append] " + str, 70);
561         if (this.connectionStatus == STATUS_BEFORE_SERVER_CONNECTION) {
562             this.byteArrayEncoder.append(str);
563         } else if (this.connectionStatus == STATUS_WRITING_REQUEST) {
564             ByteArrayEncoder bae = new ByteArrayEncoderHTTP(this.uploadPolicy, this.byteArrayEncoder.getBoundary(),
565                     this.byteArrayEncoder.getEncoding());
566             bae.append(str);
567             bae.close();
568             this.append(bae);
569         }
570         return this;
571     }
572 
573     /**
574      * Appends a string to the current HTTP request.
575      * 
576      * @param bae The ByteArrayEncoder to write. It is expected to be correctly encoded. That is: it is up to the caller
577      *            to check that its encoding is the same as the current HTTP request encoding.
578      * @return The current HTTPConnectionHelper
579      * @throws JUploadIOException If any problem occurs during the writing operation.
580      * @see #append(byte[])
581      */
582     public synchronized HTTPConnectionHelper append(ByteArrayEncoder bae) throws JUploadIOException {
583         this.uploadPolicy.displayDebug("[HTTPConnectionHelper append] " + bae.getString(), 70);
584         return this.append(bae.getEncodedByteArray());
585     }
586 
587     /**
588      * Read the response of the server. This method delegates the work to the HTTPInputStreamReader. handles the chunk
589      * HTTP response.
590      * 
591      * @return The HTTP status. Should be 200, when everything is right.
592      * @throws JUploadException
593      */
594     public synchronized int readHttpResponse() throws JUploadException {
595         // This method expects that the connection is writing data to the
596         // server.
597         if (this.connectionStatus != STATUS_WRITING_REQUEST) {
598             throw new JUploadIOException("Bad status of the HttpConnectionHelper in readHttpResponse: "
599                     + getStatusLabel());
600         }
601         this.connectionStatus = STATUS_READING_RESPONSE;
602 
603         // Let's connect in InputStream to read this server response.
604         if (this.httpInputStreamReader == null) {
605             this.httpInputStreamReader = new HTTPInputStreamReader(this, this.uploadPolicy);
606         }
607 
608         // Let's do the job
609         try {
610             this.outputStream.flush();
611         } catch (IOException ioe) {
612             throw new JUploadIOException("flushing outputStream, in " + getClass().getName() + ".readHttpResponse()");
613         }
614         this.httpInputStreamReader.readHttpResponse();
615 
616         if (this.httpInputStreamReader.gotClose) {
617             // RFC 2868, section 8.1.2.1
618             dispose();
619         }
620 
621         // We got the response
622         this.connectionStatus = STATUS_CONNECTION_CLOSED;
623 
624         // FIXME Should close the connection, from time to time...
625 
626         //
627         return this.httpInputStreamReader.gethttpStatusCode();
628     }
629 
630     // ////////////////////////////////////////////////////////////////////////////////////
631     // /////////////////////// PRIVATE METHODS
632     // ////////////////////////////////////////////////////////////////////////////////////
633 
634     /**
635      * creating of a new {@link ByteArrayEncoderHTTP}, and initializing of the following header items: First line (POST
636      * currentProtocol URI), Host, Connection, Keep-Alive, Proxy-Connection.
637      * 
638      * @throws JUploadIOException
639      */
640     private synchronized void initByteArrayEncoder() throws JUploadIOException {
641         if (this.byteArrayEncoder != null && !this.byteArrayEncoder.isClosed()) {
642             this.byteArrayEncoder.close();
643             this.byteArrayEncoder = null;
644         }
645         this.byteArrayEncoder = new ByteArrayEncoderHTTP(this.uploadPolicy, this.boundary);
646         this.connectionStatus = STATUS_BEFORE_SERVER_CONNECTION;
647         this.proxy = null;
648         try {
649             this.proxy = ProxySelector.getDefault().select(this.url.toURI()).get(0);
650         } catch (URISyntaxException e) {
651             throw new JUploadIOException("Error while managing url " + this.url.toExternalForm(), e);
652         }
653         this.useProxy = ((this.proxy != null) && (this.proxy.type() != Proxy.Type.DIRECT));
654         this.useSSL = this.url.getProtocol().equals("https");
655 
656         // Header: Request line
657         // Let's clear it. Useful only for chunked uploads.
658         this.byteArrayEncoder.append(this.method);
659         this.byteArrayEncoder.append(" ");
660         this.uploadPolicy.displayDebug("[initByteArrayEncoder] proxy=" + proxy + ", proxy.type=" + this.proxy.type()
661                 + ", useProxy=" + useProxy + ", url.host=" + this.url.getHost() + ", url.port=" + this.url.getPort(),
662                 80);
663         if (this.useProxy && (!this.useSSL)) {
664             // with a proxy we need the absolute URL, but only if not
665             // using SSL. (with SSL, we first use the proxy CONNECT method,
666             // and then a plain request.)
667             this.byteArrayEncoder.append(this.url.getProtocol()).append("://").append(this.url.getHost());
668             // TODO port in proxy mode: to be tested !
669             if (this.url.getPort() > 0) {
670                 this.byteArrayEncoder.append(":").append(Integer.toString(this.url.getPort()));
671             }
672         }
673         this.byteArrayEncoder.append(this.url.getPath());
674 
675         // Append the query params.
676         // TODO: This probably can be removed as we now have everything in POST
677         // data. However in order to be
678         // backwards-compatible, it stays here for now. So we now provide
679         // *both* GET and POST params.
680         if (null != this.url.getQuery() && !"".equals(this.url.getQuery()))
681             this.byteArrayEncoder.append("?").append(this.url.getQuery());
682 
683         this.byteArrayEncoder.append(" ").append(this.uploadPolicy.getServerProtocol()).append("\r\n");
684 
685         // Header: General
686         this.byteArrayEncoder.append("Host: ").append(this.url.getHost());
687         if (this.url.getPort() > 0) {
688             this.byteArrayEncoder.append(":").append(Integer.toString(this.url.getPort()));
689         }
690         this.byteArrayEncoder.append("\r\nAccept: */*\r\n");
691 
692         // We do not want gzipped or compressed responses, so we must
693         // specify that here (RFC 2616, Section 14.3)
694         this.byteArrayEncoder.append("Accept-Encoding: identity\r\n");
695 
696         // Seems like the Keep-alive doesn't work properly, at least on my
697         // local dev (Etienne).
698         if (!this.uploadPolicy.getAllowHttpPersistent()) {
699             this.byteArrayEncoder.append("Connection: close\r\n");
700         } else {
701             if (!this.bChunkEnabled || this.bLastChunk || this.useProxy
702                     || !this.uploadPolicy.getServerProtocol().equals("HTTP/1.1")) { // RFC 2086, section 19.7.1
703                 this.byteArrayEncoder.append("Connection: close\r\n");
704             } else {
705                 this.byteArrayEncoder.append("Keep-Alive: 300\r\n");
706                 if (this.useProxy)
707                     this.byteArrayEncoder.append("Proxy-Connection: keep-alive\r\n");
708                 else
709                     this.byteArrayEncoder.append("Connection: keep-alive\r\n");
710             }
711         }
712 
713         // Get specific headers for this upload.
714         this.uploadPolicy.onAppendHeader(this.byteArrayEncoder);
715     }
716 
717     /**
718      * Indicates whether the current socket should be reused ... if any.
719      */
720     private boolean isKeepAlive() {
721         if (this.socket == null) {
722             return false;
723         } else if (!this.uploadPolicy.getAllowHttpPersistent()) {
724             return false;
725         } else {
726             if (!this.bChunkEnabled || this.bLastChunk || this.useProxy
727                     || !this.uploadPolicy.getServerProtocol().equals("HTTP/1.1")) { // RFC 2086, section 19.7.1
728                 return false;
729             } else {
730                 return true;
731             }
732         }
733     }
734 
735     /**
736      * Construction of a random boundary, to separate the uploaded files, in the HTTP upload request.
737      * 
738      * @return The calculated boundary.
739      */
740     private final String calculateRandomBoundary() {
741         StringBuffer sbRan = new StringBuffer(11);
742         sbRan.append("-----------------------------");
743         String alphaNum = "1234567890abcdefghijklmnopqrstuvwxyz";
744         int num;
745         for (int i = 0; i < 11; i++) {
746             num = (int) (Math.random() * (alphaNum.length() - 1));
747             sbRan.append(alphaNum.charAt(num));
748         }
749         return sbRan.toString();
750     }
751 
752     // ////////////////////////////////////////////////////////////////////////////////////
753     // /////////////////// OVERRIDE OF OutputStream METHODS
754     // ////////////////////////////////////////////////////////////////////////////////////
755     /** {@inheritDoc} */
756     @Override
757     public void write(int b) throws IOException {
758         try {
759             append(b);
760         } catch (JUploadIOException e) {
761             // Hum, HTTPConnectionHelper catch IOException, and throws a
762             // JUploadIOException. Now we get the cause, that is the original
763             // IOException. Not optimized.
764             if (e.getCause() == null) {
765                 // This should not happen
766                 throw new IOException();
767             } else if (e.getCause() instanceof IOException) {
768                 throw (IOException) e.getCause();
769             } else {
770                 // Hum, can something like an OutOfMemory. We must throw it.
771                 throw new IOException(e.getCause().getClass().getName() + ": " + e.getCause().getMessage());
772             }
773         }
774     }
775 
776     /** {@inheritDoc} */
777     @Override
778     public void write(byte[] b, int off, int len) throws IOException {
779         try {
780             append(b, off, len);
781         } catch (JUploadIOException e) {
782             // Hum, HTTPConnectionHelper catch IOException, and throws a
783             // JUploadIOException. Now we get the cause, that is the original
784             // IOException. Not optimized.
785             if (e.getCause() == null) {
786                 // This should not happen
787                 throw new IOException();
788             } else if (e.getCause() instanceof IOException) {
789                 throw (IOException) e.getCause();
790             } else {
791                 // Hum, can something like an OutOfMemory. We must throw it.
792                 throw new IOException(e.getCause().getClass().getName() + ": " + e.getCause().getMessage());
793             }
794         }
795     }
796 
797     /** {@inheritDoc} */
798     @Override
799     public void write(byte[] b) throws IOException {
800         write(b, 0, b.length);
801     }
802 
803     /**
804      * This method is the override of {@link OutputStream#close()} one. It may not been called. You must use the
805      * {@link #sendRequest()} or {@link #readHttpResponse()} methods instead.
806      * 
807      * @see java.io.OutputStream#close()
808      */
809     @Override
810     public void close() throws IOException {
811         this.uploadPolicy
812                 .displayWarn("This method should not be called (HTTPConnectionHelper.close()). Please use the "
813                         + getClass().getName() + ".sendRequest() method");
814         super.close();
815     }
816 
817     /**
818      * Flushes the output stream. Useful only when the HTTPConnectionHelper is writing to the socket toward the server,
819      * that is when the status is: STATUS_WRITING_REQUEST.
820      * 
821      * @see java.io.OutputStream#flush()
822      */
823     @Override
824     public void flush() throws IOException {
825         if (this.connectionStatus == STATUS_WRITING_REQUEST) {
826             this.outputStream.flush();
827         } else {
828             throw new IOException("Wrong status in " + getClass().getName() + ".flush method: " + getStatusLabel());
829         }
830     }
831 }