1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
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
56
57 protected abstract String getDiskFilename();
58
59
60
61
62 protected abstract String getPrefix();
63
64
65
66
67 protected abstract String getBaseDirectory();
68
69
70
71
72 protected abstract String getPostfix();
73
74
75
76
77 protected abstract boolean deleteOnExit();
78
79
80
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
93 tmpFile = PlatformDependent.createTempFile(getPrefix(), newpostfix, null);
94 } else {
95 tmpFile = PlatformDependent.createTempFile(getPrefix(), newpostfix, new File(
96 getBaseDirectory()));
97 }
98 if (deleteOnExit()) {
99
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
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
149
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
189
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
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
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) {
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) {
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
449
450
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 }