From 7b79583bde2036ad3bd4144a18b8dce893761b06 Mon Sep 17 00:00:00 2001 From: Matthew Kelly Date: Mon, 6 May 2019 13:08:55 +0100 Subject: [PATCH] #243: Logic for static routes to download files --- src/Tools/Middleware.ps1 | 13 +++++--- src/Tools/Responses.ps1 | 44 ++++++++++++++++----------- src/Tools/Routes.ps1 | 38 +++++++++++++++++------ tests/unit/Tools/Middleware.Tests.ps1 | 10 +++--- 4 files changed, 69 insertions(+), 36 deletions(-) diff --git a/src/Tools/Middleware.ps1 b/src/Tools/Middleware.ps1 index 4864c8ad7..b20073f01 100644 --- a/src/Tools/Middleware.ps1 +++ b/src/Tools/Middleware.ps1 @@ -127,8 +127,8 @@ function Get-PodePublicMiddleware param($e) # get the static file path - $path = Get-PodeStaticRoutePath -Route $e.Path -Protocol $e.Protocol -Endpoint $e.Endpoint - if ($null -eq $path) { + $info = Get-PodeStaticRoutePath -Route $e.Path -Protocol $e.Protocol -Endpoint $e.Endpoint + if (Test-Empty $info.Path) { return $true } @@ -147,8 +147,13 @@ function Get-PodePublicMiddleware } } - # write the file to the response - File -Path $path -MaxAge $PodeContext.Server.Web.Static.Cache.MaxAge -Cache:$caching + # write, or attach, the file to the response + if ($info.Download) { + Attach -Path $info.Path -Literal + } + else { + File -Path $info.Path -MaxAge $PodeContext.Server.Web.Static.Cache.MaxAge -Cache:$caching + } # static content found, stop return $false diff --git a/src/Tools/Responses.ps1 b/src/Tools/Responses.ps1 index a2338f8c9..76a687642 100644 --- a/src/Tools/Responses.ps1 +++ b/src/Tools/Responses.ps1 @@ -144,14 +144,19 @@ function Attach { param ( [Parameter(Mandatory=$true)] - [ValidateNotNullOrEmpty()] [Alias('p')] [string] - $Path + $Path, + + [switch] + [Alias('l')] + $Literal ) - # only attach files from public/static-route directories - $Path = Get-PodeStaticRoutePath -Route $Path + # only attach files from public/static-route directories when path is relative + if (!$Literal) { + $Path = (Get-PodeStaticRoutePath -Route $Path).Path + } # test the file path, and set status accordingly if (!(Test-PodePath $Path)) { @@ -161,24 +166,27 @@ function Attach $filename = Get-PodeFileName -Path $Path $ext = Get-PodeFileExtension -Path $Path -TrimPeriod - # open up the file as a stream - $fs = (Get-Item $Path).OpenRead() + try { + # open up the file as a stream + $fs = (Get-Item $Path).OpenRead() - # setup the response details and headers - $WebEvent.Response.ContentLength64 = $fs.Length - $WebEvent.Response.SendChunked = $false - $WebEvent.Response.ContentType = (Get-PodeContentType -Extension $ext) - $WebEvent.Response.AddHeader('Content-Disposition', "attachment; filename=$($filename)") + # setup the response details and headers + $WebEvent.Response.ContentLength64 = $fs.Length + $WebEvent.Response.SendChunked = $false + $WebEvent.Response.ContentType = (Get-PodeContentType -Extension $ext) + $WebEvent.Response.AddHeader('Content-Disposition', "attachment; filename=$($filename)") - # set file as an attachment on the response - $buffer = [byte[]]::new(64 * 1024) - $read = 0 + # set file as an attachment on the response + $buffer = [byte[]]::new(64 * 1024) + $read = 0 - while (($read = $fs.Read($buffer, 0, $buffer.Length)) -gt 0) { - $WebEvent.Response.OutputStream.Write($buffer, 0, $read) + while (($read = $fs.Read($buffer, 0, $buffer.Length)) -gt 0) { + $WebEvent.Response.OutputStream.Write($buffer, 0, $read) + } + } + finally { + dispose $fs } - - dispose $fs } function Save diff --git a/src/Tools/Routes.ps1 b/src/Tools/Routes.ps1 index 64b012611..7e7919d53 100644 --- a/src/Tools/Routes.ps1 +++ b/src/Tools/Routes.ps1 @@ -77,6 +77,7 @@ function Get-PodeRoute 'Defaults' = $found.Defaults; 'Protocol' = $found.Protocol; 'Endpoint' = $found.Endpoint; + 'Download' = $found.Download; 'File' = $Matches['file']; } } @@ -113,11 +114,19 @@ function Get-PodeStaticRoutePath # attempt to get a static route for the path $found = Get-PodeRoute -HttpMethod 'static' -Route $Route -Protocol $Protocol -Endpoint $Endpoint + $path = $null + $download = $false # if we have a defined static route, use that if ($null -ne $found) { + # is the found route set as download only? + if ($found.Download) { + $download = $true + $path = (Join-Path $found.Path (coalesce $found.File ([string]::Empty))) + } + # if there's no file, we need to check defaults - if (!(Test-PodePathIsFile $found.File) -and (Get-PodeCount @($found.Defaults)) -gt 0) + elseif (!(Test-PodePathIsFile $found.File) -and (Get-PodeCount @($found.Defaults)) -gt 0) { $found.File = (coalesce $found.File ([string]::Empty)) @@ -134,16 +143,19 @@ function Get-PodeStaticRoutePath } } - return (Join-Path $found.Path $found.File) + $path = (Join-Path $found.Path $found.File) } # else, use the public static directory (but only if path is a file, and a public dir is present) - if ((Test-PodePathIsFile $Route) -and !(Test-Empty $PodeContext.Server.InbuiltDrives['public'])) { - return (Join-Path $PodeContext.Server.InbuiltDrives['public'] $Route) + elseif ((Test-PodePathIsFile $Route) -and !(Test-Empty $PodeContext.Server.InbuiltDrives['public'])) { + $path = (Join-Path $PodeContext.Server.InbuiltDrives['public'] $Route) } - # otherwise, just return null - return $null + # return the route details + return @{ + 'Path' = $path; + 'Download' = $download; + } } function Get-PodeRouteByUrl @@ -228,7 +240,11 @@ function Route [switch] [Alias('rm')] - $Remove + $Remove, + + [switch] + [Alias('do')] + $DownloadOnly ) # uppercase the method @@ -262,7 +278,7 @@ function Route # add a new dynamic or static route if ($HttpMethod -ieq 'static') { Add-PodeStaticRoute -Route $Route -Path ([string](@($Middleware))[0]) -Protocol $Protocol ` - -Endpoint $Endpoint -Defaults $Defaults + -Endpoint $Endpoint -Defaults $Defaults -DownloadOnly:$DownloadOnly } else { if ((Get-PodeCount $Defaults) -gt 0) { @@ -479,7 +495,10 @@ function Add-PodeStaticRoute [Parameter()] [string] - $Endpoint + $Endpoint, + + [switch] + $DownloadOnly ) # store the route method @@ -523,6 +542,7 @@ function Add-PodeStaticRoute 'Defaults' = $Defaults; 'Protocol' = $Protocol; 'Endpoint' = $Endpoint.Trim(); + 'Download' = $DownloadOnly; }) } diff --git a/tests/unit/Tools/Middleware.Tests.ps1 b/tests/unit/Tools/Middleware.Tests.ps1 index d60ba9178..3c8a4fdaa 100644 --- a/tests/unit/Tools/Middleware.Tests.ps1 +++ b/tests/unit/Tools/Middleware.Tests.ps1 @@ -451,7 +451,7 @@ Describe 'Get-PodePublicMiddleware' { $r.Name | Should Be '@public' $r.Logic | Should Not Be $null - Mock Get-PodeStaticRoutePath { return $null } + Mock Get-PodeStaticRoutePath { return @{ 'Path' = $null } } (. $r.Logic @{ 'Path' = '/'; 'Protocol' = 'http'; 'Endpoint' = ''; }) | Should Be $true @@ -470,7 +470,7 @@ Describe 'Get-PodePublicMiddleware' { }} }} - Mock Get-PodeStaticRoutePath { return '/' } + Mock Get-PodeStaticRoutePath { return @{ 'Path' = '/' } } Mock File { } (. $r.Logic @{ 'Path' = '/'; 'Protocol' = 'http'; 'Endpoint' = ''; @@ -491,7 +491,7 @@ Describe 'Get-PodePublicMiddleware' { }} }} - Mock Get-PodeStaticRoutePath { return '/' } + Mock Get-PodeStaticRoutePath { return @{ 'Path' = '/' } } Mock File { } (. $r.Logic @{ 'Path' = '/'; 'Protocol' = 'http'; 'Endpoint' = ''; @@ -512,7 +512,7 @@ Describe 'Get-PodePublicMiddleware' { }} }} - Mock Get-PodeStaticRoutePath { return '/' } + Mock Get-PodeStaticRoutePath { return @{ 'Path' = '/' } } Mock File { } (. $r.Logic @{ 'Path' = '/'; 'Protocol' = 'http'; 'Endpoint' = ''; @@ -532,7 +532,7 @@ Describe 'Get-PodePublicMiddleware' { }} }} - Mock Get-PodeStaticRoutePath { return '/' } + Mock Get-PodeStaticRoutePath { return @{ 'Path' = '/' } } Mock File { } (. $r.Logic @{ 'Path' = '/'; 'Protocol' = 'http'; 'Endpoint' = '';