Geoff Chappell - Software Analyst
This function deletes a group from the Content cache container.
BOOL DeleteUrlCacheGroup ( GROUPID GroupId, DWORD dwFlags, LPVOID lpReserved);
The GroupId argument specifies which group to delete.
The dwFlags argument varies the behaviour of the function. No bits are rejected as invalid but only one bit is meaningful:
|0x02||CACHEGROUP_FLAG_FLUSHURL_ONDELETE||delete all URL entries that belong only to this group|
The lpReserved argument must be NULL.
If successful, the function returns TRUE. The function returns FALSE to indicate failure. An error code can be retrieved by calling GetLastError.
If lpReserved is not NULL, the function fails.
If URL caching is not yet initialised, it gets initialised as part of this function. Among other things, this involves loading the registry configuration of all cache containers in the applicable registry set and creating default groups in the Content container. If this initialisation fails, so too does the function.
This function works only with the Content container of the applicable registry set. If no Content container has been loaded, the function fails. The function also fails if it cannot secure its use of the Content container.
There must exist in the container file a GROUP_ENTRY structure for the given group, i.e., with a matching group ID. Otherwise, the group is not defined, and the function fails.
Much of the point to a group’s existence is that URL entries may have been assigned to the group. Now that the group is to be deleted, there needs to be at the very least some bookkeeping so that none of those URL entries retain any reference to the group. Neither should they retain any property that they possess only by belonging to the group. The function therefore enumerates the entries that belong to the group, intending to tidy each in turn:
Premature exit from the enumeration because of an error is not an error for the function.
Removing any connection between entry and group ought to be straightforward, especially if the entry belongs to no other group. Worth confirming may be that if a URL entry has a list of groups that it belongs to, then the LIST_GROUP_ENTRY for this group’s presence in that list is removed from the list and is freed for reuse. It perhaps matters only for performance, rather than as a defect, but if the URL entry is left belonging just to one group, then although the LIST_GROUP_ENTRY for the remaining group might also be freed for reuse, it is not, and the entry is kept as belonging to a one-group list, as opposed to belonging directly to the one group.
The main property a URL entry can have that depends on group membership is its stickiness. If the group that is being deleted has the 0x1000000000000000 bit set in its group ID, then it is a sticky group. URL entries assigned to a sticky group become sticky themselves just for belonging to the group. Such entries are distinguished by having the STICKY_CACHE_ENTRY type but no exempt delta. Each such entry loses its stickiness unless it still belongs to some other sticky group.
Through the dwFlags argument, entries that belong to a group can be made to depend on that group for their very existence: if CACHEGROUP_FLAG_FLUSHURL_ONDELETE is specified in dwFlags, then the function deletes URL entries that belong to this group but to no other.
Having dealt with all URL entries that belong to the group, the function deletes the group itself. In the container file, this means freeing any GROUP_DATA_ENTRY structure that holds a group name or owner storage attribute for the group, and clearing the GROUP_ENTRY structure.
The bookkeeping described above has two defects, as actually coded. First, it ignores some entries. Second, it does not certainly remove all references to the deleted group.
Enumeration of URL entries for the bookkeeping is limited just to entries that match the default filter, meaning specifically those whose cache entry type has no set bit that is not in either of the collections URLCACHE_FIND_DEFAULT_FILTER or INCLUDE_BY_DEFAULT_CACHE_ENTRY. This allows some curious but surely unwelcome effects even for entries that have been created entirely in accordance with the documentation, notably ones that have the EDITED_CACHE_ENTRY or SPARSE_CACHE_ENTRY types. For what looks to be the simplest example, try the following sequence:
The last two steps are just to clean up. Step 6 is the one to watch. The expectation there is that the second group, being newly created at step 5, is empty. This expectation ordinarily is satisfied. However, if the EDITED_CACHE_ENTRY or SPARSE_CACHE_ENTRY type is specified when creating the URL entry at step 2, then the group deletion at step 4 does not even attempt to tidy the URL entry, which is left pointing to a freed GROUP_ENTRY which gets reused at step 5. When the new group is enumerated at step 6 (taking care to specify an interest in the EDITED_CACHE_ENTRY and SPARSE_CACHE_ENTRY types), the URL entry that was assigned to the since-deleted group magically appears to be assigned already to the newly created group.
Note that the behaviour is no different whether CACHEGROUP_FLAG_FLUSHURL_ONDELETE is specified or not at step 4. The same omission that allows the entry’s link to the deleted group to persist for reuse at step 5 also allows the entry itself to persist even when deleting the group is supposed to delete its entries.
Even URL entries that are enumerated can run into another coding error. If the URL entry belongs to a single group (as opposed to a list of groups that happens to have only one member) before the group is deleted, then the function does not correctly update the flags in the hash item for the URL entry. Specifically, it omits to clear the flag that marks the entry as belonging to a group. Other code, for enumerating URL entries, assumes that if this flag is set, then the dword at offset 0x28 in the URL entry is the file offset of a GROUP_ENTRY. It will instead be zero. An effect is that the URL entry will seem to have the file header’s signature as its group entry:
Again, the last step is just to clean up. The problem is at step 5, where the entry that was created at step 2 turns up in an enumeration with a silly group ID.
It is not clear whether the following behaviour is by design or is permitted only by oversight: the function can delete built-in groups. Of course, a deleted built-in group will be re-created, albeit as an empty group, when URL caching is next initialised (for the same registry set, but by any process) and there is anyway only the one built-in group, specifically the one with group ID CACHEGROUP_ID_BUILTIN_STICKY.
The DeleteUrlCacheGroup function is exported by name from WININET.DLL version 4.71 and higher. It has long been documented.
The behaviour described in this note is of version 7.0 from the original Windows Vista.