I'm trying to use libcurl to read mail from a POP3 mailbox, and have come across an issue with the Curl_pop3_write() function not detecting the end of body marker properly.
In curl 7.21.7 it's defined as "\r\n.\r\n", but this assumes that there is at least one line of reply in the body to supply the first "\r\n". For the LIST command when there are no messages, this is not the case, and curl_easy_perform() hangs. A side effect of this bug is that the final line looses its "\r\n" ending. Another problem is that the byte-stuffed sequence "\r\n.." (ie. escaped EOB) is not being un-stuffed. I also needed to add the DELE command, to be able to remove mail messages once they have been read. Following the style of the list option, I added a delete option CURLOPT_DELETE to allow this. There is a minor problems in smtp.c too, to do with EOB handling. If there is a . at the start of the first line of the DATA body, it is not escaped properly and the data body is lost. (This is not likely for useful data bodies, but I include a fix along the same lines as the pop3 changes for completeness.) Attached is a set of patches based on 7.21.7. cheers, Graeme Gill.
--- curl-7.21.7/docs/libcurl/curl_easy_setopt.3 2011-06-14 07:09:52.000000000 +1000 +++ 7217/docs/libcurl/curl_easy_setopt.3 2011-07-09 13:55:32.000000000 +1000 @@ -1343,7 +1343,7 @@ .IP CURLOPT_DIRLISTONLY A parameter set to 1 tells the library to just list the names of files in a directory, instead of doing a full directory listing that would include file -sizes, dates etc. This works for FTP and SFTP URLs. +sizes, dates etc. This works for FTP, SFTP and POP3 URLs. This causes an FTP NLST command to be sent on an FTP server. Beware that some FTP servers list only files in their response to NLST; they might not include @@ -1356,6 +1356,12 @@ effectively break that feature then. (This option was known as CURLOPT_FTPLISTONLY up to 7.16.4) +.IP CURLOPT_DELETE +A parameter set to 1 tells the library to delete the message ID specified +by the URL (i.e. "pop3://mail.server.com/1" will delete the first message). +This works for POP3 URLs. +This option will be ignored if no message ID is specified. + .IP CURLOPT_APPEND A parameter set to 1 tells the library to append to the remote file instead of overwrite it. This is only useful when uploading to an FTP site. --- curl-7.21.7/include/curl/curl.h 2011-05-19 06:56:46.000000000 +1000 +++ 7217/include/curl/curl.h 2011-07-09 13:23:38.000000000 +1000 @@ -1483,6 +1483,8 @@ CINIT(CLOSESOCKETFUNCTION, FUNCTIONPOINT, 208), CINIT(CLOSESOCKETDATA, OBJECTPOINT, 209), + CINIT(DELETE, LONG, 210), /* Delete POP3 message */ + CURLOPT_LASTENTRY /* the last unused */ } CURLoption; --- curl-7.21.7/lib/pop3.c 2011-06-22 01:55:39.000000000 +1000 +++ 7217/lib/pop3.c 2011-07-11 18:54:24.000000000 +1000 @@ -217,6 +217,10 @@ { char *line = pp->linestart_resp; size_t len = pp->nread_resp; + struct pop3_conn *pop3c = &pp->conn->proto.pop3c; + + pop3c->eob = 2; /* Assume \0xd\0xa already recognized */ + pop3c->crlfwr = true; /* But it's already been written */ if(((len >= 3) && !memcmp("+OK", line, 3)) || ((len >= 4) && !memcmp("-ERR", line, 4))) { @@ -242,6 +246,7 @@ "LIST", "LIST_SINGLE", "RETR", + "DELE", "QUIT", /* LAST */ }; @@ -403,6 +408,25 @@ } +/* for the dele response */ +static CURLcode pop3_state_dele_resp(struct connectdata *conn, + int pop3code, + pop3state instate) +{ + CURLcode result = CURLE_OK; + + (void)instate; /* no use for this yet */ + + if('O' != pop3code) { + state(conn, POP3_STOP); + return CURLE_RECV_ERROR; + } + + state(conn, POP3_STOP); + return result; +} + + /* for the list response */ static CURLcode pop3_state_list_resp(struct connectdata *conn, int pop3code, @@ -475,6 +499,20 @@ return result; } +/* start the DO phase for DELE */ +static CURLcode pop3_dele(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct pop3_conn *pop3c = &conn->proto.pop3c; + + result = Curl_pp_sendf(&conn->proto.pop3c.pp, "DELE %s", pop3c->mailbox); + if(result) + return result; + + state(conn, POP3_DELE); + return result; +} + /* start the DO phase for LIST */ static CURLcode pop3_list(struct connectdata *conn) { @@ -550,6 +588,10 @@ result = pop3_state_retr_resp(conn, pop3code, pop3c->state); break; + case POP3_DELE: + result = pop3_state_dele_resp(conn, pop3code, pop3c->state); + break; + case POP3_LIST: result = pop3_state_list_resp(conn, pop3code, pop3c->state); break; @@ -778,7 +820,9 @@ /* If mailbox is empty, then assume user wants listing for mail IDs, * otherwise, attempt to retrieve the mail-id stored in mailbox */ - if(strlen(pop3c->mailbox) && !conn->data->set.ftp_list_only) + if(strlen(pop3c->mailbox) && conn->data->set.pop3_delete_mesg) + result = pop3_dele(conn); + else if(strlen(pop3c->mailbox) && !conn->data->set.ftp_list_only) result = pop3_retr(conn); else result = pop3_list(conn); @@ -1013,55 +1057,109 @@ return CURLE_OK; } -/* this is the 5-bytes End-Of-Body marker for POP3 */ -#define POP3_EOB "\x0d\x0a\x2e\x0d\x0a" -#define POP3_EOB_LEN 5 - /* * This function scans the body after the end-of-body and writes everything - * until the end is found. + * until the end is found. It also un "byte-stuffs" a line that would have + * been confused with an end-of-body marker. */ CURLcode Curl_pop3_write(struct connectdata *conn, char *str, size_t nread) { /* This code could be made into a special function in the handler struct. */ - CURLcode result; + CURLcode result = CURLE_OK; struct SessionHandle *data = conn->data; struct SingleRequest *k = &data->req; - - /* Detect the end-of-body marker, which is 5 bytes: - 0d 0a 2e 0d 0a. This marker can of course be spread out - over up to 5 different data chunks. Deal with it! */ - struct pop3_conn *pop3c = &conn->proto.pop3c; - size_t checkmax = (nread >= POP3_EOB_LEN?POP3_EOB_LEN:nread); - size_t checkleft = POP3_EOB_LEN-pop3c->eob; - size_t check = (checkmax >= checkleft?checkleft:checkmax); - - if(!memcmp(POP3_EOB, &str[nread - check], check)) { - /* substring match */ - pop3c->eob += check; - if(pop3c->eob == POP3_EOB_LEN) { - /* full match, the transfer is done! */ - str[nread - check] = '\0'; - nread -= check; - k->keepon &= ~KEEP_RECV; - pop3c->eob = 0; + struct pop3_conn *pop3c = &conn->proto.pop3c; + size_t i; + size_t segstart = 0; /* Offset into str[] of start of pass through */ + size_t segend = 0; /* Offset into str[] of end of pass through */ + + /* We have a simple state machine to detect and deal with either the EOB sequence */ + /* 0d 0a 2e 0d 0a or the byte-stuffed seqence 0d 0a 2e 2e. */ + for(i = 0; i < nread; i++) { + char c = str[i]; + + switch(pop3c->eob) { + case 0: + if (c == 0x0d) { + pop3c->eob = 1; + segend = i; /* We need to write the string up to this char */ + } else { + pop3c->eob = 0; /* No match */ + pop3c->crlfwr = false; /* Not directly after first line now */ + segend = i + 1; /* We need to write this char */ + } + break; + case 1: + if (c == 0x0a) { + pop3c->eob = 2; + } else { + pop3c->eob = 0; /* No match */ + pop3c->crlfwr = false; /* Not directly after first line now */ + segend = i + 1; /* We need to write this char */ + } + break; + case 2: + if (c == 0x2e) { + pop3c->eob = 3; + } else { + pop3c->eob = 0; /* No match */ + pop3c->crlfwr = false; /* Not directly after first line now */ + segend = i + 1; /* We need to write this char */ + } + break; + case 3: + if (c == 0x0d) { /* Possible EOB */ + pop3c->eob = 4; + } else if (c == 0x2e) { /* byte stuff sequence */ + /* Write characters up to the start of the byte-stuffed string */ + if (segend > segstart) { + result = Curl_client_write(conn, CLIENTWRITE_BODY, str + segstart, segend-segstart); + if (result != CURLE_OK) + return result; + } + if (pop3c->crlfwr) /* Un-stuff the byte */ + result = Curl_client_write(conn, CLIENTWRITE_BODY, "\x2e", 1); + else /* write withheld bytes + un-stuffed byte */ + result = Curl_client_write(conn, CLIENTWRITE_BODY, "\x0d\x0a\x2e", 3); + if (result != CURLE_OK) + return result; + pop3c->eob = 0; + segstart = segend = i + 1; + } else { + pop3c->eob = 0; /* No match */ + pop3c->crlfwr = false; /* Not directly after first line now */ + segend = i + 1; /* We need to write this char */ + } + break; + case 4: + if (c == 0x0a) { + /* Write characters up to the start of the EOB string */ + if (segend > segstart) { + result = Curl_client_write(conn, CLIENTWRITE_BODY, str + segstart, segend-segstart); + if (result != CURLE_OK) + return result; + } + if (!pop3c->crlfwr) { /* Write crlf to complete last line */ + result = Curl_client_write(conn, CLIENTWRITE_BODY, "\x0d\x0a", 2); + if (result != CURLE_OK) + return result; + } + k->keepon &= ~KEEP_RECV; /* We're done */ + pop3c->eob = 0; + segstart = segend = i + 1; + } else { + pop3c->eob = 0; /* No match */ + pop3c->crlfwr = false; /* Not directly after first line now */ + segend = i + 1; /* We need to write this char */ + } + break; } } - else if(pop3c->eob) { - /* not a match, but we matched a piece before so we must now - send that part as body first, before we move on and send - this buffer */ - result = Curl_client_write(conn, CLIENTWRITE_BODY, - (char *)POP3_EOB, pop3c->eob); - if(result) - return result; - pop3c->eob = 0; - } - - result = Curl_client_write(conn, CLIENTWRITE_BODY, str, nread); - + /* Emit remainder of string */ + if (segend > segstart) + result = Curl_client_write(conn, CLIENTWRITE_BODY, str + segstart, segend-segstart); return result; } --- curl-7.21.7/lib/pop3.h 2011-03-30 22:51:31.000000000 +1100 +++ 7217/lib/pop3.h 2011-07-11 15:18:33.000000000 +1000 @@ -35,6 +35,7 @@ POP3_LIST, POP3_LIST_SINGLE, POP3_RETR, + POP3_DELE, POP3_QUIT, POP3_LAST /* never used */ } pop3state; @@ -44,8 +45,10 @@ struct pop3_conn { struct pingpong pp; char *mailbox; /* what to RETR */ - size_t eob; /* number of bytes of the EOB (End Of Body) that has been - received thus far */ + + int eob; /* EOB/byte un-stuff match state count */ + bool crlfwr; /* Initial crlf have already been written */ + pop3state state; /* always use pop3.c:state() to change state! */ }; --- curl-7.21.7/lib/smtp.c 2011-05-09 18:20:30.000000000 +1000 +++ 7217/lib/smtp.c 2011-07-11 22:59:34.000000000 +1000 @@ -878,6 +878,8 @@ } /* send DATA */ + conn->proto.smtpc.eob = 2; + conn->proto.smtpc.crlfwr = true; result = Curl_pp_sendf(&conn->proto.smtpc.pp, "DATA"); if(result) return result; @@ -1498,9 +1500,12 @@ /* When sending SMTP payload, we must detect CRLF.CRLF sequences in * the data and make sure it is sent as CRLF..CRLF instead, as * otherwise it will wrongly be detected as end of data by the server. + * Note that a last CRLF is not sent, as the EOB will provide it. */ ssize_t i; - ssize_t si; + ssize_t si = 0; + ssize_t segstart = 0; /* Offset into str[] of start of pass through */ + ssize_t segend = 0; /* Offset into str[] of end of pass through */ struct smtp_conn *smtpc = &conn->proto.smtpc; struct SessionHandle *data = conn->data; @@ -1510,40 +1515,59 @@ failf (data, "Failed to alloc scratch buffer!"); return CURLE_OUT_OF_MEMORY; } - /* This loop can be improved by some kind of Boyer-Moore style of - approach but that is saved for later... */ - for(i = 0, si = 0; i < nread; i++, si++) { - ssize_t left = nread - i; - - if(left>= (ssize_t)(SMTP_EOB_LEN-smtpc->eob)) { - if(!memcmp(SMTP_EOB+smtpc->eob, &data->req.upload_fromhere[i], - SMTP_EOB_LEN-smtpc->eob)) { - /* It matched, copy the replacement data to the target buffer - instead. Note that the replacement does not contain the - trailing CRLF but we instead continue to match on that one - to deal with repeated sequences. Like CRLF.CRLF.CRLF etc - */ - memcpy(&data->state.scratch[si], SMTP_EOB_REPL, - SMTP_EOB_REPL_LEN); - si+=SMTP_EOB_REPL_LEN-1; /* minus one since the for() increments - it */ - i+=SMTP_EOB_LEN-smtpc->eob-1-2; - smtpc->eob = 0; /* start over */ - continue; - } + + /* We have a simple state machine to do the byte-stuffing */ + for(i = 0; i < nread; i++) { + char c = data->req.upload_fromhere[i]; + + switch(smtpc->eob) { + case 0: + if (c == 0x0d) { + smtpc->eob = 1; + segend = i; /* We need to write the string up to this char */ + } else { + smtpc->eob = 0; /* No match */ + smtpc->crlfwr = false; /* Not directly after first line now */ + segend = i + 1; /* We need to write this char */ + } + break; + case 1: + if (c == 0x0a) { + smtpc->eob = 2; + } else { + smtpc->eob = 0; /* No match */ + smtpc->crlfwr = false; /* Not directly after first line now */ + segend = i + 1; /* We need to write this char */ + } + break; + case 2: + if (c == 0x2e) { /* Found EOB sequence, so byte stuff it */ + /* Copy characters up to the byte-stuffed string */ + memcpy(data->state.scratch+si, data->req.upload_fromhere+segstart, segend-segstart); + si += segend-segstart; + if (smtpc->crlfwr) { /* stuff the byte */ + memcpy(data->state.scratch+si,"\x2e\x2e", 2); + si += 2; + } else { /* write withheld bytes + stuffed byte */ + memcpy(data->state.scratch+si,"\x0d\x0a\x2e\x2e", 4); + si += 4; + } + segstart = segend = i + 1; /* Segment has been written */ + } else { + segend = i + 1; /* We need to write this char */ + } + smtpc->eob = 0; + smtpc->crlfwr = false; /* Not directly after first line now */ + break; } - else if(!memcmp(SMTP_EOB+smtpc->eob, &data->req.upload_fromhere[i], - left)) { - /* the last piece of the data matches the EOB so we can't send that - until we know the rest of it */ - smtpc->eob += left; - break; + } + if(si > 0) { + /* Copy the balance of the input */ + if (segend > segstart) { + memcpy(data->state.scratch+si, data->req.upload_fromhere+segstart, segend-segstart); + si += segend-segstart; } - data->state.scratch[si] = data->req.upload_fromhere[i]; - } /* for() */ - - if(si != nread) { /* only use the new buffer if we replaced something */ nread = si; --- curl-7.21.7/lib/smtp.h 2011-04-27 17:09:35.000000000 +1000 +++ 7217/lib/smtp.h 2011-07-11 22:49:04.000000000 +1000 @@ -54,8 +54,8 @@ struct smtp_conn { struct pingpong pp; char *domain; /* what to send in the EHLO */ - size_t eob; /* number of bytes of the EOB (End Of Body) that has been - received thus far */ + int eob; /* EOB/byte un-stuff match state count */ + bool crlfwr; /* Initial crlf have already been written */ unsigned int authmechs; /* Accepted authentication methods. */ smtpstate state; /* always use smtp.c:state() to change state! */ struct curl_slist *rcpt; --- curl-7.21.7/lib/url.c 2011-06-14 07:09:52.000000000 +1000 +++ 7217/lib/url.c 2011-07-09 13:15:34.000000000 +1000 @@ -990,6 +990,12 @@ */ data->set.ftp_list_only = (bool)(0 != va_arg(param, long)); break; + case CURLOPT_DELETE: + /* + * An option that changes the command to one that deletes a message + */ + data->set.pop3_delete_mesg = (bool)(0 != va_arg(param, long)); + break; case CURLOPT_APPEND: /* * We want to upload and append to an existing file. --- curl-7.21.7/lib/urldata.h 2011-06-08 03:31:53.000000000 +1000 +++ 7217/lib/urldata.h 2011-07-09 13:14:26.000000000 +1000 @@ -1498,6 +1498,7 @@ long new_directory_perms; /* Permissions to use when creating remote dirs */ bool proxy_transfer_mode; /* set transfer mode (;type=<a|i>) when doing FTP via an HTTP proxy */ + bool pop3_delete_mesg; /* Delete a specific pop3 message */ char *str[STRING_LAST]; /* array of strings, pointing to allocated memory */ unsigned int scope; /* address scope for IPv6 */ long allowed_protocols;
------------------------------------------------------------------- List admin: http://cool.haxx.se/list/listinfo/curl-library Etiquette: http://curl.haxx.se/mail/etiquette.html