Allow specifying a custom charset for a multipart Attribute

- Fixed #2025
- Adapted from @BabyDuncan's pull request: #2031
- Overall ugliness clean-up
This commit is contained in:
Trustin Lee 2013-12-05 11:23:23 +09:00
parent 6431be8954
commit f2bb5f1b4c
14 changed files with 167 additions and 53 deletions

View File

@ -18,6 +18,8 @@ package io.netty.handler.codec.http.multipart;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http.HttpConstants;
import io.netty.util.internal.EmptyArrays;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import java.io.File;
import java.io.FileInputStream;
@ -35,7 +37,9 @@ import static io.netty.buffer.Unpooled.*;
*/
public abstract class AbstractDiskHttpData extends AbstractHttpData {
protected File file;
private static final InternalLogger logger = InternalLoggerFactory.getInstance(AbstractDiskHttpData.class);
private File file;
private boolean isRenamed;
private FileChannel fileChannel;
@ -110,7 +114,9 @@ public abstract class AbstractDiskHttpData extends AbstractHttpData {
}
if (buffer.readableBytes() == 0) {
// empty file
file.createNewFile();
if (!file.createNewFile()) {
throw new IOException("file exists already: " + file);
}
return;
}
FileOutputStream outputStream = new FileOutputStream(file);
@ -124,7 +130,7 @@ public abstract class AbstractDiskHttpData extends AbstractHttpData {
localfileChannel.force(false);
localfileChannel.close();
outputStream.close();
completed = true;
setCompleted();
} finally {
// Release the buffer as it was retained before and we not need a reference to it at all
// See https://github.com/netty/netty/issues/1516
@ -174,7 +180,7 @@ public abstract class AbstractDiskHttpData extends AbstractHttpData {
fileChannel.force(false);
fileChannel.close();
fileChannel = null;
completed = true;
setCompleted();
} else {
if (buffer == null) {
throw new NullPointerException("buffer");
@ -191,7 +197,7 @@ public abstract class AbstractDiskHttpData extends AbstractHttpData {
size = file.length();
checkSize(size);
isRenamed = true;
completed = true;
setCompleted();
}
@Override
@ -219,19 +225,23 @@ public abstract class AbstractDiskHttpData extends AbstractHttpData {
localfileChannel.close();
size = written;
if (definedSize > 0 && definedSize < size) {
file.delete();
if (!file.delete()) {
logger.warn("Failed to delete: {}", file);
}
file = null;
throw new IOException("Out of size: " + size + " > " + definedSize);
}
isRenamed = true;
completed = true;
setCompleted();
}
@Override
public void delete() {
if (! isRenamed) {
if (file != null) {
file.delete();
if (!file.delete()) {
logger.warn("Failed to delete: {}", file);
}
}
}
}
@ -329,12 +339,16 @@ public abstract class AbstractDiskHttpData extends AbstractHttpData {
in.close();
out.close();
if (position == size) {
file.delete();
if (!file.delete()) {
logger.warn("Failed to delete: {}", file);
}
file = dest;
isRenamed = true;
return true;
} else {
dest.delete();
if (!dest.delete()) {
logger.warn("Failed to delete: {}", dest);
}
return false;
}
}

View File

@ -32,12 +32,12 @@ public abstract class AbstractHttpData extends AbstractReferenceCounted implemen
private static final Pattern STRIP_PATTERN = Pattern.compile("(?:^\\s+|\\s+$|\\n)");
private static final Pattern REPLACE_PATTERN = Pattern.compile("[\\r\\t]");
protected final String name;
private final String name;
protected long definedSize;
protected long size;
protected Charset charset = HttpConstants.DEFAULT_CHARSET;
protected boolean completed;
protected long maxSize = DefaultHttpDataFactory.MAXSIZE;
private Charset charset = HttpConstants.DEFAULT_CHARSET;
private boolean completed;
private long maxSize = DefaultHttpDataFactory.MAXSIZE;
protected AbstractHttpData(String name, Charset charset, long size) {
if (name == null) {
@ -58,10 +58,15 @@ public abstract class AbstractHttpData extends AbstractReferenceCounted implemen
definedSize = size;
}
@Override
public long getMaxSize() { return maxSize; }
@Override
public void setMaxSize(long maxSize) {
this.maxSize = maxSize;
}
@Override
public void checkSize(long newSize) throws IOException {
if (maxSize >= 0 && newSize > maxSize) {
throw new IOException("Size exceed allowed maximum capacity");
@ -78,6 +83,10 @@ public abstract class AbstractHttpData extends AbstractReferenceCounted implemen
return completed;
}
protected void setCompleted() {
completed = true;
}
@Override
public Charset getCharset() {
return charset;

View File

@ -37,7 +37,6 @@ public abstract class AbstractMemoryHttpData extends AbstractHttpData {
private ByteBuf byteBuf;
private int chunkPosition;
protected boolean isRenamed;
protected AbstractMemoryHttpData(String name, Charset charset, long size) {
super(name, charset, size);
@ -59,7 +58,7 @@ public abstract class AbstractMemoryHttpData extends AbstractHttpData {
}
byteBuf = buffer;
size = localsize;
completed = true;
setCompleted();
}
@Override
@ -85,7 +84,7 @@ public abstract class AbstractMemoryHttpData extends AbstractHttpData {
byteBuf.release();
}
byteBuf = buffer;
completed = true;
setCompleted();
}
@Override
@ -113,7 +112,7 @@ public abstract class AbstractMemoryHttpData extends AbstractHttpData {
}
}
if (last) {
completed = true;
setCompleted();
} else {
if (buffer == null) {
throw new NullPointerException("buffer");
@ -148,7 +147,7 @@ public abstract class AbstractMemoryHttpData extends AbstractHttpData {
}
byteBuf = wrappedBuffer(Integer.MAX_VALUE, byteBuffer);
size = newsize;
completed = true;
setCompleted();
}
@Override
@ -224,8 +223,9 @@ public abstract class AbstractMemoryHttpData extends AbstractHttpData {
}
if (byteBuf == null) {
// empty file
dest.createNewFile();
isRenamed = true;
if (!dest.createNewFile()) {
throw new IOException("file exists already: " + dest);
}
return true;
}
int length = byteBuf.readableBytes();
@ -247,7 +247,6 @@ public abstract class AbstractMemoryHttpData extends AbstractHttpData {
fileChannel.force(false);
fileChannel.close();
outputStream.close();
isRenamed = true;
return written == length;
}

View File

@ -15,6 +15,7 @@
*/
package io.netty.handler.codec.http.multipart;
import io.netty.handler.codec.http.HttpConstants;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.util.internal.PlatformDependent;
@ -50,6 +51,8 @@ public class DefaultHttpDataFactory implements HttpDataFactory {
private long maxSize = MAXSIZE;
private Charset charset = HttpConstants.DEFAULT_CHARSET;
/**
* Keep all HttpDatas until cleanAllHttpDatas() is called.
*/
@ -66,6 +69,11 @@ public class DefaultHttpDataFactory implements HttpDataFactory {
minSize = MINSIZE;
}
public DefaultHttpDataFactory(Charset charset) {
this();
this.charset = charset;
}
/**
* HttpData will be always on Disk if useDisk is True, else always in Memory if False
*/
@ -74,6 +82,10 @@ public class DefaultHttpDataFactory implements HttpDataFactory {
checkSize = false;
}
public DefaultHttpDataFactory(boolean useDisk, Charset charset) {
this(useDisk);
this.charset = charset;
}
/**
* HttpData will be on Disk if the size of the file is greater than minSize, else it
* will be in memory. The type will be Mixed.
@ -84,8 +96,14 @@ public class DefaultHttpDataFactory implements HttpDataFactory {
this.minSize = minSize;
}
public void setMaxLimit(long max) {
this.maxSize = max;
public DefaultHttpDataFactory(long minSize, Charset charset) {
this(minSize);
this.charset = charset;
}
@Override
public void setMaxLimit(long maxSize) {
this.maxSize = maxSize;
}
/**
@ -103,14 +121,14 @@ public class DefaultHttpDataFactory implements HttpDataFactory {
@Override
public Attribute createAttribute(HttpRequest request, String name) {
if (useDisk) {
Attribute attribute = new DiskAttribute(name);
Attribute attribute = new DiskAttribute(name, charset);
attribute.setMaxSize(maxSize);
List<HttpData> fileToDelete = getList(request);
fileToDelete.add(attribute);
return attribute;
}
if (checkSize) {
Attribute attribute = new MixedAttribute(name, minSize);
Attribute attribute = new MixedAttribute(name, minSize, charset);
attribute.setMaxSize(maxSize);
List<HttpData> fileToDelete = getList(request);
fileToDelete.add(attribute);
@ -123,9 +141,8 @@ public class DefaultHttpDataFactory implements HttpDataFactory {
/**
* Utility method
* @param data
*/
private void checkHttpDataSize(HttpData data) {
private static void checkHttpDataSize(HttpData data) {
try {
data.checkSize(data.length());
} catch (IOException e) {
@ -138,11 +155,11 @@ public class DefaultHttpDataFactory implements HttpDataFactory {
if (useDisk) {
Attribute attribute;
try {
attribute = new DiskAttribute(name, value);
attribute = new DiskAttribute(name, value, charset);
attribute.setMaxSize(maxSize);
} catch (IOException e) {
// revert to Mixed mode
attribute = new MixedAttribute(name, value, minSize);
attribute = new MixedAttribute(name, value, minSize, charset);
attribute.setMaxSize(maxSize);
}
checkHttpDataSize(attribute);
@ -151,7 +168,7 @@ public class DefaultHttpDataFactory implements HttpDataFactory {
return attribute;
}
if (checkSize) {
Attribute attribute = new MixedAttribute(name, value, minSize);
Attribute attribute = new MixedAttribute(name, value, minSize, charset);
attribute.setMaxSize(maxSize);
checkHttpDataSize(attribute);
List<HttpData> fileToDelete = getList(request);
@ -159,7 +176,7 @@ public class DefaultHttpDataFactory implements HttpDataFactory {
return attribute;
}
try {
MemoryAttribute attribute = new MemoryAttribute(name, value);
MemoryAttribute attribute = new MemoryAttribute(name, value, charset);
attribute.setMaxSize(maxSize);
checkHttpDataSize(attribute);
return attribute;

View File

@ -20,6 +20,7 @@ import io.netty.channel.ChannelException;
import io.netty.handler.codec.http.HttpConstants;
import java.io.IOException;
import java.nio.charset.Charset;
import static io.netty.buffer.Unpooled.*;
@ -39,11 +40,19 @@ public class DiskAttribute extends AbstractDiskHttpData implements Attribute {
* Constructor used for huge Attribute
*/
public DiskAttribute(String name) {
super(name, HttpConstants.DEFAULT_CHARSET, 0);
this(name, HttpConstants.DEFAULT_CHARSET);
}
public DiskAttribute(String name, Charset charset) {
super(name, charset, 0);
}
public DiskAttribute(String name, String value) throws IOException {
super(name, HttpConstants.DEFAULT_CHARSET, 0); // Attribute have no default size
this(name, value, HttpConstants.DEFAULT_CHARSET);
}
public DiskAttribute(String name, String value, Charset charset) throws IOException {
super(name, charset, 0); // Attribute have no default size
setValue(value);
}
@ -55,7 +64,7 @@ public class DiskAttribute extends AbstractDiskHttpData implements Attribute {
@Override
public String getValue() throws IOException {
byte [] bytes = get();
return new String(bytes, charset.name());
return new String(bytes, getCharset());
}
@Override
@ -63,7 +72,7 @@ public class DiskAttribute extends AbstractDiskHttpData implements Attribute {
if (value == null) {
throw new NullPointerException("value");
}
byte [] bytes = value.getBytes(charset.name());
byte [] bytes = value.getBytes(getCharset());
checkSize(bytes.length);
ByteBuf buffer = wrappedBuffer(bytes);
if (definedSize > 0) {

View File

@ -125,11 +125,18 @@ public class DiskFileUpload extends AbstractDiskHttpData implements FileUpload {
@Override
public String toString() {
File file = null;
try {
file = getFile();
} catch (IOException e) {
// Should not occur.
}
return HttpPostBodyUtil.CONTENT_DISPOSITION + ": " +
HttpPostBodyUtil.FORM_DATA + "; " + HttpPostBodyUtil.NAME + "=\"" + getName() +
"\"; " + HttpPostBodyUtil.FILENAME + "=\"" + filename + "\"\r\n" +
HttpHeaders.Names.CONTENT_TYPE + ": " + contentType +
(charset != null? "; " + HttpHeaders.Values.CHARSET + '=' + charset + "\r\n" : "\r\n") +
(getCharset() != null? "; " + HttpHeaders.Values.CHARSET + '=' + getCharset() + "\r\n" : "\r\n") +
HttpHeaders.Names.CONTENT_LENGTH + ": " + length() + "\r\n" +
"Completed: " + isCompleted() +
"\r\nIsInMemory: " + isInMemory() + "\r\nRealFile: " +

View File

@ -27,20 +27,23 @@ import java.nio.charset.Charset;
* Extended interface for InterfaceHttpData
*/
public interface HttpData extends InterfaceHttpData, ByteBufHolder {
/**
* Returns the maxSize for this HttpData.
*/
long getMaxSize();
/**
* Set the maxSize for this HttpData. When limit will be reached, an exception will be raised.
* Setting it to (-1) means no limitation.
*
* By default, to be set from the HttpDataFactory.
* @param maxSize
*/
void setMaxSize(long maxSize);
/**
* Check if the new size is not reaching the max limit allowed.
* The limit is always computed in term of bytes.
* @param newSize
* @throws IOException
*/
void checkSize(long newSize) throws IOException;

View File

@ -32,8 +32,10 @@ import java.util.List;
*
*/
public class HttpPostRequestDecoder implements InterfaceHttpPostRequestDecoder {
protected static final int DEFAULT_DISCARD_THRESHOLD = 10 * 1024 * 1024;
protected InterfaceHttpPostRequestDecoder decoder;
static final int DEFAULT_DISCARD_THRESHOLD = 10 * 1024 * 1024;
private final InterfaceHttpPostRequestDecoder decoder;
/**
*

View File

@ -45,6 +45,7 @@ import static io.netty.buffer.Unpooled.*;
*
*/
public class HttpPostStandardRequestDecoder implements InterfaceHttpPostRequestDecoder {
/**
* Factory used to create InterfaceHttpData
*/

View File

@ -31,6 +31,7 @@ final class InternalAttribute extends AbstractReferenceCounted implements Interf
private final List<ByteBuf> value = new ArrayList<ByteBuf>();
private final Charset charset;
private int size;
public InternalAttribute(Charset charset) {
this.charset = charset;
}

View File

@ -20,6 +20,7 @@ import io.netty.channel.ChannelException;
import io.netty.handler.codec.http.HttpConstants;
import java.io.IOException;
import java.nio.charset.Charset;
import static io.netty.buffer.Unpooled.*;
@ -29,11 +30,19 @@ import static io.netty.buffer.Unpooled.*;
public class MemoryAttribute extends AbstractMemoryHttpData implements Attribute {
public MemoryAttribute(String name) {
super(name, HttpConstants.DEFAULT_CHARSET, 0);
this(name, HttpConstants.DEFAULT_CHARSET);
}
public MemoryAttribute(String name, Charset charset) {
super(name, charset, 0);
}
public MemoryAttribute(String name, String value) throws IOException {
super(name, HttpConstants.DEFAULT_CHARSET, 0); // Attribute have no default size
this(name, value, HttpConstants.DEFAULT_CHARSET); // Attribute have no default size
}
public MemoryAttribute(String name, String value, Charset charset) throws IOException {
super(name, charset, 0); // Attribute have no default size
setValue(value);
}
@ -44,7 +53,7 @@ public class MemoryAttribute extends AbstractMemoryHttpData implements Attribute
@Override
public String getValue() {
return getByteBuf().toString(charset);
return getByteBuf().toString(getCharset());
}
@Override
@ -52,7 +61,7 @@ public class MemoryAttribute extends AbstractMemoryHttpData implements Attribute
if (value == null) {
throw new NullPointerException("value");
}
byte [] bytes = value.getBytes(charset.name());
byte [] bytes = value.getBytes(getCharset());
checkSize(bytes.length);
ByteBuf buffer = wrappedBuffer(bytes);
if (definedSize > 0) {

View File

@ -123,7 +123,7 @@ public class MemoryFileUpload extends AbstractMemoryHttpData implements FileUplo
HttpPostBodyUtil.FORM_DATA + "; " + HttpPostBodyUtil.NAME + "=\"" + getName() +
"\"; " + HttpPostBodyUtil.FILENAME + "=\"" + filename + "\"\r\n" +
HttpHeaders.Names.CONTENT_TYPE + ": " + contentType +
(charset != null? "; " + HttpHeaders.Values.CHARSET + '=' + charset + "\r\n" : "\r\n") +
(getCharset() != null? "; " + HttpHeaders.Values.CHARSET + '=' + getCharset() + "\r\n" : "\r\n") +
HttpHeaders.Names.CONTENT_LENGTH + ": " + length() + "\r\n" +
"Completed: " + isCompleted() +
"\r\nIsInMemory: " + isInMemory();

View File

@ -16,6 +16,7 @@
package io.netty.handler.codec.http.multipart;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http.HttpConstants;
import java.io.File;
import java.io.IOException;
@ -29,40 +30,55 @@ public class MixedAttribute implements Attribute {
private Attribute attribute;
private final long limitSize;
protected long maxSize = DefaultHttpDataFactory.MAXSIZE;
private long maxSize = DefaultHttpDataFactory.MAXSIZE;
public MixedAttribute(String name, long limitSize) {
this(name, limitSize, HttpConstants.DEFAULT_CHARSET);
}
public MixedAttribute(String name, long limitSize, Charset charset) {
this.limitSize = limitSize;
attribute = new MemoryAttribute(name);
attribute = new MemoryAttribute(name, charset);
}
public MixedAttribute(String name, String value, long limitSize) {
this(name, value, limitSize, HttpConstants.DEFAULT_CHARSET);
}
public MixedAttribute(String name, String value, long limitSize, Charset charset) {
this.limitSize = limitSize;
if (value.length() > this.limitSize) {
try {
attribute = new DiskAttribute(name, value);
attribute = new DiskAttribute(name, value, charset);
} catch (IOException e) {
// revert to Memory mode
try {
attribute = new MemoryAttribute(name, value);
attribute = new MemoryAttribute(name, value, charset);
} catch (IOException e1) {
throw new IllegalArgumentException(e);
}
}
} else {
try {
attribute = new MemoryAttribute(name, value);
attribute = new MemoryAttribute(name, value, charset);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
}
@Override
public long getMaxSize() {
return maxSize;
}
@Override
public void setMaxSize(long maxSize) {
this.maxSize = maxSize;
attribute.setMaxSize(maxSize);
}
@Override
public void checkSize(long newSize) throws IOException {
if (maxSize >= 0 && newSize > maxSize) {
throw new IOException("Size exceed allowed maximum capacity");
@ -188,6 +204,16 @@ public class MixedAttribute implements Attribute {
return attribute.getName();
}
@Override
public int hashCode() {
return attribute.hashCode();
}
@Override
public boolean equals(Object obj) {
return attribute.equals(obj);
}
@Override
public int compareTo(InterfaceHttpData o) {
return attribute.compareTo(o);

View File

@ -32,7 +32,7 @@ public class MixedFileUpload implements FileUpload {
private final long limitSize;
private final long definedSize;
protected long maxSize = DefaultHttpDataFactory.MAXSIZE;
private long maxSize = DefaultHttpDataFactory.MAXSIZE;
public MixedFileUpload(String name, String filename, String contentType,
String contentTransferEncoding, Charset charset, long size,
@ -48,11 +48,18 @@ public class MixedFileUpload implements FileUpload {
definedSize = size;
}
@Override
public long getMaxSize() {
return maxSize;
}
@Override
public void setMaxSize(long maxSize) {
this.maxSize = maxSize;
fileUpload.setMaxSize(maxSize);
}
@Override
public void checkSize(long newSize) throws IOException {
if (maxSize >= 0 && newSize > maxSize) {
throw new IOException("Size exceed allowed maximum capacity");
@ -241,6 +248,16 @@ public class MixedFileUpload implements FileUpload {
return fileUpload.getName();
}
@Override
public int hashCode() {
return fileUpload.hashCode();
}
@Override
public boolean equals(Object obj) {
return fileUpload.equals(obj);
}
@Override
public int compareTo(InterfaceHttpData o) {
return fileUpload.compareTo(o);