source: 3thparty/jupload/src/main/java/wjhk/jupload2/upload/FileUploadThreadHTTP.java @ 3951

Revision 3951, 19.2 KB checked in by alexandrecorreia, 13 years ago (diff)

Ticket #1709 - Adicao de codigo fonte java do componente jupload

Line 
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 10:27:42 -0200 (Ter, 14 Dez 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.
20package wjhk.jupload2.upload;
21
22import java.io.IOException;
23import java.io.OutputStream;
24import java.io.UnsupportedEncodingException;
25import java.net.URL;
26import java.net.URLDecoder;
27import java.net.URLEncoder;
28import java.util.HashMap;
29import java.util.Iterator;
30import java.util.Map;
31import java.util.Set;
32import java.util.concurrent.BlockingQueue;
33
34import wjhk.jupload2.exception.JUploadException;
35import wjhk.jupload2.exception.JUploadIOException;
36import wjhk.jupload2.policies.UploadPolicy;
37import wjhk.jupload2.upload.helper.ByteArrayEncoder;
38import wjhk.jupload2.upload.helper.ByteArrayEncoderHTTP;
39import 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 */
47public 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    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    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    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        super("FileUploadThreadHTTP thread", packetQueue, uploadPolicy,
82                fileUploadManagerThread);
83        this.uploadPolicy.displayDebug("  Using " + this.getClass().getName(),
84                30);
85
86        uploadPolicy.displayDebug("Upload done by using the "
87                + getClass().getName() + " class", 30);
88        // Name the thread (useful for debugging)
89        setName("FileUploadThreadHTTP");
90        // FIXME There are two such initializations in this class. Necessary ??
91        this.connectionHelper = new HTTPConnectionHelper(uploadPolicy);
92    }
93
94    /** @see DefaultFileUploadThread#beforeRequest(UploadFilePacket) */
95    @Override
96    void beforeRequest(UploadFilePacket packet) throws JUploadException {
97        if (this.connectionHelper != null) {
98            // It must be retring an upload. We clear any previous work.
99            this.connectionHelper.dispose();
100        }
101        this.connectionHelper = new HTTPConnectionHelper(uploadPolicy);
102        setAllHead(packet, this.connectionHelper.getBoundary());
103        setAllTail(packet, this.connectionHelper.getBoundary());
104    }
105
106    /** @see DefaultFileUploadThread#getAdditionnalBytesForUpload(UploadFileData) */
107    @Override
108    long getAdditionnalBytesForUpload(UploadFileData uploadFileData)
109            throws JUploadIOException {
110        return this.heads.get(uploadFileData).getEncodedLength()
111                + this.tails.get(uploadFileData).getEncodedLength();
112    }
113
114    /** @see DefaultFileUploadThread#afterFile(UploadFileData) */
115    @Override
116    void afterFile(UploadFileData uploadFileData) throws JUploadIOException {
117        this.connectionHelper.append(this.tails.get(uploadFileData));
118        this.uploadPolicy.displayDebug("--- filetail start (len="
119                + this.tails.get(uploadFileData).getEncodedLength() + "):", 70);
120        this.uploadPolicy.displayDebug(quoteCRLF(this.tails.get(uploadFileData)
121                .getString()), 70);
122        this.uploadPolicy.displayDebug("--- filetail end", 70);
123    }
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            this.connectionHelper.append(this.heads.get(uploadFileData)
135                    .getEncodedByteArray());
136
137            // Debug output: always called, so that the debug file is correctly
138            // filled.
139            this.uploadPolicy.displayDebug("--- fileheader start (len="
140                    + this.heads.get(uploadFileData).getEncodedLength() + "):",
141                    70);
142            this.uploadPolicy.displayDebug(quoteCRLF(this.heads.get(
143                    uploadFileData).getString()), 70);
144            this.uploadPolicy.displayDebug("--- fileheader end", 70);
145        } catch (Exception e) {
146            throw new JUploadException(e);
147        }
148    }
149
150    /** @see DefaultFileUploadThread#cleanAll() */
151    @Override
152    void cleanAll() throws JUploadException {
153        // Nothing to do in HTTP mode.
154    }
155
156    /** @see DefaultFileUploadThread#cleanRequest() */
157    @Override
158    void cleanRequest() throws JUploadException {
159        try {
160            this.connectionHelper.dispose();
161        } catch (JUploadIOException e) {
162            this.uploadPolicy.displayErr(this.uploadPolicy
163                    .getLocalizedString("errDuringUpload"), e);
164            throw e;
165        }
166    }
167
168    @Override
169    int finishRequest() throws JUploadException {
170        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                Thread.sleep(400);
175            } catch (InterruptedException e) {
176            }
177        }
178        int status = this.connectionHelper.readHttpResponse();
179        setResponseMsg(this.connectionHelper.getResponseMsg());
180        setResponseBody(this.connectionHelper.getResponseBody());
181        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            if (this.connectionHelper != null) {
193                this.connectionHelper.dispose();
194                this.connectionHelper = null;
195            }
196
197            if (this.heads != null) {
198                for (UploadFileData uploadFileData : this.heads.keySet()) {
199                    ByteArrayEncoder bae = this.heads.get(uploadFileData);
200                    if (bae != null) {
201                        bae.close();
202                    }
203                }
204                this.heads = null;
205            }
206            if (this.tails != null) {
207                for (UploadFileData uploadFileData : this.tails.keySet()) {
208                    ByteArrayEncoder bae = this.tails.get(uploadFileData);
209                    if (bae != null) {
210                        bae.close();
211                    }
212                }
213                this.tails = null;
214            }
215        } catch (Exception e) {
216            this.uploadPolicy.displayWarn("Exception in "
217                    + getClass().getName() + ".interruptionReceived() ("
218                    + e.getClass().getName() + "): " + e.getMessage());
219        }
220    }
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        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            String chunkHttpParam = "jupart=" + chunkPart + "&jufinal="
240                    + (bLastChunk ? "1" : "0");
241            this.uploadPolicy.displayDebug("chunkHttpParam: " + chunkHttpParam,
242                    30);
243
244            URL url = new URL(this.uploadPolicy.getPostURL());
245
246            // Add the chunking query params to the URL if there are any
247            if (bChunkEnabled) {
248                if (null != url.getQuery() && !"".equals(url.getQuery())) {
249                    url = new URL(url.toExternalForm() + "&" + chunkHttpParam);
250                } else {
251                    url = new URL(url.toExternalForm() + "?" + chunkHttpParam);
252                }
253            }
254
255            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            ByteArrayEncoder formParams = getFormParamsForPostRequest(url);
261            contentLength += formParams.getEncodedLength();
262
263            this.connectionHelper.append(
264                    "Content-Type: multipart/form-data; boundary=").append(
265                    this.connectionHelper.getBoundary().substring(2)).append(
266                    "\r\n");
267            this.connectionHelper.append("Content-Length: ").append(
268                    String.valueOf(contentLength)).append("\r\n");
269
270            // Blank line (end of header)
271            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            this.connectionHelper.append(formParams);
279
280            // Let's call the server
281            this.connectionHelper.sendRequest();
282
283            // Debug output: always called, so that the debug file is correctly
284            // filled.
285            this.uploadPolicy.displayDebug("=== main header (len="
286                    + this.connectionHelper.getByteArrayEncoder()
287                            .getEncodedLength()
288                    + "):\n"
289                    + quoteCRLF(this.connectionHelper.getByteArrayEncoder()
290                            .getString()), 70);
291            this.uploadPolicy.displayDebug("=== main header end", 70);
292        } catch (IOException e) {
293            throw new JUploadIOException(e);
294        } catch (IllegalArgumentException e) {
295            throw new JUploadException(e);
296        }
297    }
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        String filenameEncoding = this.uploadPolicy.getFilenameEncoding();
318        String mimetype = uploadFileData.getMimeType();
319        String uploadFilename = uploadFileData
320                .getUploadFilename(numInCurrentUpload);
321        ByteArrayEncoder bae = new ByteArrayEncoderHTTP(this.uploadPolicy,
322                bound);
323
324        if (numInCurrentUpload == 0) {
325            // Only once when uploading multiple files in same request.
326            // We'll encode the output stream into UTF-8.
327            String form = this.uploadPolicy.getFormdata();
328            if (null != form) {
329                bae.appendFormVariables(form);
330            }
331        }
332        // We ask the current FileData to add itself its properties.
333        uploadFileData.appendFileProperties(bae, numInCurrentUpload);
334
335        // boundary.
336        bae.append(bound).append("\r\n");
337
338        // Content-Disposition.
339        bae.append("Content-Disposition: form-data; name=\"");
340        bae.append(uploadFileData.getUploadName(numInCurrentUpload)).append(
341                "\"; filename=\"");
342        if (filenameEncoding == null) {
343            bae.append(uploadFilename);
344        } else {
345            try {
346                this.uploadPolicy.displayDebug("Encoded filename: "
347                        + URLEncoder.encode(uploadFilename, filenameEncoding),
348                        70);
349                bae.append(URLEncoder.encode(uploadFilename, filenameEncoding));
350            } catch (UnsupportedEncodingException e) {
351                this.uploadPolicy
352                        .displayWarn(e.getClass().getName() + ": "
353                                + e.getMessage()
354                                + " (in UploadFileData.getFileHeader)");
355                bae.append(uploadFilename);
356            }
357        }
358        bae.append("\"\r\n");
359
360        // Line 3: Content-Type.
361        bae.append("Content-Type: ").append(mimetype).append("\r\n");
362
363        // An empty line to finish the header.
364        bae.append("\r\n");
365
366        // The ByteArrayEncoder is now filled.
367        bae.close();
368        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        this.heads = new HashMap<UploadFileData, ByteArrayEncoder>(packet
381                .size());
382        int numInCurrentUpload = 0;
383        for (UploadFileData uploadFileData : packet) {
384            this.heads.put(uploadFileData, getFileHeader(uploadFileData,
385                    numInCurrentUpload++, bound, -1));
386        }
387    }
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        this.tails = new HashMap<UploadFileData, ByteArrayEncoder>(packet
397                .size());
398        for (int i = 0; i < packet.size(); i++) {
399            // We'll encode the output stream into UTF-8.
400            ByteArrayEncoder bae = new ByteArrayEncoderHTTP(this.uploadPolicy,
401                    bound);
402
403            bae.append("\r\n");
404
405            if (this.uploadPolicy.getSendMD5Sum()) {
406                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            if (i == packet.size() - 1) {
412                bae.append(bound).append("--\r\n");
413            }
414
415            // Let's store this tail.
416            bae.close();
417
418            this.tails.put(packet.get(i), bae);
419        }
420
421    }
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        ByteArrayEncoder bae = new ByteArrayEncoderHTTP(this.uploadPolicy,
436                this.connectionHelper.getBoundary());
437
438        // Get the query string
439        String query = url.getQuery();
440
441        if (null != query) {
442            // Split this into parameters
443            HashMap<String, String> requestParameters = new HashMap<String, String>();
444            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            for (String param : paramPairs) {
451                if (param.contains("=")) {
452                    oneParamArray = param.split("=");
453                    if (oneParamArray.length > 1) {
454                        // There is a value for this parameter
455                        try {
456                            // Correction of URL double encoding bug
457                            requestParameters.put(oneParamArray[0], URLDecoder
458                                    .decode(oneParamArray[1], "UTF-8"));
459                        } catch (UnsupportedEncodingException e) {
460                            throw new JUploadIOException(e.getClass().getName()
461                                    + ": " + e.getMessage()
462                                    + " (when trying to decode "
463                                    + oneParamArray[1] + ")");
464                        }
465                    } else {
466                        // There is no value for this parameter
467                        requestParameters.put(oneParamArray[0], "");
468                    }
469                }
470            }
471
472            // Now add one multipart segment for each
473            Set<Map.Entry<String, String>> entrySet = requestParameters
474                    .entrySet();
475            Map.Entry<String, String> entry;
476            Iterator<Map.Entry<String, String>> i = entrySet.iterator();
477            while (i.hasNext()) {
478                entry = i.next();
479                bae.appendTextProperty(entry.getKey(), entry.getValue(), -1);
480            }
481        }
482        // Return the body content
483        bae.close();
484
485        return bae;
486    }// getFormParamsForPostRequest
487}
Note: See TracBrowser for help on using the repository browser.