All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.xmlcalabash.library.HttpRequest Maven / Gradle / Ivy

The newest version!
package com.xmlcalabash.library;

/*
 * HttpRequest.java
 *
 * Copyright 2008 Mark Logic Corporation.
 * Portions Copyright 2007 Sun Microsystems, Inc.
 * All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common
 * Development and Distribution License("CDDL") (collectively, the
 * "License"). You may not use this file except in compliance with the
 * License. You can obtain a copy of the License at
 * https://runtime.dev.java.net/public/CDDL+GPL.html or
 * docs/CDDL+GPL.txt in the distribution. See the License for the
 * specific language governing permissions and limitations under the
 * License. When distributing the software, include this License Header
 * Notice in each file and include the License file at docs/CDDL+GPL.txt.
 */

import com.xmlcalabash.core.XMLCalabash;
import com.xmlcalabash.core.XProcConstants;
import com.xmlcalabash.core.XProcException;
import com.xmlcalabash.core.XProcRuntime;
import com.xmlcalabash.io.DataStore;
import com.xmlcalabash.io.DataStore.DataReader;
import com.xmlcalabash.io.ReadablePipe;
import com.xmlcalabash.io.WritablePipe;
import com.xmlcalabash.runtime.XAtomicStep;
import com.xmlcalabash.util.AxisNodes;
import com.xmlcalabash.util.Base64;
import com.xmlcalabash.util.HttpUtils;
import com.xmlcalabash.util.JSONtoXML;
import com.xmlcalabash.util.MIMEReader;
import com.xmlcalabash.util.S9apiUtils;
import com.xmlcalabash.util.TreeWriter;
import com.xmlcalabash.util.XMLtoJSON;
import net.sf.saxon.s9api.Axis;
import net.sf.saxon.s9api.QName;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.Serializer;
import net.sf.saxon.s9api.XdmNode;
import net.sf.saxon.s9api.XdmNodeKind;
import net.sf.saxon.s9api.XdmSequenceIterator;
import org.apache.http.Consts;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CookieStore;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.AuthSchemes;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpPatch;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.cookie.Cookie;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.StandardHttpRequestRetryHandler;
import org.apache.http.message.BasicHeader;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpCoreContext;
import org.apache.http.util.EntityUtils;
import org.json.JSONTokener;
import org.xml.sax.InputSource;

import javax.xml.XMLConstants;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.List;
import java.util.Vector;


@XMLCalabash(
        name = "p:http-request",
        type = "{http://www.w3.org/ns/xproc}http-request")

public class HttpRequest extends DefaultStep {
    private static final QName c_request = new QName("c", XProcConstants.NS_XPROC_STEP, "request");
    private static final QName cx_timeout = new QName("cx",XProcConstants.NS_CALABASH_EX,"timeout");
    private static final QName cx_cookies = new QName("cx",XProcConstants.NS_CALABASH_EX,"cookies");
    private static final QName cx_save_cookies = new QName("cx",XProcConstants.NS_CALABASH_EX,"save-cookies");
    private static final QName cx_use_cookies = new QName("cx",XProcConstants.NS_CALABASH_EX,"use-cookies");
    private static final QName cx_send_binary = new QName("cx", XProcConstants.NS_CALABASH_EX, "send-binary");

    public static final QName _href = new QName("", "href");
    public static final QName _detailed = new QName("", "detailed");
    public static final QName _status_only = new QName("", "status-only");
    public static final QName _username = new QName("", "username");
    public static final QName _password = new QName("", "password");
    public static final QName _auth_method = new QName("", "auth-method");
    public static final QName _send_authorization = new QName("", "send-authorization");
    public static final QName _override_content_type = new QName("", "override-content-type");
    public static final QName _content_type = new QName("", "content-type");
    public static final QName _name = new QName("", "name");
    public static final QName _value = new QName("", "value");
    public static final QName _id = new QName("", "id");
    public static final QName _description = new QName("", "description");
    public static final QName _disposition = new QName("", "disposition");
    public static final QName _status = new QName("", "status");
    public static final QName _boundary = new QName("", "boundary");
    public static final QName _charset = new QName("", "charset");

    private static final int bufSize = 912 * 8; // A multiple of 3, 4, and 75 for base64 line breaking

