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 com.eviware.soapui.config.PartsConfig;
16 import com.eviware.soapui.config.PartsConfig.Part;
17 import com.eviware.soapui.impl.wsdl.AttachmentContainer;
18 import com.eviware.soapui.impl.wsdl.HttpAttachmentPart;
19 import com.eviware.soapui.impl.wsdl.WsdlAttachmentContainer;
20 import com.eviware.soapui.impl.wsdl.WsdlOperation;
21 import com.eviware.soapui.impl.wsdl.support.MessageXmlPart;
22 import com.eviware.soapui.impl.wsdl.support.soap.SoapVersion;
23 import com.eviware.soapui.impl.wsdl.support.wsdl.WsdlContext;
24 import com.eviware.soapui.impl.wsdl.support.wsdl.WsdlValidator;
25 import com.eviware.soapui.impl.wsdl.support.xsd.SchemaUtils;
26 import com.eviware.soapui.model.iface.Attachment;
27 import com.eviware.soapui.model.iface.Attachment.AttachmentEncoding;
28 import com.eviware.soapui.support.StringUtils;
29 import com.eviware.soapui.support.Tools;
30 import com.eviware.soapui.support.editor.inspectors.attachments.ContentTypeHandler;
31 import com.eviware.soapui.support.types.StringToStringMap;
32 import org.apache.commons.codec.binary.Base64;
33 import org.apache.commons.codec.binary.Hex;
34 import org.apache.log4j.Logger;
35 import org.apache.xmlbeans.*;
36
37 import javax.activation.DataHandler;
38 import javax.mail.MessagingException;
39 import javax.mail.Session;
40 import javax.mail.internet.MimeBodyPart;
41 import javax.mail.internet.MimeMultipart;
42 import javax.mail.internet.PreencodedMimeBodyPart;
43 import javax.wsdl.Input;
44 import javax.wsdl.Output;
45 import javax.xml.namespace.QName;
46 import java.io.File;
47 import java.io.FileInputStream;
48 import java.io.IOException;
49 import java.io.InputStream;
50 import java.net.URI;
51 import java.util.*;
52
53 /***
54 * Attachment-related utility classes
55 *
56 * @author ole.matzura
57 */
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( WsdlAttachmentContainer 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
82
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
106 new URI( textContent );
107 contentIds.put( textContent, textContent );
108 }
109 catch( RuntimeException e )
110 {
111
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 ) || SchemaUtils.isAnyType( schemaType ) )
142 {
143 String xmimeContentType = getXmlMimeContentType( cursor );
144
145
146 String textContent = cursor.getTextValue();
147 Attachment attachment = null;
148 boolean isXopAttachment = false;
149
150
151 if( textContent.startsWith( "file:" ) && container.isInlineFilesEnabled() )
152 {
153 String filename = textContent.substring( 5 );
154 if( container.isMtomEnabled() )
155 {
156 MimeBodyPart part = new PreencodedMimeBodyPart( "binary" );
157
158 if( StringUtils.isNullOrEmpty( xmimeContentType ) )
159 xmimeContentType = ContentTypeHandler.getContentTypeFromFilename( filename );
160
161 part.setDataHandler( new DataHandler( new XOPPartDataSource( new File( filename ), xmimeContentType, schemaType ) ) );
162 part.setContentID( "<" + filename + ">" );
163 mp.addBodyPart( part );
164
165 isXopAttachment = true;
166 }
167 else
168 {
169 inlineData( cursor, schemaType, new FileInputStream( filename ) );
170 }
171 }
172
173 else if( textContent.startsWith( "cid:" ) )
174 {
175 textContent = textContent.substring( 4 );
176
177 Attachment[] attachments = container.getAttachmentsForPart( textContent );
178 if( attachments.length == 1 )
179 {
180 attachment = attachments[0];
181 }
182 else if( attachments.length > 1 )
183 {
184 attachment = buildMulitpartAttachment( attachments );
185 }
186
187 isXopAttachment = container.isMtomEnabled();
188 contentIds.put( textContent, textContent );
189 }
190
191 else if( container.isMtomEnabled() )
192 {
193 MimeBodyPart part = new PreencodedMimeBodyPart( "binary" );
194
195 part.setDataHandler( new DataHandler( new XOPPartDataSource( textContent, xmimeContentType, schemaType ) ) );
196
197 textContent = "http://www.soapui.org/" + System.nanoTime();
198
199 part.setContentID( "<" + textContent + ">" );
200 mp.addBodyPart( part );
201
202 isXopAttachment = true;
203 }
204
205
206 if( isXopAttachment && container.isMtomEnabled() )
207 {
208 buildXopInclude( cursor, textContent );
209 isXop = true;
210 }
211
212 else if( attachment != null )
213 {
214 inlineAttachment( cursor, schemaType, attachment );
215 }
216 }
217 }
218
219 cursor.toNextToken();
220 }
221 }
222 finally
223 {
224 cursor.dispose();
225 }
226
227 return isXop;
228 }
229
230 private static void inlineAttachment( XmlCursor cursor, SchemaType schemaType, Attachment attachment ) throws Exception
231 {
232 inlineData( cursor, schemaType, attachment.getInputStream() );
233 }
234
235 private static void inlineData( XmlCursor cursor, SchemaType schemaType, InputStream in ) throws IOException
236 {
237 String content = null;
238 byte[] data = Tools.readAll( in, -1 ).toByteArray();
239
240 if( SchemaUtils.isInstanceOf( schemaType, XmlHexBinary.type ) )
241 {
242 content = new String( Hex.encodeHex( data ) );
243 }
244 else if( SchemaUtils.isInstanceOf( schemaType, XmlBase64Binary.type ) )
245 {
246 content = new String( Base64.encodeBase64( data ) );
247 }
248
249 XmlCursor c = cursor.newCursor();
250 c.setTextValue( content );
251 c.dispose();
252 }
253
254 private static void buildXopInclude( XmlCursor cursor, String contentId )
255 {
256
257 XmlCursor c = cursor.newCursor();
258 c.removeXmlContents();
259 c.toFirstContentToken();
260 c.beginElement( XOP_INCLUDE_QNAME );
261 c.insertAttributeWithValue( XOP_HREF_QNAME, "cid:" + contentId );
262 c.toNextSibling();
263 c.removeXml();
264 c.dispose();
265 }
266
267 private static Attachment buildMulitpartAttachment( Attachment[] attachments )
268 {
269 System.out.println( "buildMulitpartAttachment(Attachment[] attachments) not implemented!" );
270 return null;
271 }
272
273 public static String buildRootPartContentType( String action, SoapVersion soapVersion )
274 {
275 String contentType = "application/xop+xml; charset=UTF-8; type=\"" + soapVersion.getContentType();
276 if( soapVersion == SoapVersion.Soap12 )
277 contentType += "; action=//\"" + action + "//\"";
278
279 return contentType + "\"";
280 }
281
282 public static String buildMTOMContentType( String header, String action, SoapVersion soapVersion )
283 {
284 int ix = header.indexOf( "boundary" );
285 String contentType = "multipart/related; type=\"application/xop+xml\"; start=\"" + ROOTPART_SOAPUI_ORG + "\"; " +
286 "start-info=\"" + soapVersion.getContentType();
287
288 if( soapVersion == SoapVersion.Soap12 && action != null )
289 contentType += "; action=//\"" + action + "//\"";
290
291 return contentType + "\"; " + header.substring( ix );
292 }
293
294 public static boolean isSwaRefType( SchemaType schemaType )
295 {
296 return schemaType.getName() != null && schemaType.getName().equals( SWAREF_QNAME );
297 }
298
299 public static String getXmlMimeContentType( XmlCursor cursor )
300 {
301 String attributeText = cursor.getAttributeText( XMLMIME_CONTENTTYPE_200411 );
302 if( attributeText == null )
303 attributeText = cursor.getAttributeText( XMLMIME_CONTENTTYPE_200505 );
304 return attributeText;
305 }
306
307 public static AttachmentEncoding getAttachmentEncoding( WsdlOperation operation, String partName, boolean isResponse )
308 {
309
310 if( operation == null || operation.getBindingOperation() == null ||
311 operation.getBindingOperation().getOperation() == null )
312 {
313 return AttachmentEncoding.NONE;
314 }
315
316 javax.wsdl.Part part = null;
317
318 if( isResponse )
319 {
320 Output output = operation.getBindingOperation().getOperation().getOutput();
321 if( output == null || output.getMessage() == null )
322 {
323 return AttachmentEncoding.NONE;
324 }
325 else
326 {
327 part = output.getMessage().getPart( partName );
328 }
329 }
330 else
331 {
332 Input input = operation.getBindingOperation().getOperation().getInput();
333 if( input == null || input.getMessage() == null )
334 {
335 return AttachmentEncoding.NONE;
336 }
337 else
338 {
339 part = input.getMessage().getPart( partName );
340 }
341 }
342
343 if( part != null )
344 {
345 QName typeName = part.getTypeName();
346 if( typeName.getNamespaceURI().equals( "http://www.w3.org/2001/XMLSchema" ) )
347 {
348 if( typeName.getLocalPart().equals( "base64Binary" ) )
349 {
350 return AttachmentEncoding.BASE64;
351 }
352 else if( typeName.getLocalPart().equals( "hexBinary" ) )
353 {
354 return AttachmentEncoding.HEX;
355 }
356 }
357 }
358
359 return AttachmentEncoding.NONE;
360 }
361
362 public static boolean isXopInclude( SchemaType schemaType )
363 {
364 return XOP_INCLUDE_QNAME.equals( schemaType.getName() );
365 }
366
367 public static List<HttpAttachmentPart> extractAttachmentParts( WsdlOperation operation, String messageContent, boolean addAnonymous, boolean isResponse, boolean forceMtom )
368 {
369 List<HttpAttachmentPart> result = new ArrayList<HttpAttachmentPart>();
370
371 PartsConfig messageParts = isResponse ? operation.getConfig().getResponseParts() : operation.getConfig().getRequestParts();
372 if( messageParts != null )
373 {
374 for( Part part : messageParts.getPartList() )
375 {
376 HttpAttachmentPart attachmentPart = new HttpAttachmentPart( part.getName(), part.getContentTypeList() );
377 attachmentPart.setType( Attachment.AttachmentType.MIME );
378 result.add( attachmentPart );
379 }
380 }
381
382 if( messageContent.length() > 0 )
383 {
384 WsdlContext wsdlContext = operation.getInterface().getWsdlContext();
385 WsdlValidator validator = new WsdlValidator( wsdlContext );
386 try
387 {
388 XmlObject[] requestDocuments = validator.getMessageParts( messageContent, operation.getName(), isResponse );
389
390 for( XmlObject partDoc : requestDocuments )
391 {
392 XmlCursor cursor = partDoc.newCursor();
393 while( !cursor.isEnddoc() )
394 {
395 if( cursor.isContainer() )
396 {
397 SchemaType schemaType = cursor.getObject().schemaType();
398 if( schemaType != null )
399 {
400 String attributeText = AttachmentUtils.getXmlMimeContentType( cursor );
401
402
403 if( SchemaUtils.isBinaryType( schemaType ) || SchemaUtils.isAnyType( schemaType ))
404 {
405 String contentId = cursor.getTextValue();
406 if( contentId.startsWith( "cid:" ) )
407 {
408 HttpAttachmentPart attachmentPart = new HttpAttachmentPart( contentId.substring( 4 ), attributeText );
409 attachmentPart.setType( attributeText == null && !forceMtom ? Attachment.AttachmentType.CONTENT : Attachment.AttachmentType.XOP );
410 result.add( attachmentPart );
411 }
412 }
413 else if( AttachmentUtils.isXopInclude( schemaType ) )
414 {
415 String contentId = cursor.getAttributeText( new QName( "href" ) );
416 if( contentId != null && contentId.length() > 0 )
417 {
418 HttpAttachmentPart attachmentPart = new HttpAttachmentPart( contentId, attributeText );
419 attachmentPart.setType( Attachment.AttachmentType.XOP );
420 result.add( attachmentPart );
421 }
422 }
423
424 else if( AttachmentUtils.isSwaRefType( schemaType ) )
425 {
426 String contentId = cursor.getTextValue();
427 if( contentId.startsWith( "cid:" ) )
428 {
429 HttpAttachmentPart attachmentPart = new HttpAttachmentPart( contentId.substring( 4 ), attributeText );
430 attachmentPart.setType( Attachment.AttachmentType.SWAREF );
431 result.add( attachmentPart );
432 }
433 }
434 }
435 }
436
437 cursor.toNextToken();
438 }
439 }
440 }
441 catch( Exception e )
442 {
443 log.warn( e.getMessage() );
444 }
445 }
446
447 if( addAnonymous )
448 result.add( new HttpAttachmentPart() );
449
450 return result;
451 }
452
453 /***
454 * Adds defined attachments as mimeparts
455 */
456
457 public static void addMimeParts( AttachmentContainer container, List<Attachment> attachments, MimeMultipart mp, StringToStringMap contentIds ) throws MessagingException
458 {
459
460 if( !container.isMultipartEnabled() )
461 {
462 for( int c = 0; c < attachments.size(); c++ )
463 {
464 Attachment att = attachments.get( c );
465 if( att.getAttachmentType() != Attachment.AttachmentType.CONTENT )
466 {
467 addSingleAttachment( mp, contentIds, att );
468 }
469 }
470 }
471 else
472 {
473
474 Map<String, List<Attachment>> attachmentsMap = new HashMap<String, List<Attachment>>();
475 for( int c = 0; c < attachments.size(); c++ )
476 {
477 Attachment att = attachments.get( c );
478 if( att.getAttachmentType() == Attachment.AttachmentType.CONTENT )
479 continue;
480
481 String partName = att.getPart();
482
483 if( !attachmentsMap.containsKey( partName ) )
484 {
485 attachmentsMap.put( partName, new ArrayList<Attachment>() );
486 }
487
488 attachmentsMap.get( partName ).add( att );
489 }
490
491
492 for( Iterator<String> i = attachmentsMap.keySet().iterator(); i.hasNext(); )
493 {
494 attachments = attachmentsMap.get( i.next() );
495 if( attachments.size() == 1 )
496 {
497 Attachment att = attachments.get( 0 );
498 addSingleAttachment( mp, contentIds, att );
499 }
500
501 else if( attachments.size() > 1 )
502 {
503 addMultipartAttachment( mp, contentIds, attachments );
504 }
505 }
506 }
507 }
508
509 /***
510 * Adds a mulitpart MimeBodyPart from an array of attachments
511 */
512
513 public static void addMultipartAttachment( MimeMultipart mp, StringToStringMap contentIds, List<Attachment> attachments ) throws MessagingException
514 {
515 MimeMultipart multipart = new MimeMultipart( "mixed" );
516 for( int c = 0; c < attachments.size(); c++ )
517 {
518 Attachment att = attachments.get( c );
519 String contentType = att.getContentType();
520
521 MimeBodyPart part = contentType.startsWith( "text/" ) ? new MimeBodyPart() : new PreencodedMimeBodyPart( "binary" );
522
523 part.setDataHandler( new DataHandler( new AttachmentDataSource( att ) ) );
524 initPartContentId( contentIds, part, att, false );
525 multipart.addBodyPart( part );
526 }
527
528 MimeBodyPart part = new PreencodedMimeBodyPart( "binary" );
529 part.setDataHandler( new DataHandler( new MultipartAttachmentDataSource( multipart ) ) );
530
531 Attachment attachment = attachments.get( 0 );
532 initPartContentId( contentIds, part, attachment, true );
533
534 mp.addBodyPart( part );
535 }
536
537 public static void initPartContentId( StringToStringMap contentIds, MimeBodyPart part, Attachment attachment, boolean isMultipart ) throws MessagingException
538 {
539 String partName = attachment.getPart();
540
541 String contentID = attachment.getContentID();
542 if( contentID != null )
543 {
544 contentID = contentID.trim();
545 int ix = contentID.indexOf( ' ' );
546 if( ix != -1 )
547 part.setContentID( "<" + (isMultipart ? contentID.substring( ix + 1 ) : contentID.substring( 0, ix )) + ">" );
548 else
549 {
550 if( !contentID.startsWith( "<" ) )
551 contentID = "<" + contentID;
552
553 if( !contentID.endsWith( ">" ) )
554 contentID = contentID + ">";
555
556 part.setContentID( contentID );
557 }
558 }
559 else if( partName != null && !partName.equals( HttpAttachmentPart.ANONYMOUS_NAME ) )
560 {
561 if( contentIds.containsKey( partName ) )
562 {
563 part.setContentID( "<" + contentIds.get( partName ) + ">" );
564 }
565 else
566 {
567 part.setContentID( "<" + partName + "=" + System.nanoTime() + "@soapui.org>" );
568 }
569 }
570 }
571
572 /***
573 * Adds a simple MimeBodyPart from an attachment
574 */
575
576 public static void addSingleAttachment( MimeMultipart mp, StringToStringMap contentIds, Attachment att ) throws MessagingException
577 {
578 String contentType = att.getContentType();
579 MimeBodyPart part = contentType.startsWith( "text/" ) ? new MimeBodyPart() : new PreencodedMimeBodyPart( "binary" );
580
581 part.setDataHandler( new DataHandler( new AttachmentDataSource( att ) ) );
582 initPartContentId( contentIds, part, att, false );
583
584 mp.addBodyPart( part );
585 }
586
587 public static final Session JAVAMAIL_SESSION = Session.getDefaultInstance( new Properties() );
588 }