/* * SoapUI, Copyright (C) 2004-2016 SmartBear Software * * Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent * versions of the EUPL (the "Licence"); * You may not use this work except in compliance with the Licence. * You may obtain a copy of the Licence at: * * http://ec.europa.eu/idabc/eupl * * Unless required by applicable law or agreed to in writing, software distributed under the Licence is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the Licence for the specific language governing permissions and limitations * under the Licence. */ package com.eviware.soapui.impl.wsdl.submit.transports.http.support.attachments; import com.eviware.soapui.SoapUI; import com.eviware.soapui.config.PartsConfig; import com.eviware.soapui.config.PartsConfig.Part; import com.eviware.soapui.impl.wsdl.AttachmentContainer; import com.eviware.soapui.impl.wsdl.HttpAttachmentPart; import com.eviware.soapui.impl.wsdl.WsdlAttachmentContainer; import com.eviware.soapui.impl.wsdl.WsdlOperation; import com.eviware.soapui.impl.wsdl.support.MessageXmlPart; import com.eviware.soapui.impl.wsdl.support.PathUtils; import com.eviware.soapui.impl.wsdl.support.soap.SoapVersion; import com.eviware.soapui.impl.wsdl.support.wsdl.WsdlContext; import com.eviware.soapui.impl.wsdl.support.wsdl.WsdlValidator; import com.eviware.soapui.impl.wsdl.support.xsd.SchemaUtils; import com.eviware.soapui.model.iface.Attachment; import com.eviware.soapui.model.iface.Attachment.AttachmentEncoding; import com.eviware.soapui.support.StringUtils; import com.eviware.soapui.support.Tools; import com.eviware.soapui.support.editor.inspectors.attachments.ContentTypeHandler; import com.eviware.soapui.support.types.StringToStringMap; import com.eviware.soapui.support.xml.XmlObjectTreeModel; import com.eviware.soapui.support.xml.XmlObjectTreeModel.XmlTreeNode; import com.eviware.soapui.support.xml.XmlUtils; import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Hex; import org.apache.log4j.Logger; import org.apache.xmlbeans.SchemaType; import org.apache.xmlbeans.XmlBase64Binary; import org.apache.xmlbeans.XmlCursor; import org.apache.xmlbeans.XmlHexBinary; import org.apache.xmlbeans.XmlObject; import javax.activation.DataHandler; import javax.mail.MessagingException; import javax.mail.Session; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMultipart; import javax.mail.internet.PreencodedMimeBodyPart; import javax.wsdl.Input; import javax.wsdl.Output; import javax.xml.namespace.QName; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; /** * Attachment-related utility classes * * @author ole.matzura */ public class AttachmentUtils { private final static Logger log = Logger.getLogger(AttachmentUtils.class); private static final QName XMLMIME_CONTENTTYPE_200505 = new QName("http://www.w3.org/2005/05/xmlmime", "contentType"); private static final QName XMLMIME_CONTENTTYPE_200411 = new QName("http://www.w3.org/2004/11/xmlmime", "contentType"); private static final QName SWAREF_QNAME = new QName("http://ws-i.org/profiles/basic/1.1/xsd", "swaRef"); public static final QName XOP_HREF_QNAME = new QName("href"); private static final QName XOP_INCLUDE_QNAME = new QName("http://www.w3.org/2004/08/xop/include", "Include"); public static final String ROOTPART_SOAPUI_ORG = "<rootpart@soapui.org>"; public static final long MAX_SIZE_IN_MEMORY_ATTACHMENT = 500 * 1024; public static boolean prepareMessagePart(WsdlAttachmentContainer container, MimeMultipart mp, MessageXmlPart messagePart, StringToStringMap contentIds) throws Exception, MessagingException { boolean isXop = false; XmlObjectTreeModel treeModel = null; XmlCursor cursor = messagePart.newCursor(); XmlObject rootXmlObject = cursor.getObject(); try { while (!cursor.isEnddoc()) { if (cursor.isContainer()) { // could be an attachment part (as of "old" SwA specs which // specify a content // element referring to the attachment) if (messagePart.isAttachmentPart()) { String href = cursor.getAttributeText(XOP_HREF_QNAME); if (href != null && href.length() > 0) { contentIds.put(messagePart.getPart().getName(), href); } break; } XmlObject xmlObj = cursor.getObject(); SchemaType schemaType = xmlObj.schemaType(); if (schemaType.isNoType()) { if (treeModel == null) { treeModel = new XmlObjectTreeModel(messagePart.getSchemaType().getTypeSystem(), rootXmlObject); } XmlTreeNode tn = treeModel.getXmlTreeNode(xmlObj); if (tn != null) { schemaType = tn.getSchemaType(); } } if (AttachmentUtils.isSwaRefType(schemaType)) { String textContent = XmlUtils.getNodeValue(cursor.getDomNode()); if (StringUtils.hasContent(textContent) && textContent.startsWith("cid:")) { textContent = textContent.substring(4); try { // is the textcontent already a URI? new URI(textContent); contentIds.put(textContent, textContent); } catch (RuntimeException e) { // not a URI.. try to create one.. String contentId = textContent + "@soapui.org"; cursor.setTextValue("cid:" + contentId); contentIds.put(textContent, contentId); } } } else if (AttachmentUtils.isXopInclude(schemaType)) { String contentId = cursor.getAttributeText(new QName("href")); if (contentId != null && contentId.length() > 0) { contentIds.put(contentId, contentId); isXop = true; Attachment[] attachments = container.getAttachmentsForPart(contentId); if (attachments.length == 1) { XmlCursor cur = cursor.newCursor(); if (cur.toParent()) { String contentType = getXmlMimeContentType(cur); if (contentType != null && contentType.length() > 0) { attachments[0].setContentType(contentType); } } cur.dispose(); } } } else { // extract contentId String textContent = XmlUtils.getNodeValue(cursor.getDomNode()); if (StringUtils.hasContent(textContent)) { Attachment attachment = null; boolean isXopAttachment = false; // is content a reference to a file? if (container.isInlineFilesEnabled() && textContent.startsWith("file:")) { String filename = textContent.substring(5); if (container.isMtomEnabled()) { MimeBodyPart part = new PreencodedMimeBodyPart("binary"); String xmimeContentType = getXmlMimeContentType(cursor); if (StringUtils.isNullOrEmpty(xmimeContentType)) { xmimeContentType = ContentTypeHandler.getContentTypeFromFilename(filename); } part.setDataHandler(new DataHandler(new XOPPartDataSource(new File(filename), xmimeContentType, schemaType))); part.setContentID("<" + filename + ">"); mp.addBodyPart(part); isXopAttachment = true; } else { if (new File(filename).exists()) { inlineData(cursor, schemaType, new FileInputStream(filename)); } else { Attachment att = getAttachmentForFilename(container, filename); if (att != null) { inlineData(cursor, schemaType, att.getInputStream()); } } } } else { Attachment[] attachmentsForPart = container.getAttachmentsForPart(textContent); if (textContent.startsWith("cid:")) { textContent = textContent.substring(4); attachmentsForPart = container.getAttachmentsForPart(textContent); Attachment[] attachments = attachmentsForPart; if (attachments.length == 1) { attachment = attachments[0]; } else if (attachments.length > 1) { attachment = buildMulitpartAttachment(attachments); } isXopAttachment = container.isMtomEnabled(); contentIds.put(textContent, textContent); } // content should be binary data; is this an XOP element // which should be serialized with MTOM? else if (container.isMtomEnabled() && (SchemaUtils.isBinaryType(schemaType) || SchemaUtils.isAnyType(schemaType))) { if ("true".equals(System.getProperty("soapui.mtom.strict"))) { if (SchemaUtils.isAnyType(schemaType)) { textContent = null; } else { for (int c = 0; c < textContent.length(); c++) { if (Character.isWhitespace(textContent.charAt(c))) { textContent = null; break; } } } } if (textContent != null) { MimeBodyPart part = new PreencodedMimeBodyPart("binary"); String xmimeContentType = getXmlMimeContentType(cursor); part.setDataHandler(new DataHandler(new XOPPartDataSource(textContent, xmimeContentType, schemaType))); textContent = "http://www.soapui.org/" + System.nanoTime(); part.setContentID("<" + textContent + ">"); mp.addBodyPart(part); isXopAttachment = true; } } else if (container.isInlineFilesEnabled() && attachmentsForPart != null && attachmentsForPart.length > 0) { attachment = attachmentsForPart[0]; } } // add XOP include? if (isXopAttachment && container.isMtomEnabled()) { buildXopInclude(cursor, textContent); isXop = true; } // inline? else if (attachment != null) { inlineAttachment(cursor, schemaType, attachment); } } } } cursor.toNextToken(); } } finally { cursor.dispose(); } return isXop; } private static Attachment getAttachmentForFilename(WsdlAttachmentContainer container, String filename) { for (Attachment attachment : container.getAttachments()) { if (filename.equals(attachment.getName())) { return attachment; } } return null; } private static void inlineAttachment(XmlCursor cursor, SchemaType schemaType, Attachment attachment) throws Exception { inlineData(cursor, schemaType, attachment.getInputStream()); } private static void inlineData(XmlCursor cursor, SchemaType schemaType, InputStream in) throws IOException { String content = null; byte[] data = Tools.readAll(in, -1).toByteArray(); if (SchemaUtils.isInstanceOf(schemaType, XmlHexBinary.type)) { content = new String(Hex.encodeHex(data)); } else if (SchemaUtils.isInstanceOf(schemaType, XmlBase64Binary.type)) { content = new String(Base64.encodeBase64(data)); } else { content = new String(data); } XmlCursor c = cursor.newCursor(); c.setTextValue(content); c.dispose(); } private static void buildXopInclude(XmlCursor cursor, String contentId) { // build xop:Include XmlCursor c = cursor.newCursor(); c.removeXmlContents(); c.toFirstContentToken(); c.beginElement(XOP_INCLUDE_QNAME); c.insertAttributeWithValue(XOP_HREF_QNAME, "cid:" + contentId); c.toNextSibling(); c.removeXml(); c.dispose(); } private static Attachment buildMulitpartAttachment(Attachment[] attachments) { System.out.println("buildMulitpartAttachment(Attachment[] attachments) not implemented!"); return null; } public static String buildRootPartContentType(String action, SoapVersion soapVersion) { String contentType = "application/xop+xml; charset=UTF-8; type=\"" + soapVersion.getContentType(); if (soapVersion == SoapVersion.Soap12) { contentType += "\"; action=\"" + action; } return contentType + "\""; } public static String buildMTOMContentType(String header, String action, SoapVersion soapVersion) { int ix = header.indexOf("boundary"); String contentType = "multipart/related; type=\"application/xop+xml\"; start=\"" + ROOTPART_SOAPUI_ORG + "\"; " + "start-info=\"" + soapVersion.getContentType(); if (soapVersion == SoapVersion.Soap12 && action != null) { contentType += "\"; action=\"" + action; } // nested or not? see // http://www.eviware.com/forums/index.php?topic=2866.new#new // contentType += "; action=\\\"" + action + "\\\"\"; action=\"" + action; return contentType + "\"; " + header.substring(ix); } public static boolean isSwaRefType(SchemaType schemaType) { return schemaType != null && schemaType.getName() != null && schemaType.getName().equals(SWAREF_QNAME); } public static String getXmlMimeContentType(XmlCursor cursor) { String attributeText = cursor.getAttributeText(XMLMIME_CONTENTTYPE_200411); if (attributeText == null) { attributeText = cursor.getAttributeText(XMLMIME_CONTENTTYPE_200505); } return attributeText; } public static AttachmentEncoding getAttachmentEncoding(WsdlOperation operation, HttpAttachmentPart httpAttachmentPart, boolean isResponse) { if (httpAttachmentPart.getSchemaType() != null) { if (SchemaUtils.isInstanceOf(httpAttachmentPart.getSchemaType(), XmlBase64Binary.type)) { return AttachmentEncoding.BASE64; } else if (SchemaUtils.isInstanceOf(httpAttachmentPart.getSchemaType(), XmlHexBinary.type)) { return AttachmentEncoding.HEX; } } return getAttachmentEncoding(operation, httpAttachmentPart.getName(), isResponse); } public static AttachmentEncoding getAttachmentEncoding(WsdlOperation operation, String partName, boolean isResponse) { // make sure we have access if (operation == null || operation.getBindingOperation() == null || operation.getBindingOperation().getOperation() == null) { return AttachmentEncoding.NONE; } javax.wsdl.Part part = null; if (isResponse) { Output output = operation.getBindingOperation().getOperation().getOutput(); if (output == null || output.getMessage() == null) { return AttachmentEncoding.NONE; } else { part = output.getMessage().getPart(partName); } } else { Input input = operation.getBindingOperation().getOperation().getInput(); if (input == null || input.getMessage() == null) { return AttachmentEncoding.NONE; } else { part = input.getMessage().getPart(partName); } } if (part != null) { QName typeName = part.getTypeName(); if (typeName.getNamespaceURI().equals("http://www.w3.org/2001/XMLSchema")) { if (typeName.getLocalPart().equals("base64Binary")) { return AttachmentEncoding.BASE64; } else if (typeName.getLocalPart().equals("hexBinary")) { return AttachmentEncoding.HEX; } } } return AttachmentEncoding.NONE; } public static boolean isXopInclude(SchemaType schemaType) { return XOP_INCLUDE_QNAME.equals(schemaType.getName()); } public static List<HttpAttachmentPart> extractAttachmentParts(WsdlOperation operation, String messageContent, boolean addAnonymous, boolean isResponse, boolean forceMtom) { List<HttpAttachmentPart> result = new ArrayList<HttpAttachmentPart>(); PartsConfig messageParts = isResponse ? operation.getConfig().getResponseParts() : operation.getConfig() .getRequestParts(); if (messageParts != null) { for (Part part : messageParts.getPartList()) { HttpAttachmentPart attachmentPart = new HttpAttachmentPart(part.getName(), part.getContentTypeList()); attachmentPart.setType(Attachment.AttachmentType.MIME); result.add(attachmentPart); } } if (messageContent.length() > 0) { WsdlContext wsdlContext = operation.getInterface().getWsdlContext(); WsdlValidator validator = new WsdlValidator(wsdlContext); try { XmlObject[] requestDocuments = validator.getMessageParts(messageContent, operation.getName(), isResponse); for (XmlObject partDoc : requestDocuments) { XmlCursor cursor = partDoc.newCursor(); while (!cursor.isEnddoc()) { if (cursor.isContainer()) { SchemaType schemaType = cursor.getObject().schemaType(); if (schemaType != null) { String attributeText = AttachmentUtils.getXmlMimeContentType(cursor); // xop? if (SchemaUtils.isBinaryType(schemaType) || SchemaUtils.isAnyType(schemaType)) { String contentId = cursor.getTextValue(); if (contentId.startsWith("cid:")) { HttpAttachmentPart attachmentPart = new HttpAttachmentPart(contentId.substring(4), attributeText); attachmentPart .setType(attributeText == null && !forceMtom ? Attachment.AttachmentType.CONTENT : Attachment.AttachmentType.XOP); result.add(attachmentPart); } } else if (AttachmentUtils.isXopInclude(schemaType)) { String contentId = cursor.getAttributeText(new QName("href")); if (contentId != null && contentId.length() > 0) { HttpAttachmentPart attachmentPart = new HttpAttachmentPart(contentId, attributeText); attachmentPart.setType(Attachment.AttachmentType.XOP); result.add(attachmentPart); } } // swaref? else if (AttachmentUtils.isSwaRefType(schemaType)) { String contentId = cursor.getTextValue(); if (contentId.startsWith("cid:")) { HttpAttachmentPart attachmentPart = new HttpAttachmentPart(contentId.substring(4), attributeText); attachmentPart.setType(Attachment.AttachmentType.SWAREF); result.add(attachmentPart); } } } } cursor.toNextToken(); } } } catch (Exception e) { if (e instanceof NullPointerException) { SoapUI.logError(e); } log.warn(e.getMessage()); } } if (addAnonymous) { result.add(new HttpAttachmentPart()); } return result; } /** * Adds defined attachments as mimeparts */ public static void addMimeParts(AttachmentContainer container, List<Attachment> attachments, MimeMultipart mp, StringToStringMap contentIds) throws MessagingException { // no multipart handling? if (!container.isMultipartEnabled()) { for (int c = 0; c < attachments.size(); c++) { Attachment att = attachments.get(c); if (att.getAttachmentType() != Attachment.AttachmentType.CONTENT) { addSingleAttachment(mp, contentIds, att); } } } else { // first identify if any part has more than one attachments Map<String, List<Attachment>> attachmentsMap = new HashMap<String, List<Attachment>>(); for (int c = 0; c < attachments.size(); c++) { Attachment att = attachments.get(c); if (att.getAttachmentType() == Attachment.AttachmentType.CONTENT) { continue; } String partName = att.getPart(); if (!attachmentsMap.containsKey(partName)) { attachmentsMap.put(partName, new ArrayList<Attachment>()); } attachmentsMap.get(partName).add(att); } // add attachments for (Iterator<String> i = attachmentsMap.keySet().iterator(); i.hasNext(); ) { attachments = attachmentsMap.get(i.next()); if (attachments.size() == 1) { Attachment att = attachments.get(0); addSingleAttachment(mp, contentIds, att); } // more than one attachment with the same part -> create multipart // attachment else if (attachments.size() > 1) { addMultipartAttachment(mp, contentIds, attachments); } } } } /** * Adds a mulitpart MimeBodyPart from an array of attachments */ public static void addMultipartAttachment(MimeMultipart mp, StringToStringMap contentIds, List<Attachment> attachments) throws MessagingException { MimeMultipart multipart = new MimeMultipart("mixed"); long totalSize = 0; for (int c = 0; c < attachments.size(); c++) { Attachment att = attachments.get(c); String contentType = att.getContentType(); totalSize += att.getSize(); MimeBodyPart part = contentType.startsWith("text/") ? new MimeBodyPart() : new PreencodedMimeBodyPart( "binary"); part.setDataHandler(new DataHandler(new AttachmentDataSource(att))); initPartContentId(contentIds, part, att, false); multipart.addBodyPart(part); } MimeBodyPart part = new PreencodedMimeBodyPart("binary"); if (totalSize > MAX_SIZE_IN_MEMORY_ATTACHMENT) { part.setDataHandler(new DataHandler(new MultipartAttachmentFileDataSource(multipart))); } else { part.setDataHandler(new DataHandler(new MultipartAttachmentDataSource(multipart))); } Attachment attachment = attachments.get(0); initPartContentId(contentIds, part, attachment, true); mp.addBodyPart(part); } public static void initPartContentId(StringToStringMap contentIds, MimeBodyPart part, Attachment attachment, boolean isMultipart) throws MessagingException { String partName = attachment.getPart(); String contentID = attachment.getContentID(); if (StringUtils.hasContent(contentID)) { contentID = contentID.trim(); int ix = contentID.indexOf(' '); if (ix != -1) { part.setContentID("<" + (isMultipart ? contentID.substring(ix + 1) : contentID.substring(0, ix)) + ">"); } else { if (!contentID.startsWith("<")) { contentID = "<" + contentID; } if (!contentID.endsWith(">")) { contentID = contentID + ">"; } part.setContentID(contentID); } } else if (partName != null && !partName.equals(HttpAttachmentPart.ANONYMOUS_NAME)) { if (contentIds.containsKey(partName)) { part.setContentID("<" + contentIds.get(partName) + ">"); } else { part.setContentID("<" + partName + "=" + System.nanoTime() + "@soapui.org>"); } } // set content-disposition String name = attachment.getName(); String file = attachment.getUrl(); if (PathUtils.isFilePath(file)) { int ix = file.lastIndexOf(File.separatorChar); if (ix == -1) { ix = file.lastIndexOf('/'); } if (ix > 0 && ix < file.length() - 1) { file = file.substring(ix + 1); } part.setDisposition("attachment; name=\"" + name + "\"; filename=\"" + file + "\""); } else { part.setDisposition("attachment; name=\"" + name + "\""); } } /** * Adds a simple MimeBodyPart from an attachment */ public static void addSingleAttachment(MimeMultipart mp, StringToStringMap contentIds, Attachment att) throws MessagingException { String contentType = att.getContentType(); MimeBodyPart part = contentType.startsWith("text/") ? new MimeBodyPart() : new PreencodedMimeBodyPart("binary"); part.setDataHandler(new DataHandler(new AttachmentDataSource(att))); initPartContentId(contentIds, part, att, false); mp.addBodyPart(part); } public static final Session JAVAMAIL_SESSION = Session.getDefaultInstance(new Properties()); }