1/*
2 * Copyright (c) 1997, 2014, 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
26package com.sun.xml.internal.ws.encoding;
27
28import com.sun.xml.internal.ws.api.SOAPVersion;
29import com.sun.xml.internal.ws.api.WSFeatureList;
30import com.sun.xml.internal.ws.api.message.Attachment;
31import com.sun.xml.internal.ws.api.message.AttachmentEx;
32import com.sun.xml.internal.ws.api.message.Message;
33import com.sun.xml.internal.ws.api.message.Packet;
34import com.sun.xml.internal.ws.api.pipe.Codec;
35import com.sun.xml.internal.ws.api.pipe.ContentType;
36import com.sun.xml.internal.ws.developer.StreamingAttachmentFeature;
37
38import java.io.IOException;
39import java.io.InputStream;
40import java.io.OutputStream;
41import java.nio.channels.ReadableByteChannel;
42import java.util.Iterator;
43import java.util.UUID;
44
45/**
46 * {@link Codec}s that uses the MIME multipart as the underlying format.
47 *
48 * <p>
49 * When the runtime needs to dynamically choose a {@link Codec}, and
50 * when there are more than one {@link Codec}s that use MIME multipart,
51 * it is often impossible to determine the right {@link Codec} unless
52 * you parse the multipart message to some extent.
53 *
54 * <p>
55 * By having all such {@link Codec}s extending from this class,
56 * the "sniffer" can decode a multipart message partially, and then
57 * pass the partial parse result to the ultimately-responsible {@link Codec}.
58 * This improves the performance.
59 *
60 * @author Kohsuke Kawaguchi
61 */
62abstract class MimeCodec implements Codec {
63
64    public static final String MULTIPART_RELATED_MIME_TYPE = "multipart/related";
65
66    protected Codec mimeRootCodec;
67    protected final SOAPVersion version;
68    protected final WSFeatureList features;
69
70    protected MimeCodec(SOAPVersion version, WSFeatureList f) {
71        this.version = version;
72        this.features = f;
73    }
74
75    public String getMimeType() {
76        return MULTIPART_RELATED_MIME_TYPE;
77    }
78
79    protected Codec getMimeRootCodec(Packet packet) {
80        return mimeRootCodec;
81    }
82
83    // TODO: preencode String literals to byte[] so that they don't have to
84    // go through char[]->byte[] conversion at runtime.
85    public ContentType encode(Packet packet, OutputStream out) throws IOException {
86        Message msg = packet.getMessage();
87        if (msg == null) {
88            return null;
89        }
90        ContentTypeImpl ctImpl = (ContentTypeImpl)getStaticContentType(packet);
91        String boundary = ctImpl.getBoundary();
92        String rootId = ctImpl.getRootId();
93        boolean hasAttachments = (boundary != null);
94        Codec rootCodec = getMimeRootCodec(packet);
95        if (hasAttachments) {
96            writeln("--"+boundary, out);
97            ContentType ct = rootCodec.getStaticContentType(packet);
98            String ctStr = (ct != null) ? ct.getContentType() : rootCodec.getMimeType();
99            if (rootId != null) writeln("Content-ID: " + rootId, out);
100            writeln("Content-Type: " + ctStr, out);
101            writeln(out);
102        }
103        ContentType primaryCt = rootCodec.encode(packet, out);
104
105        if (hasAttachments) {
106            writeln(out);
107            // Encode all the attchments
108            for (Attachment att : msg.getAttachments()) {
109                writeln("--"+boundary, out);
110                //SAAJ's AttachmentPart.getContentId() returns content id already enclosed with
111                //angle brackets. For now put angle bracket only if its not there
112                String cid = att.getContentId();
113                if(cid != null && cid.length() >0 && cid.charAt(0) != '<')
114                    cid = '<' + cid + '>';
115                writeln("Content-Id:" + cid, out);
116                writeln("Content-Type: " + att.getContentType(), out);
117                writeCustomMimeHeaders(att, out);
118                writeln("Content-Transfer-Encoding: binary", out);
119                writeln(out);                    // write \r\n
120                att.writeTo(out);
121                writeln(out);                    // write \r\n
122            }
123            writeAsAscii("--"+boundary, out);
124            writeAsAscii("--", out);
125        }
126        // TODO not returing correct multipart/related type(no boundary)
127        return hasAttachments ? ctImpl : primaryCt;
128    }
129
130    private void writeCustomMimeHeaders(Attachment att, OutputStream out) throws IOException {
131        if (att instanceof AttachmentEx) {
132            Iterator<AttachmentEx.MimeHeader> allMimeHeaders = ((AttachmentEx) att).getMimeHeaders();
133            while (allMimeHeaders.hasNext()) {
134                AttachmentEx.MimeHeader mh = allMimeHeaders.next();
135                String name = mh.getName();
136
137                if (!"Content-Type".equalsIgnoreCase(name) && !"Content-Id".equalsIgnoreCase(name)) {
138                    writeln(name +": " + mh.getValue(), out);
139                }
140            }
141        }
142    }
143
144    public ContentType getStaticContentType(Packet packet) {
145        ContentType ct = (ContentType) packet.getInternalContentType();
146        if ( ct != null ) return ct;
147        Message msg = packet.getMessage();
148        boolean hasAttachments = !msg.getAttachments().isEmpty();
149        Codec rootCodec = getMimeRootCodec(packet);
150
151        if (hasAttachments) {
152            String boundary = "uuid:" + UUID.randomUUID().toString();
153            String boundaryParameter = "boundary=\"" + boundary + "\"";
154            // TODO use primaryEncoder to get type
155            String messageContentType =  MULTIPART_RELATED_MIME_TYPE +
156                    "; type=\"" + rootCodec.getMimeType() + "\"; " +
157                    boundaryParameter;
158            ContentTypeImpl impl = new ContentTypeImpl(messageContentType, packet.soapAction, null);
159            impl.setBoundary(boundary);
160            impl.setBoundaryParameter(boundaryParameter);
161            packet.setContentType(impl);
162            return impl;
163        } else {
164            ct = rootCodec.getStaticContentType(packet);
165            packet.setContentType(ct);
166            return ct;
167        }
168    }
169
170    /**
171     * Copy constructor.
172     */
173    protected MimeCodec(MimeCodec that) {
174        this.version = that.version;
175        this.features = that.features;
176    }
177
178    public void decode(InputStream in, String contentType, Packet packet) throws IOException {
179        MimeMultipartParser parser = new MimeMultipartParser(in, contentType, features.get(StreamingAttachmentFeature.class));
180        decode(parser,packet);
181    }
182
183    public void decode(ReadableByteChannel in, String contentType, Packet packet) {
184        throw new UnsupportedOperationException();
185    }
186
187    /**
188     * Parses a {@link Packet} from a {@link MimeMultipartParser}.
189     */
190    protected abstract void decode(MimeMultipartParser mpp, Packet packet) throws IOException;
191
192    public abstract MimeCodec copy();
193
194
195    public static void writeln(String s,OutputStream out) throws IOException {
196        writeAsAscii(s,out);
197        writeln(out);
198    }
199
200    /**
201     * Writes a string as ASCII string.
202     */
203    public static void writeAsAscii(String s,OutputStream out) throws IOException {
204        int len = s.length();
205        for( int i=0; i<len; i++ )
206            out.write((byte)s.charAt(i));
207    }
208
209    public static void writeln(OutputStream out) throws IOException {
210        out.write('\r');
211        out.write('\n');
212    }
213}
214