wininet: Add a read-ahead buffer to allow InternetQueryDataAvailable to return the right values in chunked mode.
diff --git a/dlls/wininet/http.c b/dlls/wininet/http.c index a4a5f76..212ff9f 100644 --- a/dlls/wininet/http.c +++ b/dlls/wininet/http.c
@@ -1615,22 +1615,171 @@ return ERROR_INTERNET_INVALID_OPTION; } +/* read some more data into the read buffer */ +static BOOL read_more_data( WININETHTTPREQW *req, int maxlen ) +{ + int len; + + if (req->read_size && req->read_pos) + { + /* move existing data to the start of the buffer */ + memmove( req->read_buf, req->read_buf + req->read_pos, req->read_size ); + req->read_pos = 0; + } + + if (maxlen == -1) maxlen = sizeof(req->read_buf); + if (!NETCON_recv( &req->netConnection, req->read_buf + req->read_size, + maxlen - req->read_size, 0, &len )) return FALSE; + req->read_size += len; + return TRUE; +} + +/* remove some amount of data from the read buffer */ +static void remove_data( WININETHTTPREQW *req, int count ) +{ + if (!(req->read_size -= count)) req->read_pos = 0; + else req->read_pos += count; +} + +static BOOL read_line( WININETHTTPREQW *req, LPSTR buffer, DWORD *len ) +{ + int count, bytes_read, pos = 0; + + for (;;) + { + char *eol = memchr( req->read_buf + req->read_pos, '\n', req->read_size ); + + if (eol) + { + count = eol - (req->read_buf + req->read_pos); + bytes_read = count + 1; + } + else count = bytes_read = req->read_size; + + count = min( count, *len - pos ); + memcpy( buffer + pos, req->read_buf + req->read_pos, count ); + pos += count; + remove_data( req, bytes_read ); + if (eol) break; + + if (!read_more_data( req, -1 )) return FALSE; + if (!req->read_size) + { + *len = 0; + TRACE( "returning empty string\n" ); + return FALSE; + } + } + + if (pos < *len) + { + if (pos && buffer[pos - 1] == '\r') pos--; + *len = pos + 1; + } + buffer[*len - 1] = 0; + TRACE( "returning %s\n", debugstr_a(buffer)); + return TRUE; +} + +/* discard data contents until we reach end of line */ +static BOOL discard_eol( WININETHTTPREQW *req ) +{ + do + { + char *eol = memchr( req->read_buf + req->read_pos, '\n', req->read_size ); + if (eol) + { + remove_data( req, (eol + 1) - (req->read_buf + req->read_pos) ); + break; + } + req->read_pos = req->read_size = 0; /* discard everything */ + if (!read_more_data( req, -1 )) return FALSE; + } while (req->read_size); + return TRUE; +} + +/* read the size of the next chunk */ +static BOOL start_next_chunk( WININETHTTPREQW *req ) +{ + DWORD chunk_size = 0; + + if (!req->dwContentLength) return TRUE; + if (req->dwContentLength == req->dwContentRead) + { + /* read terminator for the previous chunk */ + if (!discard_eol( req )) return FALSE; + req->dwContentLength = ~0u; + req->dwContentRead = 0; + } + for (;;) + { + while (req->read_size) + { + char ch = req->read_buf[req->read_pos]; + if (ch >= '0' && ch <= '9') chunk_size = chunk_size * 16 + ch - '0'; + else if (ch >= 'a' && ch <= 'f') chunk_size = chunk_size * 16 + ch - 'a' + 10; + else if (ch >= 'A' && ch <= 'F') chunk_size = chunk_size * 16 + ch - 'A' + 10; + else if (ch == ';' || ch == '\r' || ch == '\n') + { + TRACE( "reading %u byte chunk\n", chunk_size ); + req->dwContentLength = chunk_size; + req->dwContentRead = 0; + if (!discard_eol( req )) return FALSE; + return TRUE; + } + remove_data( req, 1 ); + } + if (!read_more_data( req, -1 )) return FALSE; + if (!req->read_size) + { + req->dwContentLength = req->dwContentRead = 0; + return TRUE; + } + } +} + +/* return the size of data available to be read immediately */ +static DWORD get_avail_data( WININETHTTPREQW *req ) +{ + if (req->read_chunked && (req->dwContentLength == ~0u || req->dwContentLength == req->dwContentRead)) + return 0; + return min( req->read_size, req->dwContentLength - req->dwContentRead ); +} + +/* check if we have reached the end of the data to read */ +static BOOL end_of_read_data( WININETHTTPREQW *req ) +{ + if (req->read_chunked) return (req->dwContentLength == 0); + if (req->dwContentLength == ~0u) return FALSE; + return (req->dwContentLength == req->dwContentRead); +} + +static BOOL refill_buffer( WININETHTTPREQW *req ) +{ + int len = sizeof(req->read_buf); + + if (req->read_chunked && (req->dwContentLength == ~0u || req->dwContentLength == req->dwContentRead)) + { + if (!start_next_chunk( req )) return FALSE; + } + + if (req->dwContentLength != ~0u) len = min( len, req->dwContentLength - req->dwContentRead ); + if (len <= req->read_size) return TRUE; + + if (!read_more_data( req, len )) return FALSE; + if (!req->read_size) req->dwContentLength = req->dwContentRead = 0; + return TRUE; +} + static void HTTP_ReceiveRequestData(WININETHTTPREQW *req, BOOL first_notif) { INTERNET_ASYNC_RESULT iar; - BYTE buffer[4096]; - int available; - BOOL res; TRACE("%p\n", req); - res = NETCON_recv(&req->netConnection, buffer, - min(sizeof(buffer), req->dwContentLength - req->dwContentRead), - MSG_PEEK, &available); - - if(res) { + if (refill_buffer( req )) { iar.dwResult = (DWORD_PTR)req->hdr.hInternet; - iar.dwError = first_notif ? 0 : available; + iar.dwError = first_notif ? 0 : get_avail_data(req); }else { iar.dwResult = 0; iar.dwError = INTERNET_GetLastError(); @@ -1640,24 +1789,35 @@ sizeof(INTERNET_ASYNC_RESULT)); } -static DWORD HTTP_Read(WININETHTTPREQW *req, void *buffer, DWORD size, DWORD *read, BOOL sync) +static DWORD HTTPREQ_Read(WININETHTTPREQW *req, void *buffer, DWORD size, DWORD *read, BOOL sync) { - int bytes_read; + int len, bytes_read = 0; - if(!NETCON_recv(&req->netConnection, buffer, min(size, req->dwContentLength - req->dwContentRead), - sync ? MSG_WAITALL : 0, &bytes_read)) { - if(req->dwContentLength != -1 && req->dwContentRead != req->dwContentLength) - ERR("not all data received %d/%d\n", req->dwContentRead, req->dwContentLength); + if (req->read_chunked && (req->dwContentLength == ~0u || req->dwContentLength == req->dwContentRead)) + { + if (!start_next_chunk( req )) goto done; + } + if (req->dwContentLength != ~0u) size = min( size, req->dwContentLength - req->dwContentRead ); - /* always return success, even if the network layer returns an error */ - *read = 0; - HTTP_FinishedReading(req); - return ERROR_SUCCESS; + if (req->read_size) + { + bytes_read = min( req->read_size, size ); + memcpy( buffer, req->read_buf + req->read_pos, bytes_read ); + remove_data( req, bytes_read ); } + if (size > bytes_read && (!bytes_read || sync)) + { + if (NETCON_recv( &req->netConnection, (char *)buffer + bytes_read, size - bytes_read, + sync ? MSG_WAITALL : 0, &len)) + bytes_read += len; + /* always return success, even if the network layer returns an error */ + } +done: req->dwContentRead += bytes_read; *read = bytes_read; + TRACE( "retrieved %u bytes (%u/%u)\n", bytes_read, req->dwContentRead, req->dwContentLength ); if(req->lpszCacheFile) { BOOL res; DWORD dwBytesWritten; @@ -1673,95 +1833,6 @@ return ERROR_SUCCESS; } -static DWORD get_chunk_size(const char *buffer) -{ - const char *p; - DWORD size = 0; - - for (p = buffer; *p; p++) - { - if (*p >= '0' && *p <= '9') size = size * 16 + *p - '0'; - else if (*p >= 'a' && *p <= 'f') size = size * 16 + *p - 'a' + 10; - else if (*p >= 'A' && *p <= 'F') size = size * 16 + *p - 'A' + 10; - else if (*p == ';') break; - } - return size; -} - -static DWORD HTTP_ReadChunked(WININETHTTPREQW *req, void *buffer, DWORD size, DWORD *read, BOOL sync) -{ - char reply[MAX_REPLY_LEN], *p = buffer; - DWORD buflen, to_read, to_write = size; - int bytes_read; - - *read = 0; - for (;;) - { - if (*read == size) break; - - if (req->dwContentLength == ~0u) /* new chunk */ - { - buflen = sizeof(reply); - if (!NETCON_getNextLine(&req->netConnection, reply, &buflen)) break; - - if (!(req->dwContentLength = get_chunk_size(reply))) - { - /* zero sized chunk marks end of transfer; read any trailing headers and return */ - HTTP_GetResponseHeaders(req, FALSE); - break; - } - } - to_read = min(to_write, req->dwContentLength - req->dwContentRead); - - if (!NETCON_recv(&req->netConnection, p, to_read, sync ? MSG_WAITALL : 0, &bytes_read)) - { - if (bytes_read != to_read) - ERR("Not all data received %d/%d\n", bytes_read, to_read); - - /* always return success, even if the network layer returns an error */ - *read = 0; - break; - } - if (!bytes_read) break; - - req->dwContentRead += bytes_read; - to_write -= bytes_read; - *read += bytes_read; - - if (req->lpszCacheFile) - { - DWORD dwBytesWritten; - - if (!WriteFile(req->hCacheFile, p, bytes_read, &dwBytesWritten, NULL)) - WARN("WriteFile failed: %u\n", GetLastError()); - } - p += bytes_read; - - if (req->dwContentRead == req->dwContentLength) /* chunk complete */ - { - req->dwContentRead = 0; - req->dwContentLength = ~0u; - - buflen = sizeof(reply); - if (!NETCON_getNextLine(&req->netConnection, reply, &buflen)) - { - ERR("Malformed chunk\n"); - *read = 0; - break; - } - } - } - if (!*read) HTTP_FinishedReading(req); - return ERROR_SUCCESS; -} - -static DWORD HTTPREQ_Read(WININETHTTPREQW *req, void *buffer, DWORD size, DWORD *read, BOOL sync) -{ - if (req->read_chunked) - return HTTP_ReadChunked(req, buffer, size, read, sync); - else - return HTTP_Read(req, buffer, size, read, sync); -} static DWORD HTTPREQ_ReadFile(WININETHANDLEHEADER *hdr, void *buffer, DWORD size, DWORD *read) { @@ -1804,22 +1875,17 @@ INTERNET_SendCallback(&req->hdr, req->hdr.dwContext, INTERNET_STATUS_RECEIVING_RESPONSE, NULL, 0); - if (hdr->dwFlags & INTERNET_FLAG_ASYNC) { - DWORD available = 0; + if ((hdr->dwFlags & INTERNET_FLAG_ASYNC) && !get_avail_data(req)) + { + WORKREQUEST workRequest; - NETCON_query_data_available(&req->netConnection, &available); - if (!available) - { - WORKREQUEST workRequest; + workRequest.asyncproc = HTTPREQ_AsyncReadFileExAProc; + workRequest.hdr = WININET_AddRef(&req->hdr); + workRequest.u.InternetReadFileExA.lpBuffersOut = buffers; - workRequest.asyncproc = HTTPREQ_AsyncReadFileExAProc; - workRequest.hdr = WININET_AddRef(&req->hdr); - workRequest.u.InternetReadFileExA.lpBuffersOut = buffers; + INTERNET_AsyncCall(&workRequest); - INTERNET_AsyncCall(&workRequest); - - return ERROR_IO_PENDING; - } + return ERROR_IO_PENDING; } res = HTTPREQ_Read(req, buffers->lpvBuffer, buffers->dwBufferLength, &buffers->dwBufferLength, @@ -1869,22 +1935,17 @@ INTERNET_SendCallback(&req->hdr, req->hdr.dwContext, INTERNET_STATUS_RECEIVING_RESPONSE, NULL, 0); - if (hdr->dwFlags & INTERNET_FLAG_ASYNC) { - DWORD available = 0; + if ((hdr->dwFlags & INTERNET_FLAG_ASYNC) && !get_avail_data(req)) + { + WORKREQUEST workRequest; - NETCON_query_data_available(&req->netConnection, &available); - if (!available) - { - WORKREQUEST workRequest; + workRequest.asyncproc = HTTPREQ_AsyncReadFileExWProc; + workRequest.hdr = WININET_AddRef(&req->hdr); + workRequest.u.InternetReadFileExW.lpBuffersOut = buffers; - workRequest.asyncproc = HTTPREQ_AsyncReadFileExWProc; - workRequest.hdr = WININET_AddRef(&req->hdr); - workRequest.u.InternetReadFileExW.lpBuffersOut = buffers; + INTERNET_AsyncCall(&workRequest); - INTERNET_AsyncCall(&workRequest); - - return ERROR_IO_PENDING; - } + return ERROR_IO_PENDING; } res = HTTPREQ_Read(req, buffers->lpvBuffer, buffers->dwBufferLength, &buffers->dwBufferLength, @@ -1924,34 +1985,39 @@ static DWORD HTTPREQ_QueryDataAvailable(WININETHANDLEHEADER *hdr, DWORD *available, DWORD flags, DWORD_PTR ctx) { WININETHTTPREQW *req = (WININETHTTPREQW*)hdr; - BYTE buffer[4048]; - BOOL async; TRACE("(%p %p %x %lx)\n", req, available, flags, ctx); - if(!NETCON_query_data_available(&req->netConnection, available) || *available) - return ERROR_SUCCESS; - - /* Even if we are in async mode, we need to determine whether - * there is actually more data available. We do this by trying - * to peek only a single byte in async mode. */ - async = (req->lpHttpSession->lpAppInfo->hdr.dwFlags & INTERNET_FLAG_ASYNC) != 0; - - if (NETCON_recv(&req->netConnection, buffer, - min(async ? 1 : sizeof(buffer), req->dwContentLength - req->dwContentRead), - MSG_PEEK, (int *)available) && async && *available) + if (!(*available = get_avail_data( req ))) { - WORKREQUEST workRequest; + if (end_of_read_data( req )) return ERROR_SUCCESS; - *available = 0; - workRequest.asyncproc = HTTPREQ_AsyncQueryDataAvailableProc; - workRequest.hdr = WININET_AddRef( &req->hdr ); + if (req->lpHttpSession->lpAppInfo->hdr.dwFlags & INTERNET_FLAG_ASYNC) + { + WORKREQUEST workRequest; - INTERNET_AsyncCall(&workRequest); + workRequest.asyncproc = HTTPREQ_AsyncQueryDataAvailableProc; + workRequest.hdr = WININET_AddRef( &req->hdr ); - return ERROR_IO_PENDING; + INTERNET_AsyncCall(&workRequest); + + return ERROR_IO_PENDING; + } + else + { + refill_buffer( req ); + *available = get_avail_data( req ); + } } + if (*available == sizeof(req->read_buf)) /* check if we have even more pending in the socket */ + { + DWORD extra; + if (NETCON_query_data_available(&req->netConnection, &extra)) + *available = min( *available + extra, req->dwContentLength - req->dwContentRead ); + } + + TRACE( "returning %u\n", *available ); return ERROR_SUCCESS; } @@ -2008,6 +2074,7 @@ lpwhr->hdr.refs = 1; lpwhr->hdr.lpfnStatusCB = lpwhs->hdr.lpfnStatusCB; lpwhr->hdr.dwInternalFlags = lpwhs->hdr.dwInternalFlags & INET_CALLBACKW; + lpwhr->dwContentLength = ~0u; WININET_AddRef( &lpwhs->hdr ); lpwhr->lpHttpSession = lpwhs; @@ -2122,7 +2189,10 @@ if (!NETCON_connected(&req->netConnection)) return; if (req->dwContentLength == -1) + { NETCON_close(&req->netConnection); + return; + } do { @@ -3134,6 +3204,7 @@ NETCON_close(&lpwhr->netConnection); if (!HTTP_ResolveName(lpwhr)) return FALSE; if (!NETCON_init(&lpwhr->netConnection, lpwhr->hdr.dwFlags & INTERNET_FLAG_SECURE)) return FALSE; + lpwhr->read_pos = lpwhr->read_size = 0; lpwhr->read_chunked = FALSE; } } @@ -3820,6 +3891,7 @@ bSuccess = TRUE; lend: + lpwhr->read_pos = lpwhr->read_size = 0; lpwhr->read_chunked = FALSE; TRACE("%d <--\n", bSuccess); @@ -3886,7 +3958,7 @@ * We should first receive 'HTTP/1.x nnn OK' where nnn is the status code. */ buflen = MAX_REPLY_LEN; - if (!NETCON_getNextLine(&lpwhr->netConnection, bufferA, &buflen)) + if (!read_line(lpwhr, bufferA, &buflen)) goto lend; rc += buflen; MultiByteToWideChar( CP_ACP, 0, bufferA, buflen, buffer, MAX_REPLY_LEN ); @@ -3938,7 +4010,7 @@ do { buflen = MAX_REPLY_LEN; - if (NETCON_getNextLine(&lpwhr->netConnection, bufferA, &buflen)) + if (read_line(lpwhr, bufferA, &buflen)) { LPWSTR * pFieldAndValue;
diff --git a/dlls/wininet/internet.h b/dlls/wininet/internet.h index fcd95d9..0a4e71b 100644 --- a/dlls/wininet/internet.h +++ b/dlls/wininet/internet.h
@@ -207,6 +207,9 @@ struct HttpAuthInfo *pAuthInfo; struct HttpAuthInfo *pProxyAuthInfo; BOOL read_chunked; /* are we reading in chunked mode? */ + DWORD read_pos; /* current read position in read_buf */ + DWORD read_size; /* valid data size in read_buf */ + char read_buf[4096]; /* buffer for already read but not returned data */ } WININETHTTPREQW, *LPWININETHTTPREQW;