darwinssl: Fix send glitchiness with data > 32 or so KB

An ambiguity in the SSLWrite() documentation lead to a bad inference in the
code where we assumed SSLWrite() returned the amount of bytes written to
the socket, when that is not actually true; it returns the amount of data
that is buffered for writing to the socket if it returns errSSLWouldBlock.
Now darwinssl_send() returns CURLE_AGAIN if data is buffered but not written.

Reference URL: http://curl.haxx.se/mail/lib-2013-02/0145.html
This commit is contained in:
Nick Zitzmann 2013-02-12 13:02:36 -07:00
parent bd93062ee5
commit 66aa9bf52d
2 changed files with 52 additions and 14 deletions

View File

@ -739,6 +739,7 @@ static CURLcode darwinssl_connect_step1(struct connectdata *conn,
return CURLE_OUT_OF_MEMORY;
}
#endif /* defined(__MAC_10_8) || defined(__IPHONE_5_0) */
connssl->ssl_write_buffered_length = 0UL; /* reset buffered write length */
/* check to see if we've been told to use an explicit SSL/TLS version */
#if defined(__MAC_10_8) || defined(__IPHONE_5_0)
@ -1424,22 +1425,58 @@ static ssize_t darwinssl_send(struct connectdata *conn,
/*struct SessionHandle *data = conn->data;*/
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
size_t processed = 0UL;
OSStatus err = SSLWrite(connssl->ssl_ctx, mem, len, &processed);
OSStatus err;
if(err != noErr) {
/* The SSLWrite() function works a little differently than expected. The
fourth argument (processed) is currently documented in Apple's
documentation as: "On return, the length, in bytes, of the data actually
written."
Now, one could interpret that as "written to the socket," but actually,
it returns the amount of data that was written to a buffer internal to
the SSLContextRef instead. So it's possible for SSLWrite() to return
errSSLWouldBlock and a number of bytes "written" because those bytes were
encrypted and written to a buffer, not to the socket.
So if this happens, then we need to keep calling SSLWrite() over and
over again with no new data until it quits returning errSSLWouldBlock. */
/* Do we have buffered data to write from the last time we were called? */
if(connssl->ssl_write_buffered_length) {
/* Write the buffered data: */
err = SSLWrite(connssl->ssl_ctx, NULL, 0UL, &processed);
switch (err) {
case errSSLWouldBlock: /* return how much we sent (if anything) */
if(processed)
return (ssize_t)processed;
case noErr:
/* processed is always going to be 0 because we didn't write to
the buffer, so return how much was written to the socket */
processed = connssl->ssl_write_buffered_length;
connssl->ssl_write_buffered_length = 0UL;
break;
case errSSLWouldBlock: /* argh, try again */
*curlcode = CURLE_AGAIN;
return -1;
break;
return -1L;
default:
failf(conn->data, "SSLWrite() return error %d", err);
failf(conn->data, "SSLWrite() returned error %d", err);
*curlcode = CURLE_SEND_ERROR;
return -1;
break;
return -1L;
}
}
else {
/* We've got new data to write: */
err = SSLWrite(connssl->ssl_ctx, mem, len, &processed);
if(err != noErr) {
switch (err) {
case errSSLWouldBlock:
/* Data was buffered but not sent, we have to tell the caller
to try sending again, and remember how much was buffered */
connssl->ssl_write_buffered_length = len;
*curlcode = CURLE_AGAIN;
return -1L;
default:
failf(conn->data, "SSLWrite() returned error %d", err);
*curlcode = CURLE_SEND_ERROR;
return -1L;
}
}
}
return (ssize_t)processed;
@ -1462,18 +1499,18 @@ static ssize_t darwinssl_recv(struct connectdata *conn,
if(processed)
return (ssize_t)processed;
*curlcode = CURLE_AGAIN;
return -1;
return -1L;
break;
case errSSLClosedGraceful: /* they're done; fail gracefully */
*curlcode = CURLE_OK;
return -1;
return -1L;
break;
default:
failf(conn->data, "SSLRead() return error %d", err);
*curlcode = CURLE_RECV_ERROR;
return -1;
return -1L;
break;
}
}

View File

@ -326,6 +326,7 @@ struct ssl_connect_data {
curl_socket_t ssl_sockfd;
ssl_connect_state connecting_state;
bool ssl_direction; /* true if writing, false if reading */
size_t ssl_write_buffered_length;
#endif /* USE_DARWINSSL */
};