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