Skip to content

Commit 0e23015

Browse files
chawyehsuse35710
authored andcommitted
fix(config): Ensure manipulating config with UTF8 encoding (ScoopInstaller#4644)
1 parent acfb3b1 commit 0e23015

4 files changed

Lines changed: 90 additions & 63 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
- **autoupdate:** Allow checksum file that contains whitespaces ([#4619](https://github.com/ScoopInstaller/Scoop/issues/4619))
2020
- **autoupdate:** Rename $response to $res ([#4706](https://github.com/ScoopInstaller/Scoop/issues/4706))
21+
- **config:** Ensure manipulating config with UTF8 encoding ([#4644](https://github.com/ScoopInstaller/Scoop/issues/4644))
2122
- **config:** Allow scoop config use Unicode characters ([#4631](https://github.com/ScoopInstaller/Scoop/issues/4631))
2223
- **config:** Fix `set_config` bugs ([#3681](https://github.com/ScoopInstaller/Scoop/issues/3681))
2324
- **current:** Remove 'current' while it's not a junction ([#4687](https://github.com/ScoopInstaller/Scoop/issues/4687))

lib/core.ps1

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@ function load_cfg($file) {
4141
}
4242

4343
try {
44-
return (Get-Content $file -Raw | ConvertFrom-Json -ErrorAction Stop)
44+
# ReadAllLines will detect the encoding of the file automatically
45+
# Ref: https://docs.microsoft.com/en-us/dotnet/api/system.io.file.readalllines?view=netframework-4.5
46+
$content = [System.IO.File]::ReadAllLines($file)
47+
return ($content | ConvertFrom-Json -ErrorAction Stop)
4548
} catch {
4649
Write-Host "ERROR loading $file`: $($_.exception.message)"
4750
}
@@ -80,7 +83,8 @@ function set_config {
8083
$scoopConfig.PSObject.Properties.Remove($name)
8184
}
8285

83-
ConvertTo-Json $scoopConfig | Set-Content -Path $configFile -Encoding Default
86+
# Save config with UTF8NoBOM encoding
87+
ConvertTo-Json $scoopConfig | Out-UTF8File -FilePath $configFile
8488
return $scoopConfig
8589
}
8690

@@ -1092,6 +1096,22 @@ function get_magic_bytes_pretty($file, $glue = ' ') {
10921096
return (get_magic_bytes $file | ForEach-Object { $_.ToString('x2') }) -join $glue
10931097
}
10941098

1099+
function Out-UTF8File {
1100+
param(
1101+
[Parameter(Mandatory = $True, Position = 0)]
1102+
[Alias("Path")]
1103+
[String] $FilePath,
1104+
[Parameter(ValueFromPipeline = $True)]
1105+
[PSObject] $InputObject
1106+
)
1107+
process {
1108+
# Ref: https://stackoverflow.com/questions/5596982
1109+
# Performance Note: `WriteAllLines` throttles memory usage while
1110+
# `WriteAllText` needs to keep the complete string in memory.
1111+
[System.IO.File]::WriteAllLines($FilePath, $InputObject)
1112+
}
1113+
}
1114+
10951115
##################
10961116
# Core Bootstrap #
10971117
##################

lib/install.ps1

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,9 @@ function dl_with_cache_aria2($app, $version, $manifest, $architecture, $dir, $co
289289
if (-not($download_finished)) {
290290
# write aria2 input file
291291
if ($urlstxt_content -ne '') {
292-
Set-Content -Path $urlstxt $urlstxt_content
292+
ensure $cachedir | Out-Null
293+
# Write aria2 input-file with UTF8NoBOM encoding
294+
$urlstxt_content | Out-UTF8File -FilePath $urlstxt
293295
}
294296

295297
# build aria2 command
@@ -298,6 +300,10 @@ function dl_with_cache_aria2($app, $version, $manifest, $architecture, $dir, $co
298300
# handle aria2 console output
299301
Write-Host 'Starting download with aria2 ...'
300302

303+
# Set console output encoding to UTF8 for non-ASCII characters printing
304+
$oriConsoleEncoding = [Console]::OutputEncoding
305+
[Console]::OutputEncoding = New-Object System.Text.UTF8Encoding
306+
301307
Invoke-Expression $aria2 | ForEach-Object {
302308
# Skip blank lines
303309
if ([String]::IsNullOrWhiteSpace($_)) { return }
@@ -333,6 +339,9 @@ function dl_with_cache_aria2($app, $version, $manifest, $architecture, $dir, $co
333339
Remove-Item $urlstxt -Force -ErrorAction SilentlyContinue
334340
Remove-Item "$($data.$url.source).aria2*" -Force -ErrorAction SilentlyContinue
335341
}
342+
343+
# Revert console encoding
344+
[Console]::OutputEncoding = $oriConsoleEncoding
336345
}
337346

338347
foreach ($url in $urls) {

test/Scoop-Config.Tests.ps1

Lines changed: 57 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -2,80 +2,77 @@
22

33
Describe 'config' -Tag 'Scoop' {
44
BeforeAll {
5-
$json = '{ "one": 1, "two": [ { "a": "a" }, "b", 2 ], "three": { "four": 4 }, "five": true, "six": false, "seven": "\/Date(1529917395805)\/", "eight": "2019-03-18T15:22:09.3930000+00:00" }'
5+
$configFile = "$env:TEMP\ScoopTestFixtures\config.json"
6+
if (Test-Path $configFile) {
7+
Remove-Item -Path $configFile -Force
8+
}
9+
$unicode = [Regex]::Unescape('\u4f60\u597d\u3053\u3093\u306b\u3061\u306f') # 你好こんにちは
610
}
711

8-
It 'converts JSON to PSObject' {
9-
$obj = ConvertFrom-Json $json
10-
11-
$obj.one | Should -BeExactly 1
12-
$obj.two[0].a | Should -Be 'a'
13-
$obj.two[1] | Should -Be 'b'
14-
$obj.two[2] | Should -BeExactly 2
15-
$obj.three.four | Should -BeExactly 4
16-
$obj.five | Should -BeTrue
17-
$obj.six | Should -BeFalse
18-
$obj.seven | Should -BeOfType [System.DateTime]
19-
if ($PSVersionTable.PSVersion.Major -lt 6) {
20-
$obj.eight | Should -BeOfType [System.String]
21-
} else {
22-
$obj.eight | Should -BeOfType [System.DateTime]
23-
}
12+
BeforeEach {
13+
$scoopConfig = $null
2414
}
2515

26-
It 'load_config should return PSObject' {
27-
Mock Get-Content { $json }
28-
Mock Test-Path { $true }
29-
(load_cfg 'file') | Should -Not -BeNullOrEmpty
30-
(load_cfg 'file') | Should -BeOfType [System.Management.Automation.PSObject]
31-
(load_cfg 'file').one | Should -BeExactly 1
16+
It 'load_cfg should return null if config file does not exist' {
17+
load_cfg $configFile | Should -Be $null
3218
}
3319

34-
It 'get_config should return exactly the same values' {
35-
$scoopConfig = ConvertFrom-Json $json
36-
get_config 'does_not_exist' 'default' | Should -Be 'default'
20+
It 'set_config should be able to save typed values correctly' {
21+
# number
22+
$scoopConfig = set_config 'one' 1
23+
$scoopConfig.one | Should -BeExactly 1
3724

38-
get_config 'one' | Should -BeExactly 1
39-
(get_config 'two')[0].a | Should -Be 'a'
40-
(get_config 'two')[1] | Should -Be 'b'
41-
(get_config 'two')[2] | Should -BeExactly 2
42-
(get_config 'three').four | Should -BeExactly 4
43-
get_config 'five' | Should -BeTrue
44-
get_config 'six' | Should -BeFalse
45-
get_config 'seven' | Should -BeOfType [System.DateTime]
46-
if ($PSVersionTable.PSVersion.Major -lt 6) {
47-
get_config 'eight' | Should -BeOfType [System.String]
48-
} else {
49-
get_config 'eight' | Should -BeOfType [System.DateTime]
50-
}
51-
}
25+
# boolean
26+
$scoopConfig = set_config 'two' $true
27+
$scoopConfig.two | Should -BeTrue
28+
$scoopConfig = set_config 'three' $false
29+
$scoopConfig.three | Should -BeFalse
5230

53-
It 'set_config should create a new PSObject and ensure existing directory' {
54-
$scoopConfig = $null
55-
$configFile = "$PSScriptRoot\.scoop"
31+
# underline key
32+
$scoopConfig = set_config 'under_line' 'four'
33+
$scoopConfig.under_line | Should -BeExactly 'four'
5634

57-
Mock ensure { $PSScriptRoot } -Verifiable -ParameterFilter { $dir -eq (Split-Path -Path $configFile) }
58-
Mock Set-Content {} -Verifiable -ParameterFilter { $Path -eq $configFile }
59-
Mock ConvertTo-Json { '' } -Verifiable -ParameterFilter { $InputObject -is [System.Management.Automation.PSObject] }
35+
# string
36+
$scoopConfig = set_config 'five' 'not null'
6037

61-
set_config 'does_not_exist' 'default'
38+
# datetime
39+
$scoopConfig = set_config 'time' ([System.DateTime]::Parse('2019-03-18T15:22:09.3930000+00:00', $null, [System.Globalization.DateTimeStyles]::AdjustToUniversal))
40+
$scoopConfig.time | Should -BeOfType [System.DateTime]
6241

63-
Assert-VerifiableMock
42+
# non-ASCII
43+
$scoopConfig = set_config 'unicode' $unicode
44+
$scoopConfig.unicode | Should -Be $unicode
6445
}
6546

66-
It "set_config should remove a value if set to `$null" {
67-
$scoopConfig = New-Object PSObject
68-
$scoopConfig | Add-Member -MemberType NoteProperty -Name 'should_be_removed' -Value 'a_value'
69-
$scoopConfig | Add-Member -MemberType NoteProperty -Name 'should_stay' -Value 'another_value'
70-
$configFile = "$PSScriptRoot\.scoop"
71-
72-
Mock Set-Content {} -Verifiable -ParameterFilter { $Path -eq $configFile }
73-
Mock ConvertTo-Json { '' } -Verifiable -ParameterFilter { $InputObject -is [System.Management.Automation.PSObject] }
47+
It 'load_cfg should return PSObject if config file exist' {
48+
$scoopConfig = load_cfg $configFile
49+
$scoopConfig | Should -Not -BeNullOrEmpty
50+
$scoopConfig | Should -BeOfType [System.Management.Automation.PSObject]
51+
$scoopConfig.one | Should -BeExactly 1
52+
$scoopConfig.two | Should -BeTrue
53+
$scoopConfig.three | Should -BeFalse
54+
$scoopConfig.under_line | Should -BeExactly 'four'
55+
$scoopConfig.five | Should -Be 'not null'
56+
$scoopConfig.time | Should -BeOfType [System.DateTime]
57+
$scoopConfig.time | Should -Be ([System.DateTime]::Parse('2019-03-18T15:22:09.3930000+00:00', $null, [System.Globalization.DateTimeStyles]::AdjustToUniversal))
58+
$scoopConfig.unicode | Should -Be $unicode
59+
}
7460

75-
$scoopConfig = set_config 'should_be_removed' $null
76-
$scoopConfig.should_be_removed | Should -BeNullOrEmpty
77-
$scoopConfig.should_stay | Should -Be 'another_value'
61+
It 'get_config should return exactly the same values' {
62+
$scoopConfig = load_cfg $configFile
63+
(get_config 'one') | Should -BeExactly 1
64+
(get_config 'two') | Should -BeTrue
65+
(get_config 'three') | Should -BeFalse
66+
(get_config 'under_line') | Should -BeExactly 'four'
67+
(get_config 'five') | Should -Be 'not null'
68+
(get_config 'time') | Should -BeOfType [System.DateTime]
69+
(get_config 'time') | Should -Be ([System.DateTime]::Parse('2019-03-18T15:22:09.3930000+00:00', $null, [System.Globalization.DateTimeStyles]::AdjustToUniversal))
70+
(get_config 'unicode') | Should -Be $unicode
71+
}
7872

79-
Assert-VerifiableMock
73+
It 'set_config should remove a value if being set to $null' {
74+
$scoopConfig = load_cfg $configFile
75+
$scoopConfig = set_config 'five' $null
76+
$scoopConfig.five | Should -BeNullOrEmpty
8077
}
8178
}

0 commit comments

Comments
 (0)