    private boolean detailed = false;
    private URI requestURI = null;
    private Vector
headers = new Vector
(); private String overrideContentType = null; private String headerContentType = null; private boolean encodeBinary = false; private HttpClientBuilder builder = null; private ReadablePipe source = null; private WritablePipe result = null; /* Creates a new instance of HttpRequest */ public HttpRequest(XProcRuntime runtime, XAtomicStep step) { super(runtime,step); builder = HttpClientBuilder.create(); } public void setInput(String port, ReadablePipe pipe) { source = pipe; } public void setOutput(String port, WritablePipe pipe) { result = pipe; } public void reset() { source.resetReader(); result.resetWriter(); builder = HttpClientBuilder.create(); } public void run() throws SaxonApiException { super.run(); XdmNode requestDoc = source.read(); XdmNode start = S9apiUtils.getDocumentElement(requestDoc); if (!c_request.equals(start.getNodeName())) { throw XProcException.stepError(40); } // Check for valid attributes XdmSequenceIterator iter = start.axisIterator(Axis.ATTRIBUTE); boolean ok = true; while (iter.hasNext()) { XdmNode attr = (XdmNode) iter.next(); QName name = attr.getNodeName(); if (_method.equals(name) || _href.equals(name) || _detailed.equals(name) || _status_only.equals(name) || _username.equals(name) || _password.equals(name) || _auth_method.equals(name) || _send_authorization.equals(name) || _override_content_type.equals(name)) { // nop } else { if (XMLConstants.DEFAULT_NS_PREFIX.equals(name.getNamespaceURI())) { throw new XProcException(step, "Unsupported attribute on c:request for p:http-request: " + name); } } } String send = step.getExtensionAttribute(cx_send_binary); encodeBinary = !"true".equals(send); boolean statusOnly = "true".equals(start.getAttributeValue(_status_only)); String method = start.getAttributeValue(_method); detailed = "true".equals(start.getAttributeValue(_detailed)); overrideContentType = start.getAttributeValue(_override_content_type); if (method == null) { throw XProcException.stepError(6); } if (statusOnly && !detailed) { throw XProcException.stepError(4); } if (start.getAttributeValue(_href) == null) { throw new XProcException(step, "The 'href' attribute must be specified on c:request for p:http-request"); } requestURI = start.getBaseURI().resolve(start.getAttributeValue(_href)); String scheme = requestURI.getScheme(); if (!"http".equalsIgnoreCase(scheme) && !"https".equalsIgnoreCase(scheme)) { doFile(start.getAttributeValue(_href), start.getBaseURI().toASCIIString()); return; } RequestConfig.Builder rqbuilder = RequestConfig.custom(); rqbuilder.setCookieSpec(CookieSpecs.DEFAULT); HttpContext localContext = new BasicHttpContext(); // What about cookies String saveCookieKey = step.getExtensionAttribute(cx_save_cookies); String useCookieKeys = step.getExtensionAttribute(cx_use_cookies); String cookieKey = step.getExtensionAttribute(cx_cookies); if (saveCookieKey == null) { saveCookieKey = cookieKey; } if (useCookieKeys == null) { useCookieKeys = cookieKey; } // If a redirect response includes cookies, those cookies should be forwarded // as appropriate to the redirected location when the redirection is followed. CookieStore cookieStore = new BasicCookieStore(); if (useCookieKeys != null && useCookieKeys.equals(saveCookieKey)) { cookieStore = runtime.getCookieStore(useCookieKeys); } else if (useCookieKeys != null) { CookieStore useCookieStore = runtime.getCookieStore(useCookieKeys); for (Cookie cookie : useCookieStore.getCookies()) { cookieStore.addCookie(cookie); } } builder.setDefaultCookieStore(cookieStore); String timeOutStr = step.getExtensionAttribute(cx_timeout); if (timeOutStr != null) { rqbuilder.setSocketTimeout(Integer.parseInt(timeOutStr)); } builder.setDefaultRequestConfig(rqbuilder.build()); if (start.getAttributeValue(_username) != null) { String user = start.getAttributeValue(_username); String pass = start.getAttributeValue(_password); String meth = start.getAttributeValue(_auth_method); List authpref; if ("basic".equalsIgnoreCase(meth)) { authpref = Collections.singletonList(AuthSchemes.BASIC); } else if ("digest".equalsIgnoreCase(meth)) { authpref = Collections.singletonList(AuthSchemes.DIGEST); } else { throw XProcException.stepError(3, "Unsupported auth-method: " + meth); } rqbuilder.setProxyPreferredAuthSchemes(authpref); String host = requestURI.getHost(); int port = requestURI.getPort(); AuthScope scope = new AuthScope(host,port); // Or this? new AuthScope(null, AuthScope.ANY_PORT) BasicCredentialsProvider bCredsProvider = new BasicCredentialsProvider(); bCredsProvider.setCredentials(scope, new UsernamePasswordCredentials(user, pass)); builder.setDefaultCredentialsProvider(bCredsProvider); } iter = start.axisIterator(Axis.CHILD); XdmNode body = null; while (iter.hasNext()) { XdmNode event = (XdmNode) iter.next(); // FIXME: What about non-whitespace text nodes? if (event.getNodeKind() == XdmNodeKind.ELEMENT) { if (body != null) { throw new UnsupportedOperationException("Elements follow c:multipart or c:body"); } if (XProcConstants.c_header.equals(event.getNodeName())) { String name = event.getAttributeValue(_name); if (name == null) { continue; // this can't happen, right? } if (name.toLowerCase().equals("content-type")) { // We'll deal with the content-type header later headerContentType = event.getAttributeValue(_value).toLowerCase(); } else { headers.add(new BasicHeader(event.getAttributeValue(_name), event.getAttributeValue(_value))); } } else if (XProcConstants.c_multipart.equals(event.getNodeName()) || XProcConstants.c_body.equals(event.getNodeName())) { body = event; } else { throw new UnsupportedOperationException("Unexpected request element: " + event.getNodeName()); } } } String lcMethod = method.toLowerCase(); // You can only have a body on PUT or POST or PATCH if (body != null && !("put".equals(lcMethod) || "post".equals(lcMethod) || "patch".equals(lcMethod))) { throw XProcException.stepError(5); } HttpUriRequest httpRequest; HttpResponse httpResult = null; if ("get".equals(lcMethod)) { httpRequest = doGet(); } else if ("post".equals(lcMethod)) { httpRequest = doPost(body); } else if ("put".equals(lcMethod)) { httpRequest = doPut(body); } else if ("patch".equals(lcMethod)) { httpRequest = doPatch(body); } else if ("head".equals(lcMethod)) { httpRequest = doHead(); } else if ("delete".equals(lcMethod)) { httpRequest = doDelete(); } else { throw new UnsupportedOperationException("Unrecognized http method: " + method); } TreeWriter tree = new TreeWriter(runtime); try { // Execute the method. builder.setRetryHandler(new StandardHttpRequestRetryHandler(3, false)); HttpClient httpClient = builder.build(); if (httpClient == null) { throw new XProcException("HTTP requests have been disabled"); } httpResult = httpClient.execute(httpRequest, localContext); int statusCode = httpResult.getStatusLine().getStatusCode(); HttpHost host = (HttpHost) localContext.getAttribute(HttpCoreContext.HTTP_TARGET_HOST); HttpUriRequest req = (HttpUriRequest) localContext.getAttribute(HttpCoreContext.HTTP_REQUEST); URI root = new URI(host.getSchemeName(), null, host.getHostName(), host.getPort(), "/", null, null); tree.startDocument(root.resolve(req.getURI())); // Deal with cookies if (saveCookieKey != null) { runtime.setCookieStore(saveCookieKey, cookieStore); } String contentType = getContentType(httpResult); if (overrideContentType != null) { if ((xmlContentType(contentType) && overrideContentType.startsWith("image/")) || (contentType.startsWith("text/") && overrideContentType.startsWith("image/")) || (contentType.startsWith("image/") && xmlContentType(overrideContentType)) || (contentType.startsWith("image/") && overrideContentType.startsWith("text/")) || (contentType.startsWith("multipart/") && !overrideContentType.startsWith("multipart/")) || (!contentType.startsWith("multipart/") && overrideContentType.startsWith("multipart/"))) { throw XProcException.stepError(30); } //System.err.println(overrideContentType + " overrides " + contentType); contentType = overrideContentType; } if (detailed) { tree.addStartElement(XProcConstants.c_response); tree.addAttribute(_status, "" + statusCode); tree.startContent(); for (Header header : httpResult.getAllHeaders()) { // I don't understand why/how HeaderElement parsing works. I get very weird results. // So I'm just going to go the long way around... String h = header.toString(); int cp = h.indexOf(":"); String name = header.getName(); String value = h.substring(cp+1).trim(); tree.addStartElement(XProcConstants.c_header); tree.addAttribute(_name, name); tree.addAttribute(_value, value); tree.startContent(); tree.addEndElement(); } if (statusOnly) { // Skip reading the result } else if (httpResult.getEntity() != null) { // Read the response body. InputStream bodyStream = httpResult.getEntity().getContent(); if (bodyStream != null) { readBodyContent(tree, bodyStream, httpResult); } } tree.addEndElement(); } else { if (statusOnly) { // Skip reading the result } else { // Read the response body. if (httpResult.getEntity() != null) { InputStream bodyStream = httpResult.getEntity().getContent(); readBodyContent(tree, bodyStream, httpResult); } else { throw XProcException.dynamicError(6, "Reading HTTP response on " + getStep().getName()); } } } } catch (XProcException e) { throw e; } catch (Exception e) { throw new XProcException(e); } finally { // Release the connection. if (httpResult != null) { EntityUtils.consumeQuietly(httpResult.getEntity()); } } tree.endDocument(); XdmNode resultNode = tree.getResult(); result.write(resultNode); } private HttpGet doGet() { HttpGet method = new HttpGet(requestURI); for (Header header : headers) { method.addHeader(header); } return method; } private HttpHead doHead() { HttpHead method = new HttpHead(requestURI); for (Header header : headers) { method.addHeader(header); } return method; } private HttpDelete doDelete() { HttpDelete method = new HttpDelete(requestURI); for (Header header : headers) { method.addHeader(header); } return method; } private HttpPut doPut(XdmNode body) { HttpPut method = new HttpPut(requestURI); doPutOrPost(method,body); return method; } private HttpPost doPost(XdmNode body) { HttpPost method = new HttpPost(requestURI); doPutOrPost(method,body); return method; } private HttpPatch doPatch(XdmNode body) { HttpPatch method = new HttpPatch(requestURI); doPutOrPost(method,body); return method; } private void doPutOrPost(HttpEntityEnclosingRequest method, XdmNode body) { if (XProcConstants.c_multipart.equals(body.getNodeName())) { doPutOrPostMultipart(method,body); } else { doPutOrPostSinglepart(method,body); } } private void doPutOrPostSinglepart(HttpEntityEnclosingRequest method, XdmNode body) { // ATTENTION: This doesn't handle multipart, that's done entirely separately // Check for consistency of content-type String contentType = body.getAttributeValue(_content_type); if (contentType == null) { throw new XProcException(step, "Content-type on c:body is required."); } String bodyId = body.getAttributeValue(_id); String bodyDescription = body.getAttributeValue(_description); String bodyDisposition = body.getAttributeValue(_disposition); boolean descriptionHeader = false; boolean idHeader = false; boolean dispositionHeader = false; if (bodyDescription != null) { for (Header header : headers) { if (header.getName().toLowerCase().equals("content-description")) { String headDescription = header.getValue(); descriptionHeader = true; if (!bodyDescription.equals(headDescription)) { throw XProcException.stepError(20); } } } if (!descriptionHeader) { headers.add(new BasicHeader("Content-Description", bodyDescription)); } } if (bodyId != null) { for (Header header : headers) { if (header.getName().toLowerCase().equals("content-id")) { String headId = header.getValue(); idHeader = true; if (!bodyId.equals(headId)) { throw XProcException.stepError(20); } } } if (!idHeader) { headers.add(new BasicHeader("Content-Id", bodyId)); } } if (bodyDisposition != null) { for (Header header : headers) { if (header.getName().toLowerCase().equals("content-disposition")) { String headDisposition = header.getValue(); dispositionHeader = true; if (!bodyDisposition.equals(headDisposition)) { throw XProcException.stepError(20); } } } if (!dispositionHeader) { headers.add(new BasicHeader("Content-Disposition", bodyDisposition)); } } if (headerContentType != null && !headerContentType.equals(contentType.toLowerCase())) { throw XProcException.stepError(20); } for (Header header : headers) { method.addHeader(header); } String encoding = body.getAttributeValue(_encoding); if (encoding != null && !"base64".equals(encoding)) { throw XProcException.stepError(52); } HttpEntity requestEntity = null; try { if ("base64".equals(encoding)) { String charset = body.getAttributeValue(_charset); // See also: https://github.com/ndw/xmlcalabash1/pull/241 and // https://github.com/ndw/xmlcalabash1/issues/242 // // The PR proposes ignoring the charset and treating the data as binary. // That's clearly necessary for the case where the data *is* binary. // However, if the base64 encoded element *has* a charset parameter, // we must pass that through (because text/html data often winds // up base64 encoded with a charset. // // In other words, the answer to the comment that used to be here, // "is utf-8 the right default?", is "no." String content = extractText(body); byte[] decoded = Base64.decode(content); if (charset == null) { // Treat as binary requestEntity = new ByteArrayEntity(decoded, ContentType.create(contentType)); } else { // Treat as encoded characters requestEntity = new ByteArrayEntity(decoded, ContentType.create(contentType, charset)); } } else { if (jsonContentType(contentType)) { requestEntity = new StringEntity(XMLtoJSON.convert(body), ContentType.create(contentType, "UTF-8")); } else if (xmlContentType(contentType)) { Serializer serializer = makeSerializer(); try { S9apiUtils.assertDocumentContent(body.axisIterator(Axis.CHILD)); } catch (XProcException xe) { throw XProcException.stepError(22); } Vector content = new Vector (); XdmSequenceIterator iter = body.axisIterator(Axis.CHILD); while (iter.hasNext()) { XdmNode node = (XdmNode) iter.next(); content.add(node); } // FIXME: set serializer properties appropriately! StringWriter writer = new StringWriter(); serializer.setOutputWriter(writer); S9apiUtils.serialize(runtime, content, serializer); writer.close(); requestEntity = new StringEntity(writer.toString(), ContentType.create(contentType, "UTF-8")); } else { requestEntity = new StringEntity(extractText(body), ContentType.create(contentType, "UTF-8")); } } method.setEntity(requestEntity); } catch (IOException ioe) { throw new XProcException(ioe); } catch (SaxonApiException sae) { throw new XProcException(sae); } } private void doPutOrPostMultipart(HttpEntityEnclosingRequest method, XdmNode multipart) { // The Apache HTTP libraries just don't handle this case...we treat it as a "single part" // and build the body ourselves, using the boundaries etc. // Check for consistency of content-type String contentType = multipart.getAttributeValue(_content_type); if (contentType == null) { contentType = "multipart/mixed"; } if (headerContentType != null && !headerContentType.equals(contentType.toLowerCase())) { throw XProcException.stepError(20); } if (!contentType.startsWith("multipart/")) { throw new UnsupportedOperationException("Multipart content-type must be multipart/..."); } for (Header header : headers) { method.addHeader(header); } String boundary = multipart.getAttributeValue(_boundary); if (boundary == null) { throw new XProcException(step, "A boundary value must be specified on c:multipart"); } if (boundary.startsWith("--")) { throw XProcException.stepError(2); } String q = "\""; if (boundary.contains(q)) { q = "'"; } if (boundary.contains(q)) { q = ""; } String multipartContentType = contentType + "; boundary=" + q + boundary + q; // FIXME: This sucks rocks. I want to write the data to be posted, not provide some way to read it MessageBytes byteContent = new MessageBytes(); byteContent.append("This is a multipart message.\r\n"); //String postContent = "This is a multipart message.\r\n"; for (XdmNode body : new AxisNodes(multipart, Axis.CHILD, AxisNodes.SIGNIFICANT)) { if (!XProcConstants.c_body.equals(body.getNodeName())) { throw new XProcException(step, "A c:multipart may only contain c:body elements."); } String bodyContentType = body.getAttributeValue(_content_type); if (bodyContentType == null) { throw new XProcException(step, "Content-type on c:body is required."); } String bodyId = body.getAttributeValue(_id); String bodyDescription = body.getAttributeValue(_description); String bodyDisposition = body.getAttributeValue(_disposition); String bodyCharset = HttpUtils.getCharset(bodyContentType); if (bodyContentType.contains(";")) { int pos = bodyContentType.indexOf(";"); bodyContentType = bodyContentType.substring(0, pos); } String bodyEncoding = body.getAttributeValue(_encoding); if (bodyEncoding != null && !"base64".equals(bodyEncoding)) { throw new UnsupportedOperationException("The '" + bodyEncoding + "' encoding is not supported"); } if (bodyCharset != null) { bodyContentType += "; charset=" + bodyCharset; } byteContent.append("--" + boundary + "\r\n"); byteContent.append("Content-Type: " + bodyContentType + "\r\n"); if (bodyDescription != null) { byteContent.append("Content-Description: " + bodyDescription + "\r\n"); } if (bodyId != null) { byteContent.append("Content-ID: " + bodyId + "\r\n"); } if (bodyDisposition != null) { byteContent.append("Content-Disposition: " + bodyDisposition + "\r\n"); } if (bodyEncoding != null) { if (encodeBinary) { byteContent.append("Content-Transfer-Encoding: " + bodyEncoding + "\r\n"); } } byteContent.append("\r\n"); try { if (xmlContentType(bodyContentType)) { Serializer serializer = makeSerializer(); Vector content = new Vector (); XdmSequenceIterator iter = body.axisIterator(Axis.CHILD); while (iter.hasNext()) { XdmNode node = (XdmNode) iter.next(); content.add(node); } // FIXME: set serializer properties appropriately! StringWriter writer = new StringWriter(); serializer.setOutputWriter(writer); S9apiUtils.serialize(runtime, content, serializer); writer.close(); byteContent.append(writer.toString()); } else if (jsonContentType(contentType)) { byteContent.append(XMLtoJSON.convert(body)); } else if (!encodeBinary && "base64".equals(bodyEncoding)) { byte[] decoded = Base64.decode(body.getStringValue()); byteContent.append(decoded, decoded.length); } else { byteContent.append(extractText(body)); } //postContent += "\r\n"; byteContent.append("\r\n"); } catch (IOException ioe) { throw new XProcException(ioe); } catch (SaxonApiException sae) { throw new XProcException(sae); } } //postContent += "--" + boundary + "--\r\n"; byteContent.append("--" + boundary + "--\r\n"); ByteArrayEntity requestEntity = new ByteArrayEntity(byteContent.content(), ContentType.create(multipartContentType)); //StringRequestEntity requestEntity = new StringRequestEntity(postContent, multipartContentType, null); method.setEntity(requestEntity); } private String getFullContentType(HttpResponse method) { Header contentTypeHeader = method.getLastHeader("Content-Type"); return getFullContentType(contentTypeHeader); } private String getFullContentType(Header contentTypeHeader) { if (contentTypeHeader == null) { // This should never happen, but if it does... return "application/octet-stream"; } HeaderElement[] contentTypes = contentTypeHeader.getElements(); if (contentTypes == null || contentTypes.length == 0) { // This should never happen return null; } String ctype = contentTypes[0].getName(); NameValuePair[] params = contentTypes[0].getParameters(); if (params != null) { for (NameValuePair pair : params) { ctype = ctype + "; " + pair.getName() + "=\"" + pair.getValue() + "\""; } } return ctype; } private String getHeaderValue(Header header) { if (header == null) { // This should never happen return null; } HeaderElement[] elems = header.getElements(); if (elems == null || elems.length == 0) { // This should never happen return null; } return elems[0].getName(); } private String getContentType(HttpResponse method) { Header contentTypeHeader = method.getLastHeader("Content-Type"); String contentType = getContentType(contentTypeHeader); if (contentType == null) { // This should never happen either... return "application/octet-stream"; } else { return contentType; } } private String getContentType(Header contentTypeHeader) { return getHeaderValue(contentTypeHeader); } private String getContentBoundary(HttpResponse method) { Header contentTypeHeader = method.getLastHeader("Content-Type"); return getContentBoundary(contentTypeHeader); } private String getContentBoundary(Header contentTypeHeader) { if (contentTypeHeader == null) { // This should never happen return null; } HeaderElement[] contentTypes = contentTypeHeader.getElements(); if (contentTypes == null || contentTypes.length == 0) { // This should never happen return null; } NameValuePair boundary = contentTypes[0].getParameterByName("boundary"); return boundary == null ? null : boundary.getValue(); } private String getContentCharset(Header contentTypeHeader) { if (contentTypeHeader == null) { // This should never happen return null; } HeaderElement[] contentTypes = contentTypeHeader.getElements(); if (contentTypes == null || contentTypes.length == 0) { // This should never happen return null; } NameValuePair cpair = contentTypes[0].getParameterByName("charset"); if (cpair == null) { return "US-ASCII"; } else { return cpair.getValue(); } } private boolean xmlContentType(String contentType) { return HttpUtils.xmlContentType(contentType); } private boolean jsonContentType(String contentType) { return runtime.transparentJSON() && HttpUtils.jsonContentType(contentType); } private boolean textContentType(String contentType) { return HttpUtils.textContentType(contentType); } private void readBodyContent(TreeWriter tree, InputStream bodyStream, HttpResponse method) throws SaxonApiException, IOException { String contentType = getFullContentType(method); Charset cs = ContentType.getOrDefault(method.getEntity()).getCharset(); String charset = cs == null ? Consts.ISO_8859_1.name() : cs.name(); String boundary = getContentBoundary(method); if (overrideContentType != null) { contentType = overrideContentType; } if (contentType.startsWith("multipart/")) { tree.addStartElement(XProcConstants.c_multipart); tree.addAttribute(_content_type, contentType); tree.addAttribute(_boundary, boundary); tree.startContent(); readMultipartContent(tree, bodyStream, boundary); tree.addEndElement(); } else { if (!detailed && (xmlContentType(contentType) || jsonContentType(contentType))) { readBodyContentPart(tree, bodyStream, contentType, charset); } else { tree.addStartElement(XProcConstants.c_body); tree.addAttribute(_content_type, contentType); if (!xmlContentType(contentType) && !textContentType(contentType) && !jsonContentType(contentType)) { tree.addAttribute(_encoding, "base64"); } tree.startContent(); readBodyContentPart(tree, bodyStream, contentType, charset); tree.addEndElement(); } } } private void readMultipartContent(TreeWriter tree, InputStream bodyStream, String boundary) throws IOException, SaxonApiException { MIMEReader reader = new MIMEReader(bodyStream, boundary); boolean done = false; while (reader.readHeaders()) { Header pctype = reader.getHeader("Content-Type"); Header pclen = reader.getHeader("Content-Length"); String contentType = getHeaderValue(pctype); String charset = getContentCharset(pctype); String partType = getHeaderValue(pctype); InputStream partStream = null; if (pclen != null) { int len = Integer.parseInt(getHeaderValue(pclen)); partStream = reader.readBodyPart(len); } else { partStream = reader.readBodyPart(); } tree.addStartElement(XProcConstants.c_body); tree.addAttribute(_content_type, contentType); if (!xmlContentType(contentType) && !textContentType(contentType)) { tree.addAttribute(_encoding, "base64"); } tree.startContent(); if (xmlContentType(partType)) { BufferedReader preader = new BufferedReader(new InputStreamReader(partStream, charset)); // Read it as XML tree.addSubtree(runtime.parse(new InputSource(preader))); } else if (textContentType(partType)) { BufferedReader preader = new BufferedReader(new InputStreamReader(partStream, charset)); // Read it as text char buf[] = new char[bufSize]; int len = preader.read(buf, 0, bufSize); while (len >= 0) { // I'm unsure about this. If I'm reading text and injecting it into XML, // I think I need to change CR/LF pairs (and CR not followed by LF) into // plain LFs. char fbuf[] = new char[bufSize]; char flen = 0; for (int pos = 0; pos < len; pos++) { if (buf[pos] == '\r') { if (pos+1 == len) { // FIXME: Check for CR/LF pairs that cross a buffer boundary! // Assume it's part of a CR/LF pair... } else { if (buf[pos+1] == '\n') { // nop } else { fbuf[flen++] = '\n'; } } } else { fbuf[flen++] = buf[pos]; } } tree.addText(new String(fbuf,0,flen)); len = preader.read(buf, 0, bufSize); } } else { // Read it as binary byte bytes[] = new byte[bufSize]; int pos = 0; int readLen = bufSize; int len = partStream.read(bytes, 0, bufSize); while (len >= 0) { pos += len; readLen -= len; if (readLen == 0) { tree.addText(Base64.encodeBytes(bytes)); pos = 0; readLen = bufSize; } len = partStream.read(bytes, pos, readLen); } if (pos > 0) { byte lastBytes[] = new byte[pos]; System.arraycopy(bytes, 0, lastBytes, 0, pos); tree.addText(Base64.encodeBytes(lastBytes)); } tree.addText("\n"); // FIXME: should we be doing this? } tree.addEndElement(); } } public void readBodyContentPart(TreeWriter tree, InputStream bodyStream, String contentType, String charset) throws SaxonApiException, IOException { if (xmlContentType(contentType)) { // Read it as XML tree.addSubtree(runtime.parse(new InputSource(bodyStream))); } else if (textContentType(contentType)) { // Read it as text InputStreamReader reader = new InputStreamReader(bodyStream, charset); char buf[] = new char[bufSize]; int len = reader.read(buf, 0, bufSize); while (len >= 0) { String s = new String(buf,0,len); tree.addText(s); len = reader.read(buf, 0, bufSize); } } else if (jsonContentType(contentType)) { InputStreamReader reader = new InputStreamReader(bodyStream); JSONTokener jt = new JSONTokener(reader); XdmNode jsonDoc = JSONtoXML.convert(runtime.getProcessor(), jt, runtime.jsonFlavor()); tree.addSubtree(jsonDoc); } else { // Read it as binary byte bytes[] = new byte[bufSize]; int pos = 0; int readLen = bufSize; int len = bodyStream.read(bytes, 0, bufSize); while (len >= 0) { pos += len; readLen -= len; if (readLen == 0) { String encoded = Base64.encodeBytes(bytes); tree.addText(encoded); pos = 0; readLen = bufSize; } len = bodyStream.read(bytes, pos, readLen); } if (pos > 0) { byte lastBytes[] = new byte[pos]; System.arraycopy(bytes, 0, lastBytes, 0, pos); tree.addText(Base64.encodeBytes(lastBytes)); } tree.addText("\n"); // FIXME: should we be doing this? } } private String extractText(XdmNode doc) { String content = ""; XdmSequenceIterator iter = doc.axisIterator(Axis.CHILD); while (iter.hasNext()) { XdmNode child = (XdmNode) iter.next(); if (child.getNodeKind() != XdmNodeKind.TEXT) { throw XProcException.stepError(28); } content += child.getStringValue(); } return content; } private void doFile(String href, String base) { try { DataStore store = runtime.getDataStore(); store.readEntry(href, base, "application/xml, text/xml, */*", overrideContentType, new DataReader() { public void load(URI id, String contentType, InputStream bodyStream, long len) throws IOException { // Get the default charset from the file.encoding system property. // Fall back to UTF-8 if that's not set. String defCharset = System.getProperty("file.encoding","UTF-8"); String charset = HttpUtils.getCharset(contentType, defCharset); TreeWriter tree = new TreeWriter(runtime); tree.startDocument(id); try { if (xmlContentType(contentType)) { readBodyContentPart(tree, bodyStream, contentType, charset); } else { tree.addStartElement(XProcConstants.c_body); tree.addAttribute(_content_type, contentType); if (!xmlContentType(contentType) && !textContentType(contentType)) { tree.addAttribute(_encoding, "base64"); } tree.startContent(); readBodyContentPart(tree, bodyStream, contentType, charset); tree.addEndElement(); } tree.endDocument(); XdmNode doc = tree.getResult(); result.write(doc); } catch (SaxonApiException sae) { throw new XProcException(sae); } } }); } catch (FileNotFoundException fnfe) { throw new XProcException(fnfe); } catch (IOException ioe) { throw new XProcException(ioe); } } private class MessageBytes { int chunkSize = 8192; byte[] byteContent = new byte[chunkSize]; int pos = 0; public MessageBytes() { } public void append(String string) { try { byte[] bytes = string.getBytes("US-ASCII"); append(bytes, bytes.length); } catch (UnsupportedEncodingException uee) { // This never happens! throw new XProcException(uee); } } public void append(byte[] bytes, int size) { if (pos + bytes.length > byteContent.length) { byte[] newBytes = new byte[byteContent.length + bytes.length + chunkSize]; System.arraycopy(byteContent, 0, newBytes, 0, byteContent.length); byteContent = newBytes; } System.arraycopy(bytes, 0, byteContent, pos, bytes.length); pos += bytes.length; } public byte[] content() { byte[] bytes = new byte[pos]; System.arraycopy(byteContent, 0, bytes, 0, pos); return bytes; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy