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