Code coverage report for lib/header-parser.js

Statements: 97.87% (46 / 47)      Branches: 94.74% (18 / 19)      Functions: 100% (6 / 6)      Lines: 97.87% (46 / 47)     

All files » lib/ » header-parser.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 1211 1 1   1   8     8 8 8 8         8 8 8 8   8 8   1           1   1           1   8 8     1 132   132   66 65 65   1   66     66 65                     65       4 61       7       54       65 65   1   66               1           6           1   6 6     1  
var Stream = require('stream');
var util = require('util');
var HeaderTokenizer = require(__dirname + '/header-tokenizer');
 
function HeaderParser() {
    // Call the Stream.Transform constructor
    Stream.Transform.call(this);
 
    // Initialize state
    this.tokenizer = new HeaderTokenizer();
    this.state = this.states.FIELD_NAME;
    this.currentFieldName;
    this.headers = {};
 
    // The "this" value in event emitter listener functions is bound to the
    // emitter object. Need to bind the HeaderParser's receiveToken and
    // done functions to the right "this" context.
    var receiveToken = this.receiveToken.bind(this);
    var done = this.done.bind(this);
    this.tokenizer.on('token', receiveToken);
    this.tokenizer.on('done', done);
 
    this.on('newListener', function(name, listener) {
        if (name === 'headers' && this.state === this.states.DONE) {
            // Re-fire the handler function.
            listener(this.headers);
        }
    });
}
 
// Set up the inheritance
util.inherits(HeaderParser, Stream.Transform);
 
HeaderParser.prototype.states = {
    FIELD_NAME: 'FIELD_NAME',
    FIELD_BODY: 'FIELD_BODY',
    DONE: 'DONE'
};
 
HeaderParser.prototype._transform = function(chunk, encoding, callback) {
    // Just forward data to the tokenizer.
    this.tokenizer.write(chunk);
    callback();
};
 
HeaderParser.prototype.receiveToken = function(token) {
    var states = this.states;
 
    switch (this.state) {
        case states.FIELD_NAME:
            if (token.type === 'field_name') {
                this.currentFieldName = token.value;
                this.state = states.FIELD_BODY;
            } else {
                this.emit('error', new Error('Expected a field_name token but got ' + token.type));
            }
            break;
 
        case states.FIELD_BODY:
            if (token.type === 'field_body') {
                var previousHeaderValue = this.headers[this.currentFieldName];
                // Note that some header fields are repeated multiple times
                // with different values within the same message (e.g., the
                // "Received" header). In that case, the value associated
                // with such a header field name will be an array of strings
                // instead of a simple string. For example:
                //
                //  {
                //      'Content-Type': 'text/plain',
                //      'Received': ['value-1', 'value-2']
                //  }
                if (previousHeaderValue && typeof previousHeaderValue === 'string') {
                    // Assert: this is the second header value for this
                    // particular field name. Create a list with the two
                    // entries we've encountered thus far.
                    this.headers[this.currentFieldName] = [previousHeaderValue, token.value];
                } else if (previousHeaderValue && typeof previousHeaderValue === 'object') {
                    // Assert: we already have a list of header field values
                    // associated with this header name. Append this one to
                    // that list.
                    this.headers[this.currentFieldName].push(token.value);
                } else {
                    // Assert: there is no entry for this header field name
                    // yet. Just create a simple string value.
                    this.headers[this.currentFieldName] = token.value;
                }
 
                // Clear out the field name and set the state.
                this.currentFieldName = null;
                this.state = states.FIELD_NAME;
            } else {
                this.emit('error', new Error('Expected a field_body token but got ' + token.type));
            }
            break;
 
        default:
            // Should never get here, but just in case...
            this.emit('error', new Error('Unknown state "' + this.state + '" in HeaderParser'));
    }
};
 
HeaderParser.prototype._flush = function(callback) {
    // Close the tokenizer. The tokenizer MAY emit one final token event,
    // but will definitely emit a single "done" event. In any case, both
    // these events will bubble up to the event handlers defined in the
    // HeaderParser's constructor and will be handled accordingly. To kick
    // these events off, we simply need to end the tokenizer's stream.
    this.tokenizer.end();
};
 
// This function is bound to the tokenizer's "done" event. It assumes the
// tokenizer has already been closed with tokenizer.end() and emits a
// "headers" event with the email headers.
HeaderParser.prototype.done = function() {
    // Emit the headers as an event
    this.emit('headers', this.headers);
    this.state = this.states.DONE;
};
 
module.exports = HeaderParser;