package io.craft.atom.protocol.http; import static io.craft.atom.protocol.http.HttpConstants.AMPERSAND; import static io.craft.atom.protocol.http.HttpConstants.EQUAL_SIGN; import static io.craft.atom.protocol.http.HttpConstants.PERCENT_SIGN; import static io.craft.atom.protocol.http.HttpConstants.PLUS_SIGN; import io.craft.atom.protocol.AbstractProtocolCodec; import io.craft.atom.protocol.ProtocolDecoder; import io.craft.atom.protocol.ProtocolException; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import lombok.ToString; /** * A {@link ProtocolDecoder} which decodes cookie string bytes into {@code Map<String, List<String>>} object, default charset is utf-8. * <br> * Only accept complete parameter bytes to decode, because this implementation is stateless and thread safe. * * @author mindwind * @version 1.0, Mar 26, 2013 */ @ToString(callSuper = true) public class HttpParameterDecoder extends AbstractProtocolCodec implements ProtocolDecoder<Map<String, List<String>>> { private static final int START = 0; private static final int NAME = 1; private static final int VALUE = 2; private static final int END = -1; // ~ ------------------------------------------------------------------------------------------------------------ public HttpParameterDecoder() {}; public HttpParameterDecoder(Charset charset) { this.charset = charset; } // ~ ------------------------------------------------------------------------------------------------------------ @Override public void reset() {} @Override public List<Map<String, List<String>>> decode(byte[] bytes) throws ProtocolException { try { return decode0(bytes); } catch (Exception e) { if (e instanceof ProtocolException) { throw (ProtocolException) e; } throw new ProtocolException(e); } } private List<Map<String, List<String>>> decode0(byte[] bytes) throws ProtocolException, UnsupportedEncodingException { List<Map<String, List<String>>> paras = new ArrayList<Map<String,List<String>>>(); Map<String, List<String>> map = null; String name = null; String value = null; int searchIndex = 0; int stateIndex = 0; int state = START; int len = bytes.length; int i = 0; boolean decode = false; while (searchIndex < len) { switch (state) { case START: state = NAME; map = new HashMap<String, List<String>>(); break; case NAME: byte nameHead = bytes[searchIndex]; if (nameHead == PLUS_SIGN || nameHead == PERCENT_SIGN) { decode = true; } for (; searchIndex < len && bytes[searchIndex] != EQUAL_SIGN; searchIndex++, i++); name = new String(bytes, stateIndex, i, charset); if (decode) { name = URLDecoder.decode(name, charset.name()); } stateIndex = ++searchIndex; i = 0; decode = false; state = VALUE; break; case VALUE: byte valueHead = bytes[searchIndex]; if (valueHead == PLUS_SIGN || valueHead == PERCENT_SIGN) { decode = true; } for (; searchIndex < len && bytes[searchIndex] != AMPERSAND; searchIndex++, i++); value = new String(bytes, stateIndex, i, charset); if (decode) { value = URLDecoder.decode(value, charset.name()); } List<String> values = map.get(name); if (values == null) { values = new ArrayList<String>(); map.put(name, values); } values.add(value); name = value = null; stateIndex = ++searchIndex; i = 0; decode = false; if (searchIndex >= len) { state = END; } else { state = NAME; } break; case END: // nothing to do break; } } if (map != null) { paras.add(map); } return paras; } }