View Javadoc

1   /*
2    *  soapUI, copyright (C) 2004-2008 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.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                 // 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 ) || SchemaUtils.isAnyType( 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:" ) && 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                   // is content a reference to an attachment?
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                   // content should be binary data; is this an XOP element which should be serialized with MTOM?
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                   // add XOP include?
206                   if( isXopAttachment && container.isMtomEnabled() )
207                   {
208                      buildXopInclude( cursor, textContent );
209                      isXop = true;
210                   }
211                   // inline?
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       // build xop:Include
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       // make sure we have access
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                         // xop?
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                         // swaref?
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       // no multipart handling?
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          // first identify if any part has more than one attachments
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          // add attachments
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             // more than one attachment with the same part -> create multipart attachment
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 }