src/share/jaxws_classes/com/sun/xml/internal/org/jvnet/mimepull/MIMEParser.java

Fri, 04 Oct 2013 16:21:34 +0100

author
mkos
date
Fri, 04 Oct 2013 16:21:34 +0100
changeset 408
b0610cd08440
parent 368
0989ad8c0860
child 637
9c07ef4934dd
permissions
-rw-r--r--

8025054: Update JAX-WS RI integration to 2.2.9-b130926.1035
Reviewed-by: chegar

ohair@286 1 /*
alanb@368 2 * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
ohair@286 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
ohair@286 4 *
ohair@286 5 * This code is free software; you can redistribute it and/or modify it
ohair@286 6 * under the terms of the GNU General Public License version 2 only, as
ohair@286 7 * published by the Free Software Foundation. Oracle designates this
ohair@286 8 * particular file as subject to the "Classpath" exception as provided
ohair@286 9 * by Oracle in the LICENSE file that accompanied this code.
ohair@286 10 *
ohair@286 11 * This code is distributed in the hope that it will be useful, but WITHOUT
ohair@286 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
ohair@286 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
ohair@286 14 * version 2 for more details (a copy is included in the LICENSE file that
ohair@286 15 * accompanied this code).
ohair@286 16 *
ohair@286 17 * You should have received a copy of the GNU General Public License version
ohair@286 18 * 2 along with this work; if not, write to the Free Software Foundation,
ohair@286 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
ohair@286 20 *
ohair@286 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
ohair@286 22 * or visit www.oracle.com if you need additional information or have any
ohair@286 23 * questions.
ohair@286 24 */
ohair@286 25
ohair@286 26 package com.sun.xml.internal.org.jvnet.mimepull;
ohair@286 27
ohair@286 28 import java.io.InputStream;
ohair@286 29 import java.io.IOException;
ohair@286 30 import java.util.*;
ohair@286 31 import java.util.logging.Logger;
ohair@286 32 import java.nio.ByteBuffer;
alanb@368 33 import java.util.logging.Level;
ohair@286 34
ohair@286 35 /**
ohair@286 36 * Pull parser for the MIME messages. Applications can use pull API to continue
ohair@286 37 * the parsing MIME messages lazily.
ohair@286 38 *
ohair@286 39 * <pre>
ohair@286 40 * for e.g.:
ohair@286 41 * <p>
ohair@286 42 *
ohair@286 43 * MIMEParser parser = ...
ohair@286 44 * Iterator<MIMEEvent> it = parser.iterator();
ohair@286 45 * while(it.hasNext()) {
ohair@286 46 * MIMEEvent event = it.next();
ohair@286 47 * ...
ohair@286 48 * }
ohair@286 49 * </pre>
ohair@286 50 *
ohair@286 51 * @author Jitendra Kotamraju
ohair@286 52 */
ohair@286 53 class MIMEParser implements Iterable<MIMEEvent> {
ohair@286 54
ohair@286 55 private static final Logger LOGGER = Logger.getLogger(MIMEParser.class.getName());
ohair@286 56
alanb@368 57 private static final String HEADER_ENCODING = "ISO8859-1";
alanb@368 58
ohair@286 59 // Actually, the grammar doesn't support whitespace characters
ohair@286 60 // after boundary. But the mail implementation checks for it.
ohair@286 61 // We will only check for these many whitespace characters after boundary
ohair@286 62 private static final int NO_LWSP = 1000;
ohair@286 63 private enum STATE {START_MESSAGE, SKIP_PREAMBLE, START_PART, HEADERS, BODY, END_PART, END_MESSAGE}
ohair@286 64 private STATE state = STATE.START_MESSAGE;
ohair@286 65
ohair@286 66 private final InputStream in;
ohair@286 67 private final byte[] bndbytes;
ohair@286 68 private final int bl;
ohair@286 69 private final MIMEConfig config;
ohair@286 70 private final int[] bcs = new int[128]; // BnM algo: Bad Character Shift table
ohair@286 71 private final int[] gss; // BnM algo : Good Suffix Shift table
ohair@286 72
ohair@286 73 /**
ohair@286 74 * Have we parsed the data from our InputStream yet?
ohair@286 75 */
ohair@286 76 private boolean parsed;
ohair@286 77
ohair@286 78 /*
ohair@286 79 * Read and process body partsList until we see the
ohair@286 80 * terminating boundary line (or EOF).
ohair@286 81 */
ohair@286 82 private boolean done = false;
ohair@286 83
ohair@286 84 private boolean eof;
ohair@286 85 private final int capacity;
ohair@286 86 private byte[] buf;
ohair@286 87 private int len;
ohair@286 88 private boolean bol; // beginning of the line
ohair@286 89
ohair@286 90 /*
ohair@286 91 * Parses the MIME content. At the EOF, it also closes input stream
ohair@286 92 */
ohair@286 93 MIMEParser(InputStream in, String boundary, MIMEConfig config) {
ohair@286 94 this.in = in;
ohair@286 95 this.bndbytes = getBytes("--"+boundary);
ohair@286 96 bl = bndbytes.length;
ohair@286 97 this.config = config;
ohair@286 98 gss = new int[bl];
ohair@286 99 compileBoundaryPattern();
ohair@286 100
ohair@286 101 // \r\n + boundary + "--\r\n" + lots of LWSP
ohair@286 102 capacity = config.chunkSize+2+bl+4+NO_LWSP;
ohair@286 103 createBuf(capacity);
ohair@286 104 }
ohair@286 105
ohair@286 106 /**
ohair@286 107 * Returns iterator for the parsing events. Use the iterator to advance
ohair@286 108 * the parsing.
ohair@286 109 *
ohair@286 110 * @return iterator for parsing events
ohair@286 111 */
alanb@368 112 @Override
ohair@286 113 public Iterator<MIMEEvent> iterator() {
ohair@286 114 return new MIMEEventIterator();
ohair@286 115 }
ohair@286 116
ohair@286 117 class MIMEEventIterator implements Iterator<MIMEEvent> {
ohair@286 118
alanb@368 119 @Override
ohair@286 120 public boolean hasNext() {
ohair@286 121 return !parsed;
ohair@286 122 }
ohair@286 123
alanb@368 124 @Override
ohair@286 125 public MIMEEvent next() {
ohair@286 126 switch(state) {
ohair@286 127 case START_MESSAGE :
alanb@368 128 if (LOGGER.isLoggable(Level.FINER)) {LOGGER.log(Level.FINER, "MIMEParser state={0}", STATE.START_MESSAGE);}
ohair@286 129 state = STATE.SKIP_PREAMBLE;
ohair@286 130 return MIMEEvent.START_MESSAGE;
ohair@286 131
ohair@286 132 case SKIP_PREAMBLE :
alanb@368 133 if (LOGGER.isLoggable(Level.FINER)) {LOGGER.log(Level.FINER, "MIMEParser state={0}", STATE.SKIP_PREAMBLE);}
ohair@286 134 skipPreamble();
ohair@286 135 // fall through
ohair@286 136 case START_PART :
alanb@368 137 if (LOGGER.isLoggable(Level.FINER)) {LOGGER.log(Level.FINER, "MIMEParser state={0}", STATE.START_PART);}
ohair@286 138 state = STATE.HEADERS;
ohair@286 139 return MIMEEvent.START_PART;
ohair@286 140
ohair@286 141 case HEADERS :
alanb@368 142 if (LOGGER.isLoggable(Level.FINER)) {LOGGER.log(Level.FINER, "MIMEParser state={0}", STATE.HEADERS);}
ohair@286 143 InternetHeaders ih = readHeaders();
ohair@286 144 state = STATE.BODY;
ohair@286 145 bol = true;
ohair@286 146 return new MIMEEvent.Headers(ih);
ohair@286 147
ohair@286 148 case BODY :
alanb@368 149 if (LOGGER.isLoggable(Level.FINER)) {LOGGER.log(Level.FINER, "MIMEParser state={0}", STATE.BODY);}
ohair@286 150 ByteBuffer buf = readBody();
ohair@286 151 bol = false;
ohair@286 152 return new MIMEEvent.Content(buf);
ohair@286 153
ohair@286 154 case END_PART :
alanb@368 155 if (LOGGER.isLoggable(Level.FINER)) {LOGGER.log(Level.FINER, "MIMEParser state={0}", STATE.END_PART);}
ohair@286 156 if (done) {
ohair@286 157 state = STATE.END_MESSAGE;
ohair@286 158 } else {
ohair@286 159 state = STATE.START_PART;
ohair@286 160 }
ohair@286 161 return MIMEEvent.END_PART;
ohair@286 162
ohair@286 163 case END_MESSAGE :
alanb@368 164 if (LOGGER.isLoggable(Level.FINER)) {LOGGER.log(Level.FINER, "MIMEParser state={0}", STATE.END_MESSAGE);}
ohair@286 165 parsed = true;
ohair@286 166 return MIMEEvent.END_MESSAGE;
ohair@286 167
ohair@286 168 default :
ohair@286 169 throw new MIMEParsingException("Unknown Parser state = "+state);
ohair@286 170 }
ohair@286 171 }
ohair@286 172
alanb@368 173 @Override
ohair@286 174 public void remove() {
ohair@286 175 throw new UnsupportedOperationException();
ohair@286 176 }
ohair@286 177 }
ohair@286 178
ohair@286 179 /**
ohair@286 180 * Collects the headers for the current part by parsing mesage stream.
ohair@286 181 *
ohair@286 182 * @return headers for the current part
ohair@286 183 */
ohair@286 184 private InternetHeaders readHeaders() {
ohair@286 185 if (!eof) {
ohair@286 186 fillBuf();
ohair@286 187 }
ohair@286 188 return new InternetHeaders(new LineInputStream());
ohair@286 189 }
ohair@286 190
ohair@286 191 /**
ohair@286 192 * Reads and saves the part of the current attachment part's content.
ohair@286 193 * At the end of this method, buf should have the remaining data
ohair@286 194 * at index 0.
ohair@286 195 *
ohair@286 196 * @return a chunk of the part's content
ohair@286 197 *
ohair@286 198 */
ohair@286 199 private ByteBuffer readBody() {
ohair@286 200 if (!eof) {
ohair@286 201 fillBuf();
ohair@286 202 }
ohair@286 203 int start = match(buf, 0, len); // matches boundary
ohair@286 204 if (start == -1) {
ohair@286 205 // No boundary is found
ohair@286 206 assert eof || len >= config.chunkSize;
ohair@286 207 int chunkSize = eof ? len : config.chunkSize;
ohair@286 208 if (eof) {
ohair@286 209 done = true;
ohair@286 210 throw new MIMEParsingException("Reached EOF, but there is no closing MIME boundary.");
ohair@286 211 }
ohair@286 212 return adjustBuf(chunkSize, len-chunkSize);
ohair@286 213 }
ohair@286 214 // Found boundary.
ohair@286 215 // Is it at the start of a line ?
ohair@286 216 int chunkLen = start;
ohair@286 217 if (bol && start == 0) {
ohair@286 218 // nothing to do
ohair@286 219 } else if (start > 0 && (buf[start-1] == '\n' || buf[start-1] =='\r')) {
ohair@286 220 --chunkLen;
ohair@286 221 if (buf[start-1] == '\n' && start >1 && buf[start-2] == '\r') {
ohair@286 222 --chunkLen;
ohair@286 223 }
ohair@286 224 } else {
ohair@286 225 return adjustBuf(start+1, len-start-1); // boundary is not at beginning of a line
ohair@286 226 }
ohair@286 227
ohair@286 228 if (start+bl+1 < len && buf[start+bl] == '-' && buf[start+bl+1] == '-') {
ohair@286 229 state = STATE.END_PART;
ohair@286 230 done = true;
ohair@286 231 return adjustBuf(chunkLen, 0);
ohair@286 232 }
ohair@286 233
ohair@286 234 // Consider all the whitespace in boundary+whitespace+"\r\n"
ohair@286 235 int lwsp = 0;
ohair@286 236 for(int i=start+bl; i < len && (buf[i] == ' ' || buf[i] == '\t'); i++) {
ohair@286 237 ++lwsp;
ohair@286 238 }
ohair@286 239
ohair@286 240 // Check for \n or \r\n in boundary+whitespace+"\n" or boundary+whitespace+"\r\n"
ohair@286 241 if (start+bl+lwsp < len && buf[start+bl+lwsp] == '\n') {
ohair@286 242 state = STATE.END_PART;
ohair@286 243 return adjustBuf(chunkLen, len-start-bl-lwsp-1);
ohair@286 244 } else if (start+bl+lwsp+1 < len && buf[start+bl+lwsp] == '\r' && buf[start+bl+lwsp+1] == '\n') {
ohair@286 245 state = STATE.END_PART;
ohair@286 246 return adjustBuf(chunkLen, len-start-bl-lwsp-2);
ohair@286 247 } else if (start+bl+lwsp+1 < len) {
ohair@286 248 return adjustBuf(chunkLen+1, len-chunkLen-1); // boundary string in a part data
ohair@286 249 } else if (eof) {
ohair@286 250 done = true;
ohair@286 251 throw new MIMEParsingException("Reached EOF, but there is no closing MIME boundary.");
ohair@286 252 }
ohair@286 253
ohair@286 254 // Some more data needed to determine if it is indeed a proper boundary
ohair@286 255 return adjustBuf(chunkLen, len-chunkLen);
ohair@286 256 }
ohair@286 257
ohair@286 258 /**
ohair@286 259 * Returns a chunk from the original buffer. A new buffer is
ohair@286 260 * created with the remaining bytes.
ohair@286 261 *
ohair@286 262 * @param chunkSize create a chunk with these many bytes
ohair@286 263 * @param remaining bytes from the end of the buffer that need to be copied to
ohair@286 264 * the beginning of the new buffer
ohair@286 265 * @return chunk
ohair@286 266 */
ohair@286 267 private ByteBuffer adjustBuf(int chunkSize, int remaining) {
ohair@286 268 assert buf != null;
ohair@286 269 assert chunkSize >= 0;
ohair@286 270 assert remaining >= 0;
ohair@286 271
ohair@286 272 byte[] temp = buf;
ohair@286 273 // create a new buf and adjust it without this chunk
ohair@286 274 createBuf(remaining);
ohair@286 275 System.arraycopy(temp, len-remaining, buf, 0, remaining);
ohair@286 276 len = remaining;
ohair@286 277
ohair@286 278 return ByteBuffer.wrap(temp, 0, chunkSize);
ohair@286 279 }
ohair@286 280
ohair@286 281 private void createBuf(int min) {
ohair@286 282 buf = new byte[min < capacity ? capacity : min];
ohair@286 283 }
ohair@286 284
ohair@286 285 /**
ohair@286 286 * Skips the preamble to find the first attachment part
ohair@286 287 */
ohair@286 288 private void skipPreamble() {
ohair@286 289
ohair@286 290 while(true) {
ohair@286 291 if (!eof) {
ohair@286 292 fillBuf();
ohair@286 293 }
ohair@286 294 int start = match(buf, 0, len); // matches boundary
ohair@286 295 if (start == -1) {
ohair@286 296 // No boundary is found
ohair@286 297 if (eof) {
ohair@286 298 throw new MIMEParsingException("Missing start boundary");
ohair@286 299 } else {
ohair@286 300 adjustBuf(len-bl+1, bl-1);
ohair@286 301 continue;
ohair@286 302 }
ohair@286 303 }
ohair@286 304
ohair@286 305 if (start > config.chunkSize) {
ohair@286 306 adjustBuf(start, len-start);
ohair@286 307 continue;
ohair@286 308 }
ohair@286 309 // Consider all the whitespace boundary+whitespace+"\r\n"
ohair@286 310 int lwsp = 0;
ohair@286 311 for(int i=start+bl; i < len && (buf[i] == ' ' || buf[i] == '\t'); i++) {
ohair@286 312 ++lwsp;
ohair@286 313 }
ohair@286 314 // Check for \n or \r\n
ohair@286 315 if (start+bl+lwsp < len && (buf[start+bl+lwsp] == '\n' || buf[start+bl+lwsp] == '\r') ) {
ohair@286 316 if (buf[start+bl+lwsp] == '\n') {
ohair@286 317 adjustBuf(start+bl+lwsp+1, len-start-bl-lwsp-1);
ohair@286 318 break;
ohair@286 319 } else if (start+bl+lwsp+1 < len && buf[start+bl+lwsp+1] == '\n') {
ohair@286 320 adjustBuf(start+bl+lwsp+2, len-start-bl-lwsp-2);
ohair@286 321 break;
ohair@286 322 }
ohair@286 323 }
ohair@286 324 adjustBuf(start+1, len-start-1);
ohair@286 325 }
alanb@368 326 if (LOGGER.isLoggable(Level.FINE)) {LOGGER.log(Level.FINE, "Skipped the preamble. buffer len={0}", len);}
ohair@286 327 }
ohair@286 328
ohair@286 329 private static byte[] getBytes(String s) {
ohair@286 330 char [] chars= s.toCharArray();
ohair@286 331 int size = chars.length;
ohair@286 332 byte[] bytes = new byte[size];
ohair@286 333
alanb@368 334 for (int i = 0; i < size;) {
ohair@286 335 bytes[i] = (byte) chars[i++];
alanb@368 336 }
ohair@286 337 return bytes;
ohair@286 338 }
ohair@286 339
ohair@286 340 /**
ohair@286 341 * Boyer-Moore search method. Copied from java.util.regex.Pattern.java
ohair@286 342 *
ohair@286 343 * Pre calculates arrays needed to generate the bad character
ohair@286 344 * shift and the good suffix shift. Only the last seven bits
ohair@286 345 * are used to see if chars match; This keeps the tables small
ohair@286 346 * and covers the heavily used ASCII range, but occasionally
ohair@286 347 * results in an aliased match for the bad character shift.
ohair@286 348 */
ohair@286 349 private void compileBoundaryPattern() {
ohair@286 350 int i, j;
ohair@286 351
ohair@286 352 // Precalculate part of the bad character shift
ohair@286 353 // It is a table for where in the pattern each
ohair@286 354 // lower 7-bit value occurs
ohair@286 355 for (i = 0; i < bndbytes.length; i++) {
ohair@286 356 bcs[bndbytes[i]&0x7F] = i + 1;
ohair@286 357 }
ohair@286 358
ohair@286 359 // Precalculate the good suffix shift
ohair@286 360 // i is the shift amount being considered
ohair@286 361 NEXT: for (i = bndbytes.length; i > 0; i--) {
ohair@286 362 // j is the beginning index of suffix being considered
ohair@286 363 for (j = bndbytes.length - 1; j >= i; j--) {
ohair@286 364 // Testing for good suffix
ohair@286 365 if (bndbytes[j] == bndbytes[j-i]) {
ohair@286 366 // src[j..len] is a good suffix
ohair@286 367 gss[j-1] = i;
ohair@286 368 } else {
ohair@286 369 // No match. The array has already been
ohair@286 370 // filled up with correct values before.
ohair@286 371 continue NEXT;
ohair@286 372 }
ohair@286 373 }
ohair@286 374 // This fills up the remaining of optoSft
ohair@286 375 // any suffix can not have larger shift amount
ohair@286 376 // then its sub-suffix. Why???
ohair@286 377 while (j > 0) {
ohair@286 378 gss[--j] = i;
ohair@286 379 }
ohair@286 380 }
ohair@286 381 // Set the guard value because of unicode compression
ohair@286 382 gss[bndbytes.length -1] = 1;
ohair@286 383 }
ohair@286 384
ohair@286 385 /**
ohair@286 386 * Finds the boundary in the given buffer using Boyer-Moore algo.
ohair@286 387 * Copied from java.util.regex.Pattern.java
ohair@286 388 *
ohair@286 389 * @param mybuf boundary to be searched in this mybuf
ohair@286 390 * @param off start index in mybuf
ohair@286 391 * @param len number of bytes in mybuf
ohair@286 392 *
ohair@286 393 * @return -1 if there is no match or index where the match starts
ohair@286 394 */
ohair@286 395 private int match(byte[] mybuf, int off, int len) {
ohair@286 396 int last = len - bndbytes.length;
ohair@286 397
ohair@286 398 // Loop over all possible match positions in text
ohair@286 399 NEXT: while (off <= last) {
ohair@286 400 // Loop over pattern from right to left
ohair@286 401 for (int j = bndbytes.length - 1; j >= 0; j--) {
ohair@286 402 byte ch = mybuf[off+j];
ohair@286 403 if (ch != bndbytes[j]) {
ohair@286 404 // Shift search to the right by the maximum of the
ohair@286 405 // bad character shift and the good suffix shift
ohair@286 406 off += Math.max(j + 1 - bcs[ch&0x7F], gss[j]);
ohair@286 407 continue NEXT;
ohair@286 408 }
ohair@286 409 }
ohair@286 410 // Entire pattern matched starting at off
ohair@286 411 return off;
ohair@286 412 }
ohair@286 413 return -1;
ohair@286 414 }
ohair@286 415
ohair@286 416 /**
ohair@286 417 * Fills the remaining buf to the full capacity
ohair@286 418 */
ohair@286 419 private void fillBuf() {
alanb@368 420 if (LOGGER.isLoggable(Level.FINER)) {LOGGER.log(Level.FINER, "Before fillBuf() buffer len={0}", len);}
ohair@286 421 assert !eof;
ohair@286 422 while(len < buf.length) {
ohair@286 423 int read;
ohair@286 424 try {
ohair@286 425 read = in.read(buf, len, buf.length-len);
ohair@286 426 } catch(IOException ioe) {
ohair@286 427 throw new MIMEParsingException(ioe);
ohair@286 428 }
ohair@286 429 if (read == -1) {
ohair@286 430 eof = true;
ohair@286 431 try {
alanb@368 432 if (LOGGER.isLoggable(Level.FINE)) {LOGGER.fine("Closing the input stream.");}
ohair@286 433 in.close();
ohair@286 434 } catch(IOException ioe) {
ohair@286 435 throw new MIMEParsingException(ioe);
ohair@286 436 }
ohair@286 437 break;
ohair@286 438 } else {
ohair@286 439 len += read;
ohair@286 440 }
ohair@286 441 }
alanb@368 442 if (LOGGER.isLoggable(Level.FINER)) {LOGGER.log(Level.FINER, "After fillBuf() buffer len={0}", len);}
ohair@286 443 }
ohair@286 444
ohair@286 445 private void doubleBuf() {
ohair@286 446 byte[] temp = new byte[2*len];
ohair@286 447 System.arraycopy(buf, 0, temp, 0, len);
ohair@286 448 buf = temp;
ohair@286 449 if (!eof) {
ohair@286 450 fillBuf();
ohair@286 451 }
ohair@286 452 }
ohair@286 453
ohair@286 454 class LineInputStream {
ohair@286 455 private int offset;
ohair@286 456
ohair@286 457 /*
ohair@286 458 * Read a line containing only ASCII characters from the input
ohair@286 459 * stream. A line is terminated by a CR or NL or CR-NL sequence.
ohair@286 460 * A common error is a CR-CR-NL sequence, which will also terminate
ohair@286 461 * a line.
ohair@286 462 * The line terminator is not returned as part of the returned
ohair@286 463 * String. Returns null if no data is available. <p>
ohair@286 464 *
ohair@286 465 * This class is similar to the deprecated
ohair@286 466 * <code>DataInputStream.readLine()</code>
ohair@286 467 */
ohair@286 468 public String readLine() throws IOException {
ohair@286 469
ohair@286 470 int hdrLen = 0;
ohair@286 471 int lwsp = 0;
ohair@286 472 while(offset+hdrLen < len) {
ohair@286 473 if (buf[offset+hdrLen] == '\n') {
ohair@286 474 lwsp = 1;
ohair@286 475 break;
ohair@286 476 }
ohair@286 477 if (offset+hdrLen+1 == len) {
ohair@286 478 doubleBuf();
ohair@286 479 }
ohair@286 480 if (offset+hdrLen+1 >= len) { // No more data in the stream
ohair@286 481 assert eof;
ohair@286 482 return null;
ohair@286 483 }
ohair@286 484 if (buf[offset+hdrLen] == '\r' && buf[offset+hdrLen+1] == '\n') {
ohair@286 485 lwsp = 2;
ohair@286 486 break;
ohair@286 487 }
ohair@286 488 ++hdrLen;
ohair@286 489 }
ohair@286 490 if (hdrLen == 0) {
ohair@286 491 adjustBuf(offset+lwsp, len-offset-lwsp);
ohair@286 492 return null;
ohair@286 493 }
ohair@286 494
alanb@368 495 String hdr = new String(buf, offset, hdrLen, HEADER_ENCODING);
ohair@286 496 offset += hdrLen+lwsp;
ohair@286 497 return hdr;
ohair@286 498 }
ohair@286 499
ohair@286 500 }
ohair@286 501
ohair@286 502 }

mercurial