From: Junio C Hamano <hidden> Date: 2016-06-15 22:47:08
Alex Riesen [off-list ref] writes:
Maybe it is as simple as that (not tested yet,
and sent through gmail, so please be careful):
I thought about this approach, but it made me worried about a case where
an otherwise sane piece of e-mail has \r at the end of one line as the
real payload. But as long as we are talking about a text e-mail (and we
are talking about mailsplit here and a binary payload with a CTE applied
counts as text), I think we can safely use an approach like this.
From: Alex Riesen <hidden> Date: 2016-06-15 22:47:09
It is not that uncommon to have mails with DOS line-ending, notably
Thunderbird and Gmail (when saving what they call "original" message).
Noticed by H. Peter Anvin.
Signed-off-by: Alex Riesen <redacted>
---
Alex Riesen [off-list ref] writes:
Maybe it is as simple as that (not tested yet,
and sent through gmail, so please be careful):
@@ -58,6 +58,8 @@ int read_line_with_nul(char *buf, int size, FILE *in)+if(len&&buf[len-1]=='\r')+--len;
That's wrong, of course. I missed the fact that \n stays in the
buffer. Corrected.
builtin-mailsplit.c | 2 ++
1 files changed, 2 insertions(+), 0 deletions(-)
How about something like:
+ if (len > 1 && buf[len - 2] == '\r' && (buf[len - 1] == '\n'
|| buf[len - 1] == '\0'))
+ buf[--len - 1] = '\n';
To make sure that we're not erasing a \r somewhere in the middle of the content?
--
Cheers,
Sverre Rabbelier
How about something like:
+ if (len > 1 && buf[len - 2] == '\r' && (buf[len - 1] == '\n'
|| buf[len - 1] == '\0'))
+ buf[--len - 1] = '\n';
To make sure that we're not erasing a \r somewhere in the middle of the content?
You may want to push the \r back into the buffer if it is the last character read
too. We may reach the limit of size characters without finding a \n, and so we
can't tell whether the last \r we read was a solitary \r or whether it is the
beginning of \r\n sequence.
So maybe we need something like this after the 'for' loop instead:
if (c == '\n') {
if (len > 1 && buf[len - 2] == '\r')
buf[--len - 1] = '\n';
} else if (c == '\r') {
ungetc(c, in);
len--;
}
-brandon
How about something like:
+ if (len > 1 && buf[len - 2] == '\r' && (buf[len - 1] == '\n'
|| buf[len - 1] == '\0'))
+ buf[--len - 1] = '\n';
To make sure that we're not erasing a \r somewhere in the middle of the content?
I think this should be enough:
if (c == '\n' && len > 1 && buf[len - 2] == '\r')
buf[--len - 1] = '\n';
I'll resend.
From: Erik Faye-Lund <hidden> Date: 2016-06-15 22:47:09
On Tue, Aug 4, 2009 at 8:46 AM, Junio C Hamano[off-list ref] wrote:
I thought about this approach, but it made me worried about a case where
an otherwise sane piece of e-mail has \r at the end of one line as the
real payload. But as long as we are talking about a text e-mail (and we
are talking about mailsplit here and a binary payload with a CTE applied
counts as text), I think we can safely use an approach like this.
RFC 2822, section 2.3 explicitly states that a CR should not occur
without a LF (and vice versa, but the e-mail client might convert CRLF
to LF when saving to file), so I think this should be safe.
--
Erik "kusma" Faye-Lund
kusmabite@gmail.com
(+47) 986 59 656
From: Alex Riesen <hidden> Date: 2016-06-15 22:47:09
Noticed by H. Peter Anvin.
It is not that uncommon to have mails with DOS line-ending,
notably Thunderbird and web mailers like Gmail (when saving
what they call "original" message).
Signed-off-by: Alex Riesen <redacted>
---
Corrected bug with unconditonal last (or very long) line shortening if
it contains a CR in next-to-last character. Noticed by Sverre Rabbelier.
It should also handle the case mentioned by Brandon Casey.
builtin-mailsplit.c | 2 ++
1 files changed, 2 insertions(+), 0 deletions(-)
From: Alex Riesen <hidden> Date: 2016-06-15 22:47:09
On Tue, Aug 4, 2009 at 22:59, Erik Faye-Lund[off-list ref] wrote:
On Tue, Aug 4, 2009 at 8:46 AM, Junio C Hamano[off-list ref] wrote:
quoted
I thought about this approach, but it made me worried about a case where
an otherwise sane piece of e-mail has \r at the end of one line as the
real payload. But as long as we are talking about a text e-mail (and we
are talking about mailsplit here and a binary payload with a CTE applied
counts as text), I think we can safely use an approach like this.
RFC 2822, section 2.3 explicitly states that a CR should not occur
without a LF (and vice versa, but the e-mail client might convert CRLF
to LF when saving to file), so I think this should be safe.
You missed this line in original posters e-mail:
"In a serious case of craniorectal immersion..."
We are not safe from that and alike.
Maybe it is as simple as that (not tested yet,
and sent through gmail, so please be careful):
I thought about this approach, but it made me worried about a case where
an otherwise sane piece of e-mail has \r at the end of one line as the
real payload. But as long as we are talking about a text e-mail (and we
are talking about mailsplit here and a binary payload with a CTE applied
counts as text), I think we can safely use an approach like this.
Is it safe to rebase a commit that introduces a carriage-return at the end of the line using the updated program?
--
Nanako Shiraishi
http://ivory.ap.teacup.com/nanako3/
From: Erik Faye-Lund <hidden> Date: 2016-06-15 22:47:09
On Tue, Aug 4, 2009 at 11:05 PM, Alex Riesen[off-list ref] wrote:
You missed this line in original posters e-mail:
"In a serious case of craniorectal immersion..."
We are not safe from that and alike.
Storing e-mails with CRLFs intact is perfectly sane. Depending on them
not being stored that way is not.
--
Erik "kusma" Faye-Lund
kusmabite@gmail.com
(+47) 986 59 656
Noticed by H. Peter Anvin.
It is not that uncommon to have mails with DOS line-ending,
notably Thunderbird and web mailers like Gmail (when saving
what they call "original" message).
Signed-off-by: Alex Riesen <redacted>
---
Corrected bug with unconditonal last (or very long) line shortening if
it contains a CR in next-to-last character. Noticed by Sverre Rabbelier.
It should also handle the case mentioned by Brandon Casey.
builtin-mailsplit.c | 2 ++
1 files changed, 2 insertions(+), 0 deletions(-)
@@ -58,6 +58,8 @@ int read_line_with_nul(char *buf, int size, FILE *in)if(c=='\n'||len+1>=size)break;}+if(c=='\n'&&len>1&&buf[len-2]=='\r')+buf[--len-1]='\n';buf[len]='\0';returnlen;
What if \r lands at character 99 and \n is at character 100? If buf has
exactly 100 characters available for writing. Wouldn't the '\r' be stored
into buf, but not the '\n'? Then the 'for' loop would terminate since len + 1
would be >= size and the code above would test whether c == '\n', and it
would not, so the '\r' would not be removed from buf as it should be.
At the point where buf has been filled, and the last character read is a '\r',
we can not tell whether the next character is a '\n' or not, so we do not know
if it is a solitary '\r' or whether it is the start of a '\r\n' sequence. It
seems to me that we must push the '\r' back into the stream and allow the next
call to read_line_with_nul() handle it, or peek to see if the next character
is a '\n'.
Be aware that if we use the version I suggested which pushes the '\r' back
into the input stream, I think we risk an infinite loop if size == 1. I don't
think that is possible from the current callers though.
-brandon
Be aware that if we use the version I suggested which pushes the '\r' back
into the input stream, I think we risk an infinite loop if size == 1. I don't
think that is possible from the current callers though.
I think something like this would avoid any potential infinite loop, however improbable:
if (c == '\n') {
if (len > 1 && buf[len - 2] == '\r')
buf[--len - 1] = '\n';
} else if (c == '\r') {
c = getc(in);
if (c == '\n')
buf[len - 1] = '\n';
else if (c != EOF)
ungetc(c, in);
}
-brandon
What if \r lands at character 99 and \n is at character 100? If buf has
exactly 100 characters available for writing. ...
Ah, yes. You're right.
I have strong dislike towards unget, though. How about this, instead:
int read_line_with_nul(char *buf, int size, FILE *in)
{
int len = 0, c;
while (len < size) {
c = getc(in);
if (c == EOF)
break;
buf[len++] = c;
if (c == '\n')
break;
else if (len == size)
c = 0;
}
if (c == '\n' && len > 1 && buf[len - 2] == '\r')
buf[--len - 1] = '\n';
buf[len] = '\0';
return len;
}
From: Junio C Hamano <hidden> Date: 2016-06-15 22:47:09
Nanako Shiraishi [off-list ref] writes:
Quoting Junio C Hamano [off-list ref]
quoted
Alex Riesen [off-list ref] writes:
quoted
Maybe it is as simple as that (not tested yet,
and sent through gmail, so please be careful):
I thought about this approach, but it made me worried about a case where
an otherwise sane piece of e-mail has \r at the end of one line as the
real payload. But as long as we are talking about a text e-mail (and we
are talking about mailsplit here and a binary payload with a CTE applied
counts as text), I think we can safely use an approach like this.
Is it safe to rebase a commit that introduces a carriage-return at the end of the line using the updated program?
Hmmm, good point. The approach does break rebase.
At least we could patch it up like this.
builtin-mailinfo.c | 2 +-
builtin-mailsplit.c | 11 +++++++++--
builtin.h | 2 +-
git-am.sh | 8 +++++++-
t/t3400-rebase.sh | 26 ++++++++++++++++++++++++--
5 files changed, 42 insertions(+), 7 deletions(-)
@@ -45,8 +45,10 @@ static int is_from_line(const char *line, int len)/* Could be as small as 64, enough to hold a Unix "From " line. */staticcharbuf[4096];+staticintkeep_cr;+/* We cannot use fgets() because our lines can contain NULs */-intread_line_with_nul(char*buf,intsize,FILE*in)+intread_line_with_nul(char*buf,intsize,FILE*in,intnuke_cr_at_eol){intlen=0,c;
@@ -58,6 +60,9 @@ int read_line_with_nul(char *buf, int size, FILE *in)if(c=='\n'||len+1>=size)break;}+if(nuke_cr_at_eol&&+(1<len&&buf[len-2]=='\r'&&buf[len-1]=='\n'))+buf[len---2]='\n';buf[len]='\0';returnlen;
@@ -93,7 +98,7 @@ static int split_one(FILE *mbox, const char *name, int allow_bare)if(fwrite(buf,1,len,output)!=len)die_errno("cannot write output");-len=read_line_with_nul(buf,sizeof(buf),mbox);+len=read_line_with_nul(buf,sizeof(buf),mbox,!keep_cr);if(len==0){if(feof(mbox)){status=1;
@@ -133,4 +134,25 @@ test_expect_success 'rebase -q is quiet' 'test!-soutput.out'+q_to_cr(){+trQ'\015'+}++test_expect_success'Rebase a commit that sprinkles CRs in''+(+echo"One"+echo"TwoQ"+echo"Three"+echo"FQur"+echo"Five"+)|q_to_cr>CR&&+gitaddCR&&+test_tick&&+gitcommit-a-m"A file with a line with CR"&&+gittagfile-with-cr&&+gitcheckoutHEAD^0&&+gitrebase--ontoHEAD^^HEAD^&&+gitdiff--exit-codefile-with-cr:CRHEAD:CR+'+ test_done
What if \r lands at character 99 and \n is at character 100? If buf has
exactly 100 characters available for writing. ...
Ah, yes. You're right.
I have strong dislike towards unget, though. How about this, instead:
int read_line_with_nul(char *buf, int size, FILE *in)
{
int len = 0, c;
while (len < size) {
c = getc(in);
if (c == EOF)
break;
buf[len++] = c;
if (c == '\n')
break;
else if (len == size)
c = 0;
}
if (c == '\n' && len > 1 && buf[len - 2] == '\r')
buf[--len - 1] = '\n';
buf[len] = '\0';
return len;
}
I don't see how this solves the problem. Still if the buffer is filled,
and the last character read is '\r', and the next character that has not
yet been read is '\n', then the '\r' will erroneously be returned in buf.
Plus, I think you'll have a problem at buf[len] = '\0' if the loop runs to
completion and len == size.
-brandon
From: Brandon Casey <redacted>
Convert mailinfo and mailsplit to use strbufs before attempting to address
the CRLF issue for saved emails.
Brandon Casey (3):
strbuf: add new function strbuf_getwholeline()
builtin-mailinfo,builtin-mailsplit: use strbufs
builtin-mailsplit.c: remove read_line_with_nul() since it is no
longer used
Junio C Hamano (1):
Allow mailsplit to handle mails with CRLF line-endings
builtin-mailinfo.c | 8 +-------
builtin-mailsplit.c | 44 ++++++++++++++------------------------------
builtin.h | 1 -
git-am.sh | 8 +++++++-
strbuf.c | 15 ++++++++++++---
strbuf.h | 1 +
t/t3400-rebase.sh | 26 ++++++++++++++++++++++++--
7 files changed, 59 insertions(+), 44 deletions(-)
From: Brandon Casey <redacted>
This function is just like strbuf_getline() except it retains the
line-termination character. This function will be used by the mailinfo
and mailsplit builtins which require the entire line for parsing.
Signed-off-by: Brandon Casey <redacted>
---
strbuf.c | 15 ++++++++++++---
strbuf.h | 1 +
2 files changed, 13 insertions(+), 3 deletions(-)
From: Junio C Hamano <redacted>
It is not that uncommon to have mails with DOS line-ending, notably
Thunderbird and web mailers like Gmail (when saving what they call
"original" message). So modify mailsplit to convert CRLF line-endings to
just LF.
Since git-rebase is built on top of git-am, add an option to mailsplit to
be used by git-am when it is acting on behalf of git-rebase, to refrain
from doing this conversion.
And add a test to make sure that rebase still works.
Signed-off-by: Brandon Casey <redacted>
---
builtin-mailsplit.c | 6 ++++++
git-am.sh | 8 +++++++-
t/t3400-rebase.sh | 26 ++++++++++++++++++++++++--
3 files changed, 37 insertions(+), 3 deletions(-)
@@ -44,6 +44,7 @@ static int is_from_line(const char *line, int len)}staticstructstrbufbuf=STRBUF_INIT;+staticintkeep_cr;/* Called with the first line (potentially partial)*alreadyinbuf[]--normallythatshouldbeginwith
@@ -69,6 +70,12 @@ static int split_one(FILE *mbox, const char *name, int allow_bare)*"From "andhavingsomethingthatlookslikeadateformat.*/for(;;){+if(!keep_cr&&buf.len>1&&buf.buf[buf.len-1]=='\n'&&+buf.buf[buf.len-2]=='\r'){+strbuf_setlen(&buf,buf.len-2);+strbuf_addch(&buf,'\n');+}+if(fwrite(buf.buf,1,buf.len,output)!=buf.len)die_errno("cannot write output");
@@ -133,4 +134,25 @@ test_expect_success 'rebase -q is quiet' 'test!-soutput.out'+q_to_cr(){+trQ'\015'+}++test_expect_success'Rebase a commit that sprinkles CRs in''+(+echo"One"+echo"TwoQ"+echo"Three"+echo"FQur"+echo"Five"+)|q_to_cr>CR&&+gitaddCR&&+test_tick&&+gitcommit-a-m"A file with a line with CR"&&+gittagfile-with-cr&&+gitcheckoutHEAD^0&&+gitrebase--ontoHEAD^^HEAD^&&+gitdiff--exit-codefile-with-cr:CRHEAD:CR+'+ test_done
From: Brandon Casey <redacted>
There should be no functional change. Just the necessary changes and
simplifications associated with calling strbuf_getwholeline() rather
than an internal function or fgets.
Signed-off-by: Brandon Casey <redacted>
---
builtin-mailinfo.c | 8 +-------
builtin-mailsplit.c | 20 ++++++++------------
2 files changed, 9 insertions(+), 19 deletions(-)
@@ -765,7 +765,6 @@ static void handle_filter(struct strbuf *line)staticvoidhandle_body(void){-intlen=0;structstrbufprev=STRBUF_INIT;/* Skip up to the first boundary */
@@ -775,8 +774,6 @@ static void handle_body(void)}do{-strbuf_setlen(&line,line.len+len);-/* process any boundary lines */if(*content_top&&is_multipart_boundary(&line)){/* flush any leftover */
@@ -42,8 +43,7 @@ static int is_from_line(const char *line, int len)return1;}-/* Could be as small as 64, enough to hold a Unix "From " line. */-staticcharbuf[4096];+staticstructstrbufbuf=STRBUF_INIT;/* We cannot use fgets() because our lines can contain NULs */intread_line_with_nul(char*buf,intsize,FILE*in)
@@ -71,10 +71,9 @@ int read_line_with_nul(char *buf, int size, FILE *in)staticintsplit_one(FILE*mbox,constchar*name,intallow_bare){FILE*output=NULL;-intlen=strlen(buf);intfd;intstatus=0;-intis_bare=!is_from_line(buf,len);+intis_bare=!is_from_line(buf.buf,buf.len);if(is_bare&&!allow_bare)gotocorrupt;
@@ -88,20 +87,17 @@ static int split_one(FILE *mbox, const char *name, int allow_bare)*"From "andhavingsomethingthatlookslikeadateformat.*/for(;;){-intis_partial=len&&buf[len-1]!='\n';--if(fwrite(buf,1,len,output)!=len)+if(fwrite(buf.buf,1,buf.len,output)!=buf.len)die_errno("cannot write output");-len=read_line_with_nul(buf,sizeof(buf),mbox);-if(len==0){+if(strbuf_getwholeline(&buf,mbox,'\n')){if(feof(mbox)){status=1;break;}die_errno("cannot read mbox");}-if(!is_partial&&!is_bare&&is_from_line(buf,len))+if(!is_bare&&is_from_line(buf.buf,buf.len))break;/* done with one message */}fclose(output);
@@ -166,7 +162,7 @@ static int split_maildir(const char *maildir, const char *dir,gotoout;}-if(fgets(buf,sizeof(buf),f)==NULL){+if(strbuf_getwholeline(&buf,f,'\n')){error("cannot read mail %s (%s)",file,strerror(errno));gotoout;}
@@ -203,7 +199,7 @@ static int split_mbox(const char *file, const char *dir, int allow_bare,}while(isspace(peek));ungetc(peek,f);-if(fgets(buf,sizeof(buf),f)==NULL){+if(strbuf_getwholeline(&buf,f,'\n')){/* empty stdin is OK */if(f!=stdin){error("cannot read mbox %s",file);
@@ -45,24 +45,6 @@ static int is_from_line(const char *line, int len)staticstructstrbufbuf=STRBUF_INIT;-/* We cannot use fgets() because our lines can contain NULs */-intread_line_with_nul(char*buf,intsize,FILE*in)-{-intlen=0,c;--for(;;){-c=getc(in);-if(c==EOF)-break;-buf[len++]=c;-if(c=='\n'||len+1>=size)-break;-}-buf[len]='\0';--returnlen;-}-/* Called with the first line (potentially partial)*alreadyinbuf[]--normallythatshouldbeginwith*theUnix"From "line.Writeitintothespecified
From: Tony Finch <dot@dotat.at> Date: 2016-06-15 22:47:11
On Tue, 4 Aug 2009, Erik Faye-Lund wrote:
RFC 2822, section 2.3 explicitly states that a CR should not occur
without a LF (and vice versa, but the e-mail client might convert CRLF
to LF when saving to file), so I think this should be safe.
The rare BINARYMIME extension relaxes this requirement.
Tony.
--
f.anthony.n.finch [off-list ref] http://dotat.at/
GERMAN BIGHT HUMBER: SOUTHWEST 5 TO 7. MODERATE OR ROUGH. SQUALLY SHOWERS.
MODERATE OR GOOD.