package org.basex.query.util.http; import static org.basex.query.util.Err.*; import static org.basex.util.Token.*; import org.basex.query.QueryException; import org.basex.query.item.ANode; import org.basex.query.item.Item; import org.basex.query.iter.AxisIter; import org.basex.query.iter.AxisMoreIter; import org.basex.query.iter.ItemCache; import org.basex.query.util.http.Request.Part; import org.basex.util.InputInfo; import org.basex.util.hash.TokenMap; import org.basex.util.list.ObjList; /** * Request parser. * @author BaseX Team 2005-12, BSD License * @author Rositsa Shadura */ public final class RequestParser { /** http:header element. */ private static final byte[] HDR = token("http:header"); /** Header attribute: name. */ private static final byte[] HDR_NAME = token("name"); /** Header attribute: value. */ private static final byte[] HDR_VALUE = token("value"); /** http:multipart element. */ private static final byte[] MULTIPART = token("http:multipart"); /** http:body element. */ private static final byte[] BODY = token("http:body"); /** Request attribute: HTTP method. */ private static final byte[] METHOD = token("method"); /** Request attribute: username. */ private static final byte[] USRNAME = token("username"); /** Request attribute: password. */ private static final byte[] PASSWD = token("password"); /** Request attribute: send-authorization. */ private static final byte[] SENDAUTH = token("send-authorization"); /** Body attribute: media-type. */ private static final byte[] MEDIATYPE = token("media-type"); /** Body attribute: media-type. */ private static final byte[] SRC = token("src"); /** HTTP method TRACE. */ private static final byte[] TRACE = token("trace"); /** HTTP method DELETE. */ private static final byte[] DELETE = token("delete"); /** Input information. */ private final InputInfo input; /** * Constructor. * @param ii input info */ public RequestParser(final InputInfo ii) { input = ii; } /** * Parses an <http:request/> element. * @param request request element * @param bodies content items * @return parsed request * @throws QueryException query exception */ public Request parse(final ANode request, final ItemCache bodies) throws QueryException { final Request r = new Request(); parseAttrs(request, r.attrs); checkRequest(r); final ANode payload = parseHdrs(request.children(), r.headers); final byte[] httpMethod = lc(r.attrs.get(METHOD)); // it is an error if content is set for HTTP verbs which must be empty if((eq(TRACE, httpMethod) || eq(DELETE, httpMethod)) && (payload != null || bodies != null)) REQINV.thrw(input, "Body not expected for method " + string(httpMethod)); if(payload != null) { // single part request if(eq(payload.name(), BODY)) { Item it = null; if(bodies != null) { // $bodies must contain exactly one item if(bodies.size() != 1) REQINV.thrw(input, "Number of items with request body content differs " + "from number of body descriptors."); it = bodies.next(); } parseBody(payload, it, r.payloadAttrs, r.bodyContent); r.isMultipart = false; // multipart request } else if(eq(payload.name(), MULTIPART)) { int i = 0; final AxisMoreIter ch = payload.children(); while(ch.next() != null) i++; // number of items in $bodies must be equal to number of body // descriptors if(bodies != null && bodies.size() != i) REQINV.thrw(input, "Number of items with request body content differs " + "from number of body descriptors."); parseMultipart(payload, bodies, r.payloadAttrs, r.parts); r.isMultipart = true; } else { REQINV.thrw(input); } } return r; } /** * Parses the attributes of an element. * @param element element * @param attrs map for parsed attributes */ private static void parseAttrs(final ANode element, final TokenMap attrs) { final AxisIter elAttrs = element.attributes(); for(ANode attr; (attr = elAttrs.next()) != null;) { attrs.add(attr.name(), attr.string()); } } /** * Parses <http:header/> children of requests and parts. * @param i iterator on request/part children * @param hdrs map for parsed headers * @return body or multipart */ private static ANode parseHdrs(final AxisMoreIter i, final TokenMap hdrs) { ANode n; while(true) { n = i.next(); if(n == null) break; final byte[] nm = n.name(); if(nm == null) continue; if(!eq(nm, HDR)) break; final AxisIter hdrAttrs = n.attributes(); byte[] name = null; byte[] value = null; for(ANode attr; (attr = hdrAttrs.next()) != null;) { if(eq(attr.name(), HDR_NAME)) name = attr.string(); if(eq(attr.name(), HDR_VALUE)) value = attr.string(); if(name != null && name.length != 0 && value != null && value.length != 0) { hdrs.add(name, value); break; } } } return n; } /** * Parses <http:body/> element. * @param body body element * @param contItem content item * @param attrs map for parsed body attributes * @param bodyContent item cache for parsed body content * @throws QueryException query exception */ private void parseBody(final ANode body, final Item contItem, final TokenMap attrs, final ItemCache bodyContent) throws QueryException { parseAttrs(body, attrs); checkBody(body, attrs); if(attrs.get(SRC) == null) { // no linked resource for setting request content if(contItem == null) { // content is set from <http:body/> children final AxisMoreIter i = body.children(); for(ANode n; (n = i.next()) != null;) bodyContent.add(n); } else { // content is set from $bodies parameter bodyContent.add(contItem); } } } /** * Parses a <http:multipart/> element. * @param multipart multipart element * @param contItems content items * @param attrs map for multipart attributes * @param parts list for multipart parts * @throws QueryException query exception */ private void parseMultipart(final ANode multipart, final ItemCache contItems, final TokenMap attrs, final ObjList<Part> parts) throws QueryException { parseAttrs(multipart, attrs); if(attrs.get(MEDIATYPE) == null) REQINV.thrw(input, "Attribute media-type of http:multipart is mandatory"); final AxisMoreIter i = multipart.children(); if(contItems == null) { // content is set from <http:body/> children of <http:part/> elements for(ANode n; (n = i.next()) != null;) parts.add(parsePart(n, null)); } else { // content is set from $bodies parameter for(ANode n; (n = i.next()) != null;) parts.add(parsePart(n, contItems.next())); } } /** * Parses a part from a <http:multipart/> element. * @param part part element * @param contItem content item * @return structure representing the part * @throws QueryException query exception */ private Part parsePart(final ANode part, final Item contItem) throws QueryException { final Part p = new Part(); final ANode partBody = parseHdrs(part.children(), p.headers); parseBody(partBody, contItem, p.bodyAttrs, p.bodyContent); return p; } /** * Checks consistency of attributes for <http:request/>. * @param r request * @throws QueryException query exception */ private void checkRequest(final Request r) throws QueryException { // @method denotes the HTTP verb and is mandatory if(r.attrs.get(METHOD) == null) REQINV.thrw(input, "Attribute method is mandatory"); // check parameters needed in case of authorization final byte[] sendAuth = r.attrs.get(SENDAUTH); if(sendAuth != null && Boolean.parseBoolean(string(sendAuth))) { final byte[] usrname = r.attrs.get(USRNAME); final byte[] passwd = r.attrs.get(PASSWD); if(usrname == null && passwd != null || usrname != null && passwd == null || usrname == null && passwd == null) REQINV.thrw(input, "Provided credentials are invalid"); } } /** * Checks consistency of attributes for <http:body/>. * @param body body * @param bodyAttrs body attributes * @throws QueryException query exception */ private void checkBody(final ANode body, final TokenMap bodyAttrs) throws QueryException { // @media-type is mandatory if(bodyAttrs.get(MEDIATYPE) == null) REQINV.thrw(input, "Attribute media-type of http:body is mandatory"); // if src attribute is used to set the content of the body, no // other attributes must be specified and no content must be present if(bodyAttrs.get(SRC) != null && (bodyAttrs.size() > 2 || body.children().more())) SRCATTR.thrw(input); } }