View Javadoc

    1   /*
    2    *  soapUI, copyright (C) 2004-2007 eviware.com
    3    *
    4    *  soapUI is free software; you can redistribute it and/or modify it under the
    5    *  terms of version 2.1 of the GNU Lesser General Public License as published by
    6    *  the Free Software Foundation.
    7    *
    8    *  soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
    9    *  even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    10   *  See the GNU Lesser General Public License for more details at gnu.org.
    11   */
    12
    13  package com.eviware.soapui.impl.wsdl.submit.transports.http;
    14
    15  import java.io.File;
    16  import java.io.FileInputStream;
    17  import java.io.IOException;
    18  import java.io.InputStream;
    19  import java.net.URI;
    20  import java.util.ArrayList;
    21  import java.util.HashMap;
    22  import java.util.Iterator;
    23  import java.util.List;
    24  import java.util.Map;
    25  import java.util.Properties;
    26
    27  import javax.activation.DataHandler;
    28  import javax.mail.MessagingException;
    29  import javax.mail.Session;
    30  import javax.mail.internet.MimeBodyPart;
    31  import javax.mail.internet.MimeMultipart;
    32  import javax.mail.internet.PreencodedMimeBodyPart;
    33  import javax.wsdl.Input;
    34  import javax.wsdl.Output;
    35  import javax.xml.namespace.QName;
    36
    37  import org.apache.commons.codec.binary.Base64;
    38  import org.apache.commons.codec.binary.Hex;
    39  import org.apache.log4j.Logger;
    40  import org.apache.xmlbeans.SchemaType;
    41  import org.apache.xmlbeans.XmlBase64Binary;
    42  import org.apache.xmlbeans.XmlCursor;
    43  import org.apache.xmlbeans.XmlHexBinary;
    44  import org.apache.xmlbeans.XmlObject;
    45
    46  import com.eviware.soapui.config.PartsConfig;
    47  import com.eviware.soapui.config.PartsConfig.Part;
    48  import com.eviware.soapui.impl.wsdl.AttachmentContainer;
    49  import com.eviware.soapui.impl.wsdl.WsdlAttachmentPart;
    50  import com.eviware.soapui.impl.wsdl.WsdlInterface;
    51  import com.eviware.soapui.impl.wsdl.WsdlOperation;
    52  import com.eviware.soapui.impl.wsdl.panels.request.components.editor.inspectors.attachments.ContentTypeHandler;
    53  import com.eviware.soapui.impl.wsdl.support.MessageXmlPart;
    54  import com.eviware.soapui.impl.wsdl.support.soap.SoapVersion;
    55  import com.eviware.soapui.impl.wsdl.support.wsdl.WsdlContext;
    56  import com.eviware.soapui.impl.wsdl.support.wsdl.WsdlValidator;
    57  import com.eviware.soapui.impl.wsdl.support.xsd.SchemaUtils;
    58  import com.eviware.soapui.model.iface.Attachment;
    59  import com.eviware.soapui.model.iface.Attachment.AttachmentEncoding;
    60  import com.eviware.soapui.support.StringUtils;
    61  import com.eviware.soapui.support.Tools;
    62  import com.eviware.soapui.support.types.StringToStringMap;
    63
    64  /***
    65   * Attachment-related utility classes
    66   *
    67   * @author ole.matzura
    68   */
    69
    70  public class AttachmentUtils
    71  {
    72  	private final static Logger log = Logger.getLogger( AttachmentUtils.class );
    73  	private static final QName XMLMIME_CONTENTTYPE_200505 = new QName( "http://www.w3.org/2005/05/xmlmime", "contentType");
    74  	private static final QName XMLMIME_CONTENTTYPE_200411 = new QName( "http://www.w3.org/2004/11/xmlmime", "contentType");
    75  	private static final QName SWAREF_QNAME = new QName("http://ws-i.org/profiles/basic/1.1/xsd","swaRef");
    76  	public static final QName XOP_HREF_QNAME = new QName( "href" );
    77  	private static final QName XOP_INCLUDE_QNAME = new QName("http://www.w3.org/2004/08/xop/include", "Include" );
    78  	public static final String ROOTPART_SOAPUI_ORG = "<rootpart@soapui.org>";
    79
    80  	public static boolean prepareMessagePart(AttachmentContainer container, MimeMultipart mp, MessageXmlPart messagePart, StringToStringMap contentIds) throws Exception, MessagingException
    81  	{
    82  		boolean isXop = false;
    83
    84  		XmlCursor cursor = messagePart.newCursor();
    85
    86  		try
    87  		{
    88  			while( !cursor.isEnddoc() )
    89  			{
    90  				if( cursor.isContainer() )
    91  				{
    92  					// could be an attachment part (as of "old" SwA specs which specify a content
    93  					// element referring to the attachment)
    94  					if( messagePart.isAttachmentPart() )
    95  					{
    96  						String href = cursor.getAttributeText( XOP_HREF_QNAME);
    97  						if( href != null && href.length() > 0 )
    98  						{
    99  							contentIds.put( messagePart.getPart().getName(), href );
    100 						}
    101
    102 						break;
    103 					}
    104
    105 					SchemaType schemaType = cursor.getObject().schemaType();
    106
    107 					if( AttachmentUtils.isSwaRefType( schemaType ))
    108 					{
    109 						String textContent = cursor.getTextValue();
    110 						if( textContent.startsWith( "cid:" ))
    111 						{
    112 							textContent = textContent.substring( 4 );
    113
    114 							try
    115 							{
    116 					   		// is the textcontent already a URI?
    117 								new URI( textContent );
    118 								contentIds.put( textContent, textContent );
    119 							}
    120 							catch( RuntimeException e )
    121 							{
    122 								// not a URI.. try to create one..
    123 								String contentId = textContent + "@soapui.org";
    124 								cursor.setTextValue( "cid:" + contentId );
    125 								contentIds.put( textContent, contentId );
    126 							}
    127 						}
    128 					}
    129 					else if( AttachmentUtils.isXopInclude( schemaType ))
    130 					{
    131 						String contentId = cursor.getAttributeText( new QName("href"));
    132 						if( contentId != null && contentId.length() > 0 )
    133 						{
    134 							contentIds.put( contentId, contentId );
    135 							isXop = true;
    136
    137 							Attachment[] attachments = container.getAttachmentsForPart( contentId );
    138 						   if( attachments.length == 1 )
    139 						   {
    140 						   	XmlCursor cur = cursor.newCursor();
    141 						   	if( cur.toParent())
    142 						   	{
    143 						   		String contentType = getXmlMimeContentType( cur );
    144 						   		if( contentType != null && contentType.length() > 0 )
    145 						   			attachments[0].setContentType( contentType );
    146 						   	}
    147
    148 						   	cur.dispose();
    149 						   }
    150 						}
    151 					}
    152 					else if( SchemaUtils.isBinaryType(schemaType) )
    153 					{
    154 						String xmimeContentType = getXmlMimeContentType( cursor );
    155
    156 						// extract contentId
    157 						String textContent = cursor.getTextValue();
    158 						Attachment attachment = null;
    159 						boolean isXopAttachment = false;
    160
    161 						// is content a reference to a file?
    162 						if( textContent.startsWith( "file:" ))
    163 						{
    164 							String filename = textContent.substring( 5 );
    165 							if( container.isMtomEnabled() )
    166 							{
    167 								MimeBodyPart part = new PreencodedMimeBodyPart( "binary" );
    168
    169 								if( StringUtils.isNullOrEmpty( xmimeContentType ))
    170 									xmimeContentType = ContentTypeHandler.getContentTypeFromFilename( filename );
    171
    172 								part.setDataHandler( new DataHandler( new XOPPartDataSource( new File(filename ), xmimeContentType, schemaType ) ));
    173 								part.setContentID( "<" + filename + ">" );
    174 								mp.addBodyPart( part );
    175
    176 								isXopAttachment = true;
    177 							}
    178 						   else
    179 							{
    180 								inlineData(cursor, schemaType, new FileInputStream( filename));
    181 							}
    182 						}
    183 						// is content a reference to an attachment?
    184 						else if( textContent.startsWith( "cid:" ))
    185 						{
    186 						   textContent = textContent.substring( 4 );
    187
    188 						   Attachment[] attachments = container.getAttachmentsForPart( textContent );
    189 						   if( attachments.length == 1 )
    190 						   {
    191 						   	attachment = attachments[0];
    192 						   }
    193 						   else if( attachments.length > 1 )
    194 						   {
    195 						   	attachment = buildMulitpartAttachment( attachments );
    196 						   }
    197
    198 						   isXopAttachment = container.isMtomEnabled();
    199 					   	contentIds.put( textContent, textContent );
    200 						}
    201 						// content should be binary data; is this an XOP element which should be serialized with MTOM?
    202 						else if( container.isMtomEnabled() )
    203 						{
    204 							MimeBodyPart part = new PreencodedMimeBodyPart( "binary" );
    205
    206 							part.setDataHandler( new DataHandler( new XOPPartDataSource( textContent, xmimeContentType, schemaType ) ));
    207
    208 							textContent = "http://www.soapui.org/" + System.nanoTime();
    209
    210 							part.setContentID( "<" + textContent + ">" );
    211 							mp.addBodyPart( part );
    212
    213 							isXopAttachment = true;
    214 						}
    215
    216 						// add XOP include?
    217 						if( isXopAttachment && container.isMtomEnabled() )
    218 						{
    219 							buildXopInclude(cursor, textContent);
    220 							isXop = true;
    221 						}
    222 						// inline?
    223 						else if( attachment != null )
    224 						{
    225 							inlineAttachment(cursor, schemaType, attachment);
    226 						}
    227 					}
    228 				}
    229
    230 				cursor.toNextToken();
    231 			}
    232 		}
    233 		finally
    234 		{
    235 			cursor.dispose();
    236 		}
    237
    238 		return isXop;
    239 	}
    240
    241 	private static void inlineAttachment(XmlCursor cursor, SchemaType schemaType, Attachment attachment) throws Exception
    242 	{
    243 		inlineData( cursor, schemaType, attachment.getInputStream() );
    244 	}
    245
    246 	private static void inlineData(XmlCursor cursor, SchemaType schemaType, InputStream in ) throws IOException
    247 	{
    248 		String content = null;
    249 		byte [] data = Tools.readAll( in, -1 ).toByteArray();
    250
    251 		if (SchemaUtils.isInstanceOf( schemaType, XmlHexBinary.type ))
    252 		{
    253 			content = new String( Hex.encodeHex( data ));
    254 		}
    255 		else if (SchemaUtils.isInstanceOf( schemaType, XmlBase64Binary.type ))
    256 		{
    257 			content = new String( Base64.encodeBase64( data ));
    258 		}
    259
    260 		XmlCursor c = cursor.newCursor();
    261 		c.setTextValue( content );
    262 		c.dispose();
    263 	}
    264
    265 	private static void buildXopInclude(XmlCursor cursor, String contentId)
    266 	{
    267 		// build xop:Include
    268 		XmlCursor c = cursor.newCursor();
    269 		c.removeXmlContents();
    270 		c.toFirstContentToken();
    271 		c.beginElement( XOP_INCLUDE_QNAME );
    272 		c.insertAttributeWithValue( XOP_HREF_QNAME, "cid:" + contentId );
    273 		c.toNextSibling();
    274 		c.removeXml();
    275 		c.dispose();
    276 	}
    277
    278 	private static Attachment buildMulitpartAttachment(Attachment[] attachments)
    279 	{
    280 		System.out.println( "buildMulitpartAttachment(Attachment[] attachments) not implemented!" );
    281 		return null;
    282 	}
    283
    284 	public static String buildRootPartContentType( String action, SoapVersion soapVersion )
    285 	{
    286 		String contentType = "application/xop+xml; charset=UTF-8; type=\"" + soapVersion.getContentType();
    287 		if( soapVersion == SoapVersion.Soap12 )
    288 			contentType += "; action=//\"" + action  + "//\"";
    289
    290 		return contentType + "\"";
    291 	}
    292
    293 	public static String buildMTOMContentType(String header, String action, SoapVersion soapVersion )
    294 	{
    295 		int ix = header.indexOf( "boundary" );
    296 		String contentType = "multipart/related; type=\"application/xop+xml\"; start=\"" + ROOTPART_SOAPUI_ORG + "\"; " +
    297 				    "start-info=\"" + soapVersion.getContentType();
    298
    299 		if( soapVersion == SoapVersion.Soap12 && action != null)
    300 			contentType += "; action=//\"" + action  + "//\"";
    301
    302 		return contentType + "\"; " + header.substring( ix );
    303 	}
    304
    305 	public static boolean isSwaRefType(SchemaType schemaType)
    306 	{
    307 		return schemaType.getName() != null && schemaType.getName().equals( SWAREF_QNAME);
    308 	}
    309
    310 	public static String getXmlMimeContentType(XmlCursor cursor)
    311 	{
    312 		String attributeText = cursor.getAttributeText( XMLMIME_CONTENTTYPE_200411);
    313 		if( attributeText == null )
    314 			attributeText = cursor.getAttributeText( XMLMIME_CONTENTTYPE_200505);
    315 		return attributeText;
    316 	}
    317
    318 	public static AttachmentEncoding getAttachmentEncoding( WsdlOperation operation, String partName, boolean isResponse )
    319 	{
    320 		// make sure we have access
    321 		if( operation == null || operation.getBindingOperation() == null ||
    322 			 operation.getBindingOperation().getOperation() == null )
    323 		{
    324 			return AttachmentEncoding.NONE;
    325 		}
    326
    327 		javax.wsdl.Part part = null;
    328
    329 		if( isResponse )
    330 		{
    331 			Output output = operation.getBindingOperation().getOperation().getOutput();
    332 			if( output == null || output.getMessage() == null )
    333 			{
    334 				return AttachmentEncoding.NONE;
    335 			}
    336 			else
    337 			{
    338 				part = output.getMessage().getPart( partName );
    339 			}
    340 		}
    341 		else
    342 		{
    343 			Input input = operation.getBindingOperation().getOperation().getInput();
    344 			if( input == null || input.getMessage() == null )
    345 			{
    346 				return AttachmentEncoding.NONE;
    347 			}
    348 			else
    349 			{
    350 				part = input.getMessage().getPart( partName );
    351 			}
    352 		}
    353
    354 		if( part != null )
    355 		{
    356 			QName typeName = part.getTypeName();
    357 			if( typeName.getNamespaceURI().equals( "http://www.w3.org/2001/XMLSchema" ))
    358 			{
    359 				if( typeName.getLocalPart().equals( "base64Binary" ))
    360 				{
    361 					return AttachmentEncoding.BASE64;
    362 				}
    363 				else if( typeName.getLocalPart().equals( "hexBinary" ))
    364 				{
    365 					return AttachmentEncoding.HEX;
    366 				}
    367 			}
    368 		}
    369
    370 		return AttachmentEncoding.NONE;
    371 	}
    372
    373 	public static boolean isXopInclude( SchemaType schemaType )
    374 	{
    375 		return XOP_INCLUDE_QNAME.equals( schemaType.getName() );
    376 	}
    377
    378 	public static List<WsdlAttachmentPart> extractAttachmentParts( WsdlOperation operation, String messageContent, boolean addAnonymous, boolean isResponse )
    379 	{
    380 		List<WsdlAttachmentPart> result = new ArrayList<WsdlAttachmentPart>();
    381
    382 		PartsConfig messageParts = isResponse ? operation.getConfig().getResponseParts() : operation.getConfig().getRequestParts();
    383 		if( messageParts != null )
    384 		{
    385 			for( Part part : messageParts.getPartList() )
    386 			{
    387 				WsdlAttachmentPart attachmentPart = new WsdlAttachmentPart( part.getName(), part.getContentTypeList() );
    388 				attachmentPart.setType( Attachment.AttachmentType.MIME );
    389 				result.add( attachmentPart );
    390 			}
    391 		}
    392
    393 		if( messageContent.length() > 0 )
    394 		{
    395 			WsdlContext wsdlContext = ((WsdlInterface)operation.getInterface()).getWsdlContext();
    396 			WsdlValidator validator = new WsdlValidator( wsdlContext );
    397 			try
    398 			{
    399 				XmlObject[] requestDocuments = validator.getMessageParts( messageContent, operation.getName(), isResponse );
    400
    401 				for( XmlObject partDoc : requestDocuments )
    402 				{
    403 					XmlCursor cursor = partDoc.newCursor();
    404 					while( !cursor.isEnddoc() )
    405 					{
    406 						if( cursor.isContainer() )
    407 						{
    408 							SchemaType schemaType = cursor.getObject().schemaType();
    409 							if( schemaType != null )
    410 							{
    411 								String attributeText = AttachmentUtils.getXmlMimeContentType(cursor);
    412
    413 								// xop?
    414 								if( SchemaUtils.isBinaryType( schemaType ))
    415 								{
    416 									String contentId = cursor.getTextValue();
    417 									if( contentId.startsWith( "cid:" ))
    418 									{
    419 										WsdlAttachmentPart attachmentPart = new WsdlAttachmentPart( contentId.substring( 4 ), attributeText);
    420 										attachmentPart.setType( attributeText == null ? Attachment.AttachmentType.CONTENT : Attachment.AttachmentType.XOP );
    421 										result.add( attachmentPart );
    422 									}
    423 								}
    424 								else if( AttachmentUtils.isXopInclude( schemaType ))
    425 								{
    426 									String contentId = cursor.getAttributeText( new QName("href"));
    427 									if( contentId != null && contentId.length() > 0 )
    428 									{
    429 										WsdlAttachmentPart attachmentPart = new WsdlAttachmentPart( contentId, attributeText);
    430 										attachmentPart.setType( Attachment.AttachmentType.XOP );
    431 										result.add( attachmentPart );
    432 									}
    433 								}
    434 								// swaref?
    435 								else if( AttachmentUtils.isSwaRefType(schemaType) )
    436 								{
    437 									String contentId = cursor.getTextValue();
    438 									if( contentId.startsWith( "cid:" ))
    439 									{
    440 										WsdlAttachmentPart attachmentPart = new WsdlAttachmentPart( contentId.substring( 4 ), attributeText);
    441 										attachmentPart.setType( Attachment.AttachmentType.SWAREF );
    442 										result.add( attachmentPart );
    443 									}
    444 								}
    445 							}
    446 						}
    447
    448 						cursor.toNextToken();
    449 					}
    450 				}
    451 			}
    452 			catch( Exception e )
    453 			{
    454 				log.warn( e.getMessage() );
    455 			}
    456 		}
    457
    458 		if( addAnonymous )
    459 			result.add( new WsdlAttachmentPart() );
    460
    461 		return result;
    462 	}
    463
    464 	/***
    465 	 * Adds defined attachments as mimeparts
    466 	 */
    467
    468 	public static void addMimeParts(AttachmentContainer container, MimeMultipart mp, StringToStringMap contentIds) throws MessagingException
    469 	{
    470 		// no multipart handling?
    471 		if( !container.isMultipartEnabled())
    472 		{
    473 			for( int c = 0; c < container.getAttachmentCount(); c++ )
    474 			{
    475 				Attachment att = container.getAttachmentAt( c );
    476 				addSingleAttachment(mp, contentIds, att);
    477 			}
    478 		}
    479 		else
    480 		{
    481 			// first identify if any part has more than one attachments
    482 			Map<String,List<Attachment> > attachmentsMap = new HashMap<String, List<Attachment> >();
    483 			for( int c = 0; c < container.getAttachmentCount(); c++ )
    484 			{
    485 				Attachment att = container.getAttachmentAt( c );
    486 				String partName = att.getPart();
    487
    488 				if( !attachmentsMap.containsKey( partName ))
    489 				{
    490 					attachmentsMap.put( partName, new ArrayList<Attachment>() );
    491 				}
    492
    493 				attachmentsMap.get( partName ).add( att );
    494 			}
    495
    496 			// add attachments
    497 			for( Iterator<String> i = attachmentsMap.keySet().iterator(); i.hasNext(); )
    498 			{
    499 				List<Attachment> attachments = attachmentsMap.get( i.next() );
    500 				if( attachments.size() == 1 )
    501 				{
    502 					Attachment att = attachments.get( 0 );
    503 					addSingleAttachment(mp, contentIds, att);
    504 				}
    505 				// more than one attachment with the same part -> create multipart attachment
    506 				else if( attachments.size() > 1 )
    507 				{
    508 					addMultipartAttachment(mp, contentIds, attachments);
    509 				}
    510 			}
    511 		}
    512 	}
    513
    514 	/***
    515 	 * Adds a mulitpart MimeBodyPart from an array of attachments
    516 	 */
    517
    518 	public static void addMultipartAttachment(MimeMultipart mp, StringToStringMap contentIds, List<Attachment> attachments) throws MessagingException
    519 	{
    520 		MimeMultipart multipart = new MimeMultipart("mixed");
    521 		for( int c = 0; c < attachments.size(); c++ )
    522 		{
    523 			Attachment att = attachments.get( c );
    524 			String contentType = att.getContentType();
    525
    526 			MimeBodyPart part = contentType.startsWith( "text/" ) ? new MimeBodyPart() : new PreencodedMimeBodyPart( "binary" );
    527
    528 			part.setDataHandler( new DataHandler( new AttachmentDataSource( att ) ));
    529 			initPartContentId( contentIds, part, att, false );
    530 			multipart.addBodyPart( part );
    531 		}
    532
    533 		MimeBodyPart part = new PreencodedMimeBodyPart( "binary" );
    534 		part.setDataHandler( new DataHandler( new MultipartAttachmentDataSource( multipart ) ));
    535
    536 		Attachment attachment = attachments.get( 0 );
    537 		initPartContentId( contentIds, part, attachment, true );
    538
    539 		mp.addBodyPart( part );
    540 	}
    541
    542 	public static void initPartContentId( StringToStringMap contentIds, MimeBodyPart part, Attachment attachment, boolean isMultipart ) throws MessagingException
    543 	{
    544 		String partName = attachment.getPart();
    545
    546 		String contentID = attachment.getContentID();
    547 		if( contentID != null )
    548 		{
    549 			contentID = contentID.trim();
    550 			int ix = contentID.indexOf( ' ' );
    551 			if( ix != -1 )
    552 				part.setContentID( "<" + (isMultipart ? contentID.substring( ix+1 ) : contentID.substring( 0, ix )) + ">" );
    553 			else
    554 			{
    555 				if( !contentID.startsWith( "<" ))
    556 					contentID = "<" + contentID;
    557
    558 				if( !contentID.endsWith( ">" ))
    559 					contentID = contentID + ">";
    560
    561 				part.setContentID( contentID );
    562 			}
    563 		}
    564 		else if( partName != null && !partName.equals( WsdlAttachmentPart.ANONYMOUS_NAME ))
    565 		{
    566 			if( contentIds.containsKey( partName ))
    567 			{
    568 				part.setContentID( "<" + contentIds.get( partName )+ ">" );
    569 			}
    570 			else
    571 			{
    572 				part.setContentID( "<" + partName + "=" + System.nanoTime() + "@soapui.org>" );
    573 			}
    574 		}
    575 	}
    576
    577 	/***
    578 	 * Adds a simple MimeBodyPart from an attachment
    579 	 */
    580
    581 	public static void addSingleAttachment(MimeMultipart mp, StringToStringMap contentIds, Attachment att) throws MessagingException
    582 	{
    583 		String contentType = att.getContentType();
    584 		MimeBodyPart part = contentType.startsWith( "text/" ) ? new MimeBodyPart() : new PreencodedMimeBodyPart( "binary" );
    585
    586 		part.setDataHandler( new DataHandler( new AttachmentDataSource( att ) ));
    587 		initPartContentId( contentIds, part, att, false );
    588
    589 		mp.addBodyPart( part );
    590 	}
    591
    592 	public static final Session JAVAMAIL_SESSION = Session.getDefaultInstance( new Properties() );
    593 }