Skip to content

Commit

Permalink
[path_provider] Support unicode encoded version info values (flutter#…
Browse files Browse the repository at this point in the history
  • Loading branch information
knopp authored and mauricioluz committed Jan 26, 2023
1 parent a7aa783 commit e1e6e84
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 16 deletions.
3 changes: 2 additions & 1 deletion packages/path_provider/path_provider_windows/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## NEXT
## 2.0.7

* Added support for unicode encoded VERSIONINFO
* Minor fixes for new analysis options.

## 2.0.6
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,43 @@ import 'package:win32/win32.dart';

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 {
Expand Down Expand Up @@ -150,6 +172,12 @@ class PathProviderWindows extends PathProviderPlatform {
}
}

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.
Expand Down Expand Up @@ -187,10 +215,10 @@ class PathProviderWindows extends PathProviderPlatform {
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 ??=
Expand Down
2 changes: 1 addition & 1 deletion packages/path_provider/path_provider_windows/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: path_provider_windows
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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,33 @@ import 'dart:io';
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() {
Expand All @@ -40,12 +58,26 @@ void main() {
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) {
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 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) {
Expand All @@ -54,6 +86,21 @@ void main() {
}
}, 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>{
Expand Down

0 comments on commit e1e6e84

Please sign in to comment.