Google App Engine for Javaで
メールを受信する処理を作ってたら、
quoted-printableエンコーディングされたメール本文をデコードすると、デコード結果が途中で切れてしまう現象に遭遇した。調べてみると、quoted-printableエンコーディングでいうところの「Soft Line Breaks」がうまく認識されないことが原因だったので対策してみた。
まず、「Soft Line Breaks」については、RFCのquoted-printableエンコーディング規則の中で以下のように説明されている。要は、長すぎる行には適宜「=」+「改行」を入れて分割する、というエンコーディングが行われるとのこと。
Rule #5 (Soft Line Breaks):
The Quoted-Printable encoding REQUIRES that encoded lines be no more than 76 characters long. If longer lines are to be encoded with the Quoted-Printable encoding, 'soft' line breaks must be used. An equal sign as the last character on a encoded line indicates such a non-significant ('soft') line break in the encoded text.
RFC 1521: MIME Part One
RFCにはこう書いてあるのに、試してみると、
JavaMail 1.4の
MimeUtility.decode()
でも、
Commons Codec 1.4の
QuotedPrintableCodec.decode()
でも、Soft Line Breaksが正しく処理されず、最初のSoft Line Breakのところまででデコード結果が切れてしまう。で、なんでかなぁと思って
QuotedPrintableCodec
のドキュメントをちゃんと読んでみると、きちんと明記されていた。
Note:
Rules #3, #4, and #5 of the quoted-printable spec are not implemented yet because the complete quoted-printable spec does not lend itself well into the byte[] oriented codec framework.
QuotedPrintableCodec (Commons Codec 1.4 API)
というわけで、自分でSoft Line Breaksを取り除いてやる必要がある。
MimeMessage
から取り出した
MimeBodyPart
をデコードする処理は、例えばこんな感じ。効率の悪いコードだけど、今のところ問題なく動いているようだ。
private String _decodeBody(MimeBodyPart bp)
{
// parse header
String contentType = null;
String contentEncoding = null;
String charset = null;
try{
contentType = bp.getContentType();
contentEncoding = bp.getEncoding();
}catch(MessagingException e){
}
String[] elems = contentType.split(";");
for(String elem : elems){
if(elem.trim().startsWith("charset=")){
charset = elem.trim().substring("charset=".length());
}
}
if(charset!=null){
if(charset.startsWith("\"")) charset = charset.substring(1);
if(charset.endsWith("\"")) charset = charset.substring(0, charset.length()-1);
}
// get inputstream
InputStream in = null;
try{
in = bp.getRawInputStream();
}catch(MessagingException e){
}
if(in==null) return "";
// convert quoted-printable
if(contentEncoding!=null && contentEncoding.equals("quoted-printable")){
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int len;
byte[] buffer = new byte[1024];
try{
while( (len=in.read(buffer, 0, buffer.length)) != -1 ){
baos.write(buffer, 0, len);
}
}catch(IOException e){
}
byte[] b = baos.toByteArray();
baos = new ByteArrayOutputStream();
for(int j=0;j<b.length;j++){
if(b[j]=='=' && j<b.length-1 && b[j+1]=='\n'){
j++;
}else{
baos.write(b[j]);
}
}
b = baos.toByteArray();
in = new ByteArrayInputStream(b);
}
// decode
if(in!=null && contentEncoding!=null){
try{
in = MimeUtility.decode(in, contentEncoding);
}catch(MessagingException e){
}
}
if(in==null) return "";
// read body
Reader r = null;
if(charset!=null){
try{
r = new InputStreamReader(in, charset);
}catch(UnsupportedEncodingException e){
}
}else{
r = new InputStreamReader(in);
}
StringBuffer sb = new StringBuffer();
BufferedReader br = null;
try{
br = new BufferedReader(r);
String line = null;
while( (line=br.readLine())!=null ){
sb.append(line.trim());
sb.append("\n");
}
}catch(IOException e){
}finally{
if(br!=null){
try{
br.close();
}catch(IOException e){}
}
}
return sb.toString();
}