001/* ***** BEGIN LICENSE BLOCK *****
002 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
003 *
004 * The contents of this file are subject to the Mozilla Public License Version
005 * 1.1 (the "License"); you may not use this file except in compliance with
006 * the License. You may obtain a copy of the License at
007 * http://www.mozilla.org/MPL/
008 *
009 * Software distributed under the License is distributed on an "AS IS" basis,
010 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
011 * for the specific language governing rights and limitations under the
012 * License.
013 *
014 * The Original Code is part of dcm4che, an implementation of DICOM(TM) in
015 * Java(TM), hosted at https://github.com/gunterze/dcm4che.
016 *
017 * The Initial Developer of the Original Code is
018 * Agfa Healthcare.
019 * Portions created by the Initial Developer are Copyright (C) 2011
020 * the Initial Developer. All Rights Reserved.
021 *
022 * Contributor(s):
023 * See @authors listed below
024 *
025 * Alternatively, the contents of this file may be used under the terms of
026 * either the GNU General Public License Version 2 or later (the "GPL"), or
027 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
028 * in which case the provisions of the GPL or the LGPL are applicable instead
029 * of those above. If you wish to allow use of your version of this file only
030 * under the terms of either the GPL or the LGPL, and not to allow others to
031 * use your version of this file under the terms of the MPL, indicate your
032 * decision by deleting the provisions above and replace them with the notice
033 * and other provisions required by the GPL or the LGPL. If you do not delete
034 * the provisions above, a recipient may use your version of this file under
035 * the terms of any one of the MPL, the GPL or the LGPL.
036 *
037 * ***** END LICENSE BLOCK ***** */
038package org.dcm4che3.mime;
039
040import java.io.EOFException;
041import java.io.FilterInputStream;
042import java.io.IOException;
043import java.io.InputStream;
044import java.util.ArrayList;
045import java.util.Comparator;
046import java.util.List;
047import java.util.Map;
048import java.util.TreeMap;
049
050/**
051 * @author Gunter Zeilinger <gunterze@gmail.com>
052 *
053 */
054public class MultipartInputStream extends FilterInputStream {
055
056    private final byte[] boundary;
057    private final byte[] buffer;
058    private int rpos;
059    private boolean boundarySeen;
060
061    protected MultipartInputStream(InputStream in, String boundary) {
062        super(in);
063        this.boundary = boundary.getBytes();
064        this.buffer = new byte[this.boundary.length];
065        this.rpos = buffer.length;
066        
067    }
068
069    @Override
070    public int read() throws IOException {
071        return isBoundary() ? -1 : buffer[rpos++];
072    }
073
074    @Override
075    public int read(byte[] b, int off, int len) throws IOException {
076        if (isBoundary())
077            return -1;
078
079        int l = Math.min(remaining(), len);
080        System.arraycopy(buffer, rpos, b, off, l);
081        rpos += l;
082        return l;
083    }
084
085    @Override
086    public long skip(long n) throws IOException {
087        if (isBoundary())
088            return 0L;
089
090        long l = Math.min(remaining(), n);
091        rpos += l;
092        return l;
093    }
094
095    public void skipAll() throws IOException {
096        while (!isBoundary())
097            rpos += remaining();
098    }
099
100    public boolean isZIP() throws IOException {
101        return !isBoundary() 
102                && buffer[rpos] == 'P'
103                && buffer[rpos+1] == 'K';
104    }
105
106    private boolean isBoundary() throws IOException {
107        if (boundarySeen)
108            return true;
109
110        if (rpos < buffer.length) {
111            if (buffer[rpos] != boundary[0])
112                return false;
113
114            System.arraycopy(buffer, rpos, buffer, 0, buffer.length - rpos);
115        }
116        readFully(in, buffer, buffer.length - rpos, rpos);
117        rpos = 0;
118
119        for (int i = 0; i < buffer.length; i++)
120            if (buffer[i] != boundary[i])
121                return false;
122
123        boundarySeen = true;
124        return true;
125    }
126
127    private static void readFully(InputStream in, byte b[], int off, int len)
128            throws IOException {
129        if (off < 0 || len < 0 || off + len > b.length)
130            throw new IndexOutOfBoundsException();
131        while (len > 0) {
132            int count = in.read(b, off, len);
133            if (count < 0)
134                throw new EOFException();
135            off += count;
136            len -= count;
137        }
138    }
139
140    private int remaining() {
141        for (int i = rpos+1; i < buffer.length; i++)
142            if (buffer[i] == boundary[0])
143                return i - rpos;
144
145        return buffer.length - rpos;
146    }
147
148    public Map<String, List<String>> readHeaderParams() throws IOException {
149        Map<String, List<String>> map = new TreeMap<String, List<String>>(
150                new Comparator<String>() {
151                    @Override
152                    public int compare(String o1, String o2) {
153                        return o1.compareToIgnoreCase(o2);
154                    }
155                });
156        Field field = new Field();
157        while (readHeaderParam(field)) {
158            String name = field.toString();
159            String value = "";
160            int endName = name.indexOf(':');
161            if (endName != -1) {
162                value =  unquote(name.substring(endName+1)).trim();
163                name = name.substring(0, endName);
164            }
165            List<String> list = map.get(name);
166            if (list == null) {
167                map.put(name.toLowerCase(), list = new ArrayList<String>(1));
168            }
169            list.add(value);
170        }
171        return map;
172    }
173
174    private static String unquote(String s) {
175        char[] cs = s.toCharArray();
176        boolean backslash = false;
177        int count = 0;
178        for (char c : cs) {
179            if (c != '\"' && c != '\\' || backslash) {
180                cs[count++] = c;
181                backslash = false;
182            } else {
183                backslash = c == '\\';
184            }
185        }
186        return new String(cs, 0, count);
187    }
188
189    private boolean readHeaderParam(Field field) throws IOException {
190        field.reset();
191        OUTER:
192        while (!isBoundary()) {
193            field.growBuffer(buffer.length);
194            while (rpos < buffer.length)
195                if (!field.append(buffer[rpos++]))
196                    break OUTER;
197        }
198        return !field.isEmpty();
199    }
200
201    private static final class Field {
202        byte[] buffer = new byte[256];
203        int length;
204
205        void reset() {
206            length = 0;
207        }
208
209        boolean isEmpty() {
210            return length == 0;
211        }
212
213        void growBuffer(int grow) {
214            if (length + grow > buffer.length) {
215                byte[] copy = new byte[length + grow];
216                System.arraycopy(buffer, 0, copy, 0, length);
217                buffer = copy;
218            }
219        }
220
221        boolean append(byte b) {
222            if (b == '\n' && length > 0 && buffer[length-1] == '\r') {
223                length--;
224                return false;
225            }
226
227            buffer[length++] = b;
228            return true;
229        }
230
231        public String toString() {
232            return new String(buffer, 0, length);
233        }
234
235    }
236
237}