bug180299 Support 'In-Reply-To: Your message of "Date" <Message-ID>

TOP | 戻る

Bug ID
(本家/日本)
 Plan  Summary/Comment
180299/
2814
- ★★★Support 'In-Reply-To: Your message of "Date" <Message-ID>'
In-Reply-To: Your message of "Date" <Message-ID>の形式(obs-in-reply-to)のときに親メッセージを認識できない
In-Reply-Toによる検索結果
/mailnews/compose/src/nsMsgCompUtils.cpp, line 682
 -- PUSH_STRING ("In-Reply-To: ");
/mailnews/imap/src/nsImapServerResponseParser.cpp, line 1332
 -- {"In-reply-to", envelopeString},
/mailnews/imap/src/nsImapServerResponseParser.cpp, line 1339
 -- // reply-to, to, cc, bcc, in-reply-to, and message-id.
/mailnews/imap/src/nsImapServerResponseParser.cpp, line 1340
 -- // The date, subject, in-reply-to, and message-id
/mailnews/imap/src/nsImapProtocol.cpp, line 122
 -- #define IMAP_DB_HEADERS "Priority X-Priority References Newsgroups In-Reply-To"
/mailnews/local/src/nsParseMailbox.cpp, line 908
 -- if (!nsCRT::strncasecmp ("In-Reply-To", buf, end - buf))
/mailnews/local/src/nsParseMailbox.cpp, line 1383
 -- // use in-reply-to header as references, if there's no references header
mozilla/ mailnews/ local/ src/ nsParseMailbox.cpp 
1383         // use in-reply-to header as references, if there's no references header
1384         if (references != nsnull)
1385           m_newMsgHdr->SetReferences(references->value);
1386         else if (inReplyTo != nsnull)
1387           m_newMsgHdr->SetReferences(inReplyTo->value);

ここが親メッセージのIDを得る部分と思われる。まずReferencesヘッダからIDを取得し、無ければIn-Reply-Toヘッダから取得する。

mozilla/ mailnews/ db/ msgdb/ src/ nsMsgHdr.cpp 
389 NS_IMETHODIMP nsMsgHdr::SetReferences(const char *references)
390 {
391   if (*references == '\0') {
392     m_numReferences = 0;
393   }
394   else {
395     ParseReferences(references);
396   }
397     
398   SetUInt32Column(m_numReferences, m_mdb->m_numReferencesColumnToken);
399   m_initedValues |= REFERENCES_INITED;
400     
401   return SetStringColumn(references, m_mdb->m_referencesColumnToken);
402 }
323 nsresult nsMsgHdr::ParseReferences(const char *references)
324 {
325     const char *startNextRef = references;
326     nsCAutoString resultReference;
327 
328     while (startNextRef && *startNextRef)
329     {
330         startNextRef = GetNextReference(startNextRef, resultReference);
331         m_references.AppendCString(resultReference);
332     }
333   m_numReferences = m_references.Count();
334     return NS_OK;
335 }
 74 typedef struct message_header
 75 {
 76   const char *value; /* The contents of a header (after ": ") */
 77   PRInt32 length;      /* The length of the data (it is not NULL-terminated.) */
 78 } message_header;

118     struct message_header m_references;
739 const char *nsMsgHdr::GetNextReference(const char *startNextRef, nsCString &reference)
740 {
741   const char *ptr = startNextRef;
742   
743   reference.Truncate(0);
744   while ((*ptr == '<' || *ptr == ' ' || *ptr == nsCRT::CR || *ptr == nsCRT::LF || *ptr == '\t') && *ptr)
745     ptr++;
746   
747   for (int i = 0; *ptr && *ptr != '>'; i++)
748     reference += *ptr++;
749   
750   if (*ptr == '>')
751     ptr++;
752   return ptr;
753 }

この関数は「Bug 2804 References:ヘッダの区切りにTABが含まれているとスレッドの繋がりが正しくならない」でも問題になった部分だ。
ここでは'<'、スペース、CR、LF、TABを読み飛ばし、'>'までの文字列を取得している。これを、'<'までを読み飛ばし、'>'までの文字列を取得するようにすればよいと思う。

744   while (*ptr!='<' && *ptr) ptr++;
745   if (*ptr) ptr++;  //skip '<'

↑こんな感じ。厳密にはRFC2822のobs-in-reply-toで許されるphraseの中にクオートした'<'が許されるのであればその処理をちゃんとする必要がある。

RFC2822ではIn-Reply-ToとReferencesは以下のように定義されている。

in-reply-to     := "In-Reply-To:" 1*msg-id CRLF
references      := "References:"  1*msg-id CRLF
obs-in-reply-to := "In-Reply-To" *WSP ":" *(phrase / msg-id) CRLF
obs-references  := "References"  *WSP ":" *(phrase / msg-id) CRLF

ここで、問題になるのはphraseにどのような文字列が許されるかだ。

