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                 byteBuffer.clear();
243                 read = inputStream.read(bytes);
244             }
245             localfileChannel.force(false);
246         } finally {
247             accessFile.close();
248         }
249         size = written;
250         if (definedSize > 0 && definedSize < size) {
251             if (!file.delete()) {
252                 logger.warn("Failed to delete: {}", file);
253             }
254             file = null;
255             throw new IOException("Out of size: " + size + " > " + definedSize);
256         }
257         isRenamed = true;
258         setCompleted();
259     }
260 
261     @Override
262     public void delete() {
263         if (fileChannel != null) {
264             try {
265                 fileChannel.force(false);
266             } catch (IOException e) {
267                 logger.warn("Failed to force.", e);
268             } finally {
269                 try {
270                     fileChannel.close();
271                 } catch (IOException e) {
272                     logger.warn("Failed to close a file.", e);
273                 }
274             }
275             fileChannel = null;
276         }
277         if (!isRenamed) {
278             String filePath = null;
279 
280             if (file != null && file.exists()) {
281                 filePath = file.getPath();
282                 if (!file.delete()) {
283                     filePath = null;
284                     logger.warn("Failed to delete: {}", file);
285                 }
286             }
287 
288             // If you turn on deleteOnExit make sure it is executed.
289             if (deleteOnExit() && filePath != null) {
290                 DeleteFileOnExitHook.remove(filePath);
291             }
292             file = null;
293         }
294     }
295 
296     @Override
297     public byte[] get() throws IOException {
298         if (file == null) {
299             return EmptyArrays.EMPTY_BYTES;
300         }
301         return readFrom(file);
302     }
303 
304     @Override
305     public ByteBuf getByteBuf() throws IOException {
306         if (file == null) {
307             return EMPTY_BUFFER;
308         }
309         byte[] array = readFrom(file);
310         return wrappedBuffer(array);
311     }
312 
313     @Override
314     public ByteBuf getChunk(int length) throws IOException {
315         if (file == null || length == 0) {
316             return EMPTY_BUFFER;
317         }
318         if (fileChannel == null) {
319             RandomAccessFile accessFile = new RandomAccessFile(file, "r");
320             fileChannel = accessFile.getChannel();
321         }
322         int read = 0;
323         ByteBuffer byteBuffer = ByteBuffer.allocate(length);
324         try {
325             while (read < length) {
326                 int readnow = fileChannel.read(byteBuffer);
327                 if (readnow == -1) {
328                     fileChannel.close();
329                     fileChannel = null;
330                     break;
331                 }
332                 read += readnow;
333             }
334         } catch (IOException e) {
335             fileChannel.close();
336             fileChannel = null;
337             throw e;
338         }
339         if (read == 0) {
340             return EMPTY_BUFFER;
341         }
342         byteBuffer.flip();
343         ByteBuf buffer = wrappedBuffer(byteBuffer);
344         buffer.readerIndex(0);
345         buffer.writerIndex(read);
346         return buffer;
347     }
348 
349     @Override
350     public String getString() throws IOException {
351         return getString(HttpConstants.DEFAULT_CHARSET);
352     }
353 
354     @Override
355     public String getString(Charset encoding) throws IOException {
356         if (file == null) {
357             return "";
358         }
359         if (encoding == null) {
360             byte[] array = readFrom(file);
361             return new String(array, HttpConstants.DEFAULT_CHARSET.name());
362         }
363         byte[] array = readFrom(file);
364         return new String(array, encoding.name());
365     }
366 
367     @Override
368     public boolean isInMemory() {
369         return false;
370     }
371 
372     @Override
373     public boolean renameTo(File dest) throws IOException {
374         ObjectUtil.checkNotNull(dest, "dest");
375         if (file == null) {
376             throw new IOException("No file defined so cannot be renamed");
377         }
378         if (!file.renameTo(dest)) {
379             // must copy
380             IOException exception = null;
381             RandomAccessFile inputAccessFile = null;
382             RandomAccessFile outputAccessFile = null;
383             long chunkSize = 8196;
384             long position = 0;
385             try {
386                 inputAccessFile = new RandomAccessFile(file, "r");
387                 outputAccessFile = new RandomAccessFile(dest, "rw");
388                 FileChannel in = inputAccessFile.getChannel();
389                 FileChannel out = outputAccessFile.getChannel();
390                 while (position < size) {
391                     if (chunkSize < size - position) {
392                         chunkSize = size - position;
393                     }
394                     position += in.transferTo(position, chunkSize, out);
395                 }
396             } catch (IOException e) {
397                 exception = e;
398             } finally {
399                 if (inputAccessFile != null) {
400                     try {
401                         inputAccessFile.close();
402                     } catch (IOException e) {
403                         if (exception == null) { // Choose to report the first exception
404                             exception = e;
405                         } else {
406                             logger.warn("Multiple exceptions detected, the following will be suppressed {}", e);
407                         }
408                     }
409                 }
410                 if (outputAccessFile != null) {
411                     try {
412                         outputAccessFile.close();
413                     } catch (IOException e) {
414                         if (exception == null) { // Choose to report the first exception
415                             exception = e;
416                         } else {
417                             logger.warn("Multiple exceptions detected, the following will be suppressed {}", e);
418                         }
419                     }
420                 }
421             }
422             if (exception != null) {
423                 throw exception;
424             }
425             if (position == size) {
426                 if (!file.delete()) {
427                     logger.warn("Failed to delete: {}", file);
428                 }
429                 file = dest;
430                 isRenamed = true;
431                 return true;
432             } else {
433                 if (!dest.delete()) {
434                     logger.warn("Failed to delete: {}", dest);
435                 }
436                 return false;
437             }
438         }
439         file = dest;
440         isRenamed = true;
441         return true;
442     }
443 
444     /**
445      * Utility function
446      *
447      * @return the array of bytes
448      */
449     private static byte[] readFrom(File src) throws IOException {
450         long srcsize = src.length();
451         if (srcsize > Integer.MAX_VALUE) {
452             throw new IllegalArgumentException(
453                     "File too big to be loaded in memory");
454         }
455         RandomAccessFile accessFile = new RandomAccessFile(src, "r");
456         byte[] array = new byte[(int) srcsize];
457         try {
458             FileChannel fileChannel = accessFile.getChannel();
459             ByteBuffer byteBuffer = ByteBuffer.wrap(array);
460             int read = 0;
461             while (read < srcsize) {
462                 read += fileChannel.read(byteBuffer);
463             }
464         } finally {
465             accessFile.close();
466         }
467         return array;
468     }
469 
470     @Override
471     public File getFile() throws IOException {
472         return file;
473     }
474 
475     @Override
476     public HttpData touch() {
477         return this;
478     }
479 
480     @Override
481     public HttpData touch(Object hint) {
482         return this;
483     }
484 }