View Javadoc
1   //
2   // $Id: DefaultFileUploadThread.java 287 2007-06-17 09:07:04 +0000 (dim., 17
3   // juin 2007) felfert $
4   //
5   // jupload - A file upload applet.
6   // Copyright 2007 The JUpload Team
7   //
8   // Created: ?
9   // Creator: William JinHua Kwong
10  // Last modified: $Date: 2015-03-10 20:59:42 +0100 (mar., 10 mars 2015) $
11  //
12  // This program is free software; you can redistribute it and/or modify it under
13  // the terms of the GNU General Public License as published by the Free Software
14  // Foundation; either version 2 of the License, or (at your option) any later
15  // version. This program is distributed in the hope that it will be useful, but
16  // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
17  // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
18  // details. You should have received a copy of the GNU General Public License
19  // along with this program; if not, write to the Free Software Foundation, Inc.,
20  // 675 Mass Ave, Cambridge, MA 02139, USA.
21  
22  package wjhk.jupload2.upload;
23  
24  import java.io.OutputStream;
25  import java.net.SocketException;
26  import java.util.concurrent.BlockingQueue;
27  import java.util.regex.Pattern;
28  
29  import wjhk.jupload2.exception.JUploadException;
30  import wjhk.jupload2.exception.JUploadExceptionUploadFailed;
31  import wjhk.jupload2.exception.JUploadIOException;
32  import wjhk.jupload2.exception.JUploadInterrupted;
33  import wjhk.jupload2.gui.DialogUploadRetry;
34  import wjhk.jupload2.policies.UploadPolicy;
35  
36  /**
37   * This class is based on the {@link FileUploadThread} class. It's an abstract class that contains the default
38   * implementation for the {@link FileUploadThread} interface. <BR>
39   * It contains the following abstract methods, which must be implemented in the children classes. These methods are
40   * called in this order: <DIR> <LI>For each upload request (for instance, upload of 3 files with nbFilesPerRequest to 2,
41   * makes 2 request: 2 files, then the last one): <DIR> <LI><I>try</I> <LI>
42   * {@link #startRequest}: start of the
43   * UploadRequest. <LI>Then, for each file to upload (according to the nbFilesPerRequest and maxChunkSize applet
44   * parameters) <DIR> <LI>beforeFile(int) is called before writting the bytes for this file (or this chunk) <LI>
45   * afterFile(int) is called after writting the bytes for this file (or this chunk) </DIR> <LI>finishRequest() </DIR></LI>
46   * <I>finally</I>cleanRequest() <LI>Call of cleanAll(), to clean up any used resources, common to the whole upload.
47   * </DIR>
48   */
49  public abstract class DefaultFileUploadThread extends Thread implements FileUploadThread {
50  
51      // ////////////////////////////////////////////////////////////////////////
52      // /////////////////////// VARIABLES //////////////////////////////////////
53      // ////////////////////////////////////////////////////////////////////////
54  
55      /**
56       * The queue that'll transmit each packet to upload to the server.
57       */
58      BlockingQueue<UploadFilePacket> packetQueue = null;
59  
60      /**
61       * The upload manager. The thread that prepares files, and is responsible to manage the upload process.
62       * 
63       * @see FileUploadManagerThread
64       */
65      FileUploadManagerThread fileUploadManagerThread = null;
66  
67      /**
68       * The upload policy contains all parameters needed to define the way files should be uploaded, including the URL.
69       */
70      protected UploadPolicy uploadPolicy = null;
71  
72      /**
73       * The value of the applet parameter maxChunkSize, or its default value.
74       */
75      long maxChunkSize;
76  
77      // ////////////////////////////////////////////////////////////////////////////////////
78      // /////////////////////// PRIVATE ATTRIBUTES
79      // ////////////////////////////////////////////////////////////////////////////////////
80  
81      // This variable should only be set to false. Setting to true should be used
82      // only to
83      // debug the JUPload retry feature.
84      static boolean sendResumableError = false;
85  
86      /**
87       * Current retry number, for the resume upload feature. The first try occurs with nbRetry=0.
88       */
89      int nbRetry = 0;
90  
91      /**
92       * The full response message from the server, if any. For instance, in HTTP mode, this contains both the headers and
93       * the body.
94       */
95      protected String responseMsg = "";
96  
97      /**
98       * The response message from the application. For instance, in HTTP mode, this contains the body response.<BR>
99       * Note: for easier management on the various server configurations, all end or line characters (CR, LF or CRLF) are
100      * changed to uniform CRLF.
101      */
102     protected String responseBody = "";
103 
104     /**
105      * Creates a new instance.
106      * 
107      * @param threadName The name of the thread, that will be displayed in the debugger and in the logs.
108      * @param packetQueue The queue from wich packets to upload are available.
109      * @param uploadPolicy The upload policy to be applied.
110      * @param fileUploadManagerThread The thread that is managing the upload.
111      */
112     public DefaultFileUploadThread(String threadName, BlockingQueue<UploadFilePacket> packetQueue,
113             UploadPolicy uploadPolicy, FileUploadManagerThread fileUploadManagerThread) {
114         // Thread parameters.
115         super(threadName);
116 
117         // Specific stuff.
118         this.packetQueue = packetQueue;
119         this.uploadPolicy = uploadPolicy;
120         this.fileUploadManagerThread = fileUploadManagerThread;
121         // Let's read up to date upload parameters.
122         this.maxChunkSize = this.uploadPolicy.getMaxChunkSize();
123 
124         this.uploadPolicy.displayDebug("DefaultFileUploadThread created", 30);
125     }
126 
127     /**
128      * This method calculates the upload overhead for the file number indexFile in the filesDataParam given to the
129      * constructor. For instance, in HTTP, the upload contains a head and a tail for each files.
130      * 
131      * @param uploadFileData The file whose additional length is asked.
132      * @return The additional number of bytes for this file.
133      */
134     abstract long getAdditionnalBytesForUpload(UploadFileData uploadFileData) throws JUploadIOException;
135 
136     /**
137      * This method is called before starting of each request. It can be used to prepare any work, before starting the
138      * request. For instance, in HTTP, the tail must be properly calculated, as the last one must be different from the
139      * others.<BR>
140      * The packets to send are available through the {@link #packetQueue} queue.
141      */
142     abstract void beforeRequest(UploadFilePacket packet) throws JUploadException;
143 
144     /**
145      * This method is called for each upload request to the server. The number of request to the server depends on:
146      * <DIR> <LI>The total number of files to upload. <LI>The value of the nbFilesPerRequest applet parameter. <LI>The
147      * value of the maxChunkSize applet parameter. </DIR> The main objective of this method is to open the connection to
148      * the server, where the files to upload will be written. It should also send any header necessary for this upload
149      * request. The {@link #getOutputStream()} methods is then called to know where the uploaded files should be
150      * written. <BR>
151      * Note: it's up to the class containing this method to internally manage the connection.
152      * 
153      * @param contentLength The total number of bytes for the files (or the chunk) to upload in this query.
154      * @param bChunkEnabled True if this upload is part of a file (can occurs only if the maxChunkSize applet parameter
155      *            is set). False otherwise.
156      * @param chunkPart The chunk number. Should be ignored if bChunkEnabled is false.
157      * @param bLastChunk True if in chunk mode, and this upload is the last one. Should be ignored if bChunkEnabled is
158      *            false.
159      */
160     abstract void startRequest(long contentLength, boolean bChunkEnabled, int chunkPart, boolean bLastChunk)
161             throws JUploadException;
162 
163     /**
164      * This method is called at the end of each request.
165      * 
166      * @return The response status code from the server (200 == OK)
167      * @see #startRequest(long, boolean, int, boolean)
168      */
169     abstract int finishRequest() throws JUploadException;
170 
171     /**
172      * Reaction of the upload thread, when an interruption has been received. This method should close all resource to
173      * the server, to allow the server to free any resource (temporary file, network connection...).
174      */
175     abstract void interruptionReceived();
176 
177     /**
178      * This method is called before sending the bytes corresponding to the file whose index is given in argument. If the
179      * file is splitted in chunks (see the maxChunkSize applet parameter), this method is called before each chunk for
180      * this file.
181      * 
182      * @param uploadFilePacket The bunch of files in the current request
183      * @param uploadFileData The next file that will be sent
184      */
185     abstract void beforeFile(UploadFilePacket uploadFilePacket, UploadFileData uploadFileData) throws JUploadException;
186 
187     /**
188      * Idem as {@link #beforeFile(UploadFilePacket, UploadFileData)}, but is called after each file (and each chunks for
189      * each file).
190      * 
191      * @param uploadFileData The file that was just sent.
192      */
193     abstract void afterFile(UploadFileData uploadFileData) throws JUploadException;
194 
195     /**
196      * Clean any used resource of the last executed request. In HTTP mode, the output stream, input stream and the
197      * socket should be cleaned here.
198      */
199     abstract void cleanRequest() throws JUploadException;
200 
201     /**
202      * Clean any used resource, like a 'permanent' connection. This method is called after the end of the last request
203      * (see on the top of this page for details).
204      */
205     abstract void cleanAll() throws JUploadException;
206 
207     /**
208      * Get the output stream where the files should be written for upload.
209      * 
210      * @return The target output stream for upload.
211      */
212     abstract OutputStream getOutputStream() throws JUploadException;
213 
214     /**
215      * Return the the body for the server response. That is: the server response without the http header. This is the
216      * functional response from the server application, that has been as the HTTP reply body, for instance: all 'echo'
217      * PHP commands. <BR>
218      * 
219      * @return The last application response (HTTP body, in HTTP upload)
220      */
221     public String getResponseBody() {
222         return this.responseBody;
223     }
224 
225     /**
226      * Get the server Output.
227      * 
228      * @return The status message from the first line of the response (e.g. "200 OK").
229      */
230     public String getResponseMsg() {
231         return this.responseMsg;
232     }
233 
234     /**
235      * @return the packetQueue
236      */
237     public BlockingQueue<UploadFilePacket> getPacketQueue() {
238         return packetQueue;
239     }
240 
241     /**
242      * Unused Store the String that contains the server response body.
243      * 
244      * @param body The response body that has been read.
245      */
246     void setResponseBody(String body) {
247         this.responseBody = normalizeCRLF(body);
248     }
249 
250     /**
251      * Add a String that has been read from the server response.
252      * 
253      * @param msg The status message from the first line of the response (e.g. "200 OK").
254      */
255     void setResponseMsg(String msg) {
256         this.responseMsg = normalizeCRLF(msg);
257     }
258 
259     // ////////////////////////////////////////////////////////////////////////////////////
260     // /////////////////////// PRIVATE FUNCTIONS
261     // ////////////////////////////////////////////////////////////////////////////////////
262 
263     /**
264      * This method waits for a packet on the packetQueue. Then, it calls the doUpload() method, to send these files to
265      * the server.
266      */
267     @Override
268     final public void run() {
269         this.uploadPolicy.displayDebug("Start of the FileUploadThread", 5);
270         int nbSentFiles = 0;
271 
272         // We'll stop the upload if an error occurs. So the try/catch is
273         // outside the while.
274         while (!this.fileUploadManagerThread.isUploadFinished()) {
275             UploadFilePacket packet = null;
276 
277             try {
278                 // We take the next packet. This method will block until a packet is ready.
279                 packet = packetQueue.take();
280 
281                 // If the packet is 'poisonned', then it's the standard end of work.
282                 if (packet.isPoisonned()) {
283                     break;
284                 }
285 
286                 // /////////////////////////////////////////////////////////////////////////////////
287                 // Let's go to work : THIS IS THE UPLOAD, surrounded by the
288                 // RESUME LOOP
289                 // /////////////////////////////////////////////////////////////////////////////////
290                 nbRetry = 0;
291                 while (true) {
292                     try {
293                         // Let's try to upload the current packet.
294                         doUpload(packet);
295 
296                         // If we are here, the last upload is a success. Let's
297                         // exit the loop.
298                         break;
299                     } catch (JUploadException jue) {
300                         // manageRetry throw the exception, if no retry should
301                         // be done.
302                         manageRetry(jue);
303 
304                         // If we are here, the applet should retry the upload.
305                         // We let it loop again.
306                         nbRetry += 1;
307 
308                         // We must clean the resources of the previous attempt.
309                         beforeRetry(packet);
310                     }
311                 }// while(resume)
312                  // /////////////////////////////////////////////////////////////////////////////////
313                  // //////////////// ENF OF RESUME LOOP
314                  // /////////////////////////////////////////////////////////////////////////////////
315 
316                 this.uploadPolicy.displayDebug("After do upload", 50);
317             } catch (InterruptedException e) {
318                 this.uploadPolicy.displayWarn(this.getClass().getName() + ".run(): received in "
319                         + e.getClass().getName() + ", exiting...");
320                 break;
321             } catch (JUploadException e) {
322                 if (this.fileUploadManagerThread.isUploadFinished()) {
323                     // We ignore the error
324                     this.uploadPolicy.displayWarn("Ignoring " + e.getClass().getName() + " because upload is finished");
325                 } else {
326                     this.fileUploadManagerThread.setUploadException(e);
327                 }
328             } catch (JUploadInterrupted e) {
329                 // The upload has been interrupted, probably by the user
330                 // (stop
331                 // button). The fileManagerUploadThread aleady knows this.
332                 this.uploadPolicy.displayInfo("Upload stopped by the user");
333                 this.uploadPolicy.displayDebug(e.getMessage(), 30);
334             } finally {
335                 // Let's free any locked resource for the current packet.
336                 // This is done here, to allow the resume feature (and, even in
337                 // case an error occurs, we free resources only after the last
338                 // retry)
339                 for (UploadFileData uploadFileData : packet) {
340                     if (uploadFileData.isPreparedForUpload()) {
341                         uploadFileData.afterUpload();
342                     }
343                 }
344                 // We free any resources associated with this thread.
345                 try {
346                     cleanAll();
347                 } catch (Exception e) {
348                     // We ignore the error
349                     this.uploadPolicy.displayWarn("[run(), After cleanAll] Ignoring " + e.getClass().getName()
350                             + " because upload is finished");
351                 }
352             }
353 
354             nbSentFiles += packet.size();
355         }// while (!isUploadFinished)
356 
357         this.uploadPolicy.displayDebug("End of the FileUploadThread (" + nbSentFiles + " have been sent)", 5);
358     }// run
359 
360     /**
361      * @param jue
362      * @throws JUploadException
363      */
364     private void manageRetry(JUploadException jue) throws JUploadException {
365         String exceptionCauseClass = (jue.getCause() == null) ? "no exception cause" : jue.getCause().getClass()
366                 .getName();
367         String errMsg = jue.getClass().getName() + " (" + jue.getMessage() + "), caused by: " + exceptionCauseClass;
368 
369         if (this.fileUploadManagerThread.isUploadFinished()) {
370             // The upload is stopped. This error may be caused
371             this.uploadPolicy.displayWarn("The following error occurs, but the upload is stopped, ignoring it ]"
372                     + errMsg + "]");
373             throw jue;
374         } else if (jue.getCause() instanceof SocketException) {
375             this.uploadPolicy.displayWarn("A 'resumable' error occurred: " + errMsg);
376             // If it was the last retry, we stop here.
377             if (nbRetry >= this.uploadPolicy.getRetryMaxNumberOf()) {
378                 this.uploadPolicy.displayWarn("Too much retries (" + nbRetry + "), exiting...");
379                 throw jue;
380             }
381 
382             DialogUploadRetry dialogUploadRetry = new DialogUploadRetry(this.uploadPolicy.getContext().getFrame(), jue,
383                     nbRetry, this.uploadPolicy);
384             // The constructor returns, when the dialog is closed. Let's check
385             // the user answer:
386             if (dialogUploadRetry.isRetryValidated()) {
387                 this.uploadPolicy.displayDebug("The user (or the timer) choosed to retry the upload", 30);
388             } else {
389                 this.uploadPolicy.displayDebug("The user refuses to retry the upload, exiting...", 30);
390                 // No retry, let's note the exception and go out
391                 throw jue;
392             }// End of resumable exceptions management.
393         } else {
394             // This exception can't be resumed. We transmit it.
395             this.uploadPolicy.displayWarn("Non resumable error occured, exiting...");
396             throw jue;
397         }
398     }
399 
400     /**
401      * Actual execution file(s) upload. It's called by the run methods, once for all files, or file by file, depending
402      * on the UploadPolicy. The list of files to upload is stored in the packet parameter.<BR>
403      * This method is called by the run() method. The prerequisite about the filesToUpload array are: <DIR> <LI>If the
404      * sum of contentLength for the files in the array is more than the maxChunkSize, then nbFilesToUploadParam is one.
405      * <LI>The number of elements in filesToUpload is less (or equal) than the nbMaxFilesPerUpload. </DIR>
406      * 
407      * @throws JUploadException
408      * @throws JUploadInterrupted Thrown when an interruption of the thread is detected.
409      */
410     void doUpload(UploadFilePacket packet) throws JUploadException, JUploadInterrupted {
411         boolean bChunkEnabled = false;
412         long totalContentLength = 0;
413         long totalFileLength = 0;
414 
415         // We are about to start a new upload.
416         this.fileUploadManagerThread.setUploadStatus(packet, packet.get(0),
417                 FileUploadManagerThread.UPLOAD_STATUS_UPLOADING);
418 
419         // Prepare upload, for all files to be uploaded.
420         beforeRequest(packet);
421 
422         for (UploadFileData uploadFileData : packet) {
423             // The upload may be finished, while we're working on the files...
424             if (this.fileUploadManagerThread.isUploadFinished()) {
425                 // Let's stop our work here.
426                 return;
427             }
428             // Total length, for HTTP upload.
429             totalContentLength += uploadFileData.getUploadLength();
430             totalContentLength += getAdditionnalBytesForUpload(uploadFileData);
431             // Total file length: used to manage the progress bar (we don't
432             // follow the bytes uploaded within headers and forms).
433             totalFileLength += uploadFileData.getUploadLength();
434 
435             this.uploadPolicy.displayDebug(
436                     "file " + uploadFileData.getFileName() + ": content=" + uploadFileData.getUploadLength()
437                             + " bytes, getAdditionnalBytesForUpload=" + getAdditionnalBytesForUpload(uploadFileData)
438                             + " bytes", 50);
439         }// for
440 
441         // Ok, now we check that the totalContentLength is less than the chunk
442         // size.
443         if (totalFileLength >= this.maxChunkSize) {
444             // hum, hum, we have to download file by file, with chunk enabled.
445             // This a prerequisite of this method.
446             if (packet.size() > 1) {
447                 this.fileUploadManagerThread.setUploadException(new JUploadException(
448                         "totalContentLength >= chunkSize: this.filesToUpload.length should be 1 (doUpload)"));
449             }
450             bChunkEnabled = true;
451         }
452 
453         // Now, we can actually do the job. This is delegate into smaller
454         // method, for easier understanding.
455         if (bChunkEnabled) {
456             // No more than one file, when in chunk mode.
457             if (packet.size() > 1) {
458                 throw new JUploadException(
459                         "totalContentLength >= chunkSize: this.filesToUpload.length should not be more than 1 (doUpload)");
460             }
461             doChunkedUpload(packet);
462         } else {
463             doNonChunkedUpload(packet, totalContentLength, totalFileLength);
464         }
465 
466         // If the request properly finished, we remove the files from the list
467         // of files to upload.
468         if (this.fileUploadManagerThread.getUploadException() == null
469                 && !this.fileUploadManagerThread.isUploadStopped()) {
470             this.fileUploadManagerThread.currentRequestIsFinished(packet);
471         }
472     }
473 
474     /**
475      * Execution of an upload, in chunk mode. This method expects that the given packet contains only one file.
476      * 
477      * @param packet The packet that contains the file to upload in chunk mode
478      * @throws JUploadException When any error occurs, or when there is more than one file in packet.
479      * @throws JUploadInterrupted Thrown when an interruption of the thread is detected.
480      */
481     void doChunkedUpload(UploadFilePacket packet) throws JUploadException, JUploadInterrupted {
482         boolean bLastChunk = false;
483         int chunkPart = 0;
484 
485         long contentLength = 0;
486         long thisChunkSize = 0;
487 
488         if (packet.size() > 1) {
489             throw new JUploadException("doChunkedUpload called with a packet of more than 1 file (" + packet.size()
490                     + " files)");
491         }
492         UploadFileData uploadFileData = packet.get(0);
493 
494         // This while enables the chunk management:
495         // In chunk mode, it loops until the last chunk is uploaded. This works
496         // only because, in chunk mode, files are uploaded one y one (the for
497         // loop within the while loops through ... 1 unique file).
498         // In normal mode, it does nothing, as the bLastChunk is set to true in
499         // the first test, within the while.
500         while (!bLastChunk && !this.fileUploadManagerThread.isUploadFinished()) {
501             // Let's manage chunk:
502             // Files are uploaded one by one. This is checked just above.
503             chunkPart += 1;
504             bLastChunk = (uploadFileData.getRemainingLength() <= this.maxChunkSize);
505 
506             // Is this the last chunk ?
507             if (bLastChunk) {
508                 thisChunkSize = uploadFileData.getRemainingLength();
509             } else {
510                 thisChunkSize = this.maxChunkSize;
511             }
512             contentLength = thisChunkSize + getAdditionnalBytesForUpload(uploadFileData);
513 
514             // We are about to start a new upload.
515             this.fileUploadManagerThread.setUploadStatus(packet, uploadFileData,
516                     FileUploadManagerThread.UPLOAD_STATUS_UPLOADING);
517 
518             // Ok, we've prepare the job for chunk upload. Let's do it!
519             startRequest(contentLength, true, chunkPart, bLastChunk);
520 
521             try {
522 
523                 // Let's add any file-specific header.
524                 beforeFile(packet, uploadFileData);
525 
526                 // Actual upload of the file:
527                 uploadFileData.uploadFile(getOutputStream(), thisChunkSize);
528 
529                 // Caution : this is for debug only. In production mode,
530                 // sendResumableError should always be 'false'
531                 if (chunkPart == 2 && sendResumableError) {
532                     sendResumableError = false;
533                     throw new JUploadException(new SocketException(
534                             "This is a debug error. Should not happen in production."));
535                 }
536 
537                 // If we are not in chunk mode, or if it was the last chunk,
538                 // upload should be finished.
539                 if (bLastChunk && uploadFileData.getRemainingLength() > 0) {
540                     throw new JUploadExceptionUploadFailed("Files has not be entirely uploaded. The remaining size is "
541                             + uploadFileData.getRemainingLength() + " bytes. File size was: "
542                             + uploadFileData.getUploadLength() + " bytes.");
543 
544                 }
545                 // Let's add any file-specific header.
546                 afterFile(uploadFileData);
547 
548                 // Let's finish the request, and wait for the server Output, if
549                 // any (not applicable in FTP)
550                 int status = finishRequest();
551 
552                 if (bLastChunk) {
553                     // We are finished with this one. Let's display it.
554                     this.fileUploadManagerThread.setUploadStatus(packet, uploadFileData,
555                             FileUploadManagerThread.UPLOAD_STATUS_FILE_UPLOADED_WAITING_FOR_RESPONSE);
556                 } else {
557                     // We are finished with the current chunk, but not with the
558                     // file. Let's display it.
559                     this.fileUploadManagerThread.setUploadStatus(packet, uploadFileData,
560                             FileUploadManagerThread.UPLOAD_STATUS_CHUNK_UPLOADED_WAITING_FOR_RESPONSE);
561                 }
562 
563                 // We now ask to the uploadPolicy, if it was a success.
564                 // If not, the isUploadSuccessful should raise an exception.
565                 this.uploadPolicy.checkUploadSuccess(status, getResponseMsg(), getResponseBody());
566             }// try
567             finally {
568                 cleanRequest();
569             }
570         }// while
571          // Let's tell our manager that we've done the job!
572         this.fileUploadManagerThread.anotherFileHasBeenSent(packet, uploadFileData);
573 
574     }// doChunkedUpload
575 
576     /**
577      * Execution of an upload, in standard mode. This method uploads all files in the given packet.
578      * 
579      * @param packet The files to upload in the current request to the server
580      * @param totalContentLength The total size of the upload, including any protocol-specific header or footer.
581      * @param totalFileLength The sum of each file length.
582      * @throws JUploadException When any error occurs
583      * @throws JUploadInterrupted Thrown when an interruption of the thread is detected.
584      */
585     void doNonChunkedUpload(UploadFilePacket packet, final long totalContentLength, final long totalFileLength)
586             throws JUploadException, JUploadInterrupted {
587 
588         // First step is to prepare all files.
589         startRequest(totalContentLength, false, 0, true);
590 
591         try {
592 
593             // Then, upload each file.
594             for (UploadFileData uploadFileData : packet) {
595                 if (this.fileUploadManagerThread.isUploadFinished()) {
596                     // Upload is finished (by the user or because of an error,
597                     // or instance)
598                     break;
599                 }
600                 // We are about to start a new upload.
601                 this.fileUploadManagerThread.setUploadStatus(packet, uploadFileData,
602                         FileUploadManagerThread.UPLOAD_STATUS_UPLOADING);
603 
604                 // Let's add any file-specific header.
605                 beforeFile(packet, uploadFileData);
606 
607                 // Actual upload of the file:
608                 if (!this.fileUploadManagerThread.isUploadFinished()) {
609                     uploadFileData.uploadFile(getOutputStream(), uploadFileData.getUploadLength());
610                 }
611 
612                 // Let's add any file-specific header.
613                 if (!this.fileUploadManagerThread.isUploadFinished()) {
614                     afterFile(uploadFileData);
615 
616                     // Let's tell our manager that we've done the job!
617                     // Ok, maybe the server will refuse it, but we won't say
618                     // that
619                     // now!
620                     this.fileUploadManagerThread.anotherFileHasBeenSent(packet, uploadFileData);
621                 }
622             }
623 
624             // We are finished with this one. Let's display it.
625             if (!this.fileUploadManagerThread.isUploadFinished()) {
626                 this.fileUploadManagerThread.setUploadStatus(packet, packet.get(packet.size() - 1),
627                         FileUploadManagerThread.UPLOAD_STATUS_FILE_UPLOADED_WAITING_FOR_RESPONSE);
628 
629                 // Let's finish the request, and wait for the server Output, if
630                 // any (not applicable in FTP)
631                 int status = finishRequest();
632 
633                 // We now ask to the uploadPolicy, if it was a success.
634                 // If not, the isUploadSuccessful should raise an exception.
635                 this.uploadPolicy.checkUploadSuccess(status, getResponseMsg(), getResponseBody());
636             }
637         }// try
638         finally {
639             cleanRequest();
640         }
641 
642     }// doNonChunkedUpload
643 
644     /**
645      * Clean any resource of the last attempt for this packet, which would be in inconsistent step, in order to retry
646      * the upload of the current packet.
647      */
648     private void beforeRetry(UploadFilePacket packet) throws JUploadException {
649         if (packet != null) {
650             for (UploadFileData uploadFileData : packet) {
651                 if (uploadFileData.isPreparedForUpload()) {
652                     uploadFileData.beforeRetry();
653                 }
654             }
655         }
656     }
657 
658     /** @see FileUploadThread#close() */
659     public void close() {
660         try {
661             cleanAll();
662         } catch (JUploadException e) {
663             this.uploadPolicy.displayErr(e);
664         }
665     }
666 
667     /**
668      * Replace single \r and \n by uniform end of line characters (CRLF). This makes it easier, to search for string
669      * within the body.
670      * 
671      * @param s The original string
672      * @return The string with single \r and \n modified changed to CRLF (\r\n).
673      */
674     public final String normalizeCRLF(String s) {
675         Pattern p = Pattern.compile("\\r\\n|\\r|\\n", Pattern.MULTILINE);
676         String[] lines = p.split(s);
677         // Worst case: the s string contains only \n or \r characters: we then
678         // need to triple the string length. Let's say double is enough.
679         StringBuffer sb = new StringBuffer(s.length() * 2);
680         for (int i = 0; i < lines.length; i += 1) {
681             sb.append(lines[i]).append("\r\n");
682         }
683 
684         return sb.toString();
685     }
686 
687     /**
688      * Replace \r and \n by correctly displayed end of line characters. Used to display debug output. It also replace
689      * any single \r or \n by \r\n, to make it easier, to search for string within the body.
690      * 
691      * @param s The original string
692      * @return The string with \r and \n modified, to be correctly displayed.
693      */
694     public final String quoteCRLF(String s) {
695         return s.replaceAll("\r\n", "\\\\r\\\\n\n");
696     }
697 
698     /**
699      * {@inheritDoc}
700      */
701     public void setFileUploadThreadManager(FileUploadManagerThread fileUploadManagerThread) throws JUploadException {
702         if (this.fileUploadManagerThread != null) {
703             throw new JUploadException(
704                     "Can not override fileUploadManagerThread (in DefaultFileUpload.setFileUploadThreadManager()");
705         }
706         this.fileUploadManagerThread = fileUploadManagerThread;
707     }
708 }