phrase   := 1*word / obs-phrase
word     := atom / quoted-string
atom     := [CFWS] 1*atext [CFWS]
atext    := ALPHA / DIGIT / "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" 
             / "/" / "=" / "?" "^" / "_" / "`" / "{" / "|" / "}" / "~"
         ; Any character except controls, SP, and specials. Used for atoms
quoted-string := [CFWS] DQUOTE *([FWS] qcontent) [FWS] DQUOTE [CFWS]
qcontent := qtext / quoted-pair
qtext    := NO-WS-CTL / %d33 / %d35-91 / %d93-126
          ; Non white space controls and The rest of the US-ASCII characters
            not including "\" or the quote character
quoted-pair := ("\" text) / obs-qp
obs-qp    := "\" (%d0-127)
obs-phrase := word *(word / "." / CFWS)
text      := %d1-9 / %d11 / %d12 / %d14-127 / obs-text
           ; Characters excluding CR and LF

ASCIIコード表: '<' = %d60, '>' = %d62

上の定義によると
phrase->word->quoted-string->"qcontent"
であるので、'<'がphraseの中に現れるのは、"<foo>"のようなケースだ。従って"..."の中を無条件にスキップし、かつ\"をも考慮(例:"<foo> \"bar\"")すると以下のようなコードで良さそうだ。

   while (*ptr && *ptr != '<') {     //skip phrase
      if (*ptr == '"') {             //skip quoted-string
         ptr++;
         while (*ptr && *ptr != '"') {
            if (*ptr == '\\' && *(ptr+1)) ptr += 2;  //skip quoted-pair
            else ptr++;
         }
         if (*ptr) ptr++;  //skip '"'
      } else ptr++;
   }
   if (*ptr) ptr++;  //skip '<'

上のコードだと、Message-IDの後ろにゴミがあるとうまくいかないようだ。例えば"<foo@bar.com> baz"。文字列の最後がちょうどMessage-IDで終わっている場合、それ以上この関数はコールされないが、後ろにゴミがついているとこの関数がもう一度呼ばれ、reference.Truncate(0)が実行されてしまうのが良くないようだ。ということで以下のように修正してみたら、これで期待通りの動作をするようになった。
ところで、C++素人なのでreference.Truncate(0)の動作がわからない。

このパッチにより、In-Reply-Toヘッダと、ReferencesヘッダからMessage-IDを抽出する際に、<Message-ID>以外の部分はすべて無視される。これにより、Referenceヘッダを間違って','区切りにしているケースでも正しく親メッセージを認識できるようになる。
Thread構築の際のIn-Reply-ToヘッダとReferencesヘッダはまったく同じように扱われるようなので(両方ある場合はReferences優先)、In-Reply-Toに複数のMessage-IDがセットされている場合、右端のIDが親のIDとなる。

Index: mailnews/db/msgdb/src/nsMsgHdr.cpp
===================================================================
RCS file: /cvsroot/mozilla/mailnews/db/msgdb/src/nsMsgHdr.cpp,v
retrieving revision 1.105
diff -u -r1.105 nsMsgHdr.cpp
--- mailnews/db/msgdb/src/nsMsgHdr.cpp  12 Nov 2002 19:22:09 -0000      1.105
+++ mailnews/db/msgdb/src/nsMsgHdr.cpp  1 Dec 2002 13:03:49 -0000
@@ -735,20 +735,36 @@
   return m_mdb->RowCellColumnToUInt32(GetMDBRow(), token, pvalue, defaultValue);
 }
 
-// get the next <> delimited reference from nextRef and copy it into reference,
+// get the next <> delimited reference from nextRef and copy it into reference.
+// Called for parsing both References and In-Reply-To headers.
+// Now, any characters outside of <> are ignored.
 const char *nsMsgHdr::GetNextReference(const char *startNextRef, nsCString &reference)
 {
   const char *ptr = startNextRef;
   
-  reference.Truncate(0);
-  while ((*ptr == '<' || *ptr == ' ' || *ptr == nsCRT::CR || *ptr == nsCRT::LF || *ptr == '\t') && *ptr)
-    ptr++;
-  
-  for (int i = 0; *ptr && *ptr != '>'; i++)
-    reference += *ptr++;
-  
-  if (*ptr == '>')
-    ptr++;
+  while (*ptr && *ptr != '<') //skip phrase
+  {
+      if (*ptr == '"')        //skip quoted-string
+      {
+          ptr++;
+          while (*ptr && *ptr != '"')
+          {
+              if (*ptr == '\\' && *(ptr+1)) ptr += 2;  //skip quoted-pair
+              else ptr++;
+          }
+          if (*ptr) ptr++;    //skip '"'
+      } else ptr++;
+  }
+  if (*ptr)
+  {
+      ptr++;                  //skip '<'
+      reference.Truncate(0);  //execute only if message-id exists
+  }
+
+  while (*ptr && *ptr != '>') reference += *ptr++;
+
+  if (*ptr) ptr++;            //skip '>'
+
   return ptr;
 }
 // Get previous <> delimited reference - used to go backwards through the