text: 
  /**
   * @fileOverview This is the main file for the MailParser library to parse raw e-mail data
   * @author <a href="mailto:andris@node.ee">Andris Reinman</a>
   * @version 0.2.21
   */
  
  var Stream = require("stream").Stream,
      utillib = require("util"),
      mimelib = require("mimelib"),
      datetime = require("./datetime"),
      Iconv = require("iconv").Iconv,
      Streams = require("./streams"),
      crypto = require("crypto");
  
  // Expose to the world
  module.exports.MailParser = MailParser;
  
  // MailParser is a FSM - it is always in one of the possible states
  var STATES = {
      header:   0x1,
      body:     0x2,
      finished: 0x3
  };
  
  /**
   * <p>Creates instance of MailParser which in turn extends Stream</p>
   * 
   * <p>Options object has the following properties:</p>
   * 
   * <ul>
   *   <li><b>debug</b> - if set to true print all incoming lines to console</li>
   *   <li><b>streamAttachments</b> - if set to true, stream attachments instead of including them</li>
   *   <li><b>unescapeSMTP</b> - if set to true replace double dots in the beginning of the file</li>
   *   <li><b>defaultCharset</b> - the default charset for text/plain, text/html content, if not set reverts to Latin-1
   * </ul>
   * 
   * @constructor
   * @param {Object} [options] Optional options object
   */
  function MailParser(options){
      
      // Make MailParser a Stream object
      Stream.call(this);
      this.writable = true;
      
      /** 
       * Options object
       * @public  */ this.options = options || {};
       
      /** 
       * Indicates current state the parser is in
       * @private */ this._state         = STATES.header;
      
      /** 
       * The remaining data from the previos chunk which is waiting to be processed
       * @private */ this._remainder     = "";
      
      /** 
       * The complete tree structure of the e-mail
       * @public  */ this.mimeTree       = this._createMimeNode();
      
      /** 
       * Current node of the multipart mime tree that is being processed
       * @private */ this._currentNode   = this.mimeTree;
       
      /** 
       * An object of already used attachment filenames
       * @private */ this._fileNames     = {};
       
      /** 
       * An array of multipart nodes
       * @private */ this._multipartTree = [];
       
       
      /** 
       * Cache for iconv converter objects
       * @private */ this._iconv         = {};
       
      /**
       * This is the final mail structure object that is returned to the client
       * @public  */ this.mailData       = {};
       
      /** 
       * Line counter for debugging
       * @private */ this._lineCounter   = 0;
       
      /** 
       * Did the last chunk end with \r
       * @private */ this._lineFeed      = false;
     
     /** 
       * Is the "headers" event already emitted
       * @private */ this._headersSent   = false;
  }
  // inherit methods and properties of Stream
  utillib.inherits(MailParser, Stream);
  
  /**
   * <p>Writes a value to the MailParser stream<p>
   * 
   * @param {Buffer|String} chunk The data to be written to the MailParser stream
   * @param {String} [encoding] The encoding to be used when "chunk" is a string
   * @returns {Boolean} Returns true
   */
  MailParser.prototype.write = function(chunk, encoding){
      if(typeof chunk == "string"){
          chunk = new Buffer(chunk, encoding);
      }
      
      if(chunk && chunk.length){
          this._remainder += chunk.toString("binary");
          process.nextTick(this._process.bind(this));
          return true;
      }else{
          return true;
      }
  };
  
  /**
   * <p>Terminates the MailParser stream</p> 
   * 
   * <p>If "chunk" is set, writes it to the Stream before terminating.</p>
   * 
   * @param {Buffer|String} chunk The data to be written to the MailParser stream
   * @param {String} [encoding] The encoding to be used when "chunk" is a string
   */
  MailParser.prototype.end = function(chunk, encoding){
      if(typeof chunk == "string"){
          chunk = new Buffer(chunk, encoding);
      }
      
      if(this.options.debug && this._remainder){
          console.log("REMAINDER: "+this._remainder);
      }
      
      chunk = chunk && chunk.toString("binary") || "";
      
      // if the last chunk ended with \r and this one begins
      // with \n, it's a split line ending. Since the last \r
      // was already used, skip the \n
      if(this._lineFeed && chunk.charAt(0) == "\n"){
          chunk = chunk.substr(1);
      }
      this._lineFeed = chunk.substr(-1) == "\r";
      
      this._remainder += chunk;
  
      process.nextTick(this._process.bind(this, true));
  };
  
  /**
   * <p>Processes the data written to the MailParser stream</p>
   * 
   * <p>The data is split into lines and each line is processed individually. Last
   * line in the batch is preserved as a remainder since it is probably not a
   * complete line but just the beginning of it. The remainder is later prepended
   * to the next batch of data.</p>
   * 
   * @param {Boolean} [finalPart=false] if set to true indicates that this is the last part of the stream
   */
  MailParser.prototype._process = function(finalPart){
      
      finalPart = !!finalPart;
      
      var lines = this._remainder.split(/\r?\n|\r/),
          line, i, len;
          
      if(!finalPart){
          this._remainder = lines.pop();
          // force line to 1MB chunks if needed 
          if(this._remainder.length>1048576){
              this._remainder = this._remainder.replace(/(.{1048576}(?!\r?\n|\r))/g,"$&\n");
          }
      }
      
      for(i=0, len=lines.length; i < len; i++){
          line = lines[i];
          
          if(this.options.unescapeSMTP && line.substr(0,2)==".."){
              line = line.substr(1);
          }
          
          if(this.options.debug){
              console.log("LINE " + (++this._lineCounter) + " ("+this._state+"): "+line);
          }
          
          if(this._state == STATES.header){
              if(this._processStateHeader(line) === true){
                  continue;
              }
          }
          
          if(this._state == STATES.body){
              
              if(this._processStateBody(line) === true){
                  continue;
              }
              
          }
      }
      
      if(finalPart){
          if(this._state == STATES.header && this._remainder){
              this._processStateHeader(this._remainder);
              if(!this._headersSent){
                  this.emit("headers", this._currentNode.parsedHeaders);
                  this._headersSent = true;
              }
          }
          if(this._currentNode.content || this._currentNode.stream){
              this._finalizeContents();
          }
          this._state = STATES.finished;
          process.nextTick(this._processMimeTree.bind(this));
      }
      
      
  };
  
  /**
   * <p>Processes a line while in header state</p>
   * 
   * <p>If header state ends and body starts, detect if the contents is an attachment
   * and create a stream for it if needed</p>
   * 
   * @param {String} line The contents of a line to be processed
   * @returns {Boolean} If state changes to body retuns true
   */
  MailParser.prototype._processStateHeader = function(line){
      var boundary, i, len, attachment, 
          lastPos = this._currentNode.headers.length - 1,
          textContent = false, extension;
  
      // Check if the header endas and body starts
      if(!line.length){
          if(lastPos>=0){
              this._processHeaderLine(lastPos);
          }
          if(!this._headersSent){
              this.emit("headers", this._currentNode.parsedHeaders);
              this._headersSent = true;
          }
          
          this._state = STATES.body;
          
          // if there's unprocessed header data, do it now
          if(lastPos >= 0){
              this._processHeaderLine(lastPos);
          }
          
          // this is a very simple e-mail, no content type set
          if(!this._currentNode.parentNode && !this._currentNode.meta.contentType){
              this._currentNode.meta.contentType = "text/plain";
          }
          
          textContent = ["text/plain", "text/html"].indexOf(this._currentNode.meta.contentType || "") >= 0;
          
          // detect if this is an attachment or a text node (some agents use inline dispositions for text)
          if(textContent && (!this._currentNode.meta.contentDisposition || this._currentNode.meta.contentDisposition == "inline")){
              this._currentNode.attachment = false;
          }else if((!textContent || ["attachment", "inline"].indexOf(this._currentNode.meta.contentDisposition)>=0) && 
            !this._currentNode.meta.mimeMultipart){
              this._currentNode.attachment = true;
          }
          
          // handle attachment start
          if(this._currentNode.attachment){
              
              this._currentNode.checksum = crypto.createHash("md5");
              
              this._currentNode.meta.generatedFileName = this._generateFileName(this._currentNode.meta.fileName, this._currentNode.meta.contentType);
              
              extension = this._currentNode.meta.generatedFileName.split(".").pop().toLowerCase();
              
              // Update content-type if it's an application/octet-stream and file extension is available
              if(this._currentNode.meta.contentType == "application/octet-stream" && mimelib.contentTypes[extension]){
                  this._currentNode.meta.contentType = mimelib.contentTypes[extension];
              }
              
              attachment = this._currentNode.meta;
              if(this.options.streamAttachments){
                  if(this._currentNode.meta.transferEncoding == "base64"){
                      this._currentNode.stream = new Streams.Base64Stream();
                  }else if(this._currentNode.meta.transferEncoding == "quoted-printable"){
                      this._currentNode.stream = new Streams.QPStream("binary");
                  }else{
                      this._currentNode.stream = new Streams.BinaryStream();
                  }
                  attachment.stream = this._currentNode.stream;
                  
                  this.emit("attachment", attachment);
              }else{
                  this._currentNode.content = undefined;
              }
          }
          
          return true;
      }
      
      // unfold header lines if needed
      if(line.match(/^\s+/) && lastPos>=0){
          this._currentNode.headers[lastPos] += " " + line.trim();
      }else{
          this._currentNode.headers.push(line.trim());
          if(lastPos>=0){
              // if a complete header line is received, process it
              this._processHeaderLine(lastPos);
          }
      }
      
      return false;
  };
  
  /**
   * <p>Processes a line while in body state</p>
   * 
   * @param {String} line The contents of a line to be processed
   * @returns {Boolean} If body ends return true
   */
  MailParser.prototype._processStateBody = function(line){
      var i, len, node,
          nodeReady = false;
      
      // Handle multipart boundaries
      if(line.substr(0, 2) == "--"){
          for(i=0, len = this._multipartTree.length; i<len; i++){
              
              // check if a new element block starts
              if(line == "--" + this._multipartTree[i].boundary){
                  
                  if(this._currentNode.content || this._currentNode.stream){
                      this._finalizeContents();
                  }
                  
                  node = this._createMimeNode(this._multipartTree[i].node);
                  this._multipartTree[i].node.childNodes.push(node);
                  this._currentNode = node;
                  this._state = STATES.header;
                  nodeReady = true;
                  break;
              }else 
              // check if a multipart block ends
                if(line == "--" + this._multipartTree[i].boundary + "--"){
                  
                  if(this._currentNode.content || this._currentNode.stream){
                      this._finalizeContents();
                  }
                  
                  if(this._multipartTree[i].node.parentNode){
                      this._currentNode = this._multipartTree[i].node.parentNode;
                  }else{
                      this._currentNode = this._multipartTree[i].node;
                  }
                  this._state = STATES.body;
                  nodeReady = true;
                  break;
              }
          }
      }
      if(nodeReady){
          return true;
      }
      
      // handle text or attachment line
      if(["text/plain", "text/html"].indexOf(this._currentNode.meta.contentType || "")>=0 && 
        !this._currentNode.attachment){
          this._handleTextLine(line);
      }else if(this._currentNode.attachment){
          this._handleAttachmentLine(line);
      }
      
      return false;
  };
  
  /**
   * <p>Processes a complete unfolded header line</p>
   * 
   * <p>Processes a line from current node headers array and replaces its value.
   * Input string is in the form of "X-Mailer: PHP" and its replacement would be
   * an object <code>{key: "x-mailer", value: "PHP"}</code></p>
   * 
   * <p>Additionally node meta object will be filled also, for example with data from
   * To: From: Cc: etc fields.</p>
   * 
   * @param {Number} pos Which header element (from an header lines array) should be processed
   */
  MailParser.prototype._processHeaderLine = function(pos){
      var key, value, parts, line;
      
      pos = pos || 0;
      
      if(!(line = this._currentNode.headers[pos]) || typeof line != "string"){
          return;
      }
  
      parts = line.split(":");
      
      key = parts.shift().toLowerCase().trim();
      value = parts.join(":").trim();
      
      switch(key){
          case "content-type":
              this._parseContentType(value);
              break;
          case "mime-version":
              this._currentNode.useMIME = true;
              break;
          case "date":
              this._currentNode.meta.date = new Date(datetime.strtotime(value)*1000 || Date.now());
              break;
          case "to":
              if(this._currentNode.to && this._currentNode.to.length){
                  this._currentNode.to = this._currentNode.to.concat(mimelib.parseAddresses(value));
              }else{
                  this._currentNode.to = mimelib.parseAddresses(value);
              }
              break;
          case "from":
              if(this._currentNode.from && this._currentNode.from.length){
                  this._currentNode.from = this._currentNode.from.concat(mimelib.parseAddresses(value));
              }else{
                  this._currentNode.from = mimelib.parseAddresses(value);
              }
              break;
          case "cc":
              if(this._currentNode.cc && this._currentNode.cc.length){
                  this._currentNode.cc = this._currentNode.cc.concat(mimelib.parseAddresses(value));
              }else{
                  this._currentNode.cc = mimelib.parseAddresses(value);
              }
              break;
          case "bcc":
              if(this._currentNode.bcc && this._currentNode.bcc.length){
                  this._currentNode.bcc = this._currentNode.bcc.concat(mimelib.parseAddresses(value));
              }else{
                  this._currentNode.bcc = mimelib.parseAddresses(value);
              }
              break;
          case "x-priority":
          case "x-msmail-priority":
          case "importance":
              value = this._parsePriority(value);
              break;
          case "message-id":
              this._currentNode.meta.messageId = this._trimQuotes(value);
              break;
          case "references":
              this._currentNode.meta.messageReferences = this._trimQuotes(value);
              break;
          case "in-reply-to":
              this._currentNode.meta.inReplyTo = this._trimQuotes(value);
              break;
          case "thread-index":
              this._currentNode.meta.threadIndex = value;
              break;
          case "content-transfer-encoding":
              this._currentNode.meta.transferEncoding = value.toLowerCase();
              break;
          case "subject":
              this._currentNode.subject = this._encodeString(value);
              break;
          case "content-disposition":
              this._parseContentDisposition(value);
              break;
          case "content-id":
              this._currentNode.meta.contentId = this._trimQuotes(value);
              break;
      }
      
      if(this._currentNode.parsedHeaders[key]){
          if(!Array.isArray(this._currentNode.parsedHeaders[key])){
              this._currentNode.parsedHeaders[key] = [this._currentNode.parsedHeaders[key]];
          } 
          this._currentNode.parsedHeaders[key].push(this._replaceMimeWords(value));
      }else{
          this._currentNode.parsedHeaders[key] = this._replaceMimeWords(value);
      }
      
      this._currentNode.headers[pos] = {key: key, value: value};
  };
  
  /**
   * <p>Creates an empty node element for the mime tree</p>
   * 
   * <p>Created element includes parentNode property and a childNodes array. This is
   * needed to later walk the whole mime tree</p>
   * 
   * @param {Object} [parentNode] the parent object for the created node
   * @returns {Object} node element for the mime tree
   */
  MailParser.prototype._createMimeNode = function(parentNode){
      var node = {
          parentNode: parentNode || this._currentNode || null,
          headers: [],
          parsedHeaders:{},
          meta: {},
          childNodes: []
      };
      
      return node;
  };
  
  /**
   * <p>Splits a header value into key-value pairs</p>
   * 
   * <p>Splits on <code>;</code> - the first value will be set as <code>defaultValue</code> property and will
   * not be handled, others will be split on <code>=</code> to key-value pairs</p>
   * 
   * <p>For example <code>content-type: text/plain; charset=utf-8</code> will become:</p>
   * 
   * <pre>
   * {
   *     defaultValue: "text/plain",
   *     charset: "utf-8"
   * }
   * </pre>
   * 
   * @param {String} value A string to be splitted into key-value pairs
   * @returns {Object} a key-value object, with defaultvalue property
   */
  MailParser.prototype._parseHeaderLineWithParams = function(value){
      var key, parts, returnValue = {};
      
      parts = value.split(";");
      returnValue.defaultValue = parts.shift().toLowerCase();
      
      for(var i=0, len = parts.length; i<len; i++){
          value = parts[i].split("=");
          key = value.shift().trim().toLowerCase();
          value = value.join("=").trim();
          
          // trim quotes
          value = this._trimQuotes(value);
          returnValue[key] = value;
      }
      
      return returnValue;
  };
  
  /**
   * <p>Parses a Content-Type header field value</p>
   * 
   * <p>Fetches additional properties from the content type (charset etc.) and fills
   * current node meta object with this data</p>
   * 
   * @param {String} value Content-Type string
   * @returns {Object} parsed contenttype object
   */
  MailParser.prototype._parseContentType = function(value){
      var fileName;
      value = this._parseHeaderLineWithParams(value);
      if(value){
          if(value.defaultValue){
              value.defaultValue = value.defaultValue.toLowerCase();
              this._currentNode.meta.contentType = value.defaultValue;
              if(value.defaultValue.substr(0,"multipart/".length)=="multipart/"){
                  this._currentNode.meta.mimeMultipart = value.defaultValue.substr("multipart/".length);
              }
          }else{
              this._currentNode.meta.contentType = "application/octet-stream";
          }
          if(value.charset){
              value.charset = value.charset.toLowerCase();
              if(value.charset.substr(0,4)=="win-"){
                  value.charset = "windows-"+value.charset.substr(4); 
              }else if(value.charset.match(/^utf\d/)){
                  value.charset = "utf-"+value.charset.substr(3);
              }else if(value.charset.match(/^latin[\-_]?\d/)){
                  value.charset = "iso-8859-"+value.charset.replace(/\D/g,""); 
              }else if(value.charset.match(/^(us\-)?ascii$/)){
                  value.charset = "utf-8"; 
              }  
              this._currentNode.meta.charset = value.charset;
          }
          if(value.format){
              this._currentNode.meta.textFormat = value.format.toLowerCase();
          }
          if(value.delsp){
              this._currentNode.meta.textDelSp = value.delsp.toLowerCase();
          }
          if(value.boundary){
              this._currentNode.meta.mimeBoundary = value.boundary;
          }
          
          if(!this._currentNode.meta.fileName && (fileName = this._detectFilename(value))){
              this._currentNode.meta.fileName = fileName;
          }
          
          if(value.boundary){
              this._currentNode.meta.mimeBoundary = value.boundary;
              this._multipartTree.push({
                  boundary: value.boundary,
                  node: this._currentNode
              });
          }
      } 
      return value;
  };
  
  /**
   * <p>Parses file name from a Content-Type or Content-Disposition field</p>
   * 
   * <p>Supports <a href="http://tools.ietf.org/html/rfc2231">RFC2231</a> for
   * folded filenames</p>
   * 
   * @param {Object} value Parsed Content-(Type|Disposition) object
   * @return {String} filename
   */
  MailParser.prototype._detectFilename = function(value){
      var fileName="", i=0, parts, encoding, name;
      
      if(value.name){
          return this._replaceMimeWords(value.name);
      }
      
      if(value.filename){
          return this._replaceMimeWords(value.filename);
      }
      
      // RFC2231
      if(value["name*"]){
          fileName = value["name*"];
      }else if(value["filename*"]){
          fileName = value["filename*"];
      }else if(value["name*0*"]){
          while(value["name*"+(i)+"*"]){
              fileName += value["name*"+(i++)+"*"];
          }
      }else if(value["filename*0*"]){
          while(value["filename*"+(i)+"*"]){
              fileName += value["filename*"+(i++)+"*"];
          }
      }
      
      if(fileName){
          parts = fileName.split("'");
          encoding = parts.shift();
          name = parts.pop();
          if(name){
              return this._replaceMimeWords(this._replaceMimeWords("=?"+(encoding || "us-ascii")+"?Q?" + name.replace(/%/g,"=")+"?="));
          }
      }
      return "";
  };
  
  /**
   * <p>Parses Content-Disposition header field value</p>
   * 
   * <p>Fetches filename to current node meta object</p>
   * 
   * @param {String} value A Content-Disposition header field
   */
  MailParser.prototype._parseContentDisposition = function(value){
      var returnValue = {},
          fileName;
      
      value = this._parseHeaderLineWithParams(value);
      
      if(value){
          if(value.defaultValue){
              this._currentNode.meta.contentDisposition = value.defaultValue.trim().toLowerCase();
          }
          if((fileName = this._detectFilename(value))){
              this._currentNode.meta.fileName = fileName;
          }
      }
  };
  
  /**
   * <p>Parses the priority of the e-mail</p>
   * 
   * @param {String} value The priority value
   * @returns {String} priority string low|normal|high
   */
  MailParser.prototype._parsePriority = function(value){
      value = value.toLowerCase().trim();
      if(!isNaN(value)){
          value = Number(value) || 0;
          if(value == 3){
              return "normal";
          }else if(value > 3){
              return "low";
          }else{
              return "high";
          }
      }else{
          switch(value){
              case "non-urgent":
              case "low":
                  return "low";
              case "urgent":
              case "hight":
                  return "high";
          }
      }
      return "normal";
  };
  
  /**
   * <p>Processes a line in text/html or text/plain node</p>
   * 
   * <p>Append the line to the content property</p>
   * 
   * @param {String} line A line to be processed 
   */
  MailParser.prototype._handleTextLine = function(line){
      
      if(["quoted-printable", "base64"].indexOf(this._currentNode.meta.transferEncoding)>=0 || this._currentNode.meta.textFormat != "flowed"){
          if(typeof this._currentNode.content != "string"){
              this._currentNode.content = line;
          }else{
              this._currentNode.content += "\n"+line;
          }
      }else{
          if(typeof this._currentNode.content != "string"){
              this._currentNode.content = line;
          }else if(this._currentNode.content.match(/[ ]{1,}$/)){
              if(this._currentNode.meta.textDelSp == "yes"){
                  this._currentNode.content = this._currentNode.content.replace(/\s+$/,"");
              }
              this._currentNode.content += line;
          }else{
              this._currentNode.content += "\n"+line;
          }
      }  
  };
  
  /**
   * <p>Processes a line in an attachment node</p>
   * 
   * <p>If a stream is set up for the attachment write the line to the
   * stream as a Buffer object, otherwise append it to the content property</p>
   * 
   * @param {String} line A line to be processed 
   */
  MailParser.prototype._handleAttachmentLine = function(line){
      if(!this._currentNode.attachment){
          return;
      }
      if(this._currentNode.stream){
          if(!this._currentNode.streamStarted){
              this._currentNode.streamStarted = true;
              this._currentNode.stream.write(new Buffer(line, "binary"));
          }else{
              this._currentNode.stream.write(new Buffer("\r\n"+line, "binary"));
          }
      }else if("content" in this._currentNode){
          if(typeof this._currentNode.content!="string"){
              this._currentNode.content = line;
          }else{
              this._currentNode.content += "\r\n" + line;
          }
      }
  };
  
  /**
   * <p>Finalizes a node processing</p>
   * 
   * <p>If the node is a text/plain or text/html, convert it to UTF-8 encoded string
   * If it is an attachment, convert it to a Buffer or if an attachment stream is
   * set up, close the stream</p>
   */
  MailParser.prototype._finalizeContents = function(){
      var streamInfo;
  
      if(this._currentNode.content){
          
          if(!this._currentNode.attachment){
              
              if(this._currentNode.meta.contentType == "text/html"){
                   this._currentNode.meta.charset = this._detectHTMLCharset(this._currentNode.content) || this._currentNode.meta.charset || this.options.defaultCharset || "iso-8859-1";
              }
              
              if(this._currentNode.meta.transferEncoding == "quoted-printable"){
                  this._currentNode.content = mimelib.decodeQuotedPrintable(this._currentNode.content, false, this._currentNode.meta.charset || this.options.defaultCharset || "iso-8859-1");
              }else if(this._currentNode.meta.transferEncoding == "base64"){
                  this._currentNode.content = mimelib.decodeBase64(this._currentNode.content, this._currentNode.meta.charset || this.options.defaultCharset || "iso-8859-1");
              }else{
                  this._currentNode.content = this._encodeString(this._currentNode.content);
              }
          }else{
              if(this._currentNode.meta.transferEncoding == "quoted-printable"){
                  this._currentNode.content = mimelib.decodeQuotedPrintable(this._currentNode.content, false, "binary");
              }else if(this._currentNode.meta.transferEncoding == "base64"){
                  this._currentNode.content = new Buffer(this._currentNode.content.replace(/[^\w\+\/=]/g,''), "base64");
              }else{
                  this._currentNode.content = new Buffer(this._currentNode.content, "binary");
              }
              this._currentNode.checksum.update(this._currentNode.content);
              this._currentNode.meta.checksum = this._currentNode.checksum.digest("hex");
              this._currentNode.meta.length = this._currentNode.content.length;
          }
          
      }
  
      if(this._currentNode.stream){
          streamInfo = this._currentNode.stream.end() || {};
          if(streamInfo.checksum){
              this._currentNode.meta.checksum = streamInfo.checksum;
          }
          if(streamInfo.length){
              this._currentNode.meta.length = streamInfo.length;
          }
      }
  };
  
  /**
   * <p>Processes the mime tree</p>
   * 
   * <p>Finds text parts and attachments from the tree. If there's several text/plain
   * or text/html parts, push the ones from the lower parts of the tree to the
   * alternatives array</p>
   * 
   * <p>Emits "end" when finished</p>
   */
  MailParser.prototype._processMimeTree = function(){
      var level = 0, htmlLevel, textLevel,
          returnValue = {}, i, len;
      
      this.mailData = {html:[], text:[], alternatives:[], attachments:[]};
      
      if(!this.mimeTree.meta.mimeMultipart){
          this._processMimeNode(this.mimeTree, 0);
      }else{
          this._walkMimeTree(this.mimeTree);
      }
      
      if(this.mailData.html.length){
          for(i=0, len=this.mailData.html.length; i<len; i++){
              if(!returnValue.html || this.mailData.html[i].level < htmlLevel){
                  if(returnValue.html){
                      if(!returnValue.alternatives){
                          returnValue.alternatives = [];
                      }
                      returnValue.alternatives.push({
                          contentType: "text/html",
                          content: returnValue.html
                      });
                  }
                  htmlLevel = this.mailData.html[i].level;
                  returnValue.html = this.mailData.html[i].content;
              }else{
                  if(!returnValue.alternatives){
                      returnValue.alternatives = [];
                  }
                  returnValue.alternatives.push({
                      contentType: "text/html",
                      content: this.mailData.html[i].content
                  });
              }
          }
      }
      
      if(this.mailData.text.length){
          for(i=0, len=this.mailData.text.length; i<len; i++){
              if(!returnValue.text || this.mailData.text[i].level < textLevel){
                  if(returnValue.text){
                      if(!returnValue.alternatives){
                          returnValue.alternatives = [];
                      }
                      returnValue.alternatives.push({
                          contentType: "text/plain",
                          content: returnValue.text
                      });
                  }
                  textLevel = this.mailData.text[i].level;
                  returnValue.text = this.mailData.text[i].content;
              }else{
                  if(!returnValue.alternatives){
                      returnValue.alternatives = [];
                  }
                  returnValue.alternatives.push({
                      contentType: "text/plain",
                      content: this.mailData.text[i].content
                  });
              }
          }
      }
      
      returnValue.headers = this.mimeTree.parsedHeaders;
      
      if(this.mimeTree.subject){
          returnValue.subject = this.mimeTree.subject;        
      }
      
      if(this.mimeTree.from){
          returnValue.from = this.mimeTree.from;        
      }
      
      if(this.mimeTree.to){
          returnValue.to = this.mimeTree.to;        
      }
      
      if(this.mimeTree.cc){
          returnValue.cc = this.mimeTree.cc;        
      }
      
      if(this.mimeTree.bcc){
          returnValue.bcc = this.mimeTree.bcc;        
      }
      
      if(this.mailData.attachments.length){
          returnValue.attachments = [];
          for(i=0, len=this.mailData.attachments.length; i<len; i++){
              returnValue.attachments.push(this.mailData.attachments[i].content);
          }
      }
  
      process.nextTick(this.emit.bind(this, "end", returnValue));
  };
  
  /**
   * <p>Walks the mime tree and runs processMimeNode on each node of the tree</p>
   * 
   * @param {Object} node A mime tree node
   * @param {Number} [level=0] current depth
   */
  MailParser.prototype._walkMimeTree = function(node, level){
      level = level || 1;
  
      for(var i=0, len = node.childNodes.length; i<len; i++){
          this._processMimeNode(node.childNodes[i], level);
          this._walkMimeTree(node.childNodes[i], level+1);
      }
  };
  
  /**
   * <p>Processes of a node in the mime tree</p>
   * 
   * <p>Pushes the node into appropriate <code>this.mailData</code> array (<code>text/html</code> to <code>this.mailData.html</code> array etc)</p>
   * 
   * @param {Object} node A mime tree node
   * @param {Number} [level=0] current depth
   */
  MailParser.prototype._processMimeNode = function(node, level){
      level = level || 0;
  
      if(!node.attachment){
          switch(node.meta.contentType){
              case "text/html":
                  this.mailData.html.push({content: this._updateHTMLCharset(node.content || ""), level: level});
                  break;
              case "text/plain":
                  this.mailData.text.push({content: node.content || "", level: level});
                  break;
          }
      }else{
          node.meta = node.meta || {};
          if(node.content){
              node.meta.content = node.content;
          }
          this.mailData.attachments.push({content: node.meta || {}, level: level});
      }  
  };
  
  /**
   * <p>Converts a string from one charset to another</p>
   * 
   * @param {Buffer|String} value A String to be converted
   * @param {String} fromCharset source charset
   * @param {String} [toCharset="UTF-8"] destination charset
   * @returns {Buffer} Converted string as a Buffer (or SlowBuffer)
   */
  MailParser.prototype._convertString = function(value, fromCharset, toCharset){
      toCharset = (toCharset || "utf-8").toUpperCase();
      fromCharset = (fromCharset || "utf-8").toUpperCase();
      
      value = typeof value=="string"?new Buffer(value, "binary"):value;
      
      if(toCharset == fromCharset){
          return value;
      }
      
      try{ // in case there is no such charset or EINVAL occurs leave the string untouched
          if(!this._iconv[fromCharset+toCharset]){
              this._iconv[fromCharset+toCharset] = new Iconv(fromCharset, toCharset+'//TRANSLIT//IGNORE');
          }
          value = this._iconv[fromCharset+toCharset].convert(value);
      }catch(E){}
      
      return value;
  };
  
  /**
   * <p>Encodes a header string to UTF-8</p>
   * 
   * @param {String} value String to be encoded
   * @returns {String} UTF-8 encoded string
   */
  MailParser.prototype._encodeString = function(value){
      value = this._replaceMimeWords(this._convertString(value, this._currentNode.meta.charset || this.options.defaultCharset || "iso-8859-1").toString("utf-8"));
      return value;
  };
  
  /**
   * <p>Replaces mime words in a string with UTF-8 encoded strings</p>
   * 
   * @param {String} value String to be converted
   * @returns {String} converted string
   */
  MailParser.prototype._replaceMimeWords = function(value){
      return value.
          replace(/(=\?[^?]+\?[QqBb]\?[^?]+\?=)\s+(?==\?[^?]+\?[QqBb]\?[^?]+\?=)/g, "$1"). // join mimeWords
          replace(/\=\?[^?]+\?[QqBb]\?[^?]+\?=/g, (function(a){
              return mimelib.decodeMimeWord(a.replace(/\s/g,''));
          }).bind(this));
  };
  
  /**
   * <p>Removes enclosing quotes ("", '', &lt;&gt;) from a string</p>
   * 
   * @param {String} value String to be converted
   * @returns {String} converted string
   */
  MailParser.prototype._trimQuotes = function(value){
      value = (value || "").trim();
      if((value.charAt(0)=='"' && value.charAt(value.length-1)=='"') || 
        (value.charAt(0)=="'" && value.charAt(value.length-1)=="'") || 
        (value.charAt(0)=="<" && value.charAt(value.length-1)==">")){
          value = value.substr(1,value.length-2);
      }
      return value;
  };
  
  /**
   * <p>Generates a context unique filename for an attachment</p>
   * 
   * <p>If a filename already exists, append a number to it</p>
   * 
   * <ul>
   *     <li>file.txt</li>
   *     <li>file-1.txt</li>
   *     <li>file-2.txt</li>
   * </ul>
   * 
   * @param {String} fileName source filename
   * @param {String} contentType source content type
   * @returns {String} generated filename
   */
  MailParser.prototype._generateFileName = function(fileName, contentType){
      var ext, defaultExt = "";
      
      if(contentType){
          defaultExt = mimelib.contentTypesReversed[contentType];
          defaultExt = defaultExt?"."+defaultExt:"";
      }
      
      fileName = fileName || "attachment"+defaultExt;
      
      // remove path if it is included in the filename
      fileName = fileName.toString().split(/[\/\\]+/).pop().replace(/^\.+/,"") || "attachment";
      
      if(fileName in this._fileNames){
          this._fileNames[fileName]++;
          ext = fileName.substr((fileName.lastIndexOf(".") || 0)+1);
          if(ext == fileName){
              fileName += "-" +  this._fileNames[fileName];
          }else{
              fileName = fileName.substr(0, fileName.length - ext.length - 1) + "-" + this._fileNames[fileName] + "." + ext;
          }
      }else{
          this._fileNames[fileName] = 0;
      }
      return fileName;  
  };
  
  
  /**
   * <p>Replaces character set to UTF-8 in HTML &lt;meta&gt; tags</p>
   * 
   * @param {String} HTML html contents
   * @returns {String} updated HTML
   */
  MailParser.prototype._updateHTMLCharset = function(html){
      
      html = html.replace(/\n/g,"\u0000").
          replace(/<meta[^>]*>/gi, function(meta){
              if(meta.match(/http\-equiv\s*=\s*"?content\-type/i)){
                  return '<meta http-equiv="content-type" content="text/html; charset=utf-8" />';
              }
              if(meta.match(/\scharset\s*=\s*['"]?[\w\-]+["'\s>\/]/i)){
                  return '<meta charset="utf-8"/>';
              }
              return meta;
          }).
          replace(/\u0000/g,"\n");
      
      return html;
  };
  
  /**
   * <p>Detects the charset of an HTML file</p>
   * 
   * @param {String} HTML html contents
   * @returns {String} Charset for the HTML
   */
  MailParser.prototype._detectHTMLCharset = function(html){
      var charset, input, meta;
      
      if(typeof html !=" string"){
          html = html.toString("ascii");
      }
      
      if((meta = html.match(/<meta\s+http-equiv=["']content-type["'][^>]*?>/i))){
          input = meta[0];
      }
  
      if(input){
          charset = input.match(/charset\s?=\s?([a-zA-Z\-_:0-9]*);?/);
          if(charset){
              charset = (charset[1] || "").trim().toLowerCase();
          }
      }
  
      if(!charset && (meta = html.match(/<meta\s+charset=["']([^'"<\/]*?)["']/i))){
          charset = (meta[1] || "").trim().toLowerCase();
      }
  
      return charset;
  };