blob: 8fc755cc83d28d7ec02061c91b3fc43eecef0703 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1998-2006 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package sun.security.provider;
27
28import java.io.*;
29import java.util.Collection;
30import java.util.*;
31import java.security.cert.*;
32import sun.security.x509.X509CertImpl;
33import sun.security.x509.X509CRLImpl;
34import sun.security.pkcs.PKCS7;
35import sun.security.provider.certpath.X509CertPath;
36import sun.security.provider.certpath.X509CertificatePair;
37import sun.security.util.DerValue;
38import sun.security.util.Cache;
39import sun.misc.BASE64Decoder;
40
41/**
42 * This class defines a certificate factory for X.509 v3 certificates &
43 * certification paths, and X.509 v2 certificate revocation lists (CRLs).
44 *
45 * @author Jan Luehe
46 * @author Hemma Prafullchandra
47 * @author Sean Mullan
48 *
49 *
50 * @see java.security.cert.CertificateFactorySpi
51 * @see java.security.cert.Certificate
52 * @see java.security.cert.CertPath
53 * @see java.security.cert.CRL
54 * @see java.security.cert.X509Certificate
55 * @see java.security.cert.X509CRL
56 * @see sun.security.x509.X509CertImpl
57 * @see sun.security.x509.X509CRLImpl
58 */
59
60public class X509Factory extends CertificateFactorySpi {
61
62 public static final String BEGIN_CERT = "-----BEGIN CERTIFICATE-----";
63 public static final String END_CERT = "-----END CERTIFICATE-----";
64
65 private static final int defaultExpectedLineLength = 80;
66
67 private static final char[] endBoundary = "-----END".toCharArray();
68
69 private static final int ENC_MAX_LENGTH = 4096 * 1024; // 4 MB MAX
70
71 private static final Cache certCache = Cache.newSoftMemoryCache(750);
72 private static final Cache crlCache = Cache.newSoftMemoryCache(750);
73
74 /**
75 * Generates an X.509 certificate object and initializes it with
76 * the data read from the input stream <code>is</code>.
77 *
78 * @param is an input stream with the certificate data.
79 *
80 * @return an X.509 certificate object initialized with the data
81 * from the input stream.
82 *
83 * @exception CertificateException on parsing errors.
84 */
85 public Certificate engineGenerateCertificate(InputStream is)
86 throws CertificateException
87 {
88 if (is == null) {
89 // clear the caches (for debugging)
90 certCache.clear();
91 X509CertificatePair.clearCache();
92 throw new CertificateException("Missing input stream");
93 }
94 try {
95 if (is.markSupported() == false) {
96 // consume the entire input stream
97 byte[] totalBytes;
98 totalBytes = getTotalBytes(new BufferedInputStream(is));
99 is = new ByteArrayInputStream(totalBytes);
100 }
101 byte[] encoding = readSequence(is);
102 if (encoding != null) {
103 X509CertImpl cert = (X509CertImpl)getFromCache(certCache, encoding);
104 if (cert != null) {
105 return cert;
106 }
107 cert = new X509CertImpl(encoding);
108 addToCache(certCache, cert.getEncodedInternal(), cert);
109 return cert;
110 } else {
111 X509CertImpl cert;
112 // determine if binary or Base64 encoding. If Base64 encoding,
113 // the certificate must be bounded at the beginning by
114 // "-----BEGIN".
115 if (isBase64(is)) {
116 // Base64
117 byte[] data = base64_to_binary(is);
118 cert = new X509CertImpl(data);
119 } else {
120 // binary
121 cert = new X509CertImpl(new DerValue(is));
122 }
123 return intern(cert);
124 }
125 } catch (IOException ioe) {
126 throw (CertificateException)new CertificateException
127 ("Could not parse certificate: " + ioe.toString()).initCause(ioe);
128 }
129 }
130
131 /**
132 * Read a DER SEQUENCE from an InputStream and return the encoding.
133 * If data does not represent a SEQUENCE, it uses indefinite length
134 * encoding, or is longer than ENC_MAX_LENGTH, the stream is reset
135 * and this method returns null.
136 */
137 private static byte[] readSequence(InputStream in) throws IOException {
138 in.mark(ENC_MAX_LENGTH);
139 byte[] b = new byte[4];
140 int i = readFully(in, b, 0, b.length);
141 if ((i != b.length) || (b[0] != 0x30)) { // first byte must be SEQUENCE
142 in.reset();
143 return null;
144 }
145 i = b[1] & 0xff;
146 int totalLength;
147 if (i < 0x80) {
148 int valueLength = i;
149 totalLength = valueLength + 2;
150 } else if (i == 0x81) {
151 int valueLength = b[2] & 0xff;
152 totalLength = valueLength + 3;
153 } else if (i == 0x82) {
154 int valueLength = ((b[2] & 0xff) << 8) | (b[3] & 0xff);
155 totalLength = valueLength + 4;
156 } else { // ignore longer length forms
157 in.reset();
158 return null;
159 }
160 if (totalLength > ENC_MAX_LENGTH) {
161 in.reset();
162 return null;
163 }
164 byte[] encoding = new byte[totalLength];
165 if( totalLength < b.length ) {
166 in.reset();
167 i = readFully(in, encoding, 0, totalLength);
168 if( i != totalLength ) {
169 in.reset();
170 return null;
171 }
172 } else {
173 System.arraycopy(b, 0, encoding, 0, b.length);
174 int n = totalLength - b.length;
175 i = readFully(in, encoding, b.length, n);
176 if (i != n) {
177 in.reset();
178 return null;
179 }
180 }
181 return encoding;
182 }
183
184 /**
185 * Read from the stream until length bytes have been read or EOF has
186 * been reached. Return the number of bytes actually read.
187 */
188 private static int readFully(InputStream in, byte[] buffer, int offset,
189 int length) throws IOException {
190 int read = 0;
191 while (length > 0) {
192 int n = in.read(buffer, offset, length);
193 if (n <= 0) {
194 break;
195 }
196 read += n;
197 length -= n;
198 offset += n;
199 }
200 return read;
201 }
202
203 /**
204 * Return an interned X509CertImpl for the given certificate.
205 * If the given X509Certificate or X509CertImpl is already present
206 * in the cert cache, the cached object is returned. Otherwise,
207 * if it is a X509Certificate, it is first converted to a X509CertImpl.
208 * Then the X509CertImpl is added to the cache and returned.
209 *
210 * Note that all certificates created via generateCertificate(InputStream)
211 * are already interned and this method does not need to be called.
212 * It is useful for certificates that cannot be created via
213 * generateCertificate() and for converting other X509Certificate
214 * implementations to an X509CertImpl.
215 */
216 public static synchronized X509CertImpl intern(X509Certificate c)
217 throws CertificateException {
218 if (c == null) {
219 return null;
220 }
221 boolean isImpl = c instanceof X509CertImpl;
222 byte[] encoding;
223 if (isImpl) {
224 encoding = ((X509CertImpl)c).getEncodedInternal();
225 } else {
226 encoding = c.getEncoded();
227 }
228 X509CertImpl newC = (X509CertImpl)getFromCache(certCache, encoding);
229 if (newC != null) {
230 return newC;
231 }
232 if (isImpl) {
233 newC = (X509CertImpl)c;
234 } else {
235 newC = new X509CertImpl(encoding);
236 encoding = newC.getEncodedInternal();
237 }
238 addToCache(certCache, encoding, newC);
239 return newC;
240 }
241
242 /**
243 * Return an interned X509CRLImpl for the given certificate.
244 * For more information, see intern(X509Certificate).
245 */
246 public static synchronized X509CRLImpl intern(X509CRL c)
247 throws CRLException {
248 if (c == null) {
249 return null;
250 }
251 boolean isImpl = c instanceof X509CRLImpl;
252 byte[] encoding;
253 if (isImpl) {
254 encoding = ((X509CRLImpl)c).getEncodedInternal();
255 } else {
256 encoding = c.getEncoded();
257 }
258 X509CRLImpl newC = (X509CRLImpl)getFromCache(crlCache, encoding);
259 if (newC != null) {
260 return newC;
261 }
262 if (isImpl) {
263 newC = (X509CRLImpl)c;
264 } else {
265 newC = new X509CRLImpl(encoding);
266 encoding = newC.getEncodedInternal();
267 }
268 addToCache(crlCache, encoding, newC);
269 return newC;
270 }
271
272 /**
273 * Get the X509CertImpl or X509CRLImpl from the cache.
274 */
275 private static synchronized Object getFromCache(Cache cache,
276 byte[] encoding) {
277 Object key = new Cache.EqualByteArray(encoding);
278 Object value = cache.get(key);
279 return value;
280 }
281
282 /**
283 * Add the X509CertImpl or X509CRLImpl to the cache.
284 */
285 private static synchronized void addToCache(Cache cache, byte[] encoding,
286 Object value) {
287 if (encoding.length > ENC_MAX_LENGTH) {
288 return;
289 }
290 Object key = new Cache.EqualByteArray(encoding);
291 cache.put(key, value);
292 }
293
294 /**
295 * Generates a <code>CertPath</code> object and initializes it with
296 * the data read from the <code>InputStream</code> inStream. The data
297 * is assumed to be in the default encoding.
298 *
299 * @param inStream an <code>InputStream</code> containing the data
300 * @return a <code>CertPath</code> initialized with the data from the
301 * <code>InputStream</code>
302 * @exception CertificateException if an exception occurs while decoding
303 * @since 1.4
304 */
305 public CertPath engineGenerateCertPath(InputStream inStream)
306 throws CertificateException
307 {
308 if (inStream == null) {
309 throw new CertificateException("Missing input stream");
310 }
311 try {
312 if (inStream.markSupported() == false) {
313 // consume the entire input stream
314 byte[] totalBytes;
315 totalBytes = getTotalBytes(new BufferedInputStream(inStream));
316 inStream = new ByteArrayInputStream(totalBytes);
317 }
318 // determine if binary or Base64 encoding. If Base64 encoding,
319 // each certificate must be bounded at the beginning by
320 // "-----BEGIN".
321 if (isBase64(inStream)) {
322 // Base64
323 byte[] data = base64_to_binary(inStream);
324 return new X509CertPath(new ByteArrayInputStream(data));
325 } else {
326 return new X509CertPath(inStream);
327 }
328 } catch (IOException ioe) {
329 throw new CertificateException(ioe.getMessage());
330 }
331 }
332
333 /**
334 * Generates a <code>CertPath</code> object and initializes it with
335 * the data read from the <code>InputStream</code> inStream. The data
336 * is assumed to be in the specified encoding.
337 *
338 * @param inStream an <code>InputStream</code> containing the data
339 * @param encoding the encoding used for the data
340 * @return a <code>CertPath</code> initialized with the data from the
341 * <code>InputStream</code>
342 * @exception CertificateException if an exception occurs while decoding or
343 * the encoding requested is not supported
344 * @since 1.4
345 */
346 public CertPath engineGenerateCertPath(InputStream inStream,
347 String encoding) throws CertificateException
348 {
349 if (inStream == null) {
350 throw new CertificateException("Missing input stream");
351 }
352 try {
353 if (inStream.markSupported() == false) {
354 // consume the entire input stream
355 byte[] totalBytes;
356 totalBytes = getTotalBytes(new BufferedInputStream(inStream));
357 inStream = new ByteArrayInputStream(totalBytes);
358 }
359 // determine if binary or Base64 encoding. If Base64 encoding,
360 // each certificate must be bounded at the beginning by
361 // "-----BEGIN".
362 if (isBase64(inStream)) {
363 // Base64
364 byte[] data = base64_to_binary(inStream);
365 return new X509CertPath(new ByteArrayInputStream(data), encoding);
366 } else {
367 return(new X509CertPath(inStream, encoding));
368 }
369 } catch (IOException ioe) {
370 throw new CertificateException(ioe.getMessage());
371 }
372 }
373
374 /**
375 * Generates a <code>CertPath</code> object and initializes it with
376 * a <code>List</code> of <code>Certificate</code>s.
377 * <p>
378 * The certificates supplied must be of a type supported by the
379 * <code>CertificateFactory</code>. They will be copied out of the supplied
380 * <code>List</code> object.
381 *
382 * @param certificates a <code>List</code> of <code>Certificate</code>s
383 * @return a <code>CertPath</code> initialized with the supplied list of
384 * certificates
385 * @exception CertificateException if an exception occurs
386 * @since 1.4
387 */
388 public CertPath
389 engineGenerateCertPath(List<? extends Certificate> certificates)
390 throws CertificateException
391 {
392 return(new X509CertPath(certificates));
393 }
394
395 /**
396 * Returns an iteration of the <code>CertPath</code> encodings supported
397 * by this certificate factory, with the default encoding first.
398 * <p>
399 * Attempts to modify the returned <code>Iterator</code> via its
400 * <code>remove</code> method result in an
401 * <code>UnsupportedOperationException</code>.
402 *
403 * @return an <code>Iterator</code> over the names of the supported
404 * <code>CertPath</code> encodings (as <code>String</code>s)
405 * @since 1.4
406 */
407 public Iterator<String> engineGetCertPathEncodings() {
408 return(X509CertPath.getEncodingsStatic());
409 }
410
411 /**
412 * Returns a (possibly empty) collection view of X.509 certificates read
413 * from the given input stream <code>is</code>.
414 *
415 * @param is the input stream with the certificates.
416 *
417 * @return a (possibly empty) collection view of X.509 certificate objects
418 * initialized with the data from the input stream.
419 *
420 * @exception CertificateException on parsing errors.
421 */
422 public Collection<? extends java.security.cert.Certificate>
423 engineGenerateCertificates(InputStream is)
424 throws CertificateException {
425 if (is == null) {
426 throw new CertificateException("Missing input stream");
427 }
428 try {
429 if (is.markSupported() == false) {
430 // consume the entire input stream
431 is = new ByteArrayInputStream
432 (getTotalBytes(new BufferedInputStream(is)));
433 }
434 return parseX509orPKCS7Cert(is);
435 } catch (IOException ioe) {
436 throw new CertificateException(ioe);
437 }
438 }
439
440 /**
441 * Generates an X.509 certificate revocation list (CRL) object and
442 * initializes it with the data read from the given input stream
443 * <code>is</code>.
444 *
445 * @param is an input stream with the CRL data.
446 *
447 * @return an X.509 CRL object initialized with the data
448 * from the input stream.
449 *
450 * @exception CRLException on parsing errors.
451 */
452 public CRL engineGenerateCRL(InputStream is)
453 throws CRLException
454 {
455 if (is == null) {
456 // clear the cache (for debugging)
457 crlCache.clear();
458 throw new CRLException("Missing input stream");
459 }
460 try {
461 if (is.markSupported() == false) {
462 // consume the entire input stream
463 byte[] totalBytes;
464 totalBytes = getTotalBytes(new BufferedInputStream(is));
465 is = new ByteArrayInputStream(totalBytes);
466 }
467 byte[] encoding = readSequence(is);
468 if (encoding != null) {
469 X509CRLImpl crl = (X509CRLImpl)getFromCache(crlCache, encoding);
470 if (crl != null) {
471 return crl;
472 }
473 crl = new X509CRLImpl(encoding);
474 addToCache(crlCache, crl.getEncodedInternal(), crl);
475 return crl;
476 } else {
477 X509CRLImpl crl;
478 // determine if binary or Base64 encoding. If Base64 encoding,
479 // the CRL must be bounded at the beginning by
480 // "-----BEGIN".
481 if (isBase64(is)) {
482 // Base64
483 byte[] data = base64_to_binary(is);
484 crl = new X509CRLImpl(data);
485 } else {
486 // binary
487 crl = new X509CRLImpl(new DerValue(is));
488 }
489 return intern(crl);
490 }
491 } catch (IOException ioe) {
492 throw new CRLException(ioe.getMessage());
493 }
494 }
495
496 /**
497 * Returns a (possibly empty) collection view of X.509 CRLs read
498 * from the given input stream <code>is</code>.
499 *
500 * @param is the input stream with the CRLs.
501 *
502 * @return a (possibly empty) collection view of X.509 CRL objects
503 * initialized with the data from the input stream.
504 *
505 * @exception CRLException on parsing errors.
506 */
507 public Collection<? extends java.security.cert.CRL> engineGenerateCRLs(InputStream
508is)
509 throws CRLException
510 {
511 if (is == null) {
512 throw new CRLException("Missing input stream");
513 }
514 try {
515 if (is.markSupported() == false) {
516 // consume the entire input stream
517 is = new ByteArrayInputStream
518 (getTotalBytes(new BufferedInputStream(is)));
519 }
520 return parseX509orPKCS7CRL(is);
521 } catch (IOException ioe) {
522 throw new CRLException(ioe.getMessage());
523 }
524 }
525
526 /*
527 * Parses the data in the given input stream as a sequence of DER
528 * encoded X.509 certificates (in binary or base 64 encoded format) OR
529 * as a single PKCS#7 encoded blob (in binary or base64 encoded format).
530 */
531 private Collection<? extends java.security.cert.Certificate>
532 parseX509orPKCS7Cert(InputStream is)
533 throws CertificateException, IOException
534 {
535 Collection<X509CertImpl> coll = new ArrayList<X509CertImpl>();
536 boolean first = true;
537 while (is.available() != 0) {
538 // determine if binary or Base64 encoding. If Base64 encoding,
539 // each certificate must be bounded at the beginning by
540 // "-----BEGIN".
541 InputStream is2 = is;
542 if (isBase64(is2)) {
543 // Base64
544 is2 = new ByteArrayInputStream(base64_to_binary(is2));
545 }
546 if (first)
547 is2.mark(is2.available());
548 try {
549 // treat as X.509 cert
550 coll.add(intern(new X509CertImpl(new DerValue(is2))));
551 } catch (CertificateException e) {
552 Throwable cause = e.getCause();
553 // only treat as PKCS#7 if this is the first cert parsed
554 // and the root cause of the decoding failure is an IOException
555 if (first && cause != null && (cause instanceof IOException)) {
556 // treat as PKCS#7
557 is2.reset();
558 PKCS7 pkcs7 = new PKCS7(is2);
559 X509Certificate[] certs = pkcs7.getCertificates();
560 // certs are optional in PKCS #7
561 if (certs != null) {
562 return Arrays.asList(certs);
563 } else {
564 // no certs provided
565 return new ArrayList<X509Certificate>(0);
566 }
567 } else {
568 throw e;
569 }
570 }
571 first = false;
572 }
573 return coll;
574 }
575
576 /*
577 * Parses the data in the given input stream as a sequence of DER encoded
578 * X.509 CRLs (in binary or base 64 encoded format) OR as a single PKCS#7
579 * encoded blob (in binary or base 64 encoded format).
580 */
581 private Collection<? extends java.security.cert.CRL>
582 parseX509orPKCS7CRL(InputStream is)
583 throws CRLException, IOException
584 {
585 Collection<X509CRLImpl> coll = new ArrayList<X509CRLImpl>();
586 boolean first = true;
587 while (is.available() != 0) {
588 // determine if binary or Base64 encoding. If Base64 encoding,
589 // the CRL must be bounded at the beginning by
590 // "-----BEGIN".
591 InputStream is2 = is;
592 if (isBase64(is)) {
593 // Base64
594 is2 = new ByteArrayInputStream(base64_to_binary(is2));
595 }
596 if (first)
597 is2.mark(is2.available());
598 try {
599 // treat as X.509 CRL
600 coll.add(new X509CRLImpl(is2));
601 } catch (CRLException e) {
602 // only treat as PKCS#7 if this is the first CRL parsed
603 if (first) {
604 is2.reset();
605 PKCS7 pkcs7 = new PKCS7(is2);
606 X509CRL[] crls = pkcs7.getCRLs();
607 // CRLs are optional in PKCS #7
608 if (crls != null) {
609 return Arrays.asList(crls);
610 } else {
611 // no crls provided
612 return new ArrayList<X509CRL>(0);
613 }
614 }
615 }
616 first = false;
617 }
618 return coll;
619 }
620
621 /*
622 * Converts a Base64-encoded X.509 certificate or X.509 CRL or PKCS#7 data
623 * to binary encoding.
624 * In all cases, the data must be bounded at the beginning by
625 * "-----BEGIN", and must be bounded at the end by "-----END".
626 */
627 private byte[] base64_to_binary(InputStream is)
628 throws IOException
629 {
630 long len = 0; // total length of base64 encoding, including boundaries
631
632 is.mark(is.available());
633
634 BufferedInputStream bufin = new BufferedInputStream(is);
635 BufferedReader br =
636 new BufferedReader(new InputStreamReader(bufin, "ASCII"));
637
638 // First read all of the data that is found between
639 // the "-----BEGIN" and "-----END" boundaries into a buffer.
640 String temp;
641 if ((temp=readLine(br))==null || !temp.startsWith("-----BEGIN")) {
642 throw new IOException("Unsupported encoding");
643 } else {
644 len += temp.length();
645 }
646 StringBuffer strBuf = new StringBuffer();
647 while ((temp=readLine(br))!=null && !temp.startsWith("-----END")) {
648 strBuf.append(temp);
649 }
650 if (temp == null) {
651 throw new IOException("Unsupported encoding");
652 } else {
653 len += temp.length();
654 }
655
656 // consume only as much as was needed
657 len += strBuf.length();
658 is.reset();
659 is.skip(len);
660
661 // Now, that data is supposed to be a single X.509 certificate or
662 // X.509 CRL or PKCS#7 formatted data... Base64 encoded.
663 // Decode into binary and return the result.
664 BASE64Decoder decoder = new BASE64Decoder();
665 return decoder.decodeBuffer(strBuf.toString());
666 }
667
668 /*
669 * Reads the entire input stream into a byte array.
670 */
671 private byte[] getTotalBytes(InputStream is) throws IOException {
672 byte[] buffer = new byte[8192];
673 ByteArrayOutputStream baos = new ByteArrayOutputStream(2048);
674 int n;
675 baos.reset();
676 while ((n = is.read(buffer, 0, buffer.length)) != -1) {
677 baos.write(buffer, 0, n);
678 }
679 return baos.toByteArray();
680 }
681
682 /*
683 * Determines if input is binary or Base64 encoded.
684 */
685 private boolean isBase64(InputStream is) throws IOException {
686 if (is.available() >= 10) {
687 is.mark(10);
688 int c1 = is.read();
689 int c2 = is.read();
690 int c3 = is.read();
691 int c4 = is.read();
692 int c5 = is.read();
693 int c6 = is.read();
694 int c7 = is.read();
695 int c8 = is.read();
696 int c9 = is.read();
697 int c10 = is.read();
698 is.reset();
699 if (c1 == '-' && c2 == '-' && c3 == '-' && c4 == '-'
700 && c5 == '-' && c6 == 'B' && c7 == 'E' && c8 == 'G'
701 && c9 == 'I' && c10 == 'N') {
702 return true;
703 } else {
704 return false;
705 }
706 } else {
707 return false;
708 }
709 }
710
711 /*
712 * Read a line of text. A line is considered to be terminated by any one
713 * of a line feed ('\n'), a carriage return ('\r'), a carriage return
714 * followed immediately by a linefeed, or an end-of-certificate marker.
715 *
716 * @return A String containing the contents of the line, including
717 * any line-termination characters, or null if the end of the
718 * stream has been reached.
719 */
720 private String readLine(BufferedReader br) throws IOException {
721 int c;
722 int i = 0;
723 boolean isMatch = true;
724 boolean matched = false;
725 StringBuffer sb = new StringBuffer(defaultExpectedLineLength);
726 do {
727 c = br.read();
728 if (isMatch && (i < endBoundary.length)) {
729 isMatch = ((char)c != endBoundary[i++]) ? false : true;
730 }
731 if (!matched)
732 matched = (isMatch && (i == endBoundary.length));
733 sb.append((char)c);
734 } while ((c != -1) && (c != '\n') && (c != '\r'));
735
736 if (!matched && c == -1) {
737 return null;
738 }
739 if (c == '\r') {
740 br.mark(1);
741 int c2 = br.read();
742 if (c2 == '\n') {
743 sb.append((char)c);
744 } else {
745 br.reset();
746 }
747 }
748 return sb.toString();
749 }
750}