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