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.xml.namespace.QName;
34  
35  import org.apache.commons.codec.binary.Base64;
36  import org.apache.commons.codec.binary.Hex;
37  import org.apache.log4j.Logger;
38  import org.apache.xmlbeans.SchemaType;
39  import org.apache.xmlbeans.XmlBase64Binary;
40  import org.apache.xmlbeans.XmlCursor;
41  import org.apache.xmlbeans.XmlHexBinary;
42  import org.apache.xmlbeans.XmlObject;
43  
44  import com.eviware.soapui.config.PartsConfig;
45  import com.eviware.soapui.config.PartsConfig.Part;
46  import com.eviware.soapui.impl.wsdl.AttachmentContainer;
47  import com.eviware.soapui.impl.wsdl.WsdlAttachmentPart;
48  import com.eviware.soapui.impl.wsdl.WsdlInterface;
49  import com.eviware.soapui.impl.wsdl.WsdlOperation;
50  import com.eviware.soapui.impl.wsdl.support.MessageXmlPart;
51  import com.eviware.soapui.impl.wsdl.support.soap.SoapVersion;
52  import com.eviware.soapui.impl.wsdl.support.wsdl.WsdlContext;
53  import com.eviware.soapui.impl.wsdl.support.wsdl.WsdlValidator;
54  import com.eviware.soapui.impl.wsdl.support.xsd.SchemaUtils;
55  import com.eviware.soapui.model.iface.Attachment;
56  import com.eviware.soapui.support.Tools;
57  import com.eviware.soapui.support.types.StringToStringMap;
58  
59  public class AttachmentUtils
60  {
61  	private final static Logger log = Logger.getLogger( AttachmentUtils.class );
62  	private static final QName XMLMIME_CONTENTTYPE_200505 = new QName( "http://www.w3.org/2005/05/xmlmime", "contentType");
63  	private static final QName XMLMIME_CONTENTTYPE_200411 = new QName( "http://www.w3.org/2004/11/xmlmime", "contentType");
64  	private static final QName SWAREF_QNAME = new QName("http://ws-i.org/profiles/basic/1.1/xsd","swaRef");
65  	public static final QName XOP_HREF_QNAME = new QName( "href" );
66  	private static final QName XOP_INCLUDE_QNAME = new QName("http://www.w3.org/2004/08/xop/include", "Include" );
67  	public static final String ROOTPART_SOAPUI_ORG = "<rootpart@soapui.org>";
68  
69  	public static boolean prepareMessagePart(AttachmentContainer container, MimeMultipart mp, MessageXmlPart messagePart, StringToStringMap contentIds) throws Exception, MessagingException
70  	{
71  		boolean isXop = false;
72  		
73  		XmlCursor cursor = messagePart.newCursor(); 
74  		
75  		try
76  		{
77  			while( !cursor.isEnddoc() )
78  			{
79  				if( cursor.isContainer() )
80  				{
81  					// could be an attachment part (as of "old" SwA specs which specify a content 
82  					// element referring to the attachment)
83  					if( messagePart.isAttachmentPart() )
84  					{
85  						String href = cursor.getAttributeText( XOP_HREF_QNAME);
86  						if( href != null && href.length() > 0 )
87  						{
88  							contentIds.put( messagePart.getPart().getName(), href );
89  						}
90  						
91  						break;
92  					}
93  					
94  					SchemaType schemaType = cursor.getObject().schemaType();
95  					
96  					if( AttachmentUtils.isSwaRefType( schemaType ))
97  					{
98  						String textContent = cursor.getTextValue();
99  						if( textContent.startsWith( "cid:" ))
100 						{
101 							textContent = textContent.substring( 4 );
102 							
103 							try
104 							{
105 					   		// is the textcontent already a URI?
106 								new URI( textContent );
107 								contentIds.put( textContent, textContent );
108 							}
109 							catch( RuntimeException e )
110 							{
111 								// not a URI.. try to create one.. 
112 								String contentId = textContent + "@soapui.org";
113 								cursor.setTextValue( "cid:" + contentId );
114 								contentIds.put( textContent, contentId );
115 							}
116 						}
117 					}
118 					else if( AttachmentUtils.isXopInclude( schemaType ))
119 					{
120 						String contentId = cursor.getAttributeText( new QName("href"));
121 						if( contentId != null && contentId.length() > 0 )
122 						{
123 							contentIds.put( contentId, contentId );
124 							isXop = true;
125 							
126 							Attachment[] attachments = container.getAttachmentsForPart( contentId );
127 						   if( attachments.length == 1 )
128 						   {
129 						   	XmlCursor cur = cursor.newCursor();
130 						   	if( cur.toParent())
131 						   	{
132 						   		String contentType = getXmlMimeContentType( cur );
133 						   		if( contentType != null && contentType.length() > 0 )
134 						   			attachments[0].setContentType( contentType );
135 						   	}
136 						   	
137 						   	cur.dispose();
138 						   }
139 						}
140 					}
141 					else if( SchemaUtils.isBinaryType(schemaType) )
142 					{
143 						String xmimeContentType = getXmlMimeContentType( cursor );
144 	
145 						// extract contentId
146 						String textContent = cursor.getTextValue();
147 						Attachment attachment = null;
148 						boolean isXopAttachment = false;
149 						
150 						// is content a reference to a file?
151 						if( textContent.startsWith( "file:" ))
152 						{
153 							String filename = textContent.substring( 5 );
154 							if( xmimeContentType == null )
155 							{
156 								inlineData(cursor, schemaType, new FileInputStream( filename));
157 							}
158 							else if( container.isMtomEnabled() )
159 							{
160 								MimeBodyPart part = new PreencodedMimeBodyPart( "binary" );
161 								
162 								part.setDataHandler( new DataHandler( new XOPPartDataSource( new File(filename ), xmimeContentType, schemaType ) ));
163 								part.setContentID( "<" + filename + ">" );
164 								mp.addBodyPart( part );
165 								
166 								isXopAttachment = true;
167 							}
168 						}
169 						// is content a reference to an attachment?
170 						else if( textContent.startsWith( "cid:" ))
171 						{
172 						   textContent = textContent.substring( 4 );
173 						   
174 						   Attachment[] attachments = container.getAttachmentsForPart( textContent );
175 						   if( attachments.length == 1 )
176 						   {
177 						   	attachment = attachments[0];
178 						   }
179 						   else if( attachments.length > 1 )
180 						   {
181 						   	attachment = buildMulitpartAttachment( attachments );
182 						   }
183 	
184 						   isXopAttachment = xmimeContentType != null;
185 					   	contentIds.put( textContent, textContent );
186 						}
187 						// content should be binary data; is this an XOP element which should be serialized with MTOM?
188 						else if( container.isMtomEnabled() && xmimeContentType != null )
189 						{
190 							MimeBodyPart part = new PreencodedMimeBodyPart( "binary" );
191 							
192 							part.setDataHandler( new DataHandler( new XOPPartDataSource( textContent, xmimeContentType, schemaType ) ));
193 							
194 							textContent = "http://www.soapui.org/" + System.nanoTime(); 
195 							
196 							part.setContentID( "<" + textContent + ">" );
197 							mp.addBodyPart( part );
198 							
199 							isXopAttachment = true;
200 						}
201 						
202 						// add XOP include?
203 						if( isXopAttachment && container.isMtomEnabled() )
204 						{
205 							buildXopInclude(cursor, textContent);
206 							isXop = true;
207 						}
208 						// inline?
209 						else if( attachment != null )
210 						{
211 							inlineAttachment(cursor, schemaType, attachment);
212 						}
213 					}
214 				}
215 				
216 				cursor.toNextToken();
217 			}
218 		}
219 		finally
220 		{
221 			cursor.dispose();
222 		}
223 		
224 		return isXop;
225 	}
226 
227 	private static void inlineAttachment(XmlCursor cursor, SchemaType schemaType, Attachment attachment) throws IOException
228 	{
229 		inlineData( cursor, schemaType, attachment.getInputStream() );
230 	}
231 	
232 	private static void inlineData(XmlCursor cursor, SchemaType schemaType, InputStream in ) throws IOException
233 	{
234 		String content = null;
235 		byte [] data = Tools.readAll( in, -1 ).toByteArray();
236 		
237 		if (SchemaUtils.isInstanceOf( schemaType, XmlHexBinary.type ))
238 		{
239 			content = new String( Hex.encodeHex( data ));
240 		}
241 		else if (SchemaUtils.isInstanceOf( schemaType, XmlBase64Binary.type ))
242 		{
243 			content = new String( Base64.encodeBase64( data ));
244 		}
245 		
246 		XmlCursor c = cursor.newCursor();
247 		c.setTextValue( content );
248 		c.dispose();
249 	}
250 
251 	private static void buildXopInclude(XmlCursor cursor, String contentId)
252 	{
253 		// build xop:Include
254 		XmlCursor c = cursor.newCursor();
255 		c.removeXmlContents();
256 		c.toFirstContentToken();
257 		c.beginElement( XOP_INCLUDE_QNAME );
258 		c.insertAttributeWithValue( XOP_HREF_QNAME, "cid:" + contentId );
259 		c.toNextSibling();
260 		c.removeXml();
261 		c.dispose();
262 	}
263 
264 	private static Attachment buildMulitpartAttachment(Attachment[] attachments)
265 	{
266 		System.out.println( "buildMulitpartAttachment(Attachment[] attachments) not implemented!" );
267 		return null;
268 	}
269 
270 	public static String buildRootPartContentType( String action, SoapVersion soapVersion )
271 	{
272 		return "application/xop+xml; charset=UTF-8; type=\"" + soapVersion.getContentType() + "; action=//\"" + 
273 			action + "//\"\"";
274 	}
275 
276 	public static String buildMTOMContentType(String header, String action, SoapVersion soapVersion )
277 	{
278 		if( action == null )
279 			action = "";
280 		
281 		int ix = header.indexOf( "boundary" );
282 		return "multipart/related; type=\"application/xop+xml\"; start=\"" + ROOTPART_SOAPUI_ORG + "\"; " + 
283 		    "startinfo=\"" + soapVersion.getContentType() + "; action=//\"" + action  + "//\"\"; " + header.substring( ix );
284 	}
285 
286 	public static boolean isSwaRefType(SchemaType schemaType)
287 	{
288 		return schemaType.getName() != null && schemaType.getName().equals( SWAREF_QNAME);
289 	}
290 
291 	public static String getXmlMimeContentType(XmlCursor cursor)
292 	{
293 		String attributeText = cursor.getAttributeText( XMLMIME_CONTENTTYPE_200411);
294 		if( attributeText == null ) 
295 			attributeText = cursor.getAttributeText( XMLMIME_CONTENTTYPE_200505);
296 		return attributeText;
297 	}
298 
299 	public static boolean isXopInclude( SchemaType schemaType )
300 	{
301 		return XOP_INCLUDE_QNAME.equals( schemaType.getName() );
302 	}
303 
304 	public static List<WsdlAttachmentPart> extractAttachmentParts( WsdlOperation operation, String requestContent, boolean addAnonymous ) 
305 	{
306 		List<WsdlAttachmentPart> result = new ArrayList<WsdlAttachmentPart>();
307 		
308 		PartsConfig requestParts = operation.getConfig().getRequestParts();
309 		if( requestParts != null )
310 		{
311 			for( Part part : requestParts.getPartList() )
312 			{
313 				WsdlAttachmentPart attachmentPart = new WsdlAttachmentPart( part.getName(), part.getContentTypeList() );
314 				attachmentPart.setType( Attachment.AttachmentType.MIME );
315 				result.add( attachmentPart );
316 			}
317 		}
318 		
319 		if( requestContent.length() > 0 )
320 		{
321 			WsdlContext wsdlContext = ((WsdlInterface)operation.getInterface()).getWsdlContext();
322 			WsdlValidator validator = new WsdlValidator( wsdlContext );
323 			try
324 			{
325 				XmlObject[] requestDocuments = validator.getInputParts( requestContent, operation.getName() );
326 				
327 				for( XmlObject partDoc : requestDocuments )
328 				{
329 					XmlCursor cursor = partDoc.newCursor(); 
330 					while( !cursor.isEnddoc() )
331 					{
332 						if( cursor.isContainer() )
333 						{
334 							SchemaType schemaType = cursor.getObject().schemaType();
335 							if( schemaType != null )
336 							{
337 								String attributeText = AttachmentUtils.getXmlMimeContentType(cursor);
338 		
339 								// xop?
340 								if( SchemaUtils.isBinaryType( schemaType ))
341 								{
342 									String contentId = cursor.getTextValue();
343 									if( contentId.startsWith( "cid:" ))
344 									{
345 										WsdlAttachmentPart attachmentPart = new WsdlAttachmentPart( contentId.substring( 4 ), attributeText);
346 										attachmentPart.setType( attributeText == null ? Attachment.AttachmentType.CONTENT : Attachment.AttachmentType.XOP );
347 										result.add( attachmentPart );
348 									}
349 								}
350 								else if( AttachmentUtils.isXopInclude( schemaType ))
351 								{
352 									String contentId = cursor.getAttributeText( new QName("href"));
353 									if( contentId != null && contentId.length() > 0 )
354 									{
355 										WsdlAttachmentPart attachmentPart = new WsdlAttachmentPart( contentId, attributeText);
356 										attachmentPart.setType( Attachment.AttachmentType.XOP );
357 										result.add( attachmentPart );
358 									}
359 								}
360 								// swaref?
361 								else if( AttachmentUtils.isSwaRefType(schemaType) )
362 								{
363 									String contentId = cursor.getTextValue();
364 									if( contentId.startsWith( "cid:" ))
365 									{
366 										WsdlAttachmentPart attachmentPart = new WsdlAttachmentPart( contentId.substring( 4 ), attributeText);
367 										attachmentPart.setType( Attachment.AttachmentType.SWAREF );
368 										result.add( attachmentPart );
369 									}
370 								}
371 							}
372 						}
373 						
374 						cursor.toNextToken();
375 					}
376 				}
377 			}
378 			catch( Exception e )
379 			{
380 				e.printStackTrace();
381 				log.warn( e.toString() );
382 			}
383 		}
384 		
385 		if( addAnonymous )
386 			result.add( new WsdlAttachmentPart() );
387 		
388 		return result;
389 	}
390 	
391 	/***
392 	 * Adds defined attachments as mimeparts
393 	 */
394 	
395 	public static void addMimeParts(AttachmentContainer container, MimeMultipart mp, StringToStringMap contentIds) throws MessagingException
396 	{
397 		// no multipart handling?
398 		if( !container.isMultipartEnabled())
399 		{
400 			for( int c = 0; c < container.getAttachmentCount(); c++ )
401 			{
402 				Attachment att = container.getAttachmentAt( c );
403 				addSingleAttachment(mp, contentIds, att);
404 			}
405 		}
406 		else
407 		{
408 			// first identify if any part has more than one attachments 
409 			Map<String,List<Attachment> > attachmentsMap = new HashMap<String, List<Attachment> >();
410 			for( int c = 0; c < container.getAttachmentCount(); c++ )
411 			{
412 				Attachment att = container.getAttachmentAt( c );
413 				String partName = att.getPart();
414 				
415 				if( !attachmentsMap.containsKey( partName ))
416 				{
417 					attachmentsMap.put( partName, new ArrayList<Attachment>() );
418 				}
419 					
420 				attachmentsMap.get( partName ).add( att );
421 			}
422 	
423 			// add attachments
424 			for( Iterator<String> i = attachmentsMap.keySet().iterator(); i.hasNext(); )
425 			{
426 				List<Attachment> attachments = attachmentsMap.get( i.next() );
427 				if( attachments.size() == 1 )
428 				{
429 					Attachment att = attachments.get( 0 );
430 					addSingleAttachment(mp, contentIds, att);
431 				}
432 				// more than one attachment with the same part -> create multipart attachment
433 				else if( attachments.size() > 1 )
434 				{
435 					addMultipartAttachment(mp, contentIds, attachments);
436 				}
437 			}
438 		}
439 	}
440 
441 	/***
442 	 * Adds a mulitpart MimeBodyPart from an array of attachments
443 	 */
444 	
445 	public static void addMultipartAttachment(MimeMultipart mp, StringToStringMap contentIds, List<Attachment> attachments) throws MessagingException
446 	{
447 		MimeMultipart multipart = new MimeMultipart("mixed");
448 		for( int c = 0; c < attachments.size(); c++ )
449 		{
450 			Attachment att = attachments.get( c );
451 			String contentType = att.getContentType();
452 
453 			MimeBodyPart part = contentType.startsWith( "text/" ) ? new MimeBodyPart() : new PreencodedMimeBodyPart( "binary" );
454 			
455 			part.setDataHandler( new DataHandler( new AttachmentDataSource( att ) ));
456 			initPartContentId( contentIds, part, att, false );
457 			multipart.addBodyPart( part );
458 		}
459 		
460 		MimeBodyPart part = new PreencodedMimeBodyPart( "binary" );
461 		part.setDataHandler( new DataHandler( new MultipartAttachmentDataSource( multipart ) ));
462 		
463 		Attachment attachment = attachments.get( 0 );
464 		initPartContentId( contentIds, part, attachment, true );
465 		
466 		mp.addBodyPart( part );
467 	}
468 
469 	public static void initPartContentId( StringToStringMap contentIds, MimeBodyPart part, Attachment attachment, boolean isMultipart ) throws MessagingException
470 	{
471 		String partName = attachment.getPart();
472 		
473 		String contentID = attachment.getContentID();
474 		if( contentID != null )
475 		{
476 			int ix = contentID.indexOf( ' ' );
477 			if( ix != -1 )
478 				part.setContentID( "<" + (isMultipart ? contentID.substring( ix+1 ) : contentID.substring( 0, ix )) + ">" );
479 			else
480 				part.setContentID( contentID );
481 			
482 		}
483 		else if( partName != null && !partName.equals( WsdlAttachmentPart.ANONYMOUS_NAME ))
484 		{
485 			if( contentIds.containsKey( partName ))
486 			{
487 				part.setContentID( "<" + contentIds.get( partName )+ ">" );
488 			}
489 			else
490 			{
491 				part.setContentID( "<" + partName + "=" + System.nanoTime() + "@soapui.org>" );
492 			}
493 		}
494 	}
495 
496 	/***
497 	 * Adds a simple MimeBodyPart from an attachment
498 	 */
499 	
500 	public static void addSingleAttachment(MimeMultipart mp, StringToStringMap contentIds, Attachment att) throws MessagingException
501 	{
502 		String contentType = att.getContentType();
503 		MimeBodyPart part = contentType.startsWith( "text/" ) ? new MimeBodyPart() : new PreencodedMimeBodyPart( "binary" );
504 		
505 		part.setDataHandler( new DataHandler( new AttachmentDataSource( att ) ));
506 		initPartContentId( contentIds, part, att, false );
507 		
508 		mp.addBodyPart( part );
509 	}
510 
511 	public static final Session JAVAMAIL_SESSION = Session.getDefaultInstance( new Properties() );
512 }