Powershellでウェブブラウザを作る3【URIクラスとファイル名に使えない文字】

コンピュータ
PowerShellのあれこれ

URI(URL)を扱う場合URIクラスを使うと便利です。
URIからホスト名やパスなどを個別に取り出したり出来るので、どのようなプロパティがあるか実際に試してみました。

<#
.SYNOPSIS
 URIオブジェクト
.DESCRIPTION
 説明
#>

param (
    [string]$Uri = "https://maywork.net/test/ua.php"
)


function Main
{
    param (
        $uriStr
    )
    
    $uri = [URI]::new($uriStr)

    Write-Host "Uriオブジェクトのプロパティ一覧"
    Write-Host "-------------------------------"
    Write-Host ("AbsolutePath:{0}" -f $uri.AbsolutePath)
    # AbsolutePath:/test/ua.php
    Write-Host ("AbsoluteUri:{0}" -f $uri.AbsoluteUri)
    # AbsoluteUri:https://maywork.net/test/ua.php
    Write-Host ("Authority:{0}" -f $uri.Authority)
    # Authority:maywork.net
    Write-Host ("DnsSafeHost:{0}" -f $uri.DnsSafeHost)
    # DnsSafeHost:maywork.net
    Write-Host ("Fragment:{0}" -f $uri.Fragment)
    # Fragment:
    Write-Host ("Host:{0}" -f $uri.Host)
    # Host:maywork.net
    Write-Host ("HostNameType:{0}" -f $uri.HostNameType)
    # HostNameType:Dns
    Write-Host ("IdnHost:{0}" -f $uri.IdnHost)
    # IdnHost:maywork.net
    Write-Host ("IsAbsoluteUri:{0}" -f $uri.IsAbsoluteUri)
    # IsAbsoluteUri:True
    Write-Host ("IsDefaultPort:{0}" -f $uri.IsDefaultPort)
    # IsDefaultPort:True
    Write-Host ("IsFile:{0}" -f $uri.IsFile)
    # IsFile:False
    Write-Host ("IsLoopback:{0}" -f $uri.IsLoopback)
    # IsLoopback:False
    Write-Host ("IsUnc:{0}" -f $uri.IsUnc)
    # IsUnc:False
    Write-Host ("LocalPath:{0}" -f $uri.LocalPath)
    # LocalPath:/test/ua.php
    Write-Host ("OriginalString:{0}" -f $uri.OriginalString)
    # OriginalString:https://maywork.net/test/ua.php
    Write-Host ("PathAndQuery:{0}" -f $uri.PathAndQuery)
    # PathAndQuery:/test/ua.php
    Write-Host ("Port:{0}" -f $uri.Port)
    # Port:443
    Write-Host ("Query:{0}" -f $uri.Query)
    # Query:
    Write-Host ("Scheme:{0}" -f $uri.Scheme)
    # Scheme:https
    Write-Host ("Segments:{0}" -f $uri.Segments)
    # Segments:/
    Write-Host ("UserEscaped:{0}" -f $uri.UserEscaped)
    # UserEscaped:False
    Write-Host ("UserInfo:{0}" -f $uri.UserInfo)
    # UserInfo:
}

Main $Uri

結構沢山プロパティがあります。

今回Webサイトをダウンロードしてローカルに保存する機能を作りたいと考えているのですが、ローカルに保存するパスをURIアドレスからホスト名+パス+クエリにしたいと思います。
ポートやユーザーはあまりお目にかかることが無いです。スキーマやフラグメントなどは盛り込もうとすると面倒なので割愛します。

この場合使えそうなプロパティは、

ホスト名
$uri.Host
パス+クエリ
$uri.PathAndQuery

になります。PathとQueryを別々に取得することもできますが、PathAndQueryを使うとひと手間減らすことが出来ます。

これを踏まえたてURIの文字列からローカルの保存先のパスを生成する関数を作ってみます。

<#
.SYNOPSIS
 URIからローカルパスを生成
.DESCRIPTION
 説明
#>

param (
    [string]$Uri = "https://maywork.net/test/ua.php",
    [string]$RootDir = ""
)


function Convert-URItoLcoalPath
{
    param (
        $uriStr,
        $rootDir
    )
    
    $uri = [URI]::new($uriStr)

    $HostPathAndQuer = $uri.Host + $uri.PathAndQuery

    $outPath = Join-Path $rootDir $HostPathAndQuer
    
    $outPath
}

if ($RootDir -eq "")
{
    $RootDir = Join-Path ([Environment]::GetFolderPath("MyDocuments")) "web"
}

Convert-URItoLcoalPath $Uri $RootDir
# C:\Users\karet\Documents\web\maywork.net\test\ua.php

URIからそのままローカルパスを生成すると、Windowsのディレクトリ名やファイル名に使えない文字が含まれる可能性があります。
その対応をしたいと思います。

Windowsのファイル名に使えない文字
\/:*?”><|

\/はWindowsでもパスの区切り文字ですのでケアする必要はなさそうです。
それ以外は特別な意味があり、パス中に存在するのはNGになります。
:ドライブレターの区切り文字
?任意の一文字を表す特殊記号
*任意の文字列を表す特殊記号
<>|はパイプやリダイレクト
”もダメです。文字列を表す区切り文字だからでしょうか?ちなみに’はOKです

使えない文字を何か別の文字に代替する必要があります。

今回はregex.replaceの置換機能で該当文字をHttpUtility.UrlEncodeでエンコードし置換することにします。

また、パスにファイル名が含まれていない場合、ローカルではディレクトリを指すパスになりすが、webサイトの場合ファイル名を省略したwebページと解釈されるケースが多いです。
今回はデフォルトファイル名としてindex.htmlを補完します。
拡張子が無い場合ローカルではファイルタイプが不明となり都合が悪いので.htmlを補います。

<#
.SYNOPSIS
 URIからローカルパスを生成
.DESCRIPTION
 説明
#>

using namespace System.Web
using namespace System.IO

param (
    [string]$Uri = "https://maywork.net/test/ngword""?|",
    [string]$RootDir = ""
)


function Convert-URItoLcoalPath
{
    param (
        $uriStr,
        $rootDir
    )
    
    $uri = [URI]::new($uriStr)
    $HostPathAndQuer = $uri.Host + $uri.PathAndQuery

    
    # エンコード
    $pattern = "[:""\*\?\<\>\|]"

    $HostPathAndQuer = [regex]::replace($HostPathAndQuer, $pattern, { [HttpUtility]::UrlEncode($args.value) })

    # 末尾が/の場合
    if ("/" -eq $HostPathAndQuer.Substring($HostPathAndQuer.Length - 1, 1))
    {
        $HostPathAndQuer = $HostPathAndQuer + "index.html"
    }


    $outPath = Join-Path $rootDir $HostPathAndQuer

    # 拡張子がない場合
    if ([Path]::GetExtension($outPath) -eq "")
    {
        $outPath = $outPath + ".html"
    }
    
    $outPath
}

if ($RootDir -eq "")
{
    $RootDir = Join-Path ([Environment]::GetFolderPath("MyDocuments")) "web"
}

Convert-URItoLcoalPath $Uri $RootDir
# C:\Users\karet\Documents\web\maywork.net\test\ngword%22%3f%7C.html

試してみて気が付いたのですがUrlEncodeで*はエンコードしてくれないようです。
個別対応が必要ですが、*が含まれるURIを見たことが無いのでそのままにしておきます。

コメント