View Javadoc
1   /*
2    * Copyright 2012 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.handler.codec.http.multipart;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.handler.codec.http.HttpConstants;
20  import io.netty.util.internal.EmptyArrays;
21  import io.netty.util.internal.logging.InternalLogger;
22  import io.netty.util.internal.logging.InternalLoggerFactory;
23  
24  import java.io.File;
25  import java.io.FileInputStream;
26  import java.io.FileOutputStream;
27  import java.io.IOException;
28  import java.io.InputStream;
29  import java.nio.ByteBuffer;
30  import java.nio.channels.FileChannel;
31  import java.nio.charset.Charset;
32  
33  import static io.netty.buffer.Unpooled.*;
34  
35  /**
36   * Abstract Disk HttpData implementation
37   */
38  public abstract class AbstractDiskHttpData extends AbstractHttpData {
39  
40      private static final InternalLogger logger = InternalLoggerFactory.getInstance(AbstractDiskHttpData.class);
41  
42      private File file;
43      private boolean isRenamed;
44      private FileChannel fileChannel;
45  
46      protected AbstractDiskHttpData(String name, Charset charset, long size) {
47          super(name, charset, size);
48      }
49  
50      /**
51       *
52       * @return the real DiskFilename (basename)
53       */
54      protected abstract String getDiskFilename();
55      /**
56       *
57       * @return the default prefix
58       */
59      protected abstract String getPrefix();
60      /**
61       *
62       * @return the default base Directory
63       */
64      protected abstract String getBaseDirectory();
65      /**
66       *
67       * @return the default postfix
68       */
69      protected abstract String getPostfix();
70      /**
71       *
72       * @return True if the file should be deleted on Exit by default
73       */
74      protected abstract boolean deleteOnExit();
75  
76      /**
77       * @return a new Temp File from getDiskFilename(), default prefix, postfix and baseDirectory
78       */
79      private File tempFile() throws IOException {
80          String newpostfix;
81          String diskFilename = getDiskFilename();
82          if (diskFilename != null) {
83              newpostfix = '_' + diskFilename;
84          } else {
85              newpostfix = getPostfix();
86          }
87          File tmpFile;
88          if (getBaseDirectory() == null) {
89              // create a temporary file
90              tmpFile = File.createTempFile(getPrefix(), newpostfix);
91          } else {
92              tmpFile = File.createTempFile(getPrefix(), newpostfix, new File(
93                      getBaseDirectory()));
94          }
95          if (deleteOnExit()) {
96              tmpFile.deleteOnExit();
97          }
98          return tmpFile;
99      }
100 
101     @Override
102     public void setContent(ByteBuf buffer) throws IOException {
103         if (buffer == null) {
104             throw new NullPointerException("buffer");
105         }
106         try {
107             size = buffer.readableBytes();
108             checkSize(size);
109             if (definedSize > 0 && definedSize < size) {
110                 throw new IOException("Out of size: " + size + " > " + definedSize);
111             }
112             if (file == null) {
113                 file = tempFile();
114             }
115             if (buffer.readableBytes() == 0) {
116                 // empty file
117                 if (!file.createNewFile()) {
118                     throw new IOException("file exists already: " + file);
119                 }
120                 return;
121             }
122             FileOutputStream outputStream = new FileOutputStream(file);
123             try {
124                 FileChannel localfileChannel = outputStream.getChannel();
125                 ByteBuffer byteBuffer = buffer.nioBuffer();
126                 int written = 0;
127                 while (written < size) {
128                     written += localfileChannel.write(byteBuffer);
129                 }
130                 buffer.readerIndex(buffer.readerIndex() + written);
131                 localfileChannel.force(false);
132             } finally {
133                 outputStream.close();
134             }
135             setCompleted();
136         } finally {
137             // Release the buffer as it was retained before and we not need a reference to it at all
138             // See https://github.com/netty/netty/issues/1516
139             buffer.release();
140         }
141     }
142 
143     @Override
144     public void addContent(ByteBuf buffer, boolean last)
145             throws IOException {
146         if (buffer != null) {
147             try {
148                 int localsize = buffer.readableBytes();
149                 checkSize(size + localsize);
150                 if (definedSize > 0 && definedSize < size + localsize) {
151                     throw new IOException("Out of size: " + (size + localsize) +
152                             " > " + definedSize);
153                 }
154                 ByteBuffer byteBuffer = buffer.nioBufferCount() == 1 ? buffer.nioBuffer() : buffer.copy().nioBuffer();
155                 int written = 0;
156                 if (file == null) {
157                     file = tempFile();
158                 }
159                 if (fileChannel == null) {
160                     FileOutputStream outputStream = new FileOutputStream(file);
161                     fileChannel = outputStream.getChannel();
162                 }
163                 while (written < localsize) {
164                     written += fileChannel.write(byteBuffer);
165                 }
166                 size += localsize;
167                 buffer.readerIndex(buffer.readerIndex() + written);
168             } finally {
169                 // Release the buffer as it was retained before and we not need a reference to it at all
170                 // See https://github.com/netty/netty/issues/1516
171                 buffer.release();
172             }
173         }
174         if (last) {
175             if (file == null) {
176                 file = tempFile();
177             }
178             if (fileChannel == null) {
179                 FileOutputStream outputStream = new FileOutputStream(file);
180                 fileChannel = outputStream.getChannel();
181             }
182             fileChannel.force(false);
183             fileChannel.close();
184             fileChannel = null;
185             setCompleted();
186         } else {
187             if (buffer == null) {
188                 throw new NullPointerException("buffer");
189             }
190         }
191     }
192 
193     @Override
194     public void setContent(File file) throws IOException {
195         if (this.file != null) {
196             delete();
197         }
198         this.file = file;
199         size = file.length();
200         checkSize(size);
201         isRenamed = true;
202         setCompleted();
203     }
204 
205     @Override
206     public void setContent(InputStream inputStream) throws IOException {
207         if (inputStream == null) {
208             throw new NullPointerException("inputStream");
209         }
210         if (file != null) {
211             delete();
212         }
213         file = tempFile();
214         FileOutputStream outputStream = new FileOutputStream(file);
215         int written = 0;
216         try {
217             FileChannel localfileChannel = outputStream.getChannel();
218             byte[] bytes = new byte[4096 * 4];
219             ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
220             int read = inputStream.read(bytes);
221             while (read > 0) {
222                 byteBuffer.position(read).flip();
223                 written += localfileChannel.write(byteBuffer);
224                 checkSize(written);
225                 read = inputStream.read(bytes);
226             }
227             localfileChannel.force(false);
228         } finally {
229             outputStream.close();
230         }
231         size = written;
232         if (definedSize > 0 && definedSize < size) {
233             if (!file.delete()) {
234                 logger.warn("Failed to delete: {}", file);
235             }
236             file = null;
237             throw new IOException("Out of size: " + size + " > " + definedSize);
238         }
239         isRenamed = true;
240         setCompleted();
241     }
242 
243     @Override
244     public void delete() {
245         if (fileChannel != null) {
246             try {
247                 fileChannel.force(false);
248                 fileChannel.close();
249             } catch (IOException e) {
250                 logger.warn("Failed to close a file.", e);
251             }
252             fileChannel = null;
253         }
254         if (! isRenamed) {
255             if (file != null && file.exists()) {
256                 if (!file.delete()) {
257                     logger.warn("Failed to delete: {}", file);
258                 }
259             }
260             file = null;
261         }
262     }
263 
264     @Override
265     public byte[] get() throws IOException {
266         if (file == null) {
267             return EmptyArrays.EMPTY_BYTES;
268         }
269         return readFrom(file);
270     }
271 
272     @Override
273     public ByteBuf getByteBuf() throws IOException {
274         if (file == null) {
275             return EMPTY_BUFFER;
276         }
277         byte[] array = readFrom(file);
278         return wrappedBuffer(array);
279     }
280 
281     @Override
282     public ByteBuf getChunk(int length) throws IOException {
283         if (file == null || length == 0) {
284             return EMPTY_BUFFER;
285         }
286         if (fileChannel == null) {
287             FileInputStream inputStream = new FileInputStream(file);
288             fileChannel = inputStream.getChannel();
289         }
290         int read = 0;
291         ByteBuffer byteBuffer = ByteBuffer.allocate(length);
292         while (read < length) {
293             int readnow = fileChannel.read(byteBuffer);
294             if (readnow == -1) {
295                 fileChannel.close();
296                 fileChannel = null;
297                 break;
298             } else {
299                 read += readnow;
300             }
301         }
302         if (read == 0) {
303             return EMPTY_BUFFER;
304         }
305         byteBuffer.flip();
306         ByteBuf buffer = wrappedBuffer(byteBuffer);
307         buffer.readerIndex(0);
308         buffer.writerIndex(read);
309         return buffer;
310     }
311 
312     @Override
313     public String getString() throws IOException {
314         return getString(HttpConstants.DEFAULT_CHARSET);
315     }
316 
317     @Override
318     public String getString(Charset encoding) throws IOException {
319         if (file == null) {
320             return "";
321         }
322         if (encoding == null) {
323             byte[] array = readFrom(file);
324             return new String(array, HttpConstants.DEFAULT_CHARSET.name());
325         }
326         byte[] array = readFrom(file);
327         return new String(array, encoding.name());
328     }
329 
330     @Override
331     public boolean isInMemory() {
332         return false;
333     }
334 
335     @Override
336     public boolean renameTo(File dest) throws IOException {
337         if (dest == null) {
338             throw new NullPointerException("dest");
339         }
340         if (file == null) {
341             throw new IOException("No file defined so cannot be renamed");
342         }
343         if (!file.renameTo(dest)) {
344             // must copy
345             IOException exception = null;
346             FileInputStream inputStream = null;
347             FileOutputStream outputStream = null;
348             long chunkSize = 8196;
349             long position = 0;
350             try {
351                 inputStream = new FileInputStream(file);
352                 outputStream = new FileOutputStream(dest);
353                 FileChannel in = inputStream.getChannel();
354                 FileChannel out = outputStream.getChannel();
355                 while (position < size) {
356                     if (chunkSize < size - position) {
357                         chunkSize = size - position;
358                     }
359                     position += in.transferTo(position, chunkSize , out);
360                 }
361             } catch (IOException e) {
362                 exception = e;
363             } finally {
364                 if (inputStream != null) {
365                     try {
366                         inputStream.close();
367                     } catch (IOException e) {
368                         if (exception == null) { // Choose to report the first exception
369                             exception = e;
370                         } else {
371                             logger.warn("Multiple exceptions detected, the following will be suppressed {}", e);
372                         }
373                     }
374                 }
375                 if (outputStream != null) {
376                     try {
377                         outputStream.close();
378                     } catch (IOException e) {
379                         if (exception == null) { // Choose to report the first exception
380                             exception = e;
381                         } else {
382                             logger.warn("Multiple exceptions detected, the following will be suppressed {}", e);
383                         }
384                     }
385                 }
386             }
387             if (exception != null) {
388                 throw exception;
389             }
390             if (position == size) {
391                 if (!file.delete()) {
392                     logger.warn("Failed to delete: {}", file);
393                 }
394                 file = dest;
395                 isRenamed = true;
396                 return true;
397             } else {
398                 if (!dest.delete()) {
399                     logger.warn("Failed to delete: {}", dest);
400                 }
401                 return false;
402             }
403         }
404         file = dest;
405         isRenamed = true;
406         return true;
407     }
408 
409     /**
410      * Utility function
411      * @return the array of bytes
412      */
413     private static byte[] readFrom(File src) throws IOException {
414         long srcsize = src.length();
415         if (srcsize > Integer.MAX_VALUE) {
416             throw new IllegalArgumentException(
417                     "File too big to be loaded in memory");
418         }
419         FileInputStream inputStream = new FileInputStream(src);
420         byte[] array = new byte[(int) srcsize];
421         try {
422             FileChannel fileChannel = inputStream.getChannel();
423             ByteBuffer byteBuffer = ByteBuffer.wrap(array);
424             int read = 0;
425             while (read < srcsize) {
426                 read += fileChannel.read(byteBuffer);
427             }
428         } finally {
429             inputStream.close();
430         }
431         return array;
432     }
433 
434     @Override
435     public File getFile() throws IOException {
436         return file;
437     }
438 
439     @Override
440     public HttpData touch() {
441         return this;
442     }
443 
444     @Override
445     public HttpData touch(Object hint) {
446         return this;
447     }
448 }