[sylpheed-jp:10706] Re: format=flowedへの対応

Taku Amano <[email protected]>
Newsgroups gmane.mail.sylpheed.general.japanese
Message-ID <[email protected]>
天野です。

ありがとうございます。

> サイズがたいしたことなければ、この ML にでも投げてみてください。
「添付ファイル: text_format_flowed.diff」になります。

できていることは、今朝のメールの繰り返しになりますが、
・format=flowed のメールを解釈して表示すること
になります。
・format=flowed としてメールを送信すること
はできません。


以下、簡単な仕様と、検討が必要になりそうな部分をまとめます。

●仕様について
http://www.ietf.org/rfc/rfc3676.txt
http://suika.fam.cx/~wakaba/-temp/wiki/wiki?RFC%203676
RFC3676での章番号と対応させて並べています。

4. The Format and DelSp Parameters
・Content-Type が text/plain の時のみ、format= と delsp= を調べます。
 text/plain 以外の場合には無視されます。

4.1. Interpreting Format=Flowed
・Content-Type で format=flowed が指定された場合には、
 "{空白文字}{改行}"で終わる行を flowed line と解釈し、
 {改行}を削除して次の行と連結します。
・delsp=yes が指定された場合はさらに{空白文字}も削除します。

4.3. Usenet Signature Convention
・署名行は"-- "なので、"{空白文字}{改行}"で終わりますが、次の行とは連結しません。
・RFCでは以下のようになっており、
 「引用符や空白文字削除前と削除後の両方で検査をする(both)」とありますが、
 削除前に署名行であった行が、削除後に署名行でなくなることはないため、
 署名行であるかどうかの検査は「削除後」の一度のみ検査しています。

 > A receiving agent needs to test for a signature line both before the
 > test for a quoted line (see Section 4.5) and also after logically
 > counting and deleting quote marks and stuffing (see Section 4.4) from
 > a quoted line.

 Thunderbird のソースでも検査は一度のようだったので、誤りではないと思うのですが、
 もしも誤っているようでしたらどなたか指摘を頂ければありがたいです。

4.4. Space-Stuffing
・行頭の空白文字は削除されます。

4.5. Quoting
・引用文が flowed line である場合は、続く行の引用符は削除された上で連結されます。
・引用の深さが変わった場合は、続く行は連結されません。
 (不正なフォーマットのメールへの対応)

その他.
・詳細な状況は未確認なのですが、Outlookで「返信」からメールを作成した場合、
 Content-Type: text/plain; format=flowed
 であるにも関わらず、返信元メッセージの前に、
 "----- Original Message ----- {改行}"
 という行が入るようです。

 RFCをそのまま適用するのであれば、次の行と連携するようになりますが、
 "----- Original Message ----- {改行}"だけを特別扱いし、
 この前後では必ず出力するという対応もありかもしれないと考えています。

 パッチには特別扱いするコードが入っています。


●コードについて
コーディングの際に検討した事項です。

・format= と delsp= の値を取得する位置について

まず最初に、以下の関数内での取得を検討したのですが、
procmime_scan_content_type_str()
ここで取得をするとなるとインターフェイスの変更が必要になるので避けました。

また次に、以下の関数内での取得を考えたのですが、
procmime_scan_content_type()
MimeParamよりも上で定義されている関数であり、
procmime_parse_mime_parameter()
を利用することができなかったので、ここでの取得も避けました。

そのような経緯があり、以下の関数内で、
procmime_scan_mime_header()
Content-Type が text であった場合にのみ、
format= と delsp= の値を調べるというようにしています。

この部分で、私としては以下の2点が気になっています。
- procmime_scan_mime_header() での取得で問題がないか。
 (取得されるべきコンテキストで取得されないというケースが発生するか)
- procmime_parse_mime_parameter() を2回呼んでいる。

検討した項目は以上です。


よろしくお願いいたします。

-- 
Name: Taku Amano
Mail: [email protected]

_______________________________________________
Sylpheed-jp mailing list
[email protected]
http://www.sraoss.jp/mailman/listinfo/sylpheed-jp
text_format_flowed.diff (text/x-diff, 9.3 KB)
Index: libsylph/procmime.c
===================================================================
--- libsylph/procmime.c	(リビジョン 2061)
+++ libsylph/procmime.c	(作業コピー)
@@ -45,7 +45,36 @@
 static GHashTable *procmime_get_mime_type_table	(void);
 static GList *procmime_get_mime_type_list	(const gchar *file);
 
