Nice little directory browser :D
at d888d67f8c52a7a84aca7845424674038b87129e 267 lines 8.2 kB view raw
1<# 2 This file is part of Utatane. 3 4 Utatane is free software: you can redistribute it and/or modify it under the 5 terms of the GNU Affero General Public License as published by the Free 6 Software Foundation, either version 3 of the License, or (at your option) 7 any later version. 8 9 Utatane is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 11 FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for 12 more details. 13 14 You should have received a copy of the GNU Affero General Public License 15 along with Utatane. If not, see <http://www.gnu.org/licenses/>. 16#> 17 18Import-Module Mizumiya 19Import-Module PSParseHTML -Function Optimize-HTML 20 21$Root = (Get-PodeConfig).Utatane.Root 22 23if (-not (Get-Command tailwindcss -ErrorAction SilentlyContinue)) { 24 _warn "The tailwindcss binary is missing. Source styles will not be synced with the public style.css." 25 _warn "This is only needed during development, don't worry about it in production" 26} else { 27 Add-PodeFileWatcher -Path $PSScriptRoot -Exclude "$PSScriptRoot/public" -ScriptBlock { 28 tailwindcss --input $PSScriptRoot/tailwind/style.tw.css --output $PSScriptRoot/public/style.css 29 } 30} 31 32_info "Serving root directory $Root" 33 34# Use-PodeScript -Path functions.ps1 # https://github.com/Badgerati/Pode/issues/1582 35Use-PodeScript -Path $PSScriptRoot/functions.ps1 36 37New-PodeLoggingMethod -Custom -ScriptBlock { 38 param ($Item) 39 40 $Date = $Item.UtcDate | Get-Date -Format s 41 $Query = $Item.Request.Query -eq '-' ? '' : '?' + $Item.Request.Query 42 43 $PrevCol = $PSStyle.Reset + $PSStyle.Foreground.BrightBlack 44 switch ($Item.Response.StatusCode) { 45 {$_ -ge 400} { $Col = $PSStyle.Foreground.BrightRed } 46 {$_ -ge 500} { $Col = $PSStyle.Foreground.White + $PSStyle.Background.Red } 47 default { $Col = '' } 48 } 49 50 $RequestLine = "$($Item.Host) $($Item.Request.Method) $($PSStyle.Foreground.White)$($Item.Request.Resource)$Query$PrevCol $($Item.Request.Protocol) by ""$($Item.Request.Agent)""" 51 $ResponseLine = "$Col$($Item.Response.StatusCode) $($Item.Response.StatusDescription)$PrevCol $(_format_size $Item.Response.Size)" 52 53 _request "$Date | $RequestLine >>> $ResponseLine" 54} | Enable-PodeRequestLogging -Raw 55 56New-PodeLoggingMethod -Custom -ScriptBlock { 57 param ($Item) 58 59 _warn "$($Item.Date | Get-Date -Format s) | $($Item.Level) ($($Item.Server):$($Item.ThreadId)) | $($Item.Category): $($Item.Message)" 60 $Item.StackTrace -split "`n" | % { _warn $_ } 61} | Enable-PodeErrorLogging -Raw -Levels Error, Warning, Informational 62 63Add-PodeEndpoint -Address * -Port 8080 -Protocol HTTP 64 65Set-PodeViewEngine -Type Mizumiya -Extension ps1 -ScriptBlock { 66 param ($Path, $Data) 67 68 . ./functions.ps1 69 70 <# 71 FOR JSON: Output any object and ConvertTo-Json will handle it 72 FOR XMLs and HTML: Your script MUST output a well-formed string in your 73 target format; PowerShell's ConvertTo-Xml is not built for this 74 purpose 75 #> 76 switch -Wildcard ($Path) { 77 '*.html.ps1' { 78 Set-PodeHeader -Name Content-Type -Value 'text/html; charset=utf-8' 79 return ([String] (. $Path -Data $Data)) | Optimize-HTML 80 } 81 82 '*.json.ps1' { 83 Set-PodeHeader -Name Content-Type -Value 'application/json; charset=utf-8' 84 return [PSCustomObject]((. $Path -Data $Data) ?? @{}) | ConvertTo-Json -Compress -Depth 8 85 } 86 87 '*.xml.ps1' { 88 Set-PodeHeader -Name Content-Type -Value 'application/xml; charset=utf-8' 89 return ([String] (. $Path -Data $Data)) 90 } 91 92 '*.xhtml.ps1' { 93 Set-PodeHeader -Name Content-Type -Value 'application/xhtml+xml; charset=utf-8' 94 return ([String] (. $Path -Data $Data)) 95 } 96 97 '*.atom.ps1' { 98 Set-PodeHeader -Name Content-Type -Value 'application/atom+xml; charset=utf-8' 99 return ([String] (. $Path -Data $Data)) 100 } 101 } 102} 103 104$Middleware_DirsHaveTrailingSlash = { 105 $Root = $using:Root 106 $Path = $WebEvent.Path 107 $JoinedPath = Get-Item -Lit (Join-Path $Root $Path) 108 109 if (-not (_is_file $JoinedPath) -and $Path -ne '/' -and $Path -notmatch '/$') { 110 Move-PodeResponseUrl -Url ("$Path/") 111 return $false 112 } 113 114 return $true 115} 116 117Add-PodeMiddleware -Name CheckRootIsAvailable -ScriptBlock { 118 $Root = $using:Root 119 120 if (-not (Test-Path -LiteralPath $Root -PathType Container)) { 121 _log "Root $Root not available!" -Type Warning 122 Set-PodeResponseStatus -Code 503 -Description 'The root directory is not available.' 123 return $false 124 } 125 126 return $true 127} 128 129Add-PodeMiddleware -Name CreateAcceptInWebEvent -ScriptBlock { 130 $WebEvent.RequestAccept = ((Get-PodeHeader 'Accept') ?? 'text/html' | ConvertFrom-AcceptHeader) 131 132 return $true 133} 134 135Add-PodeMiddleware -Name __pode_mw_static_content__ -ScriptBlock { 136 if ($WebEvent.Path -eq '/favicon.ico') { 137 $pubRoute = Find-PodePublicRoute -Path '/favicon.ico' 138 139 if ($null -eq $pubRoute) { 140 Set-PodeResponseStatus -Code 404 141 return $false 142 } 143 144 $cachable = Test-PodeRouteValidForCaching -Path '/favicon.ico' 145 146 Write-PodeFileResponse -FileInfo $pubRoute.FileInfo -MaxAge $PodeContext.Server.Web.Static.Cache.MaxAge -Cache:$cachable 147 return $false 148 } 149 150 if (-not ($WebEvent.Path -like '/.nhnd/*') -or $WebEvent.Path.Length -lt 6) { 151 return $true 152 } 153 154 if ($WebEvent.Path -in ('/.nhnd', '/.nhnd/')) { 155 Set-PodeResponseStatus -Code 404 156 return $false 157 } 158 159 $_path = $WebEvent.Path.SubString(6) # strip /.nhnd 160 $pubRoute = Find-PodePublicRoute -Path $_path 161 162 if ($null -eq $pubRoute) { 163 # using our own directory so we can just blanket deny 164 Set-PodeResponseStatus -Code 404 165 return $false 166 } 167 168 # check current state of caching 169 $cachable = Test-PodeRouteValidForCaching -Path $_path 170 171 # write the file to the response 172 Write-PodeFileResponse -FileInfo $pubRoute.FileInfo -MaxAge $PodeContext.Server.Web.Static.Cache.MaxAge -Cache:$cachable 173 return $false 174} 175 176Enable-PodeSessionMiddleware -Duration 120 -Extend 177 178Add-PodeRouteGroup -Path /api -Routes { 179 Add-PodeRoute -Path /files -Method GET -ScriptBlock { 180 $Root = $using:Root 181 $Path = $WebEvent.Query.Path 182 $JoinedPath = Join-Path $Root $Path 183 184 foreach ($Redirect in (Get-PodeConfig).Utatane.RedirectMap.GetEnumerator()) { 185 if (-not ($Path -eq $Redirect.Key -or ($Path + '/') -eq $Redirect.Key)) { 186 continue 187 } 188 189 $Url = ("/api/files?path=" + (_encode $Redirect.Value)) 190 191 # api doesn't get a message since they probably aren't keeping the 192 # cookie needed for session tracking 193 Move-PodeResponseUrl -Url $Url 194 return 195 } 196 197 if (-not (_check_path $Root $JoinedPath)) { 198 _warn "/api/files: query for $JoinedPath ($Path) refused!" 199 Set-PodeResponseStatus -Code 404 200 return; 201 } 202 203 $Data = @{ Root=$Root; Path=$Path; JoinedPath=$JoinedPath} 204 _render_view -Page api/files -Data $Data 205 } 206 207 Add-PodeRoute -Path /* -Method GET -ScriptBlock { 208 Set-PodeResponseStatus -Code 404 209 } 210} 211 212Add-PodeRoute -Method GET -Path /about -ScriptBlock { 213 _render_view about 214} 215 216Add-PodeRoute -Method GET -Path /* -Middleware $Middleware_DirsHaveTrailingSlash -ScriptBlock { 217 $Root = $using:Root 218 $Path = $WebEvent.Path ?? '/' 219 $JoinedPath = Get-Item -Lit (Join-Path $Root $Path) 220 221 foreach ($Redirect in (Get-PodeConfig).Utatane.RedirectMap.GetEnumerator()) { 222 if ($Path -ne $Redirect.Key) { 223 continue; 224 } 225 226 Add-PodeFlashMessage -name redirect-notice -Message @{ 227 From = $Path 228 To = $Redirect.Value 229 } 230 231 Move-PodeResponseUrl -Url $Redirect.Value 232 return; 233 } 234 235 if ($null -eq $JoinedPath) { 236 _warn "1failed: '$(Join-Path $Root $Path)' for query $($WebEvent.Query|convertto-json -compress)" 237 Set-PodeResponseStatus -Code 404 238 return 239 } 240 241 # No path traversal, no blacklist! 242 if (-not (_check_path $Root $JoinedPath)) { 243 _warn "denied $Path ($JoinedPath)" 244 Set-PodeResponseStatus -Code 404 245 return 246 } 247 248 $IsFile = _is_file $JoinedPath 249 250 if ($IsFile) { 251 Set-PodeResponseAttachment -path ([WildcardPattern]::Escape($JoinedPath)) 252 return 253 } else { 254 # also don't let the server serve itself 255 if ("$JoinedPath/".StartsWith($using:PSScriptRoot)) { 256 _warn 'failed: refusing to serve the server root' 257 Set-PodeResponseStatus -Code 404 258 } 259 260 _render_view -Page index -Data @{ Root=$Root ; Path=$Path } 261 return 262 } 263 264 _warn "failed: '$Root$Rath' for query $($WebEvent.Query|convertto-json -compress)" 265 Set-PodeResponseStatus -Code 404 266 return 267}