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                     if (file.length() == 0) {
119                         return;
120                     } else {
121                         if (!file.delete() || !file.createNewFile()) {
122                             throw new IOException("file exists already: " + file);
123                         }
124                     }
125                 }
126                 return;
127             }
128             FileOutputStream outputStream = new FileOutputStream(file);
129             try {
130                 FileChannel localfileChannel = outputStream.getChannel();
131                 ByteBuffer byteBuffer = buffer.nioBuffer();
132                 int written = 0;
133                 while (written < size) {
134                     written += localfileChannel.write(byteBuffer);
135                 }
136                 buffer.readerIndex(buffer.readerIndex() + written);
137                 localfileChannel.force(false);
138             } finally {
139                 outputStream.close();
140             }
141             setCompleted();
142         } finally {
143             // Release the buffer as it was retained before and we not need a reference to it at all
144             // See https://github.com/netty/netty/issues/1516
145             buffer.release();
146         }
147     }
148 
149     @Override
150     public void addContent(ByteBuf buffer, boolean last)
151             throws IOException {
152         if (buffer != null) {
153             try {
154                 int localsize = buffer.readableBytes();
155                 checkSize(size + localsize);
156                 if (definedSize > 0 && definedSize < size + localsize) {
157                     throw new IOException("Out of size: " + (size + localsize) +
158                             " > " + definedSize);
159                 }
160                 ByteBuffer byteBuffer = buffer.nioBufferCount() == 1 ? buffer.nioBuffer() : buffer.copy().nioBuffer();
161                 int written = 0;
162                 if (file == null) {
163                     file = tempFile();
164                 }
165                 if (fileChannel == null) {
166                     FileOutputStream outputStream = new FileOutputStream(file);
167                     fileChannel = outputStream.getChannel();
168                 }
169                 while (written < localsize) {
170                     written += fileChannel.write(byteBuffer);
171                 }
172                 size += localsize;
173                 buffer.readerIndex(buffer.readerIndex() + written);
174             } finally {
175                 // Release the buffer as it was retained before and we not need a reference to it at all
176                 // See https://github.com/netty/netty/issues/1516
177                 buffer.release();
178             }
179         }
180         if (last) {
181             if (file == null) {
182                 file = tempFile();
183             }
184             if (fileChannel == null) {
185                 FileOutputStream outputStream = new FileOutputStream(file);
186                 fileChannel = outputStream.getChannel();
187             }
188             fileChannel.force(false);
189             fileChannel.close();
190             fileChannel = null;
191             setCompleted();
192         } else {
193             if (buffer == null) {
194                 throw new NullPointerException("buffer");
195             }
196         }
197     }
198 
199     @Override
200     public void setContent(File file) throws IOException {
201         if (this.file != null) {
202             delete();
203         }
204         this.file = file;
205         size = file.length();
206         checkSize(size);
207         isRenamed = true;
208         setCompleted();
209     }
210 
211     @Override
212     public void setContent(InputStream inputStream) throws IOException {
213         if (inputStream == null) {
214             throw new NullPointerException("inputStream");
215         }
216         if (file != null) {
217             delete();
218         }
219         file = tempFile();
220         FileOutputStream outputStream = new FileOutputStream(file);
221         int written = 0;
222         try {
223             FileChannel localfileChannel = outputStream.getChannel();
224             byte[] bytes = new byte[4096 * 4];
225             ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
226             int read = inputStream.read(bytes);
227             while (read > 0) {
228                 byteBuffer.position(read).flip();
229                 written += localfileChannel.write(byteBuffer);
230                 checkSize(written);
231                 read = inputStream.read(bytes);
232             }
233             localfileChannel.force(false);
234         } finally {
235             outputStream.close();
236         }
237         size = written;
238         if (definedSize > 0 && definedSize < size) {
239             if (!file.delete()) {
240                 logger.warn("Failed to delete: {}", file);
241             }
242             file = null;
243             throw new IOException("Out of size: " + size + " > " + definedSize);
244         }
245         isRenamed = true;
246         setCompleted();
247     }
248 
249     @Override
250     public void delete() {
251         if (fileChannel != null) {
252             try {
253                 fileChannel.force(false);
254                 fileChannel.close();
255             } catch (IOException e) {
256                 logger.warn("Failed to close a file.", e);
257             }
258             fileChannel = null;
259         }
260         if (! isRenamed) {
261             if (file != null && file.exists()) {
262                 if (!file.delete()) {
263                     logger.warn("Failed to delete: {}", file);
264                 }
265             }
266             file = null;
267         }
268     }
269 
270     @Override
271     public byte[] get() throws IOException {
272         if (file == null) {
273             return EmptyArrays.EMPTY_BYTES;
274         }
275         return readFrom(file);
276     }
277 
278     @Override
279     public ByteBuf getByteBuf() throws IOException {
280         if (file == null) {
281             return EMPTY_BUFFER;
282         }
283         byte[] array = readFrom(file);
284         return wrappedBuffer(array);
285     }
286 
287     @Override
288     public ByteBuf getChunk(int length) throws IOException {
289         if (file == null || length == 0) {
290             return EMPTY_BUFFER;
291         }
292         if (fileChannel == null) {
293             FileInputStream inputStream = new FileInputStream(file);
294             fileChannel = inputStream.getChannel();
295         }
296         int read = 0;
297         ByteBuffer byteBuffer = ByteBuffer.allocate(length);
298         while (read < length) {
299             int readnow = fileChannel.read(byteBuffer);
300             if (readnow == -1) {
301                 fileChannel.close();
302                 fileChannel = null;
303                 break;
304             } else {
305                 read += readnow;
306             }
307         }
308         if (read == 0) {
309             return EMPTY_BUFFER;
310         }
311         byteBuffer.flip();
312         ByteBuf buffer = wrappedBuffer(byteBuffer);
313         buffer.readerIndex(0);
314         buffer.writerIndex(read);
315         return buffer;
316     }
317 
318     @Override
319     public String getString() throws IOException {
320         return getString(HttpConstants.DEFAULT_CHARSET);
321     }
322 
323     @Override
324     public String getString(Charset encoding) throws IOException {
325         if (file == null) {
326             return "";
327         }
328         if (encoding == null) {
329             byte[] array = readFrom(file);
330             return new String(array, HttpConstants.DEFAULT_CHARSET.name());
331         }
332         byte[] array = readFrom(file);
333         return new String(array, encoding.name());
334     }
335 
336     @Override
337     public boolean isInMemory() {
338         return false;
339     }
340 
341     @Override
342     public boolean renameTo(File dest) throws IOException {
343         if (dest == null) {
344             throw new NullPointerException("dest");
345         }
346         if (file == null) {
347             throw new IOException("No file defined so cannot be renamed");
348         }
349         if (!file.renameTo(dest)) {
350             // must copy
351             IOException exception = null;
352             FileInputStream inputStream = null;
353             FileOutputStream outputStream = null;
354             long chunkSize = 8196;
355             long position = 0;
356             try {
357                 inputStream = new FileInputStream(file);
358                 outputStream = new FileOutputStream(dest);
359                 FileChannel in = inputStream.getChannel();
360                 FileChannel out = outputStream.getChannel();
361                 while (position < size) {
362                     if (chunkSize < size - position) {
363                         chunkSize = size - position;
364                     }
365                     position += in.transferTo(position, chunkSize , out);
366                 }
367             } catch (IOException e) {
368                 exception = e;
369             } finally {
370                 if (inputStream != null) {
371                     try {
372                         inputStream.close();
373                     } catch (IOException e) {
374                         if (exception == null) { // Choose to report the first exception
375                             exception = e;
376                         } else {
377                             logger.warn("Multiple exceptions detected, the following will be suppressed {}", e);
378                         }
379                     }
380                 }
381                 if (outputStream != null) {
382                     try {
383                         outputStream.close();
384                     } catch (IOException e) {
385                         if (exception == null) { // Choose to report the first exception
386                             exception = e;
387                         } else {
388                             logger.warn("Multiple exceptions detected, the following will be suppressed {}", e);
389                         }
390                     }
391                 }
392             }
393             if (exception != null) {
394                 throw exception;
395             }
396             if (position == size) {
397                 if (!file.delete()) {
398                     logger.warn("Failed to delete: {}", file);
399                 }
400                 file = dest;
401                 isRenamed = true;
402                 return true;
403             } else {
404                 if (!dest.delete()) {
405                     logger.warn("Failed to delete: {}", dest);
406                 }
407                 return false;
408             }
409         }
410         file = dest;
411         isRenamed = true;
412         return true;
413     }
414 
415     /**
416      * Utility function
417      * @return the array of bytes
418      */
419     private static byte[] readFrom(File src) throws IOException {
420         long srcsize = src.length();
421         if (srcsize > Integer.MAX_VALUE) {
422             throw new IllegalArgumentException(
423                     "File too big to be loaded in memory");
424         }
425         FileInputStream inputStream = new FileInputStream(src);
426         byte[] array = new byte[(int) srcsize];
427         try {
428             FileChannel fileChannel = inputStream.getChannel();
429             ByteBuffer byteBuffer = ByteBuffer.wrap(array);
430             int read = 0;
431             while (read < srcsize) {
432                 read += fileChannel.read(byteBuffer);
433             }
434         } finally {
435             inputStream.close();
436         }
437         return array;
438     }
439 
440     @Override
441     public File getFile() throws IOException {
442         return file;
443     }
444 
445     @Override
446     public HttpData touch() {
447         return this;
448     }
449 
450     @Override
451     public HttpData touch(Object hint) {
452         return this;
453     }
454 }