The function is implemented by setting cache file pointer to fill read_buf. Consequently, when HTTPREQ_ReadFile is called, it will try reading data from cache after read_buf is depleted, before continuing reading from http stream.
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=26570
-- v6: wininet: Partially implement InternetSetFilePointer
From: Jason Kuo j20001970@gmail.com
--- dlls/wininet/tests/http.c | 97 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+)
diff --git a/dlls/wininet/tests/http.c b/dlls/wininet/tests/http.c index 8fcb9df293a..8e30b220c10 100644 --- a/dlls/wininet/tests/http.c +++ b/dlls/wininet/tests/http.c @@ -581,6 +581,102 @@ static void close_async_handle(HINTERNET handle, int handle_cnt) CHECK_NOTIFIED2(INTERNET_STATUS_HANDLE_CLOSING, handle_cnt); }
+static void InternetSetFilePointer_test(void) +{ + HINTERNET hi = 0, hic = 0, hor = 0; + BOOL res, expect; + DWORD i, pos, err; + + hi = InternetOpenA("Winetest", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0); + ok((hi != 0x0), "InternetOpen failed with error %lu\n", GetLastError()); + if(hi == 0x0) goto abort; + + hic = InternetConnectA(hi, "test.winehq.org", INTERNET_DEFAULT_HTTP_PORT, + NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0); + ok((hic != 0x0), "InternetConnect failed with error %lu\n", GetLastError()); + if(hic == 0x0) goto abort; + + hor = HttpOpenRequestA(hic, NULL, "/favicon.ico", NULL, NULL, NULL, + INTERNET_FLAG_RELOAD|INTERNET_FLAG_DONT_CACHE, + 0x0); + ok((hor != 0x0), "HttpOpenRequest failed with error %lu\n", GetLastError()); + if(hor == 0x0) goto abort; + + pos = InternetSetFilePointer(hor, 0, NULL, FILE_BEGIN, 0); + err = pos == INVALID_SET_FILE_POINTER ? GetLastError() : NO_ERROR; + expect = pos == INVALID_SET_FILE_POINTER && err == ERROR_INTERNET_INVALID_OPERATION; + ok(expect, "Expected position %#x. Got %#lx. GetLastError() %lu\n", INVALID_SET_FILE_POINTER, pos, err); + if(hor) InternetCloseHandle(hor); + + hor = HttpOpenRequestA(hic, NULL, "/favicon.ico", NULL, NULL, NULL, + INTERNET_FLAG_RELOAD, + 0x0); + ok((hor != 0x0), "HttpOpenRequest failed with error %lu\n", GetLastError()); + if(hor == 0x0) goto abort; + + res = HttpSendRequestW(hor, NULL, 0, NULL, 0); + ok(res, "HttpSendRequest failed with error %lu\n", GetLastError()); + + i = 0; + while(i < 4) { + pos = InternetSetFilePointer(hor, i, NULL, FILE_BEGIN, 0); + err = pos == INVALID_SET_FILE_POINTER ? GetLastError() : NO_ERROR; + expect = pos == i && err == NO_ERROR; + ok(expect, "Expected position %#lx. Got %#lx. GetLastError() %lu\n", i, pos, err); + i = i + 1; + } + while(i > 0) { + pos = InternetSetFilePointer(hor, i, NULL, FILE_BEGIN, 0); + err = pos == INVALID_SET_FILE_POINTER ? GetLastError() : NO_ERROR; + expect = pos == i && err == NO_ERROR; + ok(expect, "Expected position %#lx. Got %#lx. GetLastError() %lu\n", i, pos, err); + i = i - 1; + } + + InternetSetFilePointer(hor, 0, NULL, FILE_BEGIN, 0); + i = 0; + while(i < 4) { + i = i + 1; + pos = InternetSetFilePointer(hor, 1, NULL, FILE_CURRENT, 0); + err = pos == INVALID_SET_FILE_POINTER ? GetLastError() : NO_ERROR; + expect = pos == i && err == NO_ERROR; + todo_wine ok(expect, "Expected position %#lx. Got %#lx. GetLastError() %lu\n", i, pos, err); + } + while(i > 0) { + i = i - 1; + pos = InternetSetFilePointer(hor, -1, NULL, FILE_CURRENT, 0); + err = pos == INVALID_SET_FILE_POINTER ? GetLastError() : NO_ERROR; + expect = pos == INVALID_SET_FILE_POINTER && err == ERROR_NEGATIVE_SEEK; + ok(expect, "Expected position %#x. Got %#lx. GetLastError() %lu\n", INVALID_SET_FILE_POINTER, pos, err); + } + + pos = InternetSetFilePointer(hor, INT_MAX, NULL, FILE_BEGIN, 0); + err = pos == INVALID_SET_FILE_POINTER ? GetLastError() : NO_ERROR; + expect = pos == INT_MAX && err == NO_ERROR; + ok(expect, "Expected position %#x. Got %#lx. GetLastError() %lu\n", INT_MAX, pos, err); + pos = InternetSetFilePointer(hor, 1, NULL, FILE_CURRENT, 0); + err = pos == INVALID_SET_FILE_POINTER ? GetLastError() : NO_ERROR; + expect = pos == (DWORD)INT_MAX+1 && err == NO_ERROR; + todo_wine ok(expect, "Expected position %#lx. Got %#lx. GetLastError() %lu\n", (DWORD)INT_MAX+1, pos, err); + + pos = InternetSetFilePointer(hor, INT_MAX, NULL, FILE_BEGIN, 0); + err = pos == INVALID_SET_FILE_POINTER ? GetLastError() : NO_ERROR; + expect = pos == INT_MAX && err == NO_ERROR; + ok(expect, "Expected position %#x. Got %#lx. GetLastError() %lu\n", INT_MAX, pos, err); + pos = InternetSetFilePointer(hor, -1, NULL, FILE_CURRENT, 0); + err = pos == INVALID_SET_FILE_POINTER ? GetLastError() : NO_ERROR; + expect = pos == INVALID_SET_FILE_POINTER && err == ERROR_NEGATIVE_SEEK; + ok(expect, "Expected position %#x. Got %#lx. GetLastError() %lu\n", INVALID_SET_FILE_POINTER, pos, err); + +abort: + if(hor) + InternetCloseHandle(hor); + if(hic) + InternetCloseHandle(hic); + if(hi) + InternetCloseHandle(hi); +} + static void InternetReadFile_test(int flags, const test_data_t *test) { char *post_data = NULL; @@ -7946,6 +8042,7 @@ START_TEST(http) InternetReadFile_chunked_test(); HttpSendRequestEx_test(); InternetReadFile_test(INTERNET_FLAG_ASYNC, &test_data[3]); + InternetSetFilePointer_test(); test_connection_failure(); test_default_service_port(); test_concurrent_header_access();
From: Jason Kuo j20001970@gmail.com
The function is implemented by setting cache file pointer to fill read_buf. Consequently, when HTTPREQ_ReadFile is called, it will try reading data from cache after read_buf is depleted, before continuing reading from http stream.
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=26570 --- dlls/wininet/http.c | 16 ++++++++++ dlls/wininet/internet.c | 71 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 84 insertions(+), 3 deletions(-)
diff --git a/dlls/wininet/http.c b/dlls/wininet/http.c index c9d1c463982..4424f490638 100644 --- a/dlls/wininet/http.c +++ b/dlls/wininet/http.c @@ -3184,6 +3184,22 @@ static DWORD HTTPREQ_ReadFile(object_header_t *hdr, void *buf, DWORD size, DWORD req->read_pos += read; }
+ if(read < size && req->hCacheFile) { + DWORD cache_pos, err; + while(read < size) { + cache_pos = SetFilePointer(req->hCacheFile, 0, NULL, FILE_CURRENT); + err = cache_pos == INVALID_SET_FILE_POINTER ? GetLastError() : NO_ERROR; + if(err != NO_ERROR) + ERR("SetFilePointer failed with error %ld\n", err); + if(cache_pos <= GetFileSize(req->hCacheFile, NULL)) + break; + res = ReadFile(req->hCacheFile, (char*)buf+read, size-read, &cread, NULL); + read += cread; + if(res != ERROR_SUCCESS || !cread) + break; + } + } + if(read < size && (!read || !(flags & IRF_NO_WAIT)) && !end_of_read_data(req)) { LeaveCriticalSection(&req->read_section); INTERNET_SendCallback(&req->hdr, req->hdr.dwContext, INTERNET_STATUS_RECEIVING_RESPONSE, NULL, 0); diff --git a/dlls/wininet/internet.c b/dlls/wininet/internet.c index 292fd44dd8e..bcb0f2fe2db 100644 --- a/dlls/wininet/internet.c +++ b/dlls/wininet/internet.c @@ -2154,14 +2154,79 @@ INTERNET_STATUS_CALLBACK WINAPI InternetSetStatusCallbackW(
/*********************************************************************** * InternetSetFilePointer (WININET.@) + * Sets read position for an open internet file. + * + * RETURNS + * Current position of the file on success + * INVALID_SET_FILE_POINTER on failure */ DWORD WINAPI InternetSetFilePointer(HINTERNET hFile, LONG lDistanceToMove, PVOID pReserved, DWORD dwMoveContext, DWORD_PTR dwContext) { - FIXME("(%p %ld %p %ld %Ix): stub\n", hFile, lDistanceToMove, pReserved, dwMoveContext, dwContext); + DWORD cache_pos, size, read, cread; + DWORD err = ERROR_INTERNET_INVALID_OPERATION, res = INVALID_SET_FILE_POINTER; + http_request_t *request; + + TRACE("(%p %ld %p %ld %Ix)\n", hFile, lDistanceToMove, pReserved, dwMoveContext, dwContext); + + request = (http_request_t*)get_handle_object(hFile); + + if(request->hdr.dwFlags & (INTERNET_FLAG_DONT_CACHE|INTERNET_FLAG_NO_CACHE_WRITE)) + goto lend;
- SetLastError(ERROR_INTERNET_INVALID_OPERATION); - return INVALID_SET_FILE_POINTER; + switch (dwMoveContext) { + case FILE_BEGIN: + cache_pos = lDistanceToMove; + break; + case FILE_CURRENT: + FIXME("dwMoveContext %ld not implemented\n", dwMoveContext); + err = ERROR_NEGATIVE_SEEK; + goto lend; + case FILE_END: + default: + FIXME("Unhandled dwMoveContext %ld\n", dwMoveContext); + goto lend; + } + res = cache_pos; + + size = GetFileSize(request->hCacheFile, NULL); + err = size == INVALID_FILE_SIZE ? GetLastError() : NO_ERROR; + if(err != NO_ERROR) { + ERR("GetFileSize failed with error %ld\n", err); + goto lend; + } + if(size < cache_pos) { + BYTE buf[1024]; + BOOL ret; + while(size < cache_pos) { + ret = InternetReadFile(hFile, buf, 1024, &cread); + if(ret != ERROR_SUCCESS || !cread) + break; + size = GetFileSize(request->hCacheFile, NULL); + } + } + + cache_pos = SetFilePointer(request->hCacheFile, lDistanceToMove, NULL, dwMoveContext); + err = cache_pos == INVALID_SET_FILE_POINTER ? GetLastError() : NO_ERROR; + if(err != NO_ERROR) { + ERR("SetFilePointer failed with error %ld\n", err); + goto lend; + } + EnterCriticalSection(&request->read_section); + read = min(READ_BUFFER_SIZE, GetFileSize(request->hCacheFile, NULL)-cache_pos); + if(ReadFile(request->hCacheFile, request->read_buf, read, &cread, NULL)) { + request->read_size = cread; + request->read_pos = 0; + } + LeaveCriticalSection(&request->read_section); + +lend: + if(res == INVALID_SET_FILE_POINTER) + SetLastError(err); + TRACE("returning %ld\n", res); + if(request) + WININET_Release( &request->hdr ); + return res; }
/***********************************************************************
On Tue Nov 8 04:15:30 2022 +0000, **** wrote:
Marvin replied on the mailing list:
Hi, It looks like your patch introduced the new failures shown below. Please investigate and fix them before resubmitting your patch. If they are not new, fixing them anyway would help a lot. Otherwise please ask for the known failures list to be updated. The tests also ran into some preexisting test failures. If you know how to fix them that would be helpful. See the TestBot job for the details: The full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=125881 Your paranoid android. === debian11 (32 bit ar:MA report) === wininet: http.c:625: Test failed: Expected position 0. Got 0xffffffff. GetLastError() 6 http.c:625: Test failed: Expected position 0x1. Got 0xffffffff. GetLastError() 6 http.c:625: Test failed: Expected position 0x2. Got 0xffffffff. GetLastError() 6 http.c:625: Test failed: Expected position 0x3. Got 0xffffffff. GetLastError() 6 http.c:632: Test failed: Expected position 0x4. Got 0xffffffff. GetLastError() 6 http.c:632: Test failed: Expected position 0x3. Got 0xffffffff. GetLastError() 6 http.c:632: Test failed: Expected position 0x2. Got 0xffffffff. GetLastError() 6 http.c:632: Test failed: Expected position 0x1. Got 0xffffffff. GetLastError() 6 http.c:656: Test failed: Expected position 0x7fffffff. Got 0xffffffff. GetLastError() 6 http.c:665: Test failed: Expected position 0x7fffffff. Got 0xffffffff. GetLastError() 6 === debian11 (32 bit hi:IN report) === wininet: http.c:625: Test failed: Expected position 0. Got 0xffffffff. GetLastError() 6 http.c:625: Test failed: Expected position 0x1. Got 0xffffffff. GetLastError() 6 http.c:625: Test failed: Expected position 0x2. Got 0xffffffff. GetLastError() 6 http.c:625: Test failed: Expected position 0x3. Got 0xffffffff. GetLastError() 6 http.c:632: Test failed: Expected position 0x4. Got 0xffffffff. GetLastError() 6 http.c:632: Test failed: Expected position 0x3. Got 0xffffffff. GetLastError() 6 http.c:632: Test failed: Expected position 0x2. Got 0xffffffff. GetLastError() 6 http.c:632: Test failed: Expected position 0x1. Got 0xffffffff. GetLastError() 6 http.c:656: Test failed: Expected position 0x7fffffff. Got 0xffffffff. GetLastError() 6 http.c:665: Test failed: Expected position 0x7fffffff. Got 0xffffffff. GetLastError() 6
The reason for those failures is that we expect tests to work after each commit. You could, for example, add todo_wines in the first patch and then remove them in the second one.
Also, it would be interesting to have tests checking the actual functionality. For example, read some data after moving the file pointer and make sure it contains expected value.
Jacek Caban (@jacek) commented about dlls/wininet/http.c:
req->read_pos += read; }
if(read < size && req->hCacheFile) {
DWORD cache_pos, err;
while(read < size) {
There is no reason for the loop here, reading from regular file will return all available data anyway.
Jacek Caban (@jacek) commented about dlls/wininet/http.c:
req->read_pos += read; }
if(read < size && req->hCacheFile) {
DWORD cache_pos, err;
while(read < size) {
cache_pos = SetFilePointer(req->hCacheFile, 0, NULL, FILE_CURRENT);
err = cache_pos == INVALID_SET_FILE_POINTER ? GetLastError() : NO_ERROR;
if(err != NO_ERROR)
ERR("SetFilePointer failed with error %ld\n", err);
if(cache_pos <= GetFileSize(req->hCacheFile, NULL))
break;
Instead of checking those manually, you could just handle it by interpreting `ReadFile` result.
Jacek Caban (@jacek) commented about dlls/wininet/internet.c:
FIXME("Unhandled dwMoveContext %ld\n", dwMoveContext);
goto lend;
- }
- res = cache_pos;
- size = GetFileSize(request->hCacheFile, NULL);
- err = size == INVALID_FILE_SIZE ? GetLastError() : NO_ERROR;
- if(err != NO_ERROR) {
ERR("GetFileSize failed with error %ld\n", err);
goto lend;
- }
- if(size < cache_pos) {
BYTE buf[1024];
BOOL ret;
while(size < cache_pos) {
ret = InternetReadFile(hFile, buf, 1024, &cread);
I think that this will not work with asynchronous request. MSDN suggests that `InternetSetFilePointer` is synchronous, but may cause later `InternetReadFile` to block. It would be interesting to have more tests for that. It may be useful to use our helpers that use local server and control when reply is sent, see `test_http_read()` for an example.
On Tue Nov 8 17:37:06 2022 +0000, Jacek Caban wrote:
The reason for those failures is that we expect tests to work after each commit. You could, for example, add todo_wines in the first patch and then remove them in the second one. Also, it would be interesting to have tests checking the actual functionality. For example, read some data after moving the file pointer and make sure it contains expected value.
Understood, will do more tests to check if the function actually works before updating the patch.