1 /* 2 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.xml.internal.ws.encoding; 27 28 import com.sun.xml.internal.ws.api.SOAPVersion; 29 import com.sun.xml.internal.ws.api.WSFeatureList; 30 import com.sun.xml.internal.ws.api.message.Attachment; 31 import com.sun.xml.internal.ws.api.message.AttachmentEx; 32 import com.sun.xml.internal.ws.api.message.Message; 33 import com.sun.xml.internal.ws.api.message.Packet; 34 import com.sun.xml.internal.ws.api.pipe.Codec; 35 import com.sun.xml.internal.ws.api.pipe.ContentType; 36 import com.sun.xml.internal.ws.developer.StreamingAttachmentFeature; 37 import javax.activation.CommandMap; 38 import javax.activation.MailcapCommandMap; 39 40 import java.io.IOException; 41 import java.io.InputStream; 42 import java.io.OutputStream; 43 import java.nio.channels.ReadableByteChannel; 44 import java.util.Iterator; 45 import java.util.UUID; 46 47 /** 48 * {@link Codec}s that uses the MIME multipart as the underlying format. 49 * 50 * <p> 51 * When the runtime needs to dynamically choose a {@link Codec}, and 52 * when there are more than one {@link Codec}s that use MIME multipart, 53 * it is often impossible to determine the right {@link Codec} unless 54 * you parse the multipart message to some extent. 55 * 56 * <p> 57 * By having all such {@link Codec}s extending from this class, 58 * the "sniffer" can decode a multipart message partially, and then 59 * pass the partial parse result to the ultimately-responsible {@link Codec}. 60 * This improves the performance. 61 * 62 * @author Kohsuke Kawaguchi 63 */ 64 abstract class MimeCodec implements Codec { 65 66 static { 67 // DataHandler.writeTo() may search for DCH. So adding some default ones. 68 try { 69 CommandMap map = CommandMap.getDefaultCommandMap(); 70 if (map instanceof MailcapCommandMap) { 71 MailcapCommandMap mailMap = (MailcapCommandMap) map; 72 String hndlrStr = ";;x-java-content-handler="; 73 // registering our DCH since javamail's DCH doesn't handle 74 // Source 75 mailMap.addMailcap( 76 "text/xml" + hndlrStr + XmlDataContentHandler.class.getName()); 77 mailMap.addMailcap( 78 "application/xml" + hndlrStr + XmlDataContentHandler.class.getName()); 79 if (map.createDataContentHandler("image/*") == null) { 80 mailMap.addMailcap( 81 "image/*" + hndlrStr + ImageDataContentHandler.class.getName()); 82 } 83 if (map.createDataContentHandler("text/plain") == null) { 84 mailMap.addMailcap( 85 "text/plain" + hndlrStr + StringDataContentHandler.class.getName()); 86 } 87 } 88 } catch (Throwable t) { 89 // ignore the exception. 90 } 91 } 92 93 public static final String MULTIPART_RELATED_MIME_TYPE = "multipart/related"; 94 95 protected Codec mimeRootCodec; 96 protected final SOAPVersion version; 97 protected final WSFeatureList features; 98 99 protected MimeCodec(SOAPVersion version, WSFeatureList f) { 100 this.version = version; 101 this.features = f; 102 } 103 104 public String getMimeType() { 105 return MULTIPART_RELATED_MIME_TYPE; 106 } 107 108 protected Codec getMimeRootCodec(Packet packet) { 109 return mimeRootCodec; 110 } 111 112 // TODO: preencode String literals to byte[] so that they don't have to 113 // go through char[]->byte[] conversion at runtime. 114 public ContentType encode(Packet packet, OutputStream out) throws IOException { 115 Message msg = packet.getMessage(); 116 if (msg == null) { 117 return null; 118 } 119 ContentTypeImpl ctImpl = (ContentTypeImpl)getStaticContentType(packet); 120 String boundary = ctImpl.getBoundary(); 121 boolean hasAttachments = (boundary != null); 122 Codec rootCodec = getMimeRootCodec(packet); 123 if (hasAttachments) { 124 writeln("--"+boundary, out); 125 ContentType ct = rootCodec.getStaticContentType(packet); 126 String ctStr = (ct != null) ? ct.getContentType() : rootCodec.getMimeType(); 127 writeln("Content-Type: " + ctStr, out); 128 writeln(out); 129 } 130 ContentType primaryCt = rootCodec.encode(packet, out); 131 132 if (hasAttachments) { 133 writeln(out); 134 // Encode all the attchments 135 for (Attachment att : msg.getAttachments()) { 136 writeln("--"+boundary, out); 137 //SAAJ's AttachmentPart.getContentId() returns content id already enclosed with 138 //angle brackets. For now put angle bracket only if its not there 139 String cid = att.getContentId(); 140 if(cid != null && cid.length() >0 && cid.charAt(0) != '<') 141 cid = '<' + cid + '>'; 142 writeln("Content-Id:" + cid, out); 143 writeln("Content-Type: " + att.getContentType(), out); 144 writeCustomMimeHeaders(att, out); 145 writeln("Content-Transfer-Encoding: binary", out); 146 writeln(out); // write \r\n 147 att.writeTo(out); 148 writeln(out); // write \r\n 149 } 150 writeAsAscii("--"+boundary, out); 151 writeAsAscii("--", out); 152 } 153 // TODO not returing correct multipart/related type(no boundary) 154 return hasAttachments ? ctImpl : primaryCt; 155 } 156 157 private void writeCustomMimeHeaders(Attachment att, OutputStream out) throws IOException { 158 if (att instanceof AttachmentEx) { 159 Iterator<AttachmentEx.MimeHeader> allMimeHeaders = ((AttachmentEx) att).getMimeHeaders(); 160 while (allMimeHeaders.hasNext()) { 161 AttachmentEx.MimeHeader mh = allMimeHeaders.next(); 162 String name = mh.getName(); 163 164 if (!"Content-Type".equalsIgnoreCase(name) && !"Content-Id".equalsIgnoreCase(name)) { 165 writeln(name +": " + mh.getValue(), out); 166 } 167 } 168 } 169 } 170 171 public ContentType getStaticContentType(Packet packet) { 172 ContentType ct = (ContentType) packet.getInternalContentType(); 173 if ( ct != null ) return ct; 174 Message msg = packet.getMessage(); 175 boolean hasAttachments = !msg.getAttachments().isEmpty(); 176 Codec rootCodec = getMimeRootCodec(packet); 177 178 if (hasAttachments) { 179 String boundary = "uuid:" + UUID.randomUUID().toString(); 180 String boundaryParameter = "boundary=\"" + boundary + "\""; 181 // TODO use primaryEncoder to get type 182 String messageContentType = MULTIPART_RELATED_MIME_TYPE + 183 "; type=\"" + rootCodec.getMimeType() + "\"; " + 184 boundaryParameter; 185 ContentTypeImpl impl = new ContentTypeImpl(messageContentType, packet.soapAction, null); 186 impl.setBoundary(boundary); 187 impl.setBoundaryParameter(boundaryParameter); 188 packet.setContentType(impl); 189 return impl; 190 } else { 191 ct = rootCodec.getStaticContentType(packet); 192 packet.setContentType(ct); 193 return ct; 194 } 195 } 196 197 /** 198 * Copy constructor. 199 */ 200 protected MimeCodec(MimeCodec that) { 201 this.version = that.version; 202 this.features = that.features; 203 } 204 205 public void decode(InputStream in, String contentType, Packet packet) throws IOException { 206 MimeMultipartParser parser = new MimeMultipartParser(in, contentType, features.get(StreamingAttachmentFeature.class)); 207 decode(parser,packet); 208 } 209 210 public void decode(ReadableByteChannel in, String contentType, Packet packet) { 211 throw new UnsupportedOperationException(); 212 } 213 214 /** 215 * Parses a {@link Packet} from a {@link MimeMultipartParser}. 216 */ 217 protected abstract void decode(MimeMultipartParser mpp, Packet packet) throws IOException; 218 219 public abstract MimeCodec copy(); 220 221 222 public static void writeln(String s,OutputStream out) throws IOException { 223 writeAsAscii(s,out); 224 writeln(out); 225 } 226 227 /** 228 * Writes a string as ASCII string. 229 */ 230 public static void writeAsAscii(String s,OutputStream out) throws IOException { 231 int len = s.length(); 232 for( int i=0; i<len; i++ ) 233 out.write((byte)s.charAt(i)); 234 } 235 236 public static void writeln(OutputStream out) throws IOException { 237 out.write('\r'); 238 out.write('\n'); 239 } 240 }