+static gint procmime_line_quote_level(gchar *line);
+static gchar *procmime_trim_line_quote(gchar *line);
+static gchar *procmime_trim_space_stuffing(gchar *line);
+static gboolean procmime_line_cmp(gchar *line, gchar *str);
+static void procmime_normalize_lbreak_fputs(gchar *buf,
+						FILE *outfp,
+						gboolean is_normalize_lbreak);
 
+typedef struct
+{
+	gchar *line_buf;
+	gboolean is_flowed;
+	gboolean is_delsp;
+	gint buff_size;
+	gint current_quote_level;
+} ProcmimeDCParam;
+
+static ProcmimeDCParam *procmime_dc_params_new(ContentType content_type,
+						MimeInfo *mimeinfo,
+						gsize buff_size);
+static void procmime_dc_params_free(ProcmimeDCParam *param);
+static void procmime_dc_process_line(ProcmimeDCParam *param,
+						gchar *buf,
+						FILE *outfp,
+						gboolean is_normalize_lbreak);
+static void procmime_dc_post_process_lines(ProcmimeDCParam *param,
+						gchar *buf,
+						FILE *outfp,
+						gboolean is_normalize_lbreak);
+
 MimeInfo *procmime_mimeinfo_new(void)
 {
 	MimeInfo *mimeinfo;
@@ -67,6 +96,8 @@
 		g_free(mimeinfo->charset);
 		g_free(mimeinfo->name);
 		g_free(mimeinfo->boundary);
+		g_free(mimeinfo->format);
+		g_free(mimeinfo->delsp);
 		g_free(mimeinfo->content_disposition);
 		g_free(mimeinfo->filename);
 
@@ -379,10 +410,14 @@
 	g_free(mimeinfo->charset);
 	g_free(mimeinfo->name);
 	g_free(mimeinfo->boundary);
+	g_free(mimeinfo->format);
+	g_free(mimeinfo->delsp);
 	mimeinfo->content_type = NULL;
 	mimeinfo->charset      = NULL;
 	mimeinfo->name         = NULL;
 	mimeinfo->boundary     = NULL;
+	mimeinfo->format       = NULL;
+	mimeinfo->delsp        = NULL;
 
 	procmime_scan_content_type_str(content_type, &mimeinfo->content_type,
 				       &mimeinfo->charset, &mimeinfo->name,
@@ -775,6 +810,25 @@
 		} else if (H_CONTENT_TYPE == hnum) {
 			procmime_scan_content_type
 				(mimeinfo, buf + strlen(hp->name));
+
+			if (mimeinfo->mime_type == MIME_TEXT) {
+				GSList *cur;
+				MimeParams *mparams;
+
+				mparams =
+					procmime_parse_mime_parameter(buf + strlen(hp->name));
+
+				for (cur = mparams->plist; cur != NULL; cur = cur->next) {
+					MimeParam *param = (MimeParam *)cur->data;
+					if (!g_ascii_strcasecmp(param->name, "format")) {
+						mimeinfo->format = g_strdup(param->value);
+					} else if (!g_ascii_strcasecmp(param->name, "delsp")) {
+						mimeinfo->delsp = g_strdup(param->value);
+					}
+				}
+
+				procmime_mime_params_free(mparams);
+			}
 		} else if (H_CONTENT_DISPOSITION == hnum) {
 			procmime_scan_content_disposition
 				(mimeinfo, buf + strlen(hp->name));
@@ -797,6 +851,193 @@
 	return mimeinfo;
 }
 
+static gint procmime_line_quote_level(gchar *line)
+{
+	gchar *orig = line;
+	while (*line == '>')
+		line++;
+
+	return line - orig;
+}
+
+static gchar *procmime_trim_line_quote(gchar *line)
+{
+	while (*line == '>')
+		line++;
+
+	return line;
+}
+
+static gchar *procmime_trim_space_stuffing(gchar *line)
+{
+	return *line == ' ' ? line+1 : line;
+}
+
+static gboolean procmime_line_cmp(gchar *line, gchar *str)
+{
+	gsize len = strlen(line);
+	while ((line[len-1] == '\r') || (line[len-1] == '\n'))
+		len--;
+	return (len == strlen(str)) && (! g_ascii_strncasecmp(line, str, len));
+}
+
+static void procmime_normalize_lbreak_fputs(gchar *buf,
+						FILE *outfp,
+						gboolean is_normalize_lbreak)
+{
+	if (is_normalize_lbreak) {
+#ifdef G_OS_WIN32
+		strretchomp(buf);
+		fputs(buf, outfp);
+		fputs("\r\n", outfp);
+#else
+		strcrchomp(buf);
+		fputs(buf, outfp);
+#endif
+	} else {
+		fputs(buf, outfp);
+	}
+}
+
+static ProcmimeDCParam *procmime_dc_params_new(ContentType content_type,
+						MimeInfo *mimeinfo,
+						gsize buff_size)
+{
+	ProcmimeDCParam *param = g_new0(ProcmimeDCParam, 1);
+	param->line_buf = g_new0(gchar, buff_size*2);
+
+	param->is_flowed =
+		content_type == MIME_TEXT
+		&& mimeinfo->format
+		&& ! g_strcasecmp(mimeinfo->format, "flowed");
+	param->is_delsp =
+		content_type == MIME_TEXT
+		&& mimeinfo->delsp
+		&& ! g_strcasecmp(mimeinfo->delsp, "yes");
+
+	param->buff_size = buff_size;
+	param->current_quote_level = 0;
+
+	return param;
+}
+
+static void procmime_dc_params_free(ProcmimeDCParam *param)
+{
+	g_free(param->line_buf);
+	g_free(param);
+}
+
+static void procmime_dc_process_line(ProcmimeDCParam *param,
+						gchar *buf,
+						FILE *outfp,
+						gboolean is_normalize_lbreak)
+{
+	gint n = 0;
+	gint len = 0;
+	gchar *trimed_buf = NULL;
+	gchar *out_buf = NULL;
+	gboolean force_output = FALSE;
+
+
+	if (! param->is_flowed) {
+		procmime_normalize_lbreak_fputs(buf, outfp, is_normalize_lbreak);
+		return;
+	}
+
+	/* remove quote */
+	trimed_buf = procmime_trim_line_quote(buf);
+
+	/* remove Space-Stuffing */
+	buf = procmime_trim_space_stuffing(buf);
+	trimed_buf = procmime_trim_space_stuffing(trimed_buf);
+
+	force_output = procmime_line_cmp(trimed_buf, "-- ");
+	if (TRUE) {
+		/* to quotation message that the Outlook generates */
+		force_output =
+			force_output
+			|| procmime_line_cmp(trimed_buf, "----- Original Message ----- ");
+	}
+
+	if (param->line_buf[0] != '\0') {
+		/* buffered line exists */
+		gint level = procmime_line_quote_level(buf);
+		if (
+				force_output
+				|| (param->current_quote_level != level)
+		   ) {
+			procmime_normalize_lbreak_fputs
+				(param->line_buf, outfp, is_normalize_lbreak);
+			param->line_buf[0] = '\0';
+			param->current_quote_level = 0;
+		}
+	}
+
+	if (force_output) {
+		/* do nothing */
+		;
+	} else if (
+			g_str_has_suffix(buf, " \r")
+			|| g_str_has_suffix(buf, " \n")
+			) {
+		n = 1;
+	} else if (g_str_has_suffix(buf, " \r\n")) {
+		n = 2;
+	}
+
+	if (n || param->line_buf[0] != '\0') {
+		if (param->line_buf[0] == '\0') {
+			param->current_quote_level =
+				procmime_line_quote_level(buf);
+		} else {
+			buf = trimed_buf;
+		}
+
+		len = g_strlcat(param->line_buf, buf, param->buff_size*2);
+	}
+
+	if (n) {
+		/* flowed line */
+
+		if (param->is_delsp) {
+			n++;
+		}
+
+		len -= n;
+		param->line_buf[len] = '\0';
+
+		if (len > param->buff_size) {
+			/* output to avoid buffer over flow */
+			out_buf = param->line_buf;
+		}
+		else {
+			/* get next line */
+			return;
+		}
+	} else if (param->line_buf[0] != '\0') {
+		/* do output concatenated lines */
+		out_buf = param->line_buf;
+	} else {
+		out_buf = buf;
+	}
+
+	procmime_normalize_lbreak_fputs(out_buf, outfp, is_normalize_lbreak);
+
+	param->line_buf[0] = '\0';
+	param->current_quote_level = 0;
+}
+
+static void procmime_dc_post_process_lines(ProcmimeDCParam *param,
+						gchar *buf,
+						FILE *outfp,
+						gboolean is_normalize_lbreak)
+{
+	if (param->line_buf[0] != '\0') {
+		procmime_normalize_lbreak_fputs
+			(param->line_buf, outfp, is_normalize_lbreak);
+	}
+}
+
 FILE *procmime_decode_content(FILE *outfp, FILE *infp, MimeInfo *mimeinfo)
 {
 	gchar buf[BUFFSIZE];
@@ -805,6 +1046,7 @@
 	gboolean tmp_file = FALSE;
 	gboolean normalize_lbreak = FALSE;
 	ContentType content_type;
+	ProcmimeDCParam *param;
 
 	g_return_val_if_fail(infp != NULL, NULL);
 	g_return_val_if_fail(mimeinfo != NULL, NULL);
@@ -829,6 +1071,8 @@
 		normalize_lbreak = TRUE;
 	}
 
+	param = procmime_dc_params_new(content_type, mimeinfo, BUFFSIZE);
+
 	if (mimeinfo->encoding_type == ENC_QUOTED_PRINTABLE) {
 		FILE *tmpfp = outfp;
 
@@ -858,15 +1102,12 @@
 			}
 			rewind(tmpfp);
 			while (fgets(buf, sizeof(buf), tmpfp) != NULL) {
-#ifdef G_OS_WIN32
-				strretchomp(buf);
-				fputs(buf, outfp);
-				fputs("\r\n", outfp);
-#else
-				strcrchomp(buf);
-				fputs(buf, outfp);
-#endif
+				procmime_dc_process_line
+					(param, buf, outfp, normalize_lbreak);
 			}
+			procmime_dc_post_process_lines
+				(param, buf, outfp, normalize_lbreak);
+
 			fclose(tmpfp);
 		}
 	} else if (mimeinfo->encoding_type == ENC_BASE64) {
@@ -907,15 +1148,12 @@
 			}
 			rewind(tmpfp);
 			while (fgets(buf, sizeof(buf), tmpfp) != NULL) {
-#ifdef G_OS_WIN32
-				strretchomp(buf);
-				fputs(buf, outfp);
-				fputs("\r\n", outfp);
-#else
-				strcrchomp(buf);
-				fputs(buf, outfp);
-#endif
+				procmime_dc_process_line
+					(param, buf, outfp, normalize_lbreak);
 			}
