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