Coverage Report - wjhk.jupload2.upload.helper.HTTPConnectionHelper
 
Classes in this File Line Coverage Branch Coverage Complexity
HTTPConnectionHelper
0 %
0/234
0 %
0/118
4,931
 
 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  0
     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  0
     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  0
     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  0
     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  0
     private PushbackInputStream inputStream = null;
 145  
 
 146  
     /**
 147  
      * The HTTP method: POST, GET, HEAD...
 148  
      */
 149  0
     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  0
     private DataOutputStream outputStream = null;
 159  
 
 160  
     /**
 161  
      * The current proxy, if any
 162  
      */
 163  0
     private Proxy proxy = null;
 164  
 
 165  
     /**
 166  
      * The network socket where the bytes should be written.
 167  
      */
 168  0
     private Socket socket = null;
 169  
 
 170  
     /**
 171  
      * The current upload policy
 172  
      */
 173  0
     private UploadPolicy uploadPolicy = null;
 174  
 
 175  
     /**
 176  
      * The current URL
 177  
      */
 178  0
     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  0
     public HTTPConnectionHelper(UploadPolicy uploadPolicy) {
 200  0
         this.uploadPolicy = uploadPolicy;
 201  0
     }
 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  0
             UploadPolicy uploadPolicy) throws JUploadIOException {
 217  0
         this.uploadPolicy = uploadPolicy;
 218  0
         initRequest(url, method, bChunkEnabled, bLastChunk);
 219  0
     }
 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  0
         if (this.connectionStatus != STATUS_NOT_INITIALIZED && this.connectionStatus != STATUS_CONNECTION_CLOSED) {
 237  0
             throw new JUploadIOException("Bad status of the HttpConnectionHelper in initRequest: " + getStatusLabel());
 238  
         }
 239  
 
 240  
         // Clean any current request.
 241  0
         if (isKeepAlive()) {
 242  0
             dispose();
 243  
         }
 244  
 
 245  
         // Load the new parameters.
 246  0
         this.url = url;
 247  0
         this.method = method;
 248  0
         this.bChunkEnabled = bChunkEnabled;
 249  0
         this.bLastChunk = bLastChunk;
 250  
         // We will write to the local ByteArrayEncoder, until a connection to
 251  
         // the server is opened.
 252  0
         initByteArrayEncoder();
 253  
 
 254  
         // Ok, the HttpConnectionHelper is now ready to get write commands.
 255  0
         this.connectionStatus = STATUS_BEFORE_SERVER_CONNECTION;
 256  0
     }
 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  0
         return this.byteArrayEncoder;
 267  
     }
 268  
 
 269  
     /**
 270  
      * @return Returns the boundary for this HTTP request.
 271  
      */
 272  
     public String getBoundary() {
 273  0
         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  0
         if (this.connectionStatus != STATUS_BEFORE_SERVER_CONNECTION) {
 286  0
             throw new JUploadIOException("Bad status of the HttpConnectionHelper in sendRequest: " + getStatusLabel());
 287  
         }
 288  
 
 289  
         try {
 290  
             // We've finished with the current encoder.
 291  0
             if (!this.byteArrayEncoder.isClosed()) {
 292  0
                 this.byteArrayEncoder.close();
 293  
             }
 294  
 
 295  
             // Let's clear any field that could have been read in a previous
 296  
             // step:
 297  0
             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  0
             if (this.socket == null || !this.uploadPolicy.getAllowHttpPersistent()) {
 303  0
                 this.socket = new HttpConnect(this.uploadPolicy).connect(this.url, this.proxy);
 304  0
                 this.outputStream = new DataOutputStream(new BufferedOutputStream(this.socket.getOutputStream()));
 305  0
                 this.inputStream = new PushbackInputStream(this.socket.getInputStream(), 1);
 306  
             }
 307  
 
 308  
             // Send http request to server
 309  0
             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  0
             this.connectionStatus = STATUS_WRITING_REQUEST;
 314  
 
 315  0
         } catch (IOException e) {
 316  0
             throw new JUploadIOException("Unable to open socket", e);
 317  0
         } catch (KeyManagementException e) {
 318  0
             throw new JUploadIOException("Unable to open socket", e);
 319  0
         } catch (UnrecoverableKeyException e) {
 320  0
             throw new JUploadIOException("Unable to open socket", e);
 321  0
         } catch (NoSuchAlgorithmException e) {
 322  0
             throw new JUploadIOException("Unable to open socket", e);
 323  0
         } catch (KeyStoreException e) {
 324  0
             throw new JUploadIOException("Unable to open socket", e);
 325  0
         } catch (CertificateException e) {
 326  0
             throw new JUploadIOException("Unable to open socket", e);
 327  0
         } catch (IllegalArgumentException e) {
 328  0
             throw new JUploadIOException("Unable to open socket", e);
 329  0
         }
 330  
 
 331  0
     }
 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  0
             if (this.socket != null && !this.useSSL && !this.socket.isOutputShutdown()) {
 344  0
                 this.socket.shutdownOutput();
 345  
             }
 346  0
         } catch (IOException e) {
 347  0
             throw new JUploadIOException(e);
 348  0
         }
 349  
         try {
 350  0
             if (this.socket != null && !this.useSSL && !this.socket.isInputShutdown()) {
 351  0
                 this.socket.shutdownInput();
 352  
             }
 353  0
         } catch (IOException e) {
 354  0
             throw new JUploadIOException(e);
 355  0
         }
 356  
 
 357  
         try {
 358  0
             if (this.outputStream != null) {
 359  0
                 this.outputStream.close();
 360  
             }
 361  0
         } catch (IOException e) {
 362  0
             throw new JUploadIOException(e);
 363  
         } finally {
 364  0
             this.outputStream = null;
 365  0
         }
 366  
 
 367  
         try {
 368  0
             if (this.inputStream != null) {
 369  0
                 this.inputStream.close();
 370  
             }
 371  0
         } catch (IOException e) {
 372  0
             throw new JUploadIOException(e);
 373  
         } finally {
 374  0
             this.inputStream = null;
 375  0
         }
 376  
 
 377  
         try {
 378  0
             if (this.socket != null) {
 379  0
                 if (!this.socket.isClosed()) {
 380  0
                     this.socket.close();
 381  
                 }
 382  
             }
 383  0
         } catch (IOException e) {
 384  0
             throw new JUploadIOException(e);
 385  
         } finally {
 386  0
             this.socket = null;
 387  0
         }
 388  0
     }
 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  0
         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  0
         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  0
         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  0
         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  0
         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  0
         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  0
         switch (this.connectionStatus) {
 458  
             case STATUS_NOT_INITIALIZED:
 459  0
                 return "Not initialized";
 460  
             case STATUS_BEFORE_SERVER_CONNECTION:
 461  0
                 return "Before server connection";
 462  
             case STATUS_WRITING_REQUEST:
 463  0
                 return "Writing request to the network";
 464  
             case STATUS_READING_RESPONSE:
 465  0
                 return "Reading server response";
 466  
             case STATUS_CONNECTION_CLOSED:
 467  0
                 return "Connection closed";
 468  
         }
 469  0
         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  0
         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  0
         if (this.connectionStatus == STATUS_BEFORE_SERVER_CONNECTION) {
 491  0
             this.byteArrayEncoder.append(b);
 492  0
         } else if (this.connectionStatus == STATUS_WRITING_REQUEST) {
 493  
             try {
 494  0
                 this.outputStream.write(b);
 495  0
             } catch (IOException e) {
 496  0
                 throw new JUploadIOException(e.getClass().getName() + " while writing to httpDataOut", e);
 497  0
             }
 498  
         } else {
 499  0
             throw new JUploadIOException("Wrong status in HTTPConnectionHelper.write() [" + getStatusLabel() + "]");
 500  
         }
 501  0
         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  0
         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  0
         if (this.connectionStatus == STATUS_BEFORE_SERVER_CONNECTION) {
 529  0
             this.byteArrayEncoder.append(bytes);
 530  0
         } else if (this.connectionStatus == STATUS_WRITING_REQUEST) {
 531  
             try {
 532  0
                 this.outputStream.write(bytes, off, len);
 533  0
             } catch (IOException e) {
 534  0
                 throw new JUploadIOException(e.getClass().getName() + " while writing to httpDataOut", e);
 535  0
             }
 536  
         } else {
 537  0
             throw new JUploadIOException("Wrong status in HTTPConnectionHelper.write() [" + getStatusLabel() + "]");
 538  
         }
 539  
 
 540  0
         if (this.uploadPolicy.getDebugLevel() > 100) {
 541  0
             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  0
         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  0
         this.uploadPolicy.displayDebug("[HTTPConnectionHelper append] " + str, 70);
 561  0
         if (this.connectionStatus == STATUS_BEFORE_SERVER_CONNECTION) {
 562  0
             this.byteArrayEncoder.append(str);
 563  0
         } else if (this.connectionStatus == STATUS_WRITING_REQUEST) {
 564  0
             ByteArrayEncoder bae = new ByteArrayEncoderHTTP(this.uploadPolicy, this.byteArrayEncoder.getBoundary(),
 565  0
                     this.byteArrayEncoder.getEncoding());
 566  0
             bae.append(str);
 567  0
             bae.close();
 568  0
             this.append(bae);
 569  
         }
 570  0
         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  0
         this.uploadPolicy.displayDebug("[HTTPConnectionHelper append] " + bae.getString(), 70);
 584  0
         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  0
         if (this.connectionStatus != STATUS_WRITING_REQUEST) {
 598  0
             throw new JUploadIOException("Bad status of the HttpConnectionHelper in readHttpResponse: "
 599  0
                     + getStatusLabel());
 600  
         }
 601  0
         this.connectionStatus = STATUS_READING_RESPONSE;
 602  
 
 603  
         // Let's connect in InputStream to read this server response.
 604  0
         if (this.httpInputStreamReader == null) {
 605  0
             this.httpInputStreamReader = new HTTPInputStreamReader(this, this.uploadPolicy);
 606  
         }
 607  
 
 608  
         // Let's do the job
 609  
         try {
 610  0
             this.outputStream.flush();
 611  0
         } catch (IOException ioe) {
 612  0
             throw new JUploadIOException("flushing outputStream, in " + getClass().getName() + ".readHttpResponse()");
 613  0
         }
 614  0
         this.httpInputStreamReader.readHttpResponse();
 615  
 
 616  0
         if (this.httpInputStreamReader.gotClose) {
 617  
             // RFC 2868, section 8.1.2.1
 618  0
             dispose();
 619  
         }
 620  
 
 621  
         // We got the response
 622  0
         this.connectionStatus = STATUS_CONNECTION_CLOSED;
 623  
 
 624  
         // FIXME Should close the connection, from time to time...
 625  
 
 626  
         //
 627  0
         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  0
         if (this.byteArrayEncoder != null && !this.byteArrayEncoder.isClosed()) {
 642  0
             this.byteArrayEncoder.close();
 643  0
             this.byteArrayEncoder = null;
 644  
         }
 645  0
         this.byteArrayEncoder = new ByteArrayEncoderHTTP(this.uploadPolicy, this.boundary);
 646  0
         this.connectionStatus = STATUS_BEFORE_SERVER_CONNECTION;
 647  0
         this.proxy = null;
 648  
         try {
 649  0
             this.proxy = ProxySelector.getDefault().select(this.url.toURI()).get(0);
 650  0
         } catch (URISyntaxException e) {
 651  0
             throw new JUploadIOException("Error while managing url " + this.url.toExternalForm(), e);
 652  0
         }
 653  0
         this.useProxy = ((this.proxy != null) && (this.proxy.type() != Proxy.Type.DIRECT));
 654  0
         this.useSSL = this.url.getProtocol().equals("https");
 655  
 
 656  
         // Header: Request line
 657  
         // Let's clear it. Useful only for chunked uploads.
 658  0
         this.byteArrayEncoder.append(this.method);
 659  0
         this.byteArrayEncoder.append(" ");
 660  0
         this.uploadPolicy.displayDebug("[initByteArrayEncoder] proxy=" + proxy + ", proxy.type=" + this.proxy.type()
 661  0
                 + ", useProxy=" + useProxy + ", url.host=" + this.url.getHost() + ", url.port=" + this.url.getPort(),
 662  
                 80);
 663  0
         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  0
             this.byteArrayEncoder.append(this.url.getProtocol()).append("://").append(this.url.getHost());
 668  
             // TODO port in proxy mode: to be tested !
 669  0
             if (this.url.getPort() > 0) {
 670  0
                 this.byteArrayEncoder.append(":").append(Integer.toString(this.url.getPort()));
 671  
             }
 672  
         }
 673  0
         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  0
         if (null != this.url.getQuery() && !"".equals(this.url.getQuery()))
 681  0
             this.byteArrayEncoder.append("?").append(this.url.getQuery());
 682  
 
 683  0
         this.byteArrayEncoder.append(" ").append(this.uploadPolicy.getServerProtocol()).append("\r\n");
 684  
 
 685  
         // Header: General
 686  0
         this.byteArrayEncoder.append("Host: ").append(this.url.getHost());
 687  0
         if (this.url.getPort() > 0) {
 688  0
             this.byteArrayEncoder.append(":").append(Integer.toString(this.url.getPort()));
 689  
         }
 690  0
         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  0
         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  0
         if (!this.uploadPolicy.getAllowHttpPersistent()) {
 699  0
             this.byteArrayEncoder.append("Connection: close\r\n");
 700  
         } else {
 701  0
             if (!this.bChunkEnabled || this.bLastChunk || this.useProxy
 702  0
                     || !this.uploadPolicy.getServerProtocol().equals("HTTP/1.1")) { // RFC 2086, section 19.7.1
 703  0
                 this.byteArrayEncoder.append("Connection: close\r\n");
 704  
             } else {
 705  0
                 this.byteArrayEncoder.append("Keep-Alive: 300\r\n");
 706  0
                 if (this.useProxy)
 707  0
                     this.byteArrayEncoder.append("Proxy-Connection: keep-alive\r\n");
 708  
                 else
 709  0
                     this.byteArrayEncoder.append("Connection: keep-alive\r\n");
 710  
             }
 711  
         }
 712  
 
 713  
         // Get specific headers for this upload.
 714  0
         this.uploadPolicy.onAppendHeader(this.byteArrayEncoder);
 715  0
     }
 716  
 
 717  
     /**
 718  
      * Indicates whether the current socket should be reused ... if any.
 719  
      */
 720  
     private boolean isKeepAlive() {
 721  0
         if (this.socket == null) {
 722  0
             return false;
 723  0
         } else if (!this.uploadPolicy.getAllowHttpPersistent()) {
 724  0
             return false;
 725  
         } else {
 726  0
             if (!this.bChunkEnabled || this.bLastChunk || this.useProxy
 727  0
                     || !this.uploadPolicy.getServerProtocol().equals("HTTP/1.1")) { // RFC 2086, section 19.7.1
 728  0
                 return false;
 729  
             } else {
 730  0
                 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  0
         StringBuffer sbRan = new StringBuffer(11);
 742  0
         sbRan.append("-----------------------------");
 743  0
         String alphaNum = "1234567890abcdefghijklmnopqrstuvwxyz";
 744  
         int num;
 745  0
         for (int i = 0; i < 11; i++) {
 746  0
             num = (int) (Math.random() * (alphaNum.length() - 1));
 747  0
             sbRan.append(alphaNum.charAt(num));
 748  
         }
 749  0
         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  0
             append(b);
 760  0
         } 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  0
             if (e.getCause() == null) {
 765  
                 // This should not happen
 766  0
                 throw new IOException();
 767  0
             } else if (e.getCause() instanceof IOException) {
 768  0
                 throw (IOException) e.getCause();
 769  
             } else {
 770  
                 // Hum, can something like an OutOfMemory. We must throw it.
 771  0
                 throw new IOException(e.getCause().getClass().getName() + ": " + e.getCause().getMessage());
 772  
             }
 773  0
         }
 774  0
     }
 775  
 
 776  
     /** {@inheritDoc} */
 777  
     @Override
 778  
     public void write(byte[] b, int off, int len) throws IOException {
 779  
         try {
 780  0
             append(b, off, len);
 781  0
         } 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  0
             if (e.getCause() == null) {
 786  
                 // This should not happen
 787  0
                 throw new IOException();
 788  0
             } else if (e.getCause() instanceof IOException) {
 789  0
                 throw (IOException) e.getCause();
 790  
             } else {
 791  
                 // Hum, can something like an OutOfMemory. We must throw it.
 792  0
                 throw new IOException(e.getCause().getClass().getName() + ": " + e.getCause().getMessage());
 793  
             }
 794  0
         }
 795  0
     }
 796  
 
 797  
     /** {@inheritDoc} */
 798  
     @Override
 799  
     public void write(byte[] b) throws IOException {
 800  0
         write(b, 0, b.length);
 801  0
     }
 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  0
         this.uploadPolicy
 812  0
                 .displayWarn("This method should not be called (HTTPConnectionHelper.close()). Please use the "
 813  0
                         + getClass().getName() + ".sendRequest() method");
 814  0
         super.close();
 815  0
     }
 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  0
         if (this.connectionStatus == STATUS_WRITING_REQUEST) {
 826  0
             this.outputStream.flush();
 827  
         } else {
 828  0
             throw new IOException("Wrong status in " + getClass().getName() + ".flush method: " + getStatusLabel());
 829  
         }
 830  0
     }
 831  
 }