org.apache.juneau.urlencoding.UrlEncodingParser Maven / Gradle / Ivy
// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file *
// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance *
// * with the License. You may obtain a copy of the License at *
// * *
// * http://www.apache.org/licenses/LICENSE-2.0 *
// * *
// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an *
// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the *
// * specific language governing permissions and limitations under the License. *
// ***************************************************************************************************************************
package org.apache.juneau.urlencoding;
import static org.apache.juneau.internal.ArrayUtils.*;
import static org.apache.juneau.internal.StringUtils.*;
import java.util.*;
import org.apache.juneau.*;
import org.apache.juneau.parser.*;
import org.apache.juneau.uon.*;
/**
* Parses URL-encoded text into POJO models.
*
* Media types:
*
* Handles Content-Type
types: application/x-www-form-urlencoded
*
* Description:
*
* Parses URL-Encoded text (e.g. "foo=bar&baz=bing" ) into POJOs.
*
*
* Expects parameter values to be in UON notation.
*
*
* This parser uses a state machine, which makes it very fast and efficient.
*/
@SuppressWarnings({ "unchecked" })
public class UrlEncodingParser extends UonParser implements PartParser {
//-------------------------------------------------------------------------------------------------------------------
// Configurable properties
//-------------------------------------------------------------------------------------------------------------------
private static final String PREFIX = "UrlEncodingParser.";
/**
* Parser bean property collections/arrays as separate key/value pairs ({@link Boolean}, default=false ).
*
*
* This is the parser-side equivalent of the {@link #URLENC_expandedParams} setting.
*
*
* This option only applies to beans.
*
*
Notes:
*
* - If parsing multi-part parameters, it's highly recommended to use
Collections
or Lists
* as bean property types instead of arrays since arrays have to be recreated from scratch every time a value
* is added to it.
*
*/
public static final String URLENC_expandedParams = PREFIX + "expandedParams";
//-------------------------------------------------------------------------------------------------------------------
// Predefined instances
//-------------------------------------------------------------------------------------------------------------------
/** Reusable instance of {@link UrlEncodingParser}. */
public static final UrlEncodingParser DEFAULT = new UrlEncodingParser(PropertyStore.create());
//-------------------------------------------------------------------------------------------------------------------
// Instance
//-------------------------------------------------------------------------------------------------------------------
private final UrlEncodingParserContext ctx;
/**
* Constructor.
*
* @param propertyStore The property store containing all the settings for this object.
*/
public UrlEncodingParser(PropertyStore propertyStore) {
super(propertyStore.copy().append(UON_decodeChars, true), "application/x-www-form-urlencoded");
this.ctx = createContext(UrlEncodingParserContext.class);
}
@Override /* CoreObject */
public UrlEncodingParserBuilder builder() {
return new UrlEncodingParserBuilder(propertyStore);
}
/**
* Parse a URL query string into a simple map of key/value pairs.
*
* @param qs The query string to parse.
* @param map The map to parse into. If null , then a new {@link TreeMap} will be used.
* @return A sorted {@link TreeMap} of query string entries.
* @throws Exception
*/
public Map parseIntoSimpleMap(String qs, Map map) throws Exception {
Map m = map == null ? new TreeMap() : map;
if (isEmpty(qs))
return m;
// We're reading from a string, so we don't need to make sure close() is called on the pipe.
ParserPipe p = new ParserPipe(qs, false, false, null, null);
UonReader r = new UonReader(p, true);
final int S1=1; // Looking for attrName start.
final int S2=2; // Found attrName start, looking for = or & or end.
final int S3=3; // Found =, looking for valStart.
final int S4=4; // Found valStart, looking for & or end.
try {
int c = r.peekSkipWs();
if (c == '?')
r.read();
int state = S1;
String currAttr = null;
while (c != -1) {
c = r.read();
if (state == S1) {
if (c != -1) {
r.unread();
r.mark();
state = S2;
}
} else if (state == S2) {
if (c == -1) {
add(m, r.getMarked(), null);
} else if (c == '\u0001') {
m.put(r.getMarked(0,-1), null);
state = S1;
} else if (c == '\u0002') {
currAttr = r.getMarked(0,-1);
state = S3;
}
} else if (state == S3) {
if (c == -1 || c == '\u0001') {
add(m, currAttr, "");
} else {
if (c == '\u0002')
r.replace('=');
r.unread();
r.mark();
state = S4;
}
} else if (state == S4) {
if (c == -1) {
add(m, currAttr, r.getMarked());
} else if (c == '\u0001') {
add(m, currAttr, r.getMarked(0,-1));
state = S1;
} else if (c == '\u0002') {
r.replace('=');
}
}
}
} finally {
r.close();
}
return m;
}
private static void add(Map m, String key, String val) {
boolean b = m.containsKey(key);
if (val == null) {
if (! b)
m.put(key, null);
} else if (b && m.get(key) != null) {
m.put(key, append(m.get(key), val));
} else {
m.put(key, new String[]{val});
}
}
@Override /* PartParser */
public T parse(PartType partType, String in, ClassMeta type) throws ParseException {
if (in == null)
return null;
if (type.isString() && in.length() > 0) {
// Shortcut - If we're returning a string and the value doesn't start with "'" or is "null", then
// just return the string since it's a plain value.
// This allows us to bypass the creation of a UonParserSession object.
char x = firstNonWhitespaceChar(in);
if (x != '\'' && x != 'n' && in.indexOf('~') == -1)
return (T)in;
if (x == 'n' && "null".equals(in))
return null;
}
UonParserSession session = createParameterSession();
ParserPipe pipe = session.createPipe(in);
try {
UonReader r = session.getUonReader(pipe, false);
return session.parseAnything(type, r, null, true, null);
} catch (ParseException e) {
throw e;
} catch (Exception e) {
throw new ParseException(session.getLastLocation(), e);
} finally {
pipe.close();
session.close();
}
}
//--------------------------------------------------------------------------------
// Entry point methods
//--------------------------------------------------------------------------------
@Override /* Parser */
public UrlEncodingParserSession createSession(ParserSessionArgs args) {
return new UrlEncodingParserSession(ctx, args);
}
}