+			procmime_dc_post_process_lines
+				(param, buf, outfp, normalize_lbreak);
+
 			fclose(tmpfp);
 		}
 	} else if (mimeinfo->encoding_type == ENC_X_UUENCODE) {
@@ -943,20 +1181,15 @@
 		while (fgets(buf, sizeof(buf), infp) != NULL &&
 		       (!boundary ||
 			!IS_BOUNDARY(buf, boundary, boundary_len))) {
-			if (normalize_lbreak) {
-#ifdef G_OS_WIN32
-				strretchomp(buf);
-				fputs(buf, outfp);
-				fputs("\r\n", outfp);
-#else
-				strcrchomp(buf);
-				fputs(buf, outfp);
-#endif
-			} else
-				fputs(buf, outfp);
+			procmime_dc_process_line
+				(param, buf, outfp, normalize_lbreak);
 		}
+		procmime_dc_post_process_lines
+			(param, buf, outfp, normalize_lbreak);
 	}
 
+	procmime_dc_params_free(param);
+
 	if (fflush(outfp) == EOF)
 		perror("fflush");
 	if (ferror(outfp) != 0) {
Index: libsylph/procmime.h
===================================================================
--- libsylph/procmime.h	(リビジョン 2061)
+++ libsylph/procmime.h	(作業コピー)
@@ -102,6 +102,8 @@
 	gchar *charset;
 	gchar *name;
 	gchar *boundary;
+	gchar *format;
+	gchar *delsp;
 
 	gchar *content_disposition;
 	gchar *filename;
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.