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