Coverage Report - wjhk.jupload2.upload.FileUploadThreadHTTP
 
Classes in this File Line Coverage Branch Coverage Complexity
FileUploadThreadHTTP
0 %
0/179
0 %
0/50
3,533
 
 1  
 //
 2  
 // $Id: FileUploadThreadHTTP.java 1469 2010-12-14 12:27:42Z 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: 2010-12-14 13:27:42 +0100 (mar., 14 déc. 2010) $
 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  
 package wjhk.jupload2.upload;
 21  
 
 22  
 import java.io.IOException;
 23  
 import java.io.OutputStream;
 24  
 import java.io.UnsupportedEncodingException;
 25  
 import java.net.URL;
 26  
 import java.net.URLDecoder;
 27  
 import java.net.URLEncoder;
 28  
 import java.util.HashMap;
 29  
 import java.util.Iterator;
 30  
 import java.util.Map;
 31  
 import java.util.Set;
 32  
 import java.util.concurrent.BlockingQueue;
 33  
 
 34  
 import wjhk.jupload2.exception.JUploadException;
 35  
 import wjhk.jupload2.exception.JUploadIOException;
 36  
 import wjhk.jupload2.policies.UploadPolicy;
 37  
 import wjhk.jupload2.upload.helper.ByteArrayEncoder;
 38  
 import wjhk.jupload2.upload.helper.ByteArrayEncoderHTTP;
 39  
 import wjhk.jupload2.upload.helper.HTTPConnectionHelper;
 40  
 
 41  
 /**
 42  
  * This class implements the file upload via HTTP POST request.
 43  
  * 
 44  
  * @author etienne_sf
 45  
  * @version $Revision: 1469 $
 46  
  */
 47  
 public class FileUploadThreadHTTP extends DefaultFileUploadThread {
 48  
 
 49  
     /**
 50  
      * The current connection helper. No initialization now: we need to wait for
 51  
      * the startRequest method, to have all needed information.
 52  
      */
 53  0
     private HTTPConnectionHelper connectionHelper = null;
 54  
 
 55  
     /**
 56  
      * local head within the multipart post, for each file. This is
 57  
      * precalculated for all files, in case the upload is not chunked. The heads
 58  
      * length are counted in the total upload size, to check that it is less
 59  
      * than the maxChunkSize. tails are calculated once, as they depend not of
 60  
      * the file position in the upload.
 61  
      */
 62  0
     private HashMap<UploadFileData, ByteArrayEncoder> heads = null;
 63  
 
 64  
     /**
 65  
      * same as heads, for the ... tail in the multipart post, for each file. But
 66  
      * tails depend on the file position (the boundary is added to the last
 67  
      * tail). So it's to be calculated for each upload.
 68  
      */
 69  0
     private HashMap<UploadFileData, ByteArrayEncoder> tails = null;
 70  
 
 71  
     /**
 72  
      * Creates a new instance.
 73  
      * 
 74  
      * @param uploadPolicy The policy to be applied.
 75  
      * @param packetQueue The queue from wich packets to upload are available.
 76  
      * @param fileUploadManagerThread
 77  
      */
 78  
     public FileUploadThreadHTTP(UploadPolicy uploadPolicy,
 79  
             BlockingQueue<UploadFilePacket> packetQueue,
 80  
             FileUploadManagerThread fileUploadManagerThread) {
 81  0
         super("FileUploadThreadHTTP thread", packetQueue, uploadPolicy,
 82  
                 fileUploadManagerThread);
 83  0
         this.uploadPolicy.displayDebug("  Using " + this.getClass().getName(),
 84  
                 30);
 85  
 
 86  0
         uploadPolicy.displayDebug("Upload done by using the "
 87  0
                 + getClass().getName() + " class", 30);
 88  
         // Name the thread (useful for debugging)
 89  0
         setName("FileUploadThreadHTTP");
 90  
         // FIXME There are two such initializations in this class. Necessary ??
 91  0
         this.connectionHelper = new HTTPConnectionHelper(uploadPolicy);
 92  0
     }
 93  
 
 94  
     /** @see DefaultFileUploadThread#beforeRequest(UploadFilePacket) */
 95  
     @Override
 96  
     void beforeRequest(UploadFilePacket packet) throws JUploadException {
 97  0
         if (this.connectionHelper != null) {
 98  
             // It must be retring an upload. We clear any previous work.
 99  0
             this.connectionHelper.dispose();
 100  
         }
 101  0
         this.connectionHelper = new HTTPConnectionHelper(uploadPolicy);
 102  0
         setAllHead(packet, this.connectionHelper.getBoundary());
 103  0
         setAllTail(packet, this.connectionHelper.getBoundary());
 104  0
     }
 105  
 
 106  
     /** @see DefaultFileUploadThread#getAdditionnalBytesForUpload(UploadFileData) */
 107  
     @Override
 108  
     long getAdditionnalBytesForUpload(UploadFileData uploadFileData)
 109  
             throws JUploadIOException {
 110  0
         return this.heads.get(uploadFileData).getEncodedLength()
 111  0
                 + this.tails.get(uploadFileData).getEncodedLength();
 112  
     }
 113  
 
 114  
     /** @see DefaultFileUploadThread#afterFile(UploadFileData) */
 115  
     @Override
 116  
     void afterFile(UploadFileData uploadFileData) throws JUploadIOException {
 117  0
         this.connectionHelper.append(this.tails.get(uploadFileData));
 118  0
         this.uploadPolicy.displayDebug("--- filetail start (len="
 119  0
                 + this.tails.get(uploadFileData).getEncodedLength() + "):", 70);
 120  0
         this.uploadPolicy.displayDebug(quoteCRLF(this.tails.get(uploadFileData)
 121  0
                 .getString()), 70);
 122  0
         this.uploadPolicy.displayDebug("--- filetail end", 70);
 123  0
     }
 124  
 
 125  
     /** @see DefaultFileUploadThread#beforeFile(UploadFilePacket, UploadFileData) */
 126  
     @Override
 127  
     void beforeFile(UploadFilePacket uploadFilePacket,
 128  
             UploadFileData uploadFileData) throws JUploadException {
 129  
         // heads[i] contains the header specific for the file, in the multipart
 130  
         // content.
 131  
         // It is initialized at the beginning of the run() method. It can be
 132  
         // override at the beginning of this loop, if in chunk mode.
 133  
         try {
 134  0
             this.connectionHelper.append(this.heads.get(uploadFileData)
 135  0
                     .getEncodedByteArray());
 136  
 
 137  
             // Debug output: always called, so that the debug file is correctly
 138  
             // filled.
 139  0
             this.uploadPolicy.displayDebug("--- fileheader start (len="
 140  0
                     + this.heads.get(uploadFileData).getEncodedLength() + "):",
 141  
                     70);
 142  0
             this.uploadPolicy.displayDebug(quoteCRLF(this.heads.get(
 143  0
                     uploadFileData).getString()), 70);
 144  0
             this.uploadPolicy.displayDebug("--- fileheader end", 70);
 145  0
         } catch (Exception e) {
 146  0
             throw new JUploadException(e);
 147  0
         }
 148  0
     }
 149  
 
 150  
     /** @see DefaultFileUploadThread#cleanAll() */
 151  
     @Override
 152  
     void cleanAll() throws JUploadException {
 153  
         // Nothing to do in HTTP mode.
 154  0
     }
 155  
 
 156  
     /** @see DefaultFileUploadThread#cleanRequest() */
 157  
     @Override
 158  
     void cleanRequest() throws JUploadException {
 159  
         try {
 160  0
             this.connectionHelper.dispose();
 161  0
         } catch (JUploadIOException e) {
 162  0
             this.uploadPolicy.displayErr(this.uploadPolicy
 163  0
                     .getLocalizedString("errDuringUpload"), e);
 164  0
             throw e;
 165  0
         }
 166  0
     }
 167  
 
 168  
     @Override
 169  
     int finishRequest() throws JUploadException {
 170  0
         if (this.uploadPolicy.getDebugLevel() > 100) {
 171  
             // Let's have a little time to check the upload messages written on
 172  
             // the progress bar.
 173  
             try {
 174  0
                 Thread.sleep(400);
 175  0
             } catch (InterruptedException e) {
 176  0
             }
 177  
         }
 178  0
         int status = this.connectionHelper.readHttpResponse();
 179  0
         setResponseMsg(this.connectionHelper.getResponseMsg());
 180  0
         setResponseBody(this.connectionHelper.getResponseBody());
 181  0
         return status;
 182  
     }
 183  
 
 184  
     /**
 185  
      * When interrupted, we close all network connection.
 186  
      */
 187  
     @Override
 188  
     void interruptionReceived() {
 189  
         // FIXME: this should manage chunked upload (to free temporary files on
 190  
         // the server)
 191  
         try {
 192  0
             if (this.connectionHelper != null) {
 193  0
                 this.connectionHelper.dispose();
 194  0
                 this.connectionHelper = null;
 195  
             }
 196  
 
 197  0
             if (this.heads != null) {
 198  0
                 for (UploadFileData uploadFileData : this.heads.keySet()) {
 199  0
                     ByteArrayEncoder bae = this.heads.get(uploadFileData);
 200  0
                     if (bae != null) {
 201  0
                         bae.close();
 202  
                     }
 203  0
                 }
 204  0
                 this.heads = null;
 205  
             }
 206  0
             if (this.tails != null) {
 207  0
                 for (UploadFileData uploadFileData : this.tails.keySet()) {
 208  0
                     ByteArrayEncoder bae = this.tails.get(uploadFileData);
 209  0
                     if (bae != null) {
 210  0
                         bae.close();
 211  
                     }
 212  0
                 }
 213  0
                 this.tails = null;
 214  
             }
 215  0
         } catch (Exception e) {
 216  0
             this.uploadPolicy.displayWarn("Exception in "
 217  0
                     + getClass().getName() + ".interruptionReceived() ("
 218  0
                     + e.getClass().getName() + "): " + e.getMessage());
 219  0
         }
 220  0
     }
 221  
 
 222  
     /**
 223  
      * @see DefaultFileUploadThread#getResponseBody()
 224  
      * @Override String getResponseBody() { return
 225  
      *           this.sbHttpResponseBody.toString(); }
 226  
      */
 227  
     /** @see DefaultFileUploadThread#getOutputStream() */
 228  
     @Override
 229  
     OutputStream getOutputStream() throws JUploadException {
 230  0
         return this.connectionHelper.getOutputStream();
 231  
     }
 232  
 
 233  
     /** @see DefaultFileUploadThread#startRequest(long, boolean, int, boolean) */
 234  
     @Override
 235  
     void startRequest(long contentLength, boolean bChunkEnabled, int chunkPart,
 236  
             boolean bLastChunk) throws JUploadException {
 237  
 
 238  
         try {
 239  0
             String chunkHttpParam = "jupart=" + chunkPart + "&jufinal="
 240  
                     + (bLastChunk ? "1" : "0");
 241  0
             this.uploadPolicy.displayDebug("chunkHttpParam: " + chunkHttpParam,
 242  
                     30);
 243  
 
 244  0
             URL url = new URL(this.uploadPolicy.getPostURL());
 245  
 
 246  
             // Add the chunking query params to the URL if there are any
 247  0
             if (bChunkEnabled) {
 248  0
                 if (null != url.getQuery() && !"".equals(url.getQuery())) {
 249  0
                     url = new URL(url.toExternalForm() + "&" + chunkHttpParam);
 250  
                 } else {
 251  0
                     url = new URL(url.toExternalForm() + "?" + chunkHttpParam);
 252  
                 }
 253  
             }
 254  
 
 255  0
             this.connectionHelper.initRequest(url, "POST", bChunkEnabled,
 256  
                     bLastChunk);
 257  
 
 258  
             // Get the GET parameters from the URL and convert them to
 259  
             // post form params
 260  0
             ByteArrayEncoder formParams = getFormParamsForPostRequest(url);
 261  0
             contentLength += formParams.getEncodedLength();
 262  
 
 263  0
             this.connectionHelper.append(
 264  0
                     "Content-Type: multipart/form-data; boundary=").append(
 265  0
                     this.connectionHelper.getBoundary().substring(2)).append(
 266  
                     "\r\n");
 267  0
             this.connectionHelper.append("Content-Length: ").append(
 268  0
                     String.valueOf(contentLength)).append("\r\n");
 269  
 
 270  
             // Blank line (end of header)
 271  0
             this.connectionHelper.append("\r\n");
 272  
 
 273  
             // formParams are not really part of the main header, but we add
 274  
             // them here anyway. We write directly into the
 275  
             // ByteArrayOutputStream, as we already encoded them, to get the
 276  
             // encoded length. We need to flush the writer first, before
 277  
             // directly writing to the ByteArrayOutputStream.
 278  0
             this.connectionHelper.append(formParams);
 279  
 
 280  
             // Let's call the server
 281  0
             this.connectionHelper.sendRequest();
 282  
 
 283  
             // Debug output: always called, so that the debug file is correctly
 284  
             // filled.
 285  0
             this.uploadPolicy.displayDebug("=== main header (len="
 286  0
                     + this.connectionHelper.getByteArrayEncoder()
 287  0
                             .getEncodedLength()
 288  
                     + "):\n"
 289  0
                     + quoteCRLF(this.connectionHelper.getByteArrayEncoder()
 290  0
                             .getString()), 70);
 291  0
             this.uploadPolicy.displayDebug("=== main header end", 70);
 292  0
         } catch (IOException e) {
 293  0
             throw new JUploadIOException(e);
 294  0
         } catch (IllegalArgumentException e) {
 295  0
             throw new JUploadException(e);
 296  0
         }
 297  0
     }
 298  
 
 299  
     // ////////////////////////////////////////////////////////////////////////////////////
 300  
     // /////////////////////// PRIVATE METHODS
 301  
     // ////////////////////////////////////////////////////////////////////////////////////
 302  
     /**
 303  
      * Returns the header for this file, within the http multipart body.
 304  
      * 
 305  
      * @param numInCurrentUpload Index of the file in the array that contains
 306  
      *            all files to upload.
 307  
      * @param bound The boundary that separate files in the http multipart post
 308  
      *            body.
 309  
      * @param chunkPart The numero of the current chunk (from 1 to n)
 310  
      * @return The encoded header for this file. The {@link ByteArrayEncoder} is
 311  
      *         closed within this method.
 312  
      * @throws JUploadException
 313  
      */
 314  
     private final ByteArrayEncoder getFileHeader(UploadFileData uploadFileData,
 315  
             int numInCurrentUpload, String bound, int chunkPart)
 316  
             throws JUploadException {
 317  0
         String filenameEncoding = this.uploadPolicy.getFilenameEncoding();
 318  0
         String mimetype = uploadFileData.getMimeType();
 319  0
         String uploadFilename = uploadFileData
 320  0
                 .getUploadFilename(numInCurrentUpload);
 321  0
         ByteArrayEncoder bae = new ByteArrayEncoderHTTP(this.uploadPolicy,
 322  
                 bound);
 323  
 
 324  0
         if (numInCurrentUpload == 0) {
 325  
             // Only once when uploading multiple files in same request.
 326  
             // We'll encode the output stream into UTF-8.
 327  0
             String form = this.uploadPolicy.getFormdata();
 328  0
             if (null != form) {
 329  0
                 bae.appendFormVariables(form);
 330  
             }
 331  
         }
 332  
         // We ask the current FileData to add itself its properties.
 333  0
         uploadFileData.appendFileProperties(bae, numInCurrentUpload);
 334  
 
 335  
         // boundary.
 336  0
         bae.append(bound).append("\r\n");
 337  
 
 338  
         // Content-Disposition.
 339  0
         bae.append("Content-Disposition: form-data; name=\"");
 340  0
         bae.append(uploadFileData.getUploadName(numInCurrentUpload)).append(
 341  
                 "\"; filename=\"");
 342  0
         if (filenameEncoding == null) {
 343  0
             bae.append(uploadFilename);
 344  
         } else {
 345  
             try {
 346  0
                 this.uploadPolicy.displayDebug("Encoded filename: "
 347  0
                         + URLEncoder.encode(uploadFilename, filenameEncoding),
 348  
                         70);
 349  0
                 bae.append(URLEncoder.encode(uploadFilename, filenameEncoding));
 350  0
             } catch (UnsupportedEncodingException e) {
 351  0
                 this.uploadPolicy
 352  0
                         .displayWarn(e.getClass().getName() + ": "
 353  0
                                 + e.getMessage()
 354  
                                 + " (in UploadFileData.getFileHeader)");
 355  0
                 bae.append(uploadFilename);
 356  0
             }
 357  
         }
 358  0
         bae.append("\"\r\n");
 359  
 
 360  
         // Line 3: Content-Type.
 361  0
         bae.append("Content-Type: ").append(mimetype).append("\r\n");
 362  
 
 363  
         // An empty line to finish the header.
 364  0
         bae.append("\r\n");
 365  
 
 366  
         // The ByteArrayEncoder is now filled.
 367  0
         bae.close();
 368  0
         return bae;
 369  
     }// getFileHeader
 370  
 
 371  
     /**
 372  
      * Construction of the head for each file.
 373  
      * 
 374  
      * @param bound The String boundary between the post data in the HTTP
 375  
      *            request.
 376  
      * @throws JUploadException
 377  
      */
 378  
     private final void setAllHead(UploadFilePacket packet, String bound)
 379  
             throws JUploadException {
 380  0
         this.heads = new HashMap<UploadFileData, ByteArrayEncoder>(packet
 381  0
                 .size());
 382  0
         int numInCurrentUpload = 0;
 383  0
         for (UploadFileData uploadFileData : packet) {
 384  0
             this.heads.put(uploadFileData, getFileHeader(uploadFileData,
 385  
                     numInCurrentUpload++, bound, -1));
 386  0
         }
 387  0
     }
 388  
 
 389  
     /**
 390  
      * Construction of the tail for each file.
 391  
      * 
 392  
      * @param bound Current boundary, to apply for these tails.
 393  
      */
 394  
     private final void setAllTail(UploadFilePacket packet, String bound)
 395  
             throws JUploadException {
 396  0
         this.tails = new HashMap<UploadFileData, ByteArrayEncoder>(packet
 397  0
                 .size());
 398  0
         for (int i = 0; i < packet.size(); i++) {
 399  
             // We'll encode the output stream into UTF-8.
 400  0
             ByteArrayEncoder bae = new ByteArrayEncoderHTTP(this.uploadPolicy,
 401  
                     bound);
 402  
 
 403  0
             bae.append("\r\n");
 404  
 
 405  0
             if (this.uploadPolicy.getSendMD5Sum()) {
 406  0
                 bae.appendTextProperty("md5sum", packet.get(i).getMD5(), i);
 407  
             }
 408  
 
 409  
             // The last tail gets an additional "--" in order to tell the
 410  
             // server we have finished.
 411  0
             if (i == packet.size() - 1) {
 412  0
                 bae.append(bound).append("--\r\n");
 413  
             }
 414  
 
 415  
             // Let's store this tail.
 416  0
             bae.close();
 417  
 
 418  0
             this.tails.put(packet.get(i), bae);
 419  
         }
 420  
 
 421  0
     }
 422  
 
 423  
     /**
 424  
      * Converts the parameters in GET form to post form
 425  
      * 
 426  
      * @param url the <code>URL</code> containing the query parameters
 427  
      * @return the parameters in a string in the correct form for a POST request
 428  
      * @throws JUploadIOException
 429  
      */
 430  
     private final ByteArrayEncoder getFormParamsForPostRequest(final URL url)
 431  
             throws JUploadIOException {
 432  
 
 433  
         // Use a string buffer
 434  
         // We'll encode the output stream into UTF-8.
 435  0
         ByteArrayEncoder bae = new ByteArrayEncoderHTTP(this.uploadPolicy,
 436  0
                 this.connectionHelper.getBoundary());
 437  
 
 438  
         // Get the query string
 439  0
         String query = url.getQuery();
 440  
 
 441  0
         if (null != query) {
 442  
             // Split this into parameters
 443  0
             HashMap<String, String> requestParameters = new HashMap<String, String>();
 444  0
             String[] paramPairs = query.split("&");
 445  
             String[] oneParamArray;
 446  
 
 447  
             // TODO This could be much more simple !
 448  
 
 449  
             // Put the parameters correctly to the Hashmap
 450  0
             for (String param : paramPairs) {
 451  0
                 if (param.contains("=")) {
 452  0
                     oneParamArray = param.split("=");
 453  0
                     if (oneParamArray.length > 1) {
 454  
                         // There is a value for this parameter
 455  
                         try {
 456  
                             // Correction of URL double encoding bug
 457  0
                             requestParameters.put(oneParamArray[0], URLDecoder
 458  0
                                     .decode(oneParamArray[1], "UTF-8"));
 459  0
                         } catch (UnsupportedEncodingException e) {
 460  0
                             throw new JUploadIOException(e.getClass().getName()
 461  0
                                     + ": " + e.getMessage()
 462  
                                     + " (when trying to decode "
 463  
                                     + oneParamArray[1] + ")");
 464  0
                         }
 465  
                     } else {
 466  
                         // There is no value for this parameter
 467  0
                         requestParameters.put(oneParamArray[0], "");
 468  
                     }
 469  
                 }
 470  
             }
 471  
 
 472  
             // Now add one multipart segment for each
 473  0
             Set<Map.Entry<String, String>> entrySet = requestParameters
 474  0
                     .entrySet();
 475  
             Map.Entry<String, String> entry;
 476  0
             Iterator<Map.Entry<String, String>> i = entrySet.iterator();
 477  0
             while (i.hasNext()) {
 478  0
                 entry = i.next();
 479  0
                 bae.appendTextProperty(entry.getKey(), entry.getValue(), -1);
 480  
             }
 481  
         }
 482  
         // Return the body content
 483  0
         bae.close();
 484  
 
 485  0
         return bae;
 486  
     }// getFormParamsForPostRequest
 487  
 }