001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: * Free SoftwareFoundation, Inc.
023: * 59 Temple Place, Suite 330
024: * Boston, MA 02111-1307 USA
025: *
026: * @author Scott Ferguson
027: */
028:
029: package com.caucho.vfs;
030:
031: import com.caucho.util.ByteBuffer;
032: import com.caucho.util.CharBuffer;
033:
034: import java.io.IOException;
035: import java.util.HashMap;
036: import java.util.Iterator;
037:
038: /*
039: * A stream for reading multipart/mime files.
040: *
041: * <p/>Each call to openRead() will return the next part and return null
042: * when complete.
043: *
044: * <pre><code>
045: * MultipartStream multi = new MultipartStream(rawIs, boundary);
046: * ReadStream is;
047: *
048: * while ((is = multi.openRead()) != null) {
049: * // read from is as a normal stream
050: * }
051: */
052: public class MultipartStream extends StreamImpl {
053: private ByteBuffer _boundary = new ByteBuffer();
054: private byte[] _boundaryBuffer;
055: private int _boundaryLength;
056:
057: private ByteBuffer _peekBuffer = new ByteBuffer();
058: private byte[] _peek;
059: private int _peekOffset;
060: private int _peekLength;
061:
062: private byte[] _dummyBuffer = new byte[32];
063:
064: private ReadStream _is;
065: private ReadStream _readStream;
066: private boolean _isPartDone;
067: private boolean _isDone;
068: private boolean _isComplete;
069: private HashMap<String, String> _headers = new HashMap<String, String>();
070: private CharBuffer _line = new CharBuffer();
071:
072: private String _defaultEncoding;
073:
074: public MultipartStream() throws IOException {
075: _boundary = new ByteBuffer();
076: }
077:
078: public MultipartStream(ReadStream is, String boundary)
079: throws IOException {
080: this ();
081:
082: init(is, boundary);
083: }
084:
085: /**
086: * Returns the default encoding.
087: */
088: public String getEncoding() {
089: return _defaultEncoding;
090: }
091:
092: /**
093: * Sets the default encoding.
094: */
095: public void setEncoding(String encoding) {
096: _defaultEncoding = encoding;
097: }
098:
099: /**
100: * Initialize the multipart stream with a given boundary. The boundary
101: * passed to <code>init</code> will have "--" prefixed.
102: *
103: * @param is the underlying stream
104: * @param headerBoundary the multipart/mime boundary.
105: */
106: public void init(ReadStream is, String headerBoundary)
107: throws IOException {
108: _is = is;
109:
110: _boundary.clear();
111: _boundary.add("--");
112: _boundary.add(headerBoundary);
113:
114: _boundaryBuffer = _boundary.getBuffer();
115: _boundaryLength = _boundary.getLength();
116:
117: _peekBuffer.setLength(_boundaryLength + 5);
118: _peek = _peekBuffer.getBuffer();
119: _peekOffset = 0;
120: _peekLength = 0;
121: _peek[_peekLength++] = (byte) '\n';
122:
123: _isPartDone = false;
124: _isDone = false;
125: _isComplete = false;
126:
127: while (read(_dummyBuffer, 0, _dummyBuffer.length) >= 0) {
128: }
129:
130: _isPartDone = true;
131: }
132:
133: /**
134: * Returns true if complete.
135: */
136: public boolean isComplete() {
137: return _isComplete;
138: }
139:
140: /**
141: * Opens the next part of the multipart/mime stream for reading. Returns
142: * null when the last section is read.
143: */
144: public ReadStream openRead() throws IOException {
145: if (_isDone)
146: return null;
147: else if (_readStream == null)
148: _readStream = new ReadStream(this , null);
149: else if (!_isPartDone) {
150: int len;
151: while ((len = read(_dummyBuffer, 0, _dummyBuffer.length)) >= 0) {
152: }
153:
154: if (_isDone)
155: return null;
156: }
157:
158: _readStream.init(this , null);
159:
160: _isPartDone = false;
161:
162: if (scanHeaders()) {
163: String contentType = (String) getAttribute("content-type");
164:
165: String charset = getAttributePart(contentType, "charset");
166:
167: if (charset != null)
168: _readStream.setEncoding(charset);
169: else if (_defaultEncoding != null)
170: _readStream.setEncoding(_defaultEncoding);
171:
172: return _readStream;
173: } else {
174: _isDone = true;
175: _readStream.close();
176: return null;
177: }
178: }
179:
180: /**
181: * Returns a read attribute from the multipart mime.
182: */
183: public Object getAttribute(String key) {
184: return _headers.get(key.toLowerCase());
185: }
186:
187: /**
188: * Returns the headers from the mime.
189: */
190: public Iterator getAttributeNames() {
191: return _headers.keySet().iterator();
192: }
193:
194: /**
195: * Scans the mime headers. The mime headers are in standard mail/http
196: * header format: "key: value".
197: */
198: private boolean scanHeaders() throws IOException {
199: int ch = read();
200:
201: _headers.clear();
202: while (ch > 0 && ch != '\n' && ch != '\r') {
203: _line.clear();
204:
205: _line.append((char) ch);
206: for (ch = read(); ch >= 0 && ch != '\n' && ch != '\r'; ch = read()) {
207: _line.append((char) ch);
208: }
209:
210: if (ch == '\r') {
211: if ((ch = read()) == '\n')
212: ch = read();
213: } else if (ch == '\n')
214: ch = read();
215:
216: int i = 0;
217: for (; i < _line.length() && _line.charAt(i) != ':'; i++) {
218: }
219:
220: String key = null;
221: String value = null;
222: if (i < _line.length()) {
223: key = _line.substring(0, i).trim().toLowerCase();
224: value = _line.substring(i + 1).trim();
225:
226: _headers.put(key, value);
227: }
228: }
229:
230: if (ch == '\r') {
231: if ((ch = read()) != '\n') {
232: _peek[0] = (byte) ch;
233: _peekOffset = 0;
234: _peekLength = 1;
235: }
236: }
237:
238: return true;
239: }
240:
241: public boolean canRead() {
242: return true;
243: }
244:
245: /**
246: * Returns the number of available bytes.
247: */
248: public int getAvailable() throws IOException {
249: if (_isPartDone)
250: return 0;
251: else if (_peekOffset < _peekLength)
252: return _peekLength - _peekOffset;
253: else {
254: int ch = read();
255: if (ch < 0)
256: return 0;
257: _peekOffset = 0;
258: _peekLength = 1;
259: _peek[0] = (byte) ch;
260:
261: return 1;
262: }
263: }
264:
265: /**
266: * Reads from the multipart mime buffer.
267: */
268: public int read(byte[] buffer, int offset, int length)
269: throws IOException {
270: int b = -1;
271:
272: if (_isPartDone)
273: return -1;
274:
275: int i = 0;
276: // Need the last peek or would miss the initial '\n'
277: while (_peekOffset + 1 < _peekLength && length > 0) {
278: buffer[offset + i++] = _peek[_peekOffset++];
279: length--;
280: }
281:
282: while (i < length && (b = read()) >= 0) {
283: boolean hasCr = false;
284:
285: if (b == '\r') {
286: hasCr = true;
287: b = read();
288:
289: // XXX: Macintosh?
290: if (b != '\n') {
291: buffer[offset + i++] = (byte) '\r';
292: _peek[0] = (byte) b;
293: _peekOffset = 0;
294: _peekLength = 1;
295: continue;
296: }
297: } else if (b != '\n') {
298: buffer[offset + i++] = (byte) b;
299: continue;
300: }
301:
302: int j;
303: for (j = 0; j < _boundaryLength && (b = read()) >= 0
304: && _boundaryBuffer[j] == b; j++) {
305: }
306:
307: if (j == _boundaryLength) {
308: _isPartDone = true;
309: if ((b = read()) == '-') {
310: if ((b = read()) == '-') {
311: _isDone = true;
312: _isComplete = true;
313: }
314: }
315:
316: for (; b > 0 && b != '\r' && b != '\n'; b = read()) {
317: }
318: if (b == '\r' && (b = read()) != '\n') {
319: _peek[0] = (byte) b;
320: _peekOffset = 0;
321: _peekLength = 1;
322: }
323:
324: return i > 0 ? i : -1;
325: }
326:
327: _peekLength = 0;
328: if (hasCr && i + 1 < length) {
329: buffer[offset + i++] = (byte) '\r';
330: buffer[offset + i++] = (byte) '\n';
331: } else if (hasCr) {
332: buffer[offset + i++] = (byte) '\r';
333: _peek[_peekLength++] = (byte) '\n';
334: } else {
335: buffer[offset + i++] = (byte) '\n';
336: }
337:
338: int k = 0;
339: while (k < j && i + 1 < length)
340: buffer[offset + i++] = _boundaryBuffer[k++];
341:
342: while (k < j)
343: _peek[_peekLength++] = _boundaryBuffer[k++];
344:
345: _peek[_peekLength++] = (byte) b;
346: _peekOffset = 0;
347: }
348:
349: if (i <= 0) {
350: _isPartDone = true;
351: if (b < 0)
352: _isDone = true;
353: return -1;
354: } else {
355: return i;
356: }
357: }
358:
359: /**
360: * Read the next byte from the peek or from the underlying stream.
361: */
362: private int read() throws IOException {
363: if (_peekOffset < _peekLength)
364: return _peek[_peekOffset++] & 0xff;
365: else
366: return _is.read();
367: }
368:
369: private static String getAttributePart(String attr, String name) {
370: if (attr == null)
371: return null;
372:
373: int length = attr.length();
374: int i = attr.indexOf(name);
375: if (i < 0)
376: return null;
377:
378: for (i += name.length(); i < length && attr.charAt(i) != '='; i++) {
379: }
380:
381: for (i++; i < length && attr.charAt(i) == ' '; i++) {
382: }
383:
384: CharBuffer value = CharBuffer.allocate();
385: if (i < length && attr.charAt(i) == '\'') {
386: for (i++; i < length && attr.charAt(i) != '\''; i++)
387: value.append(attr.charAt(i));
388: } else if (i < length && attr.charAt(i) == '"') {
389: for (i++; i < length && attr.charAt(i) != '"'; i++)
390: value.append(attr.charAt(i));
391: } else if (i < length) {
392: char ch;
393: for (; i < length && (ch = attr.charAt(i)) != ' '
394: && ch != ';'; i++)
395: value.append(ch);
396: }
397:
398: return value.close();
399: }
400: }
|