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