001/**
002 * Java Web Archive Toolkit - Software to read and validate ARC, WARC
003 * and GZip files. (http://jwat.org/)
004 * Copyright 2011-2012 Netarkivet.dk (http://netarkivet.dk/)
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.jwat.common;
019
020import java.io.IOException;
021import java.io.InputStream;
022import java.io.PushbackInputStream;
023
024/**
025 * Advanced header/line reader which can be configured into difference modes.
026 * The reader can either read normal lines or header lines.
027 * Supported encodings are raw, US-ASCII, ISO-8859-1 and UTF-8.
028 *
029 * Furthermore header lines can employ linear white space (LWS), quoted text
030 * and encoded words.
031 *
032 * After calling the readLine method additional information is available from
033 * public fields on the reader.
034 *
035 * @author nicl
036 */
037public class HeaderLineReader {
038
039    /*
040     * Internal states.
041     */
042
043    /** Initial state for reading a normal line. */
044    protected static final int S_LINE = 0;
045    /** Initial state for reading a header line. */
046    protected static final int S_NAME = 1;
047    /** State for reading a header value. */
048    protected static final int S_VALUE = 2;
049    /** State for reading a LWS character sequence. */
050    protected static final int S_LWS = 3;
051    /** State for reading a quoted string. */
052    protected static final int S_QUOTED_TEXT = 4;
053    /** State for reading a quoted pair character. */
054    protected static final int S_QUOTED_PAIR = 5;
055    /** State for reading a quoted LWS character sequence. */
056    protected static final int S_QUOTED_LWS = 6;
057    /** Status for reading an encoded word character sequence. */
058    //protected static final int S_ENCODED_WORD_EQ = 7;
059
060    /*
061     * 8-bit character characteristics.
062     */
063
064    /** Control character characteristic. */
065    protected static final int CC_CONTROL = 1;
066    /** Separator character characteristic. */
067    protected static final int CC_SEPARATOR_WS = 2;
068
069    /** rfc2616 separator characters. */
070    public static final String separatorsWs = "()<>@,;:\\\"/[]?={} \t";
071
072    /** Table of separator characters. */
073    public static final byte[] charCharacteristicsTab = new byte[256];
074
075    /**
076     * Populate table of separators.
077     */
078    static {
079        for (int i=0; i<32; ++i) {
080            if (i != '\t') {
081                charCharacteristicsTab[i] |= CC_CONTROL;
082            }
083        }
084        charCharacteristicsTab[127] |= CC_CONTROL;
085        for (int i=0; i<separatorsWs.length(); ++i) {
086            charCharacteristicsTab[separatorsWs.charAt(i)] |= CC_SEPARATOR_WS;
087        }
088    }
089
090    /*
091     * Encoding.
092     */
093
094    /** Raw encoding identifier. */
095    public static final int ENC_RAW = 0;
096    /** US-ASCII encoding identifier. */
097    public static final int ENC_US_ASCII = 1;
098    /** ISO-8859-1 encoding identifier. */
099    public static final int ENC_ISO8859_1 = 2;
100    /** UTF-8 encoding identifier. */
101    public static final int ENC_UTF8 = 3;
102
103    /** Reusable UTF-8 validation object instance. */
104    protected final UTF8 utf8 = new UTF8();
105
106    /*
107     * EOL.
108     */
109
110    /** LF end of line identifier. */
111    public static final int EOL_LF = 0;
112    /** CRLF end of line identifier. */
113    public static final int EOL_CRLF = 1;
114
115    /*
116     * Configuration.
117     */
118
119    /** Parses a headerline if true and a normal line if false. */
120    public boolean bNameValue;
121    /** Identifier for the expected character encoding. */
122    public int encoding = ENC_RAW;
123    /** Identifier for the Expected end of line character sequence. */
124    public int eol = EOL_CRLF;
125    /** Support LWS. */
126    public boolean bLWS;
127    /** Support quoted text. */
128    public boolean bQuotedText;
129    /** Support encoded words. */
130    public boolean bEncodedWords;
131
132    /** Reusable <code>StringBuffer</code> for lines. */
133    protected final StringBuffer lineSb = new StringBuffer();
134    /** Reusable <code>StringBuffer</code> for name/value strings. */
135    protected final StringBuffer nvSb = new StringBuffer();
136    /** Stream used to record the raw characters read by the parser. */
137    protected ByteArrayOutputStreamWithUnread bytesOut = new ByteArrayOutputStreamWithUnread();
138
139    /*
140     * Error reporting.
141     */
142
143    /** Bit denoting unexpected EOF. */
144    public static final int E_BIT_EOF = 1 << 0;
145    /** Bit denoting a misplaced CR. */
146    public static final int E_BIT_MISPLACED_CR = 1 << 1;
147    /** Bit denoting a missing CR. */
148    public static final int E_BIT_MISSING_CR = 1 << 2;
149    /** Bit denoting an unexpected CR. */
150    public static final int E_BIT_UNEXPECTED_CR = 1 << 3;
151    /** Bit denoting an invalid UTF-8 encoded character. */
152    public static final int E_BIT_INVALID_UTF8_ENCODING = 1 << 4;
153    /** Bit denoting an invalid US-ASCII character. */
154    public static final int E_BIT_INVALID_US_ASCII_CHAR = 1 << 5;
155    /** Bit denoting an invalid control character. */
156    public static final int E_BIT_INVALID_CONTROL_CHAR = 1 << 6;
157    /** Bit denoting an invalid separator character. */
158    public static final int E_BIT_INVALID_SEPARATOR_CHAR = 1 << 7;
159    /** Bit denoting a missing quote character. */
160    public static final int E_BIT_MISSING_QUOTE = 1 << 8;
161    /** Bit denoting a missing quoted pair character. */
162    public static final int E_BIT_MISSING_QUOTED_PAIR_CHAR = 1 << 9;
163    /** Bit denoting an invalid quoted pair character. */
164    public static final int E_BIT_INVALID_QUOTED_PAIR_CHAR = 1 << 10;
165    /** Bit denoting an invalid encoding. */
166    public static final int E_BIT_INVALID_CHARSET = 1 << 11;
167
168    /*
169     * Internal state.
170     */
171
172    /** True if the previous character was a CR. */
173    protected boolean bCr = false;
174
175    /** Used by decode method to indicated valid or non valid character. */
176    protected boolean bValidChar;
177
178    /*
179     * Exposed state.
180     */
181
182    /** Boolean indicating whether or not EOF has been reached on stream. */
183    public boolean bEof;
184
185    /** Bit field of errors encountered while attempting to read a line. */
186    public int bfErrors;
187
188    /**
189     * Prohibit public construction.
190     */
191    protected HeaderLineReader() {
192    }
193
194    /**
195     * Returns a reader with default configuration values.
196     * @return a reader with default configuration values
197     */
198    public static HeaderLineReader getReader() {
199        return new HeaderLineReader();
200    }
201
202    /**
203     * Returns a reader initialized to read normal lines.
204     * Normal lines being lines with no LWS or key:value headers.
205     * The reader is configured to expect US-ASCII characters.
206     * @return a reader to read normal lines
207     */
208    public static HeaderLineReader getLineReader() {
209        HeaderLineReader hlr = new HeaderLineReader();
210        hlr.bNameValue = false;
211        hlr.encoding = ENC_US_ASCII;
212        return hlr;
213    }
214
215    /**
216     * Returns a reader initialized to read header lines.
217     * The reader is configured to expect ISO-8859-1 encoding, LWS,
218     * quoted text and encoded words. Besides reading key:value headers it will
219     * also read and return normal lines as defined in the method above.
220     * @return a reader to read header lines
221     */
222    public static HeaderLineReader getHeaderLineReader() {
223        HeaderLineReader hlr = new HeaderLineReader();
224        hlr.bNameValue = true;
225        hlr.encoding = ENC_ISO8859_1;
226        hlr.eol = EOL_CRLF;
227        hlr.bLWS = true;
228        hlr.bQuotedText = true;
229        hlr.bEncodedWords = true;
230        return hlr;
231    }
232
233    /**
234     * Reads a header/line according to the configuration.
235     * After calling the readLine method additional information is available
236     * from public fields on the reader.
237     * @param in <code>InputStream</code> with characters
238     * @return result wrapped in a <code>HeaderLine</code> object
239     * @throws IOException if an I/O error occurs in the underlying input stream
240     */
241    public HeaderLine readLine(PushbackInputStream in) throws IOException {
242        HeaderLine headerLine = new HeaderLine();
243        int state;
244        if (!bNameValue) {
245            state = S_LINE;
246        } else {
247            state = S_NAME;
248        }
249        lineSb.setLength(0);
250        nvSb.setLength(0);
251        bytesOut = new ByteArrayOutputStreamWithUnread();
252        bfErrors = 0;
253        int c;
254        bCr = false;
255        boolean bLoop = true;
256        while (bLoop) {
257            c = in.read();
258            if (c != -1) {
259                bytesOut.write(c);
260            }
261            switch (state) {
262            case S_LINE:
263                switch (c) {
264                case -1:
265                    // EOF.
266                    bfErrors |= E_BIT_EOF;
267                    headerLine.type = HeaderLine.HLT_LINE;
268                    headerLine.line = lineSb.toString();
269                    lineSb.setLength(0);
270                    bLoop = false;
271                    break;
272                case '\r':
273                    bCr = true;
274                    break;
275                case '\n':
276                    headerLine.type = HeaderLine.HLT_LINE;
277                    headerLine.line = lineSb.toString();
278                    lineSb.setLength(0);
279                    // Check EOL.
280                    check_eol();
281                    bLoop = false;
282                    break;
283                default:
284                    if (bCr) {
285                        // Misplaced CR.
286                        bfErrors |= E_BIT_MISPLACED_CR;
287                        bCr = false;
288                    }
289                    // Decode character.
290                    c = decode(c, in);
291                    if (c == -1) {
292                        // EOF.
293                        bfErrors |= E_BIT_EOF;
294                        headerLine.type = HeaderLine.HLT_LINE;
295                        headerLine.line = lineSb.toString();
296                        lineSb.setLength(0);
297                        bLoop = false;
298                    } else {
299                        if (bValidChar && encoding != ENC_RAW) {
300                            if (c < 256 && ((charCharacteristicsTab[c] & CC_CONTROL) == CC_CONTROL)) {
301                                bValidChar = false;
302                                // Invalid control char
303                                bfErrors |= E_BIT_INVALID_CONTROL_CHAR;
304                            }
305                        }
306                        if (bValidChar) {
307                            lineSb.append((char) c);
308                        }
309                    }
310                    break;
311                }
312                break;
313            case S_NAME:
314                switch (c) {
315                case -1:
316                    // EOF.
317                    bfErrors |= E_BIT_EOF;
318                    headerLine.type = HeaderLine.HLT_LINE;
319                    headerLine.line = lineSb.toString();
320                    lineSb.setLength(0);
321                    nvSb.setLength(0);
322                    bLoop = false;
323                    break;
324                case '\r':
325                    bCr = true;
326                    break;
327                case '\n':
328                    headerLine.type = HeaderLine.HLT_LINE;
329                    headerLine.line = lineSb.toString();
330                    lineSb.setLength(0);
331                    nvSb.setLength(0);
332                    // Check EOL.
333                    check_eol();
334                    bLoop = false;
335                    break;
336                case ':':
337                    headerLine.type = HeaderLine.HLT_HEADERLINE;
338                    headerLine.name = nvSb.toString();
339                    lineSb.setLength(0);
340                    nvSb.setLength(0);
341                    if (bCr) {
342                        // Misplaced CR.
343                        bfErrors |= E_BIT_MISPLACED_CR;
344                        bCr = false;
345                    }
346                    state = S_VALUE;
347                    break;
348                default:
349                    if (bCr) {
350                        // Misplaced CR.
351                        bfErrors |= E_BIT_MISPLACED_CR;
352                        bCr = false;
353                    }
354                    // Decode character.
355                    c = decode(c, in);
356                    if (c == -1) {
357                        // EOF.
358                        bfErrors |= E_BIT_EOF;
359                        headerLine.type = HeaderLine.HLT_LINE;
360                        headerLine.line = lineSb.toString();
361                        lineSb.setLength(0);
362                        nvSb.setLength(0);
363                        bLoop = false;
364                    } else {
365                        if (bValidChar && encoding != ENC_RAW) {
366                            if (c < 256 && ((charCharacteristicsTab[c] & CC_CONTROL) == CC_CONTROL)) {
367                                bValidChar = false;
368                                // Invalid control char
369                                bfErrors |= E_BIT_INVALID_CONTROL_CHAR;
370                            }
371                        }
372                        if (bValidChar) {
373                            lineSb.append((char) c);
374                            if (c < 256 && ((charCharacteristicsTab[c] & CC_SEPARATOR_WS) == CC_SEPARATOR_WS)) {
375                                bValidChar = false;
376                                // Invalid separator in name
377                                bfErrors |= E_BIT_INVALID_SEPARATOR_CHAR;
378                            }
379                        }
380                        if (bValidChar) {
381                            nvSb.append((char) c);
382                        }
383                    }
384                    break;
385                }
386                break;
387            case S_VALUE:
388                switch (c) {
389                case -1:
390                    // EOF.
391                    bfErrors |= E_BIT_EOF;
392                    headerLine.value = trim(nvSb);
393                    nvSb.setLength(0);
394                    bLoop = false;
395                    break;
396                case '\r':
397                    bCr = true;
398                    break;
399                case '\n':
400                    // Check EOL.
401                    check_eol();
402                    if (bLWS) {
403                        state = S_LWS;
404                    } else {
405                        headerLine.value = trim(nvSb);
406                        nvSb.setLength(0);
407                        bLoop = false;
408                    }
409                    break;
410                default:
411                    if (bCr) {
412                        // Misplaced CR.
413                        bfErrors |= E_BIT_MISPLACED_CR;
414                        bCr = false;
415                    }
416                    // Decode character.
417                    c = decode(c, in);
418                    if (c == -1) {
419                        // EOF.
420                        bfErrors |= E_BIT_EOF;
421                        headerLine.value = trim(nvSb);
422                        nvSb.setLength(0);
423                        bLoop = false;
424                    } else {
425                        if (bValidChar && encoding != ENC_RAW) {
426                            if (c < 256 && ((charCharacteristicsTab[c] & CC_CONTROL) == CC_CONTROL)) {
427                                bValidChar = false;
428                                // Invalid control char
429                                bfErrors |= E_BIT_INVALID_CONTROL_CHAR;
430                            }
431                        }
432                        if (bValidChar) {
433                            switch (c) {
434                            case '\"':
435                                nvSb.append((char)c);
436                                if (bQuotedText) {
437                                    state = S_QUOTED_TEXT;
438                                }
439                                break;
440/*
441                            case '=':
442                                if (bEncodedWords) {
443                                    state = S_ENCODED_WORD_EQ;
444                                } else {
445                                    nvSb.append((char)c);
446                                }
447                                break;
448*/
449                            default:
450                                nvSb.append((char)c);
451                                break;
452                            }
453                        }
454                    }
455                    break;
456                }
457                break;
458            case S_LWS:
459                switch (c) {
460                case -1:
461                    // EOF.
462                    //bfErrors |= E_BIT_EOF;
463                    headerLine.value = trim(nvSb);
464                    nvSb.setLength(0);
465                    bLoop = false;
466                    break;
467                case ' ':
468                case '\t':
469                    nvSb.append(" ");
470                    state = S_VALUE;
471                    break;
472                default:
473                    in.unread(c);
474                    bytesOut.unread(c);
475                    headerLine.value = trim(nvSb);
476                    nvSb.setLength(0);
477                    bLoop = false;
478                    break;
479                }
480                break;
481            case S_QUOTED_TEXT:
482                switch (c) {
483                case -1:
484                    // EOF.
485                    bfErrors |= E_BIT_MISSING_QUOTE | E_BIT_EOF;
486                    headerLine.value = trim(nvSb);
487                    nvSb.setLength(0);
488                    bLoop = false;
489                    break;
490                case '\"':
491                    if (bCr) {
492                        // Misplaced CR.
493                        bfErrors |= E_BIT_MISPLACED_CR;
494                        bCr = false;
495                    }
496                    nvSb.append((char)c);
497                    state = S_VALUE;
498                    break;
499                case '\\':
500                    if (bCr) {
501                        // Misplaced CR.
502                        bfErrors |= E_BIT_MISPLACED_CR;
503                        bCr = false;
504                    }
505                    state = S_QUOTED_PAIR;
506                    break;
507                case '\r':
508                    bCr = true;
509                    break;
510                case '\n':
511                    // Check EOL.
512                    check_eol();
513                    if (bLWS) {
514                        state = S_QUOTED_LWS;
515                    } else {
516                        headerLine.value = trim(nvSb);
517                        nvSb.setLength(0);
518                        bLoop = false;
519                    }
520                    break;
521                default:
522                    if (bCr) {
523                        // Misplaced CR.
524                        bfErrors |= E_BIT_MISPLACED_CR;
525                        bCr = false;
526                    }
527                    // Decode character.
528                    c = decode(c, in);
529                    if (c == -1) {
530                        // EOF.
531                        bfErrors |= E_BIT_MISSING_QUOTE | E_BIT_EOF;
532                        headerLine.value = trim(nvSb);
533                        nvSb.setLength(0);
534                        bLoop = false;
535                    } else {
536                        if (bValidChar && encoding != ENC_RAW) {
537                            if (c < 256 && ((charCharacteristicsTab[c] & CC_CONTROL) == CC_CONTROL)) {
538                                bValidChar = false;
539                                // Invalid control char
540                                bfErrors |= E_BIT_INVALID_CONTROL_CHAR;
541                            }
542                        }
543                        if (bValidChar) {
544                            nvSb.append((char)c);
545                        }
546                    }
547                    break;
548                }
549                break;
550            case S_QUOTED_PAIR:
551                switch (c) {
552                case -1:
553                    nvSb.append('\\');
554                    // EOF.
555                    bfErrors |= E_BIT_MISSING_QUOTED_PAIR_CHAR | E_BIT_MISSING_QUOTE | E_BIT_EOF;
556                    headerLine.value = trim(nvSb);
557                    nvSb.setLength(0);
558                    bLoop = false;
559                    break;
560                default:
561                    // Decode character.
562                    c = decode(c, in);
563                    if (c == -1) {
564                        // EOF.
565                        bfErrors |= E_BIT_MISSING_QUOTED_PAIR_CHAR | E_BIT_MISSING_QUOTE | E_BIT_EOF;
566                        headerLine.value = trim(nvSb);
567                        nvSb.setLength(0);
568                        bLoop = false;
569                    } else {
570                        nvSb.append('\\');
571                        nvSb.append((char)c);
572                        if (!bValidChar) {
573                            bfErrors |= E_BIT_INVALID_QUOTED_PAIR_CHAR;
574                        }
575                        state = S_QUOTED_TEXT;
576                    }
577                    break;
578                }
579                break;
580            case S_QUOTED_LWS:
581                switch (c) {
582                case -1:
583                    // EOF.
584                    bfErrors |= E_BIT_MISSING_QUOTE;
585                    headerLine.value = trim(nvSb);
586                    nvSb.setLength(0);
587                    bLoop = false;
588                    break;
589                case ' ':
590                case '\t':
591                    nvSb.append(" ");
592                    state = S_QUOTED_TEXT;
593                    break;
594                default:
595                    in.unread(c);
596                    bytesOut.unread(c);
597                    bfErrors |= E_BIT_MISSING_QUOTE;
598                    headerLine.value = trim(nvSb);
599                    nvSb.setLength(0);
600                    bLoop = false;
601                    break;
602                }
603                break;
604/*
605            case S_ENCODED_WORD_EQ:
606                switch (c) {
607                case -1:
608                    nvSb.append('=');
609                    // EOF.
610                    bfErrors |= E_BIT_EOF;
611                    headerLine.value = trim(nvSb);
612                    nvSb.setLength(0);
613                    bLoop = false;
614                    break;
615                case '?':
616                    //  Unread "=?", so it can be parsed as an EncodedWord which always starts with "=?"
617                    in.unread('?');
618                    in.unread('=');
619                    bytesOut.unread('?');
620                    bytesOut.unread('=');
621                    EncodedWords ew = EncodedWords.parseEncodedWords(in, true);
622*/
623                    /*
624                    if (!ew.bIsValid) {
625                        // TODO Decide whether to report encoded word errors or interpret as non encoded words.
626                    }
627                    */
628/*
629                    if (!ew.bValidCharset) {
630                        // In case of invalid charset errors, try to report.
631                        bfErrors |= E_BIT_INVALID_CHARSET;
632                        // Possible message : "Invalid charset : " + ew.charsetStr;
633                    }
634                    nvSb.append("=?");
635                    in.unread(ew.line, 2, ew.line.length - 2);
636                    bytesOut.write("=?".getBytes());
637                    state = S_VALUE;
638                    break;
639                default:
640                    nvSb.append('=');
641                    in.unread(c);
642                    bytesOut.unread(c);
643                    state = S_VALUE;
644                    break;
645                }
646                break;
647*/
648            }
649        }
650        headerLine.raw = bytesOut.toByteArray();
651        headerLine.bfErrors = bfErrors;
652        bEof = (headerLine.raw.length == 0);
653        return headerLine;
654    }
655
656    /**
657     * Decode a character according to the expected encoding.
658     * @param c first character of the possibly encoded character sequence
659     * @param in <code>InputStream</code> with possible extra encoded characters.
660     * @return decoded character
661     * @throws IOException if an I/O error occurs in the underlying input stream
662     */
663    protected int decode(int c, InputStream in) throws IOException {
664        switch (encoding) {
665        case ENC_UTF8:
666            c = utf8.readUtf8(c, in);
667            bytesOut.write(utf8.chars_read);
668            bValidChar = utf8.bValidChar;
669            if (c != -1) {
670                if (!bValidChar) {
671                    // Invalid UTF-8 char
672                    bfErrors |= E_BIT_INVALID_UTF8_ENCODING;
673                }
674            }
675            break;
676        case ENC_US_ASCII:
677            bValidChar = (c <= 127);
678            if (!bValidChar) {
679                // Invalid US-ASCII char
680                bfErrors |= E_BIT_INVALID_US_ASCII_CHAR;
681            }
682            break;
683        case ENC_ISO8859_1:
684            // ISO-8859-1 utilizes all 8-bits and requires no decoding.
685        case ENC_RAW:
686            // Raw 8-bit character needs no decoding.
687        default:
688             bValidChar = true;
689            break;
690        }
691        return c;
692    }
693
694    /**
695     * Check and report whether the line ended as expected.
696     */
697    protected void check_eol() {
698        switch (eol) {
699        case EOL_LF:
700            if (!bCr) {
701                // Unexpected CR.
702                bfErrors |= E_BIT_UNEXPECTED_CR;
703            }
704            break;
705        case EOL_CRLF:
706            if (!bCr) {
707                // Missing CR.
708                bfErrors |= E_BIT_MISSING_CR;
709            }
710            break;
711        }
712        bCr = false;
713    }
714
715    /**
716     * Trims the whitespace characters found in the beginning and end of a
717     * string. Differs from the String method in that it leaves control
718     * characters.
719     * @param sb <code>StringBuffer</code> to be trimmed
720     * @return trimmed string
721     */
722    public static String trim(StringBuffer sb) {
723        int sIdx = 0;
724        int eIdx = sb.length();
725        while (sIdx < eIdx && sb.charAt(sIdx) == ' ') {
726            ++sIdx;
727        }
728        while (eIdx > sIdx && sb.charAt(eIdx - 1) == ' ') {
729            --eIdx;
730        }
731        return sb.substring(sIdx, eIdx);
732    }
733
734    /**
735     * Report bit field errors as diagnoses.
736     * @param bfErrors bit field with indicated errors
737     * @param diagnostics diagnostics object used to report diagnoses
738     */
739    public static void report_error(int bfErrors, Diagnostics diagnostics) {
740        if (diagnostics == null) {
741            throw new IllegalArgumentException("'diagnostics' argument is null");
742        }
743        if ((bfErrors & E_BIT_EOF) != 0) {
744            diagnostics.addError(new Diagnosis(DiagnosisType.ERROR, "header/line", "Unexpected EOF"));
745        }
746        if ((bfErrors & E_BIT_MISPLACED_CR) != 0) {
747            diagnostics.addError(new Diagnosis(DiagnosisType.ERROR, "header/line", "Misplaced CR"));
748        }
749        if ((bfErrors & E_BIT_MISSING_CR) != 0) {
750            diagnostics.addError(new Diagnosis(DiagnosisType.ERROR, "header/line", "Missing CR"));
751        }
752        if ((bfErrors & E_BIT_UNEXPECTED_CR) != 0) {
753            diagnostics.addError(new Diagnosis(DiagnosisType.ERROR, "header/line", "Unexpected CR"));
754        }
755        if ((bfErrors & E_BIT_INVALID_UTF8_ENCODING) != 0) {
756            diagnostics.addError(new Diagnosis(DiagnosisType.ERROR, "header/line", "Invalid UTF-8 encoded character"));
757        }
758        if ((bfErrors & E_BIT_INVALID_US_ASCII_CHAR) != 0) {
759            diagnostics.addError(new Diagnosis(DiagnosisType.ERROR, "header/line", "Invalid US-ASCII character"));
760        }
761        if ((bfErrors & E_BIT_INVALID_CONTROL_CHAR) != 0) {
762            diagnostics.addError(new Diagnosis(DiagnosisType.ERROR, "header/line", "Invalid control character"));
763        }
764        if ((bfErrors & E_BIT_INVALID_SEPARATOR_CHAR) != 0) {
765            diagnostics.addError(new Diagnosis(DiagnosisType.ERROR, "header/line", "Invalid separator character"));
766        }
767        if ((bfErrors & E_BIT_MISSING_QUOTE) != 0) {
768            diagnostics.addError(new Diagnosis(DiagnosisType.ERROR, "header/line", "Missing quote character"));
769        }
770        if ((bfErrors & E_BIT_MISSING_QUOTED_PAIR_CHAR) != 0) {
771            diagnostics.addError(new Diagnosis(DiagnosisType.ERROR, "header/line", "Missing quoted pair character"));
772        }
773        if ((bfErrors & E_BIT_INVALID_QUOTED_PAIR_CHAR) != 0) {
774            diagnostics.addError(new Diagnosis(DiagnosisType.ERROR, "header/line", "Invalid quoted pair character"));
775        }
776        if ((bfErrors & E_BIT_INVALID_CHARSET) != 0) {
777            diagnostics.addError(new Diagnosis(DiagnosisType.ERROR, "header/line", "Invalid charset"));
778        }
779    }
780
781}