Skip to content

Edge Cases and Errors

m4bwav edited this page Sep 30, 2026 · 1 revision

What comes back for odd input and failing servers, on .NET 10 and on .NET Framework 4.8. Online outputs came from the local stand-in described on How URLs are checked, which also lists what each path answers.

Not a validator

Both methods guess. HasImageExtension reads a name, and IsImageUrlAsync trusts the server's Content-Type header without looking at the bytes. A server that sends a PNG as application/octet-stream gets false, and any body labelled image/png gets true, since the body is never read. application/x-imagefile is false in 2.0.0 because it does not start with image/ (1.0.2's IsImageUrl said true, and still does).

Input that is not an http or https URL never reaches the network in IsImageUrlAsync: a relative avatar.png is false even though HasImageExtension("avatar.png") is true. Check with HasImageExtension first when your input may be a bare file name.

Exceptions

HasImageExtension never throws. IsImageUrlAsync throws:

Exception When
ArgumentNullException the HttpClient you passed is null; thrown by the call, before any task exists
HttpRequestException the server cannot be reached, closes without answering, answers something that is not HTTP, or fails TLS
TaskCanceledException the client's timeout passed (10 seconds for the shared client) or the token was cancelled

It never throws for a malformed URL, another scheme or an unhelpful answer: those are false.

The messages differ by runtime. On .NET 10 (and the same on .NET 8):

http://images.test/close => HttpRequestException: An error occurred while sending the request.
  inner HttpIOException: The response ended prematurely. (ResponseEnded)
http://images.test/garbage => HttpRequestException: Received an invalid status line: 'NOT HTTP AT ALL'.
https://secure.test/avatar => HttpRequestException: The SSL connection could not be established, see inner exception.
  inner AuthenticationException: The remote certificate is invalid because of errors in the certificate chain: UntrustedRoot

On .NET Framework 4.8 every one has the same outer message, and the detail is in an inner WebException:

http://images.test/close => HttpRequestException: An error occurred while sending the request.
  inner WebException: The underlying connection was closed: The connection was closed unexpectedly.
http://images.test/garbage => HttpRequestException: An error occurred while sending the request.
  inner WebException: The server committed a protocol violation. Section=ResponseStatusLine
https://secure.test/avatar => HttpRequestException: An error occurred while sending the request.
  inner WebException: The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel.
  inner AuthenticationException: The remote certificate is invalid according to the validation procedure.

Catch the exception type, not the message.

A null client

using IsImageUrlDotNet;

HttpClient? none = null;
try
{
    Task<bool> pending = "http://images.test/avatar".IsImageUrlAsync(none!);
    Console.WriteLine("returned a task");
}
catch (ArgumentNullException e)
{
    Console.WriteLine(e.Message);
}

On .NET 10 and .NET 8 this prints:

Value cannot be null. (Parameter 'httpClient')

and on .NET Framework 4.8:

Value cannot be null.
Parameter name: httpClient

"returned a task" never prints: the exception comes from the call, not from awaiting it.

Timeouts and cancellation

Against a stand-in path that accepts the request and never answers:

shared client: TaskCanceledException after 10 s: The request was canceled due to the configured HttpClient.Timeout of 10 seconds elapsing.
  inner TimeoutException: The operation was canceled.
  inner TaskCanceledException: The operation was canceled.
  inner IOException: Unable to read data from the transport connection: The I/O operation has been aborted because of either a thread exit or an application request..
  inner SocketException: The I/O operation has been aborted because of either a thread exit or an application request.
client with Timeout 1 s: TaskCanceledException after 1 s: The request was canceled due to the configured HttpClient.Timeout of 1 seconds elapsing.
cancelled token, http://images.test/avatar => TaskCanceledException   (no request)
cancelled token, http://images.test/a.png => True   (no request)

That is .NET 10 on Windows; on Linux the two innermost messages read Operation canceled. On .NET Framework 4.8 both timeouts say only A task was canceled., with no inner exception:

shared client: TaskCanceledException after 10 s: A task was canceled.
client with Timeout 1 s: TaskCanceledException after 1 s: A task was canceled.

A token that is already cancelled stops the call before any request, except when the extension decides: then the answer is true and the token is never looked at. Elapsed times are rounded to whole seconds.

Redirects by client

With the shared client, or any client whose handler has AllowAutoRedirect = false, the package follows redirects under its own rules. A client that follows redirects itself (the default for new HttpClient()) applies the handler's rules first; the package then follows any 3xx the handler still returns. On .NET 10:

AllowAutoRedirect=false http://images.test/photo => True   [GET http://images.test/photo; GET http://images.test/avatar]
AllowAutoRedirect=false http://images.test/loop => False   [GET http://images.test/loop x 11]
AllowAutoRedirect=false http://images.test/to-ftp => False   [GET http://images.test/to-ftp]
AllowAutoRedirect=false http://images.test/to-https => HttpRequestException   [GET http://images.test/to-https; CONNECT secure.test:443]
new HttpClient() http://images.test/photo => True   [GET http://images.test/photo; GET http://images.test/avatar]
new HttpClient() http://images.test/loop => False   [GET http://images.test/loop x 561]
new HttpClient() http://images.test/to-ftp => False   [GET http://images.test/to-ftp; GET http://images.test/a.png]
new HttpClient() http://images.test/to-https => HttpRequestException   [GET http://images.test/to-https; CONNECT secure.test:443]
stand-in TLS, AllowAutoRedirect=false https://secure.test/avatar => True   [CONNECT secure.test:443; GET /avatar]
stand-in TLS, AllowAutoRedirect=false https://secure.test/photo => True   [CONNECT secure.test:443; GET /photo; CONNECT secure.test:443; GET /avatar]
stand-in TLS, AllowAutoRedirect=false https://secure.test/to-http => False   [CONNECT secure.test:443; GET /to-http]
stand-in TLS, AllowAutoRedirect=true https://secure.test/to-http => False   [CONNECT secure.test:443; GET /to-http]

The "stand-in TLS" rows used a test client that trusts only the stand-in's throwaway certificate, so the https rules could be seen; /to-http on secure.test redirects to http://images.test/avatar. What the rows show:

  • A redirect loop through a client that follows redirects took 561 requests: the handler followed 50 redirects each time (51 requests), and the package asked again 10 more times.
  • .NET 10's handler followed the ftp://images.test/a.png location as if it were http (GET http://images.test/a.png). The package itself does not follow it.
  • Neither .NET 10's handler nor the package follows https to http.

On .NET Framework 4.8 two rows differ. The ftp: row:

new HttpClient() http://images.test/to-ftp => False   [GET http://images.test/to-ftp]

and the https to http row:

stand-in TLS, AllowAutoRedirect=true https://secure.test/to-http => True   [CONNECT secure.test:443; GET /to-http; GET http://images.test/avatar]

.NET Framework's handler does not follow the ftp: location, but it does follow https to http, and the answer then comes from the http page. To keep the package's rules on every runtime, pass a client with AllowAutoRedirect = false (recipe). .NET 8 matched .NET 10.

Untrusted URLs

IsImageUrlAsync requests whatever http or https URL it is given, loopback and private addresses included, and follows redirects to other hosts. The README's advice stands for a service that checks URLs from its users: pass your own client whose handler refuses private and loopback addresses and has AllowAutoRedirect = false. That handler was not written or run for this wiki. Never pass such URLs to the obsolete IsImageUrl, which follows redirects to any host and reads local files for file: URLs (Versions and upgrading).

C# compile errors

url.IsImageUrlAsync(default) does not compile (CS0121, ambiguous between the token and the client overloads). The message is on the API reference.

Clone this wiki locally