tmail/tmail/mails.rb

#
# mails.rb
#
#   Copyright (c) 1999 Minero Aoki <aamine@dp.u-netsurf.ne.jp>
#
#   This program is free software.
#   You can distribute/modify this program under the terms of
#   the GNU Lesser General Public License version 2 or later.
#

require 'racc/scanner'

begin
  require 'tmail/mails.so'
rescue LoadError


module TMail


class MailScanner < Racc::Scanner

  # mode

  MODE_NONE = :MODE_NONE

  MODE_822  = :MODE_822
  MODE_MIME = :MODE_MIME

  MODE_RECV = :MODE_RECV


  def initialize( str, header, comments )
    super( str )
    @header = header
    @comments = comments

    @m_atom = :MODE_822
    @m_recv = :MODE_NONE

    case header
    when 'CTypeH', 'CEncodingH', 'CDispositionH'
      @m_atom = :MODE_MIME
    when 'RecvH'
      @m_recv = :MODE_RECV
    end
  end


  # exp

  atomchars  = Regexp.quote( "\#!$%&`'*+{|}~^/=?" ) + '\\-'
  tokenchars = Regexp.quote( "\#!$%&`'*+{|}~^." )   + '\\-'
  eucchars   = "\xa1\xa1-\xf3\xfe"
  jisstr     = "\\e..[^\\e]*\\e.."

  ATOM    = /\A[\w#{atomchars}#{eucchars}]+|#{jisstr}/o
  TOKEN   = /\A[\w#{tokenchars}#{eucchars}]+|#{jisstr}/o
  DIGIT   = /\A\d+\z/o

  LWSP    = /\A(?:\n|\r\n|\r)?[ \t]+/o

  BACKSLASH = /\A\\/o

  BEGIN_Q = /\A"/o
  Q_ENT   = /\A[^"\\\e]+|#{jisstr}/o
  END_Q   = /\A"/o

  BEGIN_C = /\A\(/o
  C_ENT   = /\A[^\)\(\\\e]+|#{jisstr}/o
  END_C   = /\A\)/o

  BEGIN_D = /\A\[/o
  D_ENT   = /\A[^\]\\]+|#{jisstr}/o
  END_D   = /\A\]/o


  OMIT = '$omit'

  def scan( ret )
    sret = nil
    vret = OMIT

    until sret do
      unless @scan.rest? then
        sret = false; vret = false
        break
      end

      @scan.skip LWSP

      if @m_atom == :MODE_822 then
        if vret = @scan.scan( ATOM ) then
          sret = :ATOM

          if DIGIT === vret then
            sret = :DIGIT
            vret = vret

          elsif @m_recv = :MODE_RECV then
            case vret.downcase
            when 'from' then sret = :FROM
            when 'by'   then sret = :BY
            when 'via'  then sret = :VIA
            when 'with' then sret = :WITH
            when 'id'   then sret = :ID
            when 'for'  then sret = :FOR
            end
          end
          break
        end

      elsif @m_atom == :MODE_MIME then
        if vret = @scan.scan( TOKEN ) then
          sret = :TOKEN
          break
        end
      else
        bug! 'atom mode is not 822/MIME'
      end

      if @scan.skip( BEGIN_Q ) then
        sret = :QUOTED
        vret = quoted

      elsif @scan.skip( BEGIN_C ) then
        @comments.push comment if @comments

      elsif @scan.skip( BEGIN_D ) then
        sret = :DOMLIT
        vret = domlit

      else
        sret = vret = @scan.getch
      end
    end

    ret[0] = sret
    ret[1] = vret
    debug_report( ret ) if @debug

    ret
  end



  private


  def quoted
    ret = ''
    while true do
      if    temp = @scan.scan( Q_ENT ) then ret << temp
      elsif @scan.skip( END_Q )        then break
      elsif @scan.skip( BACKSLASH )    then ret << @scan.getch
      else
        unless @scan.rest? then
          scan_error! "found unterminated quoted-string"
        end
        bug! 'in quoted, no match'
      end
    end

    ret
  end

  
  def comment
    ret = ''
    nest = 1

    while nest > 0 and @scan.rest? do
      if    temp = @scan.scan( C_ENT ) then ret << temp
      elsif @scan.skip( END_C )        then nest -= 1
      elsif @scan.skip( BEGIN_C )      then nest += 1
      elsif @scan.skip( BACKSLASH )    then ret << @scan.getch
      else
        unless @scan.rest? then
          scan_error! "found unterminated comment"
        end
        bug! 'in comment, no match'
      end
    end
    if nest > 0 then
      scan_error! "found unterminated comment"
    end

    ret
  end

  
  def domlit
    ret = ''

    while true do
      if temp = @scan.scan( D_ENT ) then
        ret << temp
      end
      if    @scan.skip( END_D )     then break
      elsif @scan.skip( BACKSLASH ) then ret << @scan.getch
      else
        unless @scan.rest? then
          scan_error! "found unterminated domain literal"
        end
        bug! 'in domlit, no match'
      end
    end

    ret
  end

end

end   # module TMail

end   # mails.so not exist