[path_provider] Support unicode encoded version info values (#4986)
diff --git a/packages/path_provider/path_provider_windows/CHANGELOG.md b/packages/path_provider/path_provider_windows/CHANGELOG.md
index d933b0d..f48093b 100644
--- a/packages/path_provider/path_provider_windows/CHANGELOG.md
+++ b/packages/path_provider/path_provider_windows/CHANGELOG.md
@@ -1,5 +1,6 @@
-## NEXT
+## 2.0.7
+* Added support for unicode encoded VERSIONINFO
* Minor fixes for new analysis options.
## 2.0.6
diff --git a/packages/path_provider/path_provider_windows/lib/src/path_provider_windows_real.dart b/packages/path_provider/path_provider_windows/lib/src/path_provider_windows_real.dart
index bfffa84..9a8b0cf 100644
--- a/packages/path_provider/path_provider_windows/lib/src/path_provider_windows_real.dart
+++ b/packages/path_provider/path_provider_windows/lib/src/path_provider_windows_real.dart
@@ -13,21 +13,43 @@
import 'folders.dart';
+/// Constant for en-US language used in VersionInfo keys.
+@visibleForTesting
+const String languageEn = '0409';
+
+/// Constant for CP1252 encoding used in VersionInfo keys
+@visibleForTesting
+const String encodingCP1252 = '04e4';
+
+/// Constant for Unicode encoding used in VersionInfo keys
+@visibleForTesting
+const String encodingUnicode = '04b0';
+
/// Wraps the Win32 VerQueryValue API call.
///
/// This class exists to allow injecting alternate metadata in tests without
/// building multiple custom test binaries.
@visibleForTesting
class VersionInfoQuerier {
- /// Returns the value for [key] in [versionInfo]s English strings section, or
- /// null if there is no such entry, or if versionInfo is null.
- String? getStringValue(Pointer<Uint8>? versionInfo, String key) {
+ /// Returns the value for [key] in [versionInfo]s in section with given
+ /// language and encoding, or null if there is no such entry,
+ /// or if versionInfo is null.
+ ///
+ /// See https://docs.microsoft.com/en-us/windows/win32/menurc/versioninfo-resource
+ /// for list of possible language and encoding values.
+ String? getStringValue(
+ Pointer<Uint8>? versionInfo,
+ String key, {
+ required String language,
+ required String encoding,
+ }) {
+ assert(language.isNotEmpty);
+ assert(encoding.isNotEmpty);
if (versionInfo == null) {
return null;
}
- const String kEnUsLanguageCode = '040904e4';
final Pointer<Utf16> keyPath =
- TEXT('\\StringFileInfo\\$kEnUsLanguageCode\\$key');
+ TEXT('\\StringFileInfo\\$language$encoding\\$key');
final Pointer<Uint32> length = calloc<Uint32>();
final Pointer<Pointer<Utf16>> valueAddress = calloc<Pointer<Utf16>>();
try {
@@ -150,6 +172,12 @@
}
}
+ String? _getStringValue(Pointer<Uint8>? infoBuffer, String key) =>
+ versionInfoQuerier.getStringValue(infoBuffer, key,
+ language: languageEn, encoding: encodingCP1252) ??
+ versionInfoQuerier.getStringValue(infoBuffer, key,
+ language: languageEn, encoding: encodingUnicode);
+
/// Returns the relative path string to append to the root directory returned
/// by Win32 APIs for application storage (such as RoamingAppDir) to get a
/// directory that is unique to the application.
@@ -187,10 +215,10 @@
infoBuffer = null;
}
}
- companyName = _sanitizedDirectoryName(
- versionInfoQuerier.getStringValue(infoBuffer, 'CompanyName'));
- productName = _sanitizedDirectoryName(
- versionInfoQuerier.getStringValue(infoBuffer, 'ProductName'));
+ companyName =
+ _sanitizedDirectoryName(_getStringValue(infoBuffer, 'CompanyName'));
+ productName =
+ _sanitizedDirectoryName(_getStringValue(infoBuffer, 'ProductName'));
// If there was no product name, use the executable name.
productName ??=
diff --git a/packages/path_provider/path_provider_windows/pubspec.yaml b/packages/path_provider/path_provider_windows/pubspec.yaml
index f75dd05..a995b9f 100644
--- a/packages/path_provider/path_provider_windows/pubspec.yaml
+++ b/packages/path_provider/path_provider_windows/pubspec.yaml
@@ -2,7 +2,7 @@
description: Windows implementation of the path_provider plugin
repository: https://github.com/flutter/plugins/tree/main/packages/path_provider/path_provider_windows
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+path_provider%22
-version: 2.0.6
+version: 2.0.7
environment:
sdk: ">=2.12.0 <3.0.0"
diff --git a/packages/path_provider/path_provider_windows/test/path_provider_windows_test.dart b/packages/path_provider/path_provider_windows/test/path_provider_windows_test.dart
index 7e4118c..571c314 100644
--- a/packages/path_provider/path_provider_windows/test/path_provider_windows_test.dart
+++ b/packages/path_provider/path_provider_windows/test/path_provider_windows_test.dart
@@ -7,15 +7,33 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:path_provider_platform_interface/path_provider_platform_interface.dart';
import 'package:path_provider_windows/path_provider_windows.dart';
+import 'package:path_provider_windows/src/path_provider_windows_real.dart'
+ show languageEn, encodingCP1252, encodingUnicode;
// A fake VersionInfoQuerier that just returns preset responses.
class FakeVersionInfoQuerier implements VersionInfoQuerier {
- FakeVersionInfoQuerier(this.responses);
+ FakeVersionInfoQuerier(
+ this.responses, {
+ this.language = languageEn,
+ this.encoding = encodingUnicode,
+ });
+ final String language;
+ final String encoding;
final Map<String, String> responses;
- String? getStringValue(Pointer<Uint8>? versionInfo, String key) =>
- responses[key];
+ String? getStringValue(
+ Pointer<Uint8>? versionInfo,
+ String key, {
+ required String language,
+ required String encoding,
+ }) {
+ if (language == this.language && encoding == this.encoding) {
+ return responses[key];
+ } else {
+ return null;
+ }
+ }
}
void main() {
@@ -40,12 +58,12 @@
expect(path, endsWith(r'flutter_tester'));
}, skip: !Platform.isWindows);
- test('getApplicationSupportPath with full version info', () async {
+ test('getApplicationSupportPath with full version info in CP1252', () async {
final PathProviderWindows pathProvider = PathProviderWindows();
pathProvider.versionInfoQuerier = FakeVersionInfoQuerier(<String, String>{
'CompanyName': 'A Company',
'ProductName': 'Amazing App',
- });
+ }, language: languageEn, encoding: encodingCP1252);
final String? path = await pathProvider.getApplicationSupportPath();
expect(path, isNotNull);
if (path != null) {
@@ -54,6 +72,35 @@
}
}, skip: !Platform.isWindows);
+ test('getApplicationSupportPath with full version info in Unicode', () async {
+ final PathProviderWindows pathProvider = PathProviderWindows();
+ pathProvider.versionInfoQuerier = FakeVersionInfoQuerier(<String, String>{
+ 'CompanyName': 'A Company',
+ 'ProductName': 'Amazing App',
+ }, language: languageEn, encoding: encodingUnicode);
+ final String? path = await pathProvider.getApplicationSupportPath();
+ expect(path, isNotNull);
+ if (path != null) {
+ expect(path, endsWith(r'AppData\Roaming\A Company\Amazing App'));
+ expect(Directory(path).existsSync(), isTrue);
+ }
+ }, skip: !Platform.isWindows);
+
+ test(
+ 'getApplicationSupportPath with full version info in Unsupported Encoding',
+ () async {
+ final PathProviderWindows pathProvider = PathProviderWindows();
+ pathProvider.versionInfoQuerier = FakeVersionInfoQuerier(<String, String>{
+ 'CompanyName': 'A Company',
+ 'ProductName': 'Amazing App',
+ }, language: '0000', encoding: '0000');
+ final String? path = await pathProvider.getApplicationSupportPath();
+ expect(path, contains(r'C:\'));
+ expect(path, contains(r'AppData'));
+ // The last path component should be the executable name.
+ expect(path, endsWith(r'flutter_tester'));
+ }, skip: !Platform.isWindows);
+
test('getApplicationSupportPath with missing company', () async {
final PathProviderWindows pathProvider = PathProviderWindows();
pathProvider.versionInfoQuerier = FakeVersionInfoQuerier(<String, String>{