¿Por qué FindNextUrlCacheEntry() de WinInet devuelve ERROR_FILE_NOT_FOUND=2 en lugar de ERROR_NO_MORE_ITEMS=295?

¿Por qué FindNextUrlCacheEntry() de WinInet devuelve ERROR_FILE_NOT_FOUND=2 en lugar de ERROR_NO_MORE_ITEMS=295?

Descripción

Según el título, WinInet FindNextUrlCacheEntry()genera un código de error no documentado de 2 (ERROR_FILE_NOT_FOUND) en lugar de ERROR_NO_MORE_ITEMS=295. ¿Alguien puede explicar por qué o sugerir otras formas de investigar?

Ambiente

  • ventanas 10
  • Es decir 11
  • VisualStudio 2006 (pero se puede reconstruir en VS2010)

Sinopsis

Detalles más adelante, pero el quid de la cuestión es que dentro de un bucle, este fragmento de código:

/ Get the next entry
if (!FindNextUrlCacheEntry(hCache, *plpCacheEntry, &bufSize)) 
{
    DWORD d = GetLastError();
    ATLTRACE(_T("getNextEntry - FindNExtUrlCacheEntry returned %d: %s ==> %s"), d, (*plpCacheEntry)->lpszSourceUrlName, (*plpCacheEntry)->lpszLocalFileName);
    // Are we done?
            // <.... more error handling logic ....>

da como resultado esta salida de seguimiento:

>>[12536] getNextEntry - FindNExtUrlCacheEntry returned 2: (null) ==> (null)

Si pirateamos nuestro código para tratar esta condición de manera similar a ERROR_NO_MORE_ITEMS, todo funcionará como se esperaba. Si ignoramos esto y continuamos intentando obtener más entradas, obtenemos un bucle infinito.

Descripción general

Nota: para esta publicación, trato un código de error de 2 como sinónimo de ERROR_FILE_NOT_FOUND, pero como es un código no documentado para la llamada API en cuestión, el 2 puede en realidad ser de otra cosa.

Tenemos un control ActiveX C++ de alrededor de VisualStudio 2006 que se ejecuta en IE. Su propósito es realizar una limpieza parcial de la caché de IE para los archivos que coinciden con el nombre de host de nuestro servidor de aplicaciones. (Esta acción se controla comparando los números de versión entre un valor de cookie de solicitud del servidor con el último almacenado en el cliente. La eliminación solo se produce cuando se detecta una discrepancia de versión).

El control ActiveX ha funcionado bien durante muchos años. Pero recientemente comenzó a fallar como se mencionó anteriormente. No sabemos si se debe a una actualización de Windows de KB o a iniciativas de seguridad recientes, o algo más. Nuestro código maneja adecuadamente el caso ERROR_NO_MORE_ITEMS y finaliza su trabajo. Pero trata el 2 como un error y la aplicación no continúa.

La documentación para FindNextUrlCacheEntry() está aquí: https://docs.microsoft.com/en-us/windows/win32/api/wininet/nf-wininet-findnexturlcacheentrya La llamada devuelve verdadero/falso y establece el código de error en ERROR_NO_MORE_ITEMS o ERROR_INSUFFICIENT_BUFFER.

Nuestra lógica:

Puedo proporcionar detalles pero dudo en publicar código propietario. Brevemente, la lógica sigue los pasos recomendados para esto. Un ejemplo esta aquihttps://support.microsoft.com/en-us/help/815718/how-to-clear-the-cache-when-your-application-hosts-a-webbrowser-controexcepto que no tratamos con "grupos de caché". Puedes desplazarte por el código que comienza después del comentario.

// Start to delete URLs that do not belong to any group.

Nuestra lógica de todos modos es la siguiente:

  1. Obtenga un control del caché a través de FindFirstUrlCacheEntry(), reasignando memoria según sea necesario para el búfer de entrada y verificando GetNextError()el éxito.
  2. Recorra esta y las siguientes entradas a través de FindNextUrlCacheEntry(), reasignando memoria según sea necesario para el búfer de entrada, verificando el valor de retorno verdadero/falso
  3. Si es verdadero: Llame DeleteUrlCacheEntry()si los nombres de host coinciden y la entrada no es del tipo: COOKIE_CACHE_ENTRYo HISTORY_CACHE_ENTRY. De lo contrario omita la entrada
  4. Si es falso: comprobamos el valor de GetNextError()y hacemos lo siguiente:
  5. ERROR_NO_MORE_ITEMS(=259): finalice el bucle y salga exitosamente del punto de entrada ActiveX
  6. Establezca el código de retorno del punto de entrada del control ActiveX al código de error (2 en nuestro caso). La persona que llama JavaScript prueba 0 y detiene la alerta () del usuario del error con instrucciones para llamar al soporte.

Solución alterna:

La única solución para permitir que la aplicación funcione es hacer que el usuario elimine todo el caché de IE. En este punto, FindFirstUrlCacheEntry() no encuentra nada y nunca se ingresa al ciclo de eliminación. (En realidad, esto es una especulación ya que no podemos probar este caso explícitamente. Consulte las notas de depuración a continuación). En cualquier caso, el error no ocurre.

Esfuerzos de depuración:

Podemos forzar que la versión de la cookie no coincida estableciendo un punto de interrupción en el código JavaScript y modificando manualmente la cadena de versión antes de realizar la comparación. Esto "engaña" al código para que llame al punto de entrada de eliminación del control ActiveX.

Estamos extremadamente limitados aquí. Sólo tenemos acceso ocasional a una única PC donde el problema se reproduce. Los esfuerzos para conectar de forma remota un depurador de VisualStudio a través de msvsmon.exe nos permiten ver la salida de ATLTRACE (también conocido como printf), pero no podemos activar los puntos de interrupción. En cambio, estamos reducidos a ATLTRACE() y DebugView de Sysinternals.

Lo que vemos es que ERROR_FILE_NOT_FOUND actúa como ERROR_NO_MORE_FILES. Es decir, si pirateamos el código C++ para terminar el bucle buscar-siguiente/eliminar en cualquiera de las condiciones, siempre elimina algunos archivos esperados y se completa sin errores. Sin embargo, es difícil saber si realmente no hay más archivos o no: interrogar manualmente el caché de IE es, en el mejor de los casos, complicado, y las medidas de seguridad de nuestros PC lo hacen aún más. Por ejemplo, las subcarpetas de C:\Users\<usr>\AppData\Local\Microsoft\Windows\INetCache\Low\IE\...no aparecen en un directorio cmd o power-shell o en el comando ls. Aún así, parece que cuantas más páginas de nuestra aplicación accedamos en el cliente, más archivos borra el control ActiveX antes de que finalice. Un caso funcional de esto se ve así en DebugView:

[12536] DeleteCacheEntries(10.3.31.28:9080) BEGIN: hr=0 pErrCode=0
[12536] deleteCacheEntriesFor(10.3.31.28:9080) BEGIN
[12536] deleteCacheEntriesFor(10.3.31.28:9080) - getFirstEntry returned 0
[12536] deleteCacheEntryIf - (lpdErrCd now=0) deleting: http://10.3.31.28:9080/images/our.jpg ==> C:\Users\<user>\AppData\Local\Microsoft\Windows\INetCache\Low\IE\Q6HU0E35\our[1].jpg
....<lots more "deleting" ...>
[12536] getNextEntry - FindNExtUrlCacheEntry returned 2: (null) ==> (null)
[12536] getNextEntry - ERROR=2: (null) ==> (null)
[12536] deleteCacheEntriesFor(10.3.31.28:9080) - getNextEntry returned 2. Breaking out of loop!
[12536] deleteCacheEntriesFor(10.3.31.28:9080) - Complete. Closing URL cache handle current. errCd=0
[12536] deleteCacheEntriesFor(10.3.31.28:9080) - Done. Returning 0

También intentamos un truco que supone que ERROR_FILE_NOT_FOUND simplemente significa que hay una entrada de caché allí, pero de alguna manera no podemos verla. Es decir, en lugar de salir del bucle, continuamos con la esperanza de encontrar otras entradas "encontrables" para posiblemente eliminar. Pero en este caso el ciclo nunca termina. Aquí "nunca" significa varios minutos y más de 2 millones de casos de:

[5712] getNextEntry - FindNExtUrlCacheEntry returned 2: (null) ==> (null)
[5712] deleteCacheEntriesFor(10.3.31.28:9080) - getNextEntry returned 2. CONTINUING!
...<forever>...

Especulación:

Aquí hay algunas especulaciones ciertamente débiles sobre lo que podría estar pasando.

  • Algún software de seguridad o parche de KB de Windows interviene en la ejecución de C++ y provoca que se llame a SetLastError(2), lo que destruye los valores documentados establecidos por FindNextUrlCacheEntry()
  • Nuestro código está vinculado a versiones anteriores del SDK de VisualStudio y estamos teniendo un código sesgado
  • Pensamos en la corrupción de pila o registro, pero el comportamiento parece demasiado reproducible para esto. Aún...?
  • Realmente hay algo ahí para FindNextUrlCacheEntry(), pero la seguridad impide que sea visible de alguna manera y su "cursor" en el caché nunca avanza

Respuesta1

Esto resulta deberse a un defecto en la API de Micrsoft WinINet. Los detalles completos se pueden encontrar en el hilo correspondiente en su foro devcommunity/visualstudioaquí. (La respuesta está en mis publicaciones finales en la parte inferior).

información relacionada