2018年以降の記事はGitHub Pagesに移行しました

.msgファイルをパースして中から添付ファイルを抜き出す

正確にはRubyというには微妙なんですが…。

前回までのあらすじ

しかし、社内セキュリティGWの設定? のためかzipとかexeとか怪しい拡張子が付いている添付ファイルは一旦msgファイルに固められるという仕様のためそのままでは添付ファイルが見れない。

こういうメールが

メール
┗添付ファイル.zip

届いた時にはこうなっている

メール
┗ラップ.msg(\あやしいファイルだから注意してDLしてね/)
 ┗本来の添付ファイル.zip

なんとかRubyからmsgファイル内を解析して添付ファイルを引っこ抜いて保存ができまいか…というのが今回の問題。

結論

以下のライブラリを駆使して実現しました。

  • msgparser(Auxiliiという所のJava用msgファイルパースライブラリ)
  • jrb(Java Ruby Bridge: RubyからJavaVMを操作できるライブラリ)

一応Rubyでmsgファイルを解析するライブラリも探してみたのですが、あまりないのかな? Downloading File SSFileDLL-MAPI-1.0-Source.zip - Outlook msg file parser DLL - SourceForge.JP こういうdllは見つけたのですが、MAPIがよくわからん上に説明を見る限りHasAttach(添付ファイルを持ってるか持ってないか?)くらいしかわからない? ようなのであきらめました。

あと、msgparserはJavaライブラリなので当初はJRubyで書けば何とかなるかなと思ってました。簡単なサンプルは Read an Outlook MSG file - Real's Java How-to このページのような感じ。が、今まで書いたコードをJRubyで実行すると失敗してしまったので方向転換;; RubyからJavaVMを操作できるrjbというライブラリを使う事に。

ダウンロード・インストール

gem install rjb

ソース

ソース全体は gosyujin/outlook_for_ruby · GitHub 。msgParse.rbが本体、ライブラリがlibに入っています。

使い方としてはMsgParseをnewしてinputMsgで.msgファイルのパス指定、saveFileで添付ファイルぶっこぬいて出力先に保存としたい。こんな感じで。

    msg = MsgParse.new
    msg.inputMsg(MSGFILE)
    msg.saveFile(SAVEDIR)

という事でMsgParser*2クラスを作成。ソースは outlook_for_ruby/msgParse.rb at master · gosyujin/outlook_for_ruby · GitHub 。まずは初期化から。

    include Rjb
    
    # JavaクラスのImport、Jarの読み込み
    # 初期化処理を行う
    def initialize
        #Rjb::load('./')
        initJavaClass()
        addJar()
        @msg = nil
    end
    
    # JavaのクラスをImportする
    def initJavaClass()
        @system = import("java.lang.System")
#        @string = import("java.lang.String")
#        @list = import("java.util.List")
        @fileOutputStream = import("java.io.FileOutputStream")
    end
    
    # Jarを読みこむ
    def addJar()
        Rjb::add_jar(File.expand_path('lib/tnef-1.3.1.jar'))
        Rjb::add_jar(File.expand_path('lib/poi-3.2-FINAL-20081019.jar'))
        Rjb::add_jar(File.expand_path('lib/msgparser-1.10.jar'))
        @msgParser = import("com.auxilii.msgparser.MsgParser")
        @fileAttachment = import("com.auxilii.msgparser.attachment.FileAttachment")
    end

java.lang.Systemとかjava.lang.Stringとか使う予定のあるクラスは全て明示的にimportしてやる必要がある。今回はjava.lang.Systemとjava.io.FileOutputStreamを使用する。
次にライブラリを読み込む。msgparserをDLしたときのtnef, poi, msgparserを追加する。追加後にcom.auxilii.msgparser.MsgParserとcom.auxilii.msgparser.attachment.FileAttachmentをimportする。※add_jarの順番でエラーになる。-classpathの記述? に順番って関係あるんだっけ?
これで今回使用したいクラスはRuby上から呼び出せるようになりました。次はファイル読み込み。

    # .msgファイルを読みこむ
    def inputMsg(path)
        @msg = @msgParser.new.parseMsg(path)
    end

parseMsg(PATH)メソッドを使用し.msgファイルを読み込めるように。最後は.msgファイル内から添付ファイルをぶっこぬく!

    # .msgファイルの添付ファイル数をカウントする
    def getAttachmentSize()
        @msg.getAttachments.size
    end
    
    # 添付ファイルをpathに保存する
    # 返り値は保存した添付ファイル名(の一つ)
    def saveFile(path)
        fileName = ""
        if getAttachmentSize() != 0 then
            for i in 0..getAttachmentSize - 1
                file = @msg.getAttachments.get(i)
                begin
                    fileName = file.getLongFilename
                rescue => ex
                    puts "File name is including WAVE DASH?:#{ex}"
                    fileName = file.getFilename
                end
                out = @fileOutputStream.new(path + fileName)
                out.write(file.getData)
                puts "■.msgファイル抽出:#{fileName}"
                out.close
            end
        else
            puts "no temp file."
        end
        return fileName
    end
end

はじめにgetAttachments.get(i)で添付ファイルを取得します。次に保存する時のファイル名として添付ファイル名を取得します。ファイル名を取得できるメソッドは2種類あります。getLongFilenameはファイル名をそのまま取得でき、getFilenameはファイル名を短縮して取得できます。*3

ここまでやったら最後はFileOutputStream#writeにgetDataで取得した添付ファイルのバイト配列を渡してやればOK!

*1:complete package including source code, binaries and third party librariesと書いてあった

*2:ライブラリと同じ名前にしてしまった…。

*3:TODOとして、添付ファイル名に波ダッシュが入っているとエラーになるので要調査…。