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