Skip to content

Commit d08e3ba

Browse files
committedMar 1, 2025
Squashed changes from PR Badgerati#1509
1 parent 440be89 commit d08e3ba

File tree

4 files changed

+121
-51
lines changed

4 files changed

+121
-51
lines changed
 

‎docs/Tutorials/Endpoints/Basics.md

+36
Original file line numberDiff line numberDiff line change
@@ -188,3 +188,39 @@ To set this property, include it in `server.psd1` configuration file as shown be
188188
}
189189
}
190190
```
191+
192+
## Favicons
193+
194+
Pode allows you to customize or disable the favicon for HTTP/HTTPS endpoints. By default, Pode serves a built-in `favicon.ico`, but you can override this behavior using the `-Favicon` and `-NoFavicon` parameters.
195+
196+
- **`-Favicon` (byte[])**: Allows you to specify a custom favicon as a byte array.
197+
- **`-NoFavicon` (switch)**: Disables the favicon, preventing browsers from requesting it.
198+
199+
### **Favicon Format and Specifications**
200+
201+
Favicons are typically stored in the `.ico` format, which is a container that can hold multiple image sizes and color depths. This ensures compatibility with different browsers and devices. Some modern browsers also support `.png` and `.svg` favicons.
202+
203+
For more details on favicon formats and specifications, refer to the [Favicon specification](https://en.wikipedia.org/wiki/Favicon) and [RFC 5988](https://datatracker.ietf.org/doc/html/rfc5988).
204+
205+
### **Favicon Size Recommendations**
206+
207+
Favicons should include multiple resolutions for optimal display across different devices. Recommended sizes include:
208+
209+
- **16x16** → Used in browser tabs, bookmarks, and address bars.
210+
- **32x32** → Used in browser tabs on higher-resolution displays.
211+
- **48x48** → Used by some older browsers and web applications.
212+
- **64x64+** → Generally not used by browsers but can be helpful for scalability in web apps.
213+
- **256x256** → Mainly for **Windows app icons** (not typically used as a favicon in browsers).
214+
215+
### **Usage Example**
216+
217+
```powershell
218+
# Load a custom favicon from file
219+
$iconBytes = [System.IO.File]::ReadAllBytes("C:\path\to\custom.ico")
220+
Add-PodeEndpoint -Address localhost -Port 8080 -Protocol Http -Favicon $iconBytes
221+
222+
# Disable favicon for an endpoint
223+
Add-PodeEndpoint -Address localhost -Port 8080 -Protocol Http -NoFavicon
224+
```
225+
226+
Using a favicon enhances the user experience by providing a recognizable site icon in browser tabs and bookmarks.

‎src/Misc/favicon.ico

1.12 KB
Binary file not shown.

‎src/Private/PodeServer.ps1

+47-40
Original file line numberDiff line numberDiff line change
@@ -210,59 +210,66 @@ function Start-PodeWebServer {
210210
if ($Request.IsAborted) {
211211
throw $Request.Error
212212
}
213-
214-
# if we have an sse clientId, verify it and then set details in WebEvent
215-
if ($WebEvent.Request.HasSseClientId) {
216-
if (!(Test-PodeSseClientIdValid)) {
217-
throw [Pode.PodeRequestException]::new("The X-PODE-SSE-CLIENT-ID value is not valid: $($WebEvent.Request.SseClientId)")
218-
}
219-
220-
if (![string]::IsNullOrEmpty($WebEvent.Request.SseClientName) -and !(Test-PodeSseClientId -Name $WebEvent.Request.SseClientName -ClientId $WebEvent.Request.SseClientId)) {
221-
throw [Pode.PodeRequestException]::new("The SSE Connection being referenced via the X-PODE-SSE-NAME and X-PODE-SSE-CLIENT-ID headers does not exist: [$($WebEvent.Request.SseClientName)] $($WebEvent.Request.SseClientId)", 404)
222-
}
223-
224-
$WebEvent.Sse = @{
225-
Name = $WebEvent.Request.SseClientName
226-
Group = $WebEvent.Request.SseClientGroup
227-
ClientId = $WebEvent.Request.SseClientId
228-
LastEventId = $null
229-
IsLocal = $false
230-
}
213+
214+
# deal with favicon if available
215+
if ($WebEvent.Path -eq '/favicon.ico' -and ($null -ne $PodeContext.Server.Endpoints[$context.EndpointName].Favicon)) {
216+
# Write the file content as the HTTP response
217+
Write-PodeTextResponse -Bytes $PodeContext.Server.Endpoints[$context.EndpointName].Favicon -ContentType 'image/png' -StatusCode 200
231218
}
219+
else {
220+
# if we have an sse clientId, verify it and then set details in WebEvent
221+
if ($WebEvent.Request.HasSseClientId) {
222+
if (!(Test-PodeSseClientIdValid)) {
223+
throw [Pode.PodeRequestException]::new("The X-PODE-SSE-CLIENT-ID value is not valid: $($WebEvent.Request.SseClientId)")
224+
}
225+
226+
if (![string]::IsNullOrEmpty($WebEvent.Request.SseClientName) -and !(Test-PodeSseClientId -Name $WebEvent.Request.SseClientName -ClientId $WebEvent.Request.SseClientId)) {
227+
throw [Pode.PodeRequestException]::new("The SSE Connection being referenced via the X-PODE-SSE-NAME and X-PODE-SSE-CLIENT-ID headers does not exist: [$($WebEvent.Request.SseClientName)] $($WebEvent.Request.SseClientId)", 404)
228+
}
232229

233-
# invoke global and route middleware
234-
if ((Invoke-PodeMiddleware -Middleware $PodeContext.Server.Middleware -Route $WebEvent.Path)) {
235-
# has the request been aborted
236-
if ($Request.IsAborted) {
237-
throw $Request.Error
230+
$WebEvent.Sse = @{
231+
Name = $WebEvent.Request.SseClientName
232+
Group = $WebEvent.Request.SseClientGroup
233+
ClientId = $WebEvent.Request.SseClientId
234+
LastEventId = $null
235+
IsLocal = $false
236+
}
238237
}
239238

240-
if ((Invoke-PodeMiddleware -Middleware $WebEvent.Route.Middleware)) {
239+
# invoke global and route middleware
240+
if ((Invoke-PodeMiddleware -Middleware $PodeContext.Server.Middleware -Route $WebEvent.Path)) {
241241
# has the request been aborted
242242
if ($Request.IsAborted) {
243243
throw $Request.Error
244244
}
245245

246-
# invoke the route
247-
if ($null -ne $WebEvent.StaticContent) {
248-
$fileBrowser = $WebEvent.Route.FileBrowser
249-
if ($WebEvent.StaticContent.IsDownload) {
250-
Write-PodeAttachmentResponseInternal -Path $WebEvent.StaticContent.Source -FileBrowser:$fileBrowser
246+
if ((Invoke-PodeMiddleware -Middleware $WebEvent.Route.Middleware)) {
247+
# has the request been aborted
248+
if ($Request.IsAborted) {
249+
throw $Request.Error
251250
}
252-
elseif ($WebEvent.StaticContent.RedirectToDefault) {
253-
$file = [System.IO.Path]::GetFileName($WebEvent.StaticContent.Source)
254-
Move-PodeResponseUrl -Url "$($WebEvent.Path)/$($file)"
251+
252+
# invoke the route
253+
if ($null -ne $WebEvent.StaticContent) {
254+
$fileBrowser = $WebEvent.Route.FileBrowser
255+
if ($WebEvent.StaticContent.IsDownload) {
256+
Write-PodeAttachmentResponseInternal -Path $WebEvent.StaticContent.Source -FileBrowser:$fileBrowser
257+
}
258+
elseif ($WebEvent.StaticContent.RedirectToDefault) {
259+
$file = [System.IO.Path]::GetFileName($WebEvent.StaticContent.Source)
260+
Move-PodeResponseUrl -Url "$($WebEvent.Path)/$($file)"
261+
}
262+
else {
263+
$cachable = $WebEvent.StaticContent.IsCachable
264+
Write-PodeFileResponseInternal -Path $WebEvent.StaticContent.Source -MaxAge $PodeContext.Server.Web.Static.Cache.MaxAge `
265+
-Cache:$cachable -FileBrowser:$fileBrowser
266+
}
255267
}
256-
else {
257-
$cachable = $WebEvent.StaticContent.IsCachable
258-
Write-PodeFileResponseInternal -Path $WebEvent.StaticContent.Source -MaxAge $PodeContext.Server.Web.Static.Cache.MaxAge `
259-
-Cache:$cachable -FileBrowser:$fileBrowser
268+
elseif ($null -ne $WebEvent.Route.Logic) {
269+
$null = Invoke-PodeScriptBlock -ScriptBlock $WebEvent.Route.Logic -Arguments $WebEvent.Route.Arguments `
270+
-UsingVariables $WebEvent.Route.UsingVariables -Scoped -Splat
260271
}
261272
}
262-
elseif ($null -ne $WebEvent.Route.Logic) {
263-
$null = Invoke-PodeScriptBlock -ScriptBlock $WebEvent.Route.Logic -Arguments $WebEvent.Route.Arguments `
264-
-UsingVariables $WebEvent.Route.UsingVariables -Scoped -Splat
265-
}
266273
}
267274
}
268275
}

‎src/Public/Endpoint.ps1

+38-11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
<#
32
.SYNOPSIS
43
Bind an endpoint to listen for incoming Requests.
@@ -19,13 +18,13 @@ An optional hostname for the endpoint, specifying a hostname restricts access to
1918
The protocol of the supplied endpoint.
2019
2120
.PARAMETER Certificate
22-
The path to a certificate that can be use to enable HTTPS
21+
The path to a certificate that can be used to enable HTTPS.
2322
2423
.PARAMETER CertificatePassword
25-
The password for the certificate file referenced in Certificate
24+
The password for the certificate file referenced in Certificate.
2625
2726
.PARAMETER CertificateKey
28-
A key file to be paired with a PEM certificate file referenced in Certificate
27+
A key file to be paired with a PEM certificate file referenced in Certificate.
2928
3029
.PARAMETER CertificateThumbprint
3130
A certificate thumbprint to bind onto HTTPS endpoints (Windows).
@@ -34,13 +33,13 @@ A certificate thumbprint to bind onto HTTPS endpoints (Windows).
3433
A certificate subject name to bind onto HTTPS endpoints (Windows).
3534
3635
.PARAMETER CertificateStoreName
37-
The name of a certifcate store where a certificate can be found (Default: My) (Windows).
36+
The name of a certificate store where a certificate can be found (Default: My) (Windows).
3837
3938
.PARAMETER CertificateStoreLocation
40-
The location of a certifcate store where a certificate can be found (Default: CurrentUser) (Windows).
39+
The location of a certificate store where a certificate can be found (Default: CurrentUser) (Windows).
4140
4241
.PARAMETER X509Certificate
43-
The raw X509 certificate that can be use to enable HTTPS
42+
The raw X509 certificate that can be used to enable HTTPS.
4443
4544
.PARAMETER TlsMode
4645
The TLS mode to use on secure connections, options are Implicit or Explicit (SMTP only) (Default: Implicit).
@@ -58,16 +57,16 @@ A quick description of the Endpoint - normally used in OpenAPI.
5857
An optional Acknowledge message to send to clients when they first connect, for TCP and SMTP endpoints only.
5958
6059
.PARAMETER SslProtocol
61-
One or more optional SSL Protocols this endpoints supports. (Default: SSL3/TLS12 - Just TLS12 on MacOS).
60+
One or more optional SSL Protocols this endpoint supports. (Default: SSL3/TLS12 - Just TLS12 on MacOS).
6261
6362
.PARAMETER CRLFMessageEnd
6463
If supplied, TCP endpoints will expect incoming data to end with CRLF.
6564
6665
.PARAMETER Force
67-
Ignore Adminstrator checks for non-localhost endpoints.
66+
Ignore Administrator checks for non-localhost endpoints.
6867
6968
.PARAMETER SelfSigned
70-
Create and bind a self-signed certifcate for HTTPS endpoints.
69+
Create and bind a self-signed certificate for HTTPS endpoints.
7170
7271
.PARAMETER AllowClientCertificate
7372
Allow for client certificates to be sent on requests.
@@ -85,6 +84,12 @@ For IPv6, this will only work if the IPv6 address can convert to a valid IPv4 ad
8584
.PARAMETER Default
8685
If supplied, this endpoint will be the default one used for internally generating URLs.
8786
87+
.PARAMETER Favicon
88+
A byte array representing a custom favicon for HTTP/HTTPS endpoints.
89+
90+
.PARAMETER NoFavicon
91+
If supplied, disables the default favicon for HTTP/HTTPS endpoints.
92+
8893
.EXAMPLE
8994
Add-PodeEndpoint -Address localhost -Port 8090 -Protocol Http
9095
@@ -208,7 +213,13 @@ function Add-PodeEndpoint {
208213
$DualMode,
209214

210215
[switch]
211-
$Default
216+
$Default,
217+
218+
[byte[]]
219+
$Favicon,
220+
221+
[switch]
222+
$NoFavicon
212223
)
213224

214225
# error if serverless
@@ -292,6 +303,20 @@ function Add-PodeEndpoint {
292303
throw ($PodeLocale.crlfMessageEndCheckOnlySupportedOnTcpEndpointsExceptionMessage)
293304
}
294305

306+
if (! $NoFavicon ) {
307+
$Favicon = $null
308+
}
309+
# Load the default favicon
310+
elseif ( ($null -eq $Favicon) -and (@('Http', 'Https') -icontains $Protocol)) {
311+
$podeRoot = Get-PodeModuleMiscPath
312+
if (Test-PodeIsPSCore) {
313+
$Favicon = (Get-Content -Path ([System.IO.Path]::Combine($podeRoot, 'favicon.ico')) -Raw -AsByteStream)
314+
}
315+
else {
316+
$Favicon = (Get-Content -Path ([System.IO.Path]::Combine($podeRoot, 'favicon.ico')) -Raw -Encoding byte)
317+
}
318+
}
319+
295320
# new endpoint object
296321
$obj = @{
297322
Name = $Name
@@ -324,8 +349,10 @@ function Add-PodeEndpoint {
324349
Acknowledge = $Acknowledge
325350
CRLFMessageEnd = $CRLFMessageEnd
326351
}
352+
Favicon = $Favicon
327353
}
328354

355+
329356
# set ssl protocols
330357
if (!(Test-PodeIsEmpty $SslProtocol)) {
331358
$obj.Ssl.Protocols = (ConvertTo-PodeSslProtocol -Protocol $SslProtocol)

0 commit comments

Comments
 (0)
Please sign in to comment.