Merge branch 'codex/windows-parity'

This commit is contained in:
Haitao Pan 2026-03-21 21:53:59 +08:00
commit c44a0dec47
37 changed files with 5389 additions and 5 deletions

View File

@ -9,6 +9,12 @@
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
analyzer:
exclude:
- third_party/**/example/**
- third_party/**/test/**
- third_party/**/analysis_options.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`

View File

@ -265,12 +265,11 @@ packages:
source: hosted
version: "1.2.1"
flutter_secure_storage_windows:
dependency: transitive
dependency: "direct overridden"
description:
name: flutter_secure_storage_windows
sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709
url: "https://pub.dev"
source: hosted
path: "third_party/flutter_secure_storage_windows"
relative: true
source: path
version: "3.1.2"
flutter_test:
dependency: "direct dev"

View File

@ -32,6 +32,10 @@ dependencies:
web_socket_channel: ^3.0.3
yaml: ^3.1.3
dependency_overrides:
flutter_secure_storage_windows:
path: third_party/flutter_secure_storage_windows
dev_dependencies:
flutter_test:
sdk: flutter

View File

@ -0,0 +1,43 @@
## 3.1.2
Reverts onCupertinoProtectedDataAvailabilityChanged and isCupertinoProtectedDataAvailable.
## 3.1.1
Updated flutter_secure_storage_platform_interface to latest version.
## 3.1.0
Fixed CompanyName and CompanyProduct on Windows are ignored when the lang-charset in the Runner.rc file is not 040904e4
## 3.0.0
- Migrated to win32 package replacing C.
- Changed PathNotFoundException to FileSystemException to be backwards compatible with Flutter SDK 2.12.0
- Applied lint suggestions
## 2.1.1
Revert changes made in version 2.1.0 due to breaking changes.
These changes will be republished under a new major version number 3.0.0.
## 2.1.0
- Changed PathNotFoundException to FileSystemException to be backwards compatible with Flutter SDK 2.12.0
- Applied lint suggestions
## 2.0.0
Write encrypted data to files instead of the windows credential system.
## 1.1.3
Updated flutter_secure_storage_platform_interface to latest version.
## 1.1.2
- Silently ignore errors when deleting keys that don't exist
## 1.1.1
- Fix application crash when key doesn't exists.
## 1.1.0
Features
- Add readAll, deleteAll and containsKey functions.
Bugfixes
- Fix implementation of delete operation to allow null value.
## 1.0.0
- Initial Windows implementation

View File

@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright 2017 German Saprykin
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,12 @@
# flutter_secure_storage_windows
The windows implementation of [`flutter_secure_storage`][1].
## Usage
This package is [endorsed][2], which means you can simply use `flutter_secure_storage`
normally. This package will be automatically included in your app when you do.
[1]: https://pub.dev/packages/flutter_secure_storage
[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin

View File

@ -0,0 +1 @@
include: package:lint/package.yaml

View File

@ -0,0 +1,16 @@
# flutter_secure_storage_windows_example
Demonstrates how to use the flutter_secure_storage_windows plugin.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

View File

@ -0,0 +1,29 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at
# https://dart-lang.github.io/linter/lints/index.html.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,383 @@
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter_secure_storage_platform_interface/flutter_secure_storage_platform_interface.dart';
import 'package:flutter_secure_storage_windows/flutter_secure_storage_windows.dart';
// testing application.
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
final TextEditingController keyFieldController = TextEditingController();
final TextEditingController valueFieldController = TextEditingController();
final TextEditingController resultSummaryFieldController =
TextEditingController();
final TextEditingController resultDetailFieldController =
TextEditingController();
final GlobalKey<LabeledCheckboxState> useMethodChannelOnlyKey = GlobalKey();
final GlobalKey<LabeledCheckboxState> useBackwardCompatibilityKey =
GlobalKey();
Future<TestResult>? _future;
FlutterSecureStoragePlatform _flutterSecureStorageWindowsPlugin =
FlutterSecureStorageWindows();
final Map<String, String> _options = {'useBackwardCompatibility': 'false'};
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: Padding(
padding: const EdgeInsets.all(8),
child: Column(children: [
TextField(
controller: keyFieldController,
decoration: const InputDecoration(label: Text('Key')),
),
TextField(
controller: valueFieldController,
decoration: const InputDecoration(label: Text('Value')),
),
LabeledCheckbox(
key: useMethodChannelOnlyKey,
initialValue: false,
label: 'UseMethodChannelOnly',
onChanged: (useMethodChannelOnly) {
setState(() {
_flutterSecureStorageWindowsPlugin = useMethodChannelOnly
? MethodChannelFlutterSecureStorage()
: FlutterSecureStorageWindows();
});
},
),
LabeledCheckbox(
key: useBackwardCompatibilityKey,
initialValue: false,
label: 'UseBackwardCompatibility',
onChanged: (useBackwardCompatibility) {
setState(() {
_options['useBackwardCompatibility'] =
useBackwardCompatibility.toString();
});
},
),
Row(
children: [
Padding(
padding: const EdgeInsets.all(4),
child: ElevatedButton(
onPressed: doRead,
child: const Text('Read'),
),
),
Padding(
padding: const EdgeInsets.all(4),
child: ElevatedButton(
onPressed: doReadAll,
child: const Text('ReadAll'),
),
),
Padding(
padding: const EdgeInsets.all(4),
child: ElevatedButton(
onPressed: doContainsKey,
child: const Text('ContainsKey'),
),
),
],
),
Row(
children: [
Padding(
padding: const EdgeInsets.all(4),
child: ElevatedButton(
onPressed: doWrite,
child: const Text('Write'),
),
),
Padding(
padding: const EdgeInsets.all(4),
child: ElevatedButton(
onPressed: doDelete,
child: const Text('Delete'),
),
),
Padding(
padding: const EdgeInsets.all(4),
child: ElevatedButton(
onPressed: doDeleteAll,
child: const Text('DeleteAll'),
),
),
],
),
Row(
children: [
Padding(
padding: const EdgeInsets.all(4),
child: ElevatedButton(
onPressed: doLegacyWrite,
child: const Text('LegacyWrite'),
),
),
Padding(
padding: const EdgeInsets.all(4),
child: ElevatedButton(
onPressed: doLegacyReadAll,
child: const Text('LegacyReadAll'),
),
),
],
),
if (_future != null)
FutureBuilder<TestResult>(
builder: (context, snapshot) {
if (!snapshot.hasData && !snapshot.hasError) {
return const CircularProgressIndicator();
}
resultSummaryFieldController.text =
(snapshot.data?.success ?? false) ? 'SUCCESS' : 'FAIL';
return TextField(
controller: resultSummaryFieldController,
decoration: const InputDecoration(label: Text('Result')),
);
},
future: _future,
),
if (_future != null)
FutureBuilder<TestResult>(
builder: (context, snapshot) {
if (!snapshot.hasData && !snapshot.hasError) {
return const CircularProgressIndicator();
}
resultDetailFieldController.text =
snapshot.error?.toString() ??
snapshot.data!.detail ??
'<null>';
return Column(
children: [
TextField(
controller: resultSummaryFieldController,
decoration:
const InputDecoration(label: Text('Result')),
),
TextField(
controller: resultDetailFieldController,
decoration:
const InputDecoration(label: Text('Detail')),
),
],
);
},
future: _future,
),
// const Expanded(child: SizedBox()),
]),
),
),
);
}
Future<TestResult> doTestCore(FutureOr<TestResult> Function() test) async {
late final TestResult result;
try {
result = await test();
} catch (e, s) {
debugPrint(e.toString());
debugPrintStack(stackTrace: s);
result = TestResult(success: false, detail: e.toString());
}
return result;
}
void doTest(FutureOr<TestResult> Function() test) {
setState(() {
_future = doTestCore(test);
});
}
void doRead() => doTest(() async {
final key = keyFieldController.text;
return TestResult(
success: true,
detail: await _flutterSecureStorageWindowsPlugin.read(
key: key,
options: _options,
),
);
});
void doReadAll() => doTest(() async {
return TestResult(
success: true,
detail: (await _flutterSecureStorageWindowsPlugin.readAll(
options: _options,
))
.toString(),
);
});
void doContainsKey() => doTest(() async {
final key = keyFieldController.text;
return TestResult(
success: true,
detail: (await _flutterSecureStorageWindowsPlugin.containsKey(
key: key,
options: _options,
))
.toString(),
);
});
void doWrite() => doTest(() async {
final key = keyFieldController.text;
final value = valueFieldController.text.isNotEmpty
? valueFieldController.text
: DateTime.now().toIso8601String();
await _flutterSecureStorageWindowsPlugin.write(
key: key,
value: value,
options: _options,
);
return TestResult(success: true, detail: value);
});
void doDelete() => doTest(() async {
final key = keyFieldController.text;
await _flutterSecureStorageWindowsPlugin.delete(
key: key,
options: _options,
);
return TestResult(
success: true,
detail: null,
);
});
void doDeleteAll() => doTest(() async {
await _flutterSecureStorageWindowsPlugin.deleteAll(
options: _options,
);
return TestResult(
success: true,
detail: null,
);
});
void doLegacyWrite() => doTest(() async {
final key = keyFieldController.text;
final value = valueFieldController.text.isNotEmpty
? valueFieldController.text
: DateTime.now().toIso8601String();
// call MethodChannelFlutterSecureStorage directly
final legacyStorage = MethodChannelFlutterSecureStorage();
await legacyStorage.write(
key: key,
value: value,
options: _options,
);
return TestResult(success: true, detail: value);
});
void doLegacyReadAll() => doTest(() async {
// call MethodChannelFlutterSecureStorage directly
final legacyStorage = MethodChannelFlutterSecureStorage();
return TestResult(
success: true,
detail: (await legacyStorage.readAll(
options: _options,
))
.toString());
});
}
class TestResult {
final bool success;
final String? detail;
TestResult({
required this.success,
required this.detail,
});
}
class LabeledCheckbox extends StatefulWidget {
final String label;
final EdgeInsetsGeometry padding;
final bool initialValue;
final ValueChanged<bool>? onChanged;
const LabeledCheckbox({
Key? key,
required this.label,
this.padding = const EdgeInsets.all(4),
this.initialValue = false,
this.onChanged,
}) : super(key: key);
@override
State<StatefulWidget> createState() => LabeledCheckboxState._();
}
class LabeledCheckboxState extends State<LabeledCheckbox> {
late bool _value;
bool get value => _value;
set value(bool v) {
setState(() {
_value = v;
});
widget.onChanged?.call(v);
}
LabeledCheckboxState._();
@override
void initState() {
super.initState();
_value = widget.initialValue;
}
@override
Widget build(BuildContext context) => InkWell(
onTap: () {
value = !value;
},
child: Padding(
padding: widget.padding,
child: Row(children: [
Expanded(child: Text(widget.label)),
Checkbox(
value: value,
onChanged: (newValue) {
if (newValue != null) {
value = newValue;
}
})
]),
),
);
}

View File

@ -0,0 +1,30 @@
name: flutter_secure_storage_windows_example
description: Demonstrates how to use the flutter_secure_storage_windows plugin.
publish_to: 'none'
environment:
sdk: '>=2.12.0 <3.0.0'
flutter: ">=2.0.0"
dependencies:
flutter:
sdk: flutter
flutter_secure_storage_platform_interface:
flutter_secure_storage_windows:
path: ../
path: ^1.8.0
path_provider: ^2.0.0
dev_dependencies:
flutter_lints: ^2.0.0
flutter_test:
sdk: flutter
integration_test:
sdk: flutter
dependency_overrides:
flutter_secure_storage_platform_interface:
path: ../../flutter_secure_storage_platform_interface
flutter:
uses-material-design: true

View File

@ -0,0 +1,101 @@
# Project-level configuration.
cmake_minimum_required(VERSION 3.14)
project(flutter_secure_storage_windows_example LANGUAGES CXX)
# The name of the executable created for the application. Change this to change
# the on-disk name of your application.
set(BINARY_NAME "flutter_secure_storage_windows_example")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake.
cmake_policy(SET CMP0063 NEW)
# Define build configuration option.
get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(IS_MULTICONFIG)
set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
CACHE STRING "" FORCE)
else()
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Debug" CACHE
STRING "Flutter build mode" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
"Debug" "Profile" "Release")
endif()
endif()
# Define settings for the Profile build mode.
set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
# Use Unicode for all projects.
add_definitions(-DUNICODE -D_UNICODE)
# Compilation settings that should be applied to most targets.
#
# Be cautious about adding new options here, as plugins use this function by
# default. In most cases, you should add new options to specific targets instead
# of modifying this function.
function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_features(${TARGET} PUBLIC cxx_std_17)
target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
target_compile_options(${TARGET} PRIVATE /EHsc)
target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
target_compile_definitions(${TARGET} PRIVATE "$<$<CONFIG:Debug>:_DEBUG>")
endfunction()
# Flutter library and tool build rules.
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
add_subdirectory(${FLUTTER_MANAGED_DIR})
# Application build; see runner/CMakeLists.txt.
add_subdirectory("runner")
# Generated plugin build rules, which manage building the plugins and adding
# them to the application.
include(flutter/generated_plugins.cmake)
# === Installation ===
# Support files are copied into place next to the executable, so that it can
# run in place. This is done instead of making a separate bundle (as on Linux)
# so that building and running from within Visual Studio will work.
set(BUILD_BUNDLE_DIR "$<TARGET_FILE_DIR:${BINARY_NAME}>")
# Make the "install" step default, as it's required to run.
set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
endif()
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime)
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
COMPONENT Runtime)
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
if(PLUGIN_BUNDLED_LIBRARIES)
install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endif()
# Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
install(CODE "
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
" COMPONENT Runtime)
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
# Install the AOT library on non-Debug builds only.
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
CONFIGURATIONS Profile;Release
COMPONENT Runtime)

View File

@ -0,0 +1,104 @@
# This file controls Flutter-level build steps. It should not be edited.
cmake_minimum_required(VERSION 3.14)
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
# Configuration provided via flutter tool.
include(${EPHEMERAL_DIR}/generated_config.cmake)
# TODO: Move the rest of this into files in ephemeral. See
# https://github.com/flutter/flutter/issues/57146.
set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
# === Flutter Library ===
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
# Published to parent scope for install step.
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
list(APPEND FLUTTER_LIBRARY_HEADERS
"flutter_export.h"
"flutter_windows.h"
"flutter_messenger.h"
"flutter_plugin_registrar.h"
"flutter_texture_registrar.h"
)
list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
add_library(flutter INTERFACE)
target_include_directories(flutter INTERFACE
"${EPHEMERAL_DIR}"
)
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
add_dependencies(flutter flutter_assemble)
# === Wrapper ===
list(APPEND CPP_WRAPPER_SOURCES_CORE
"core_implementations.cc"
"standard_codec.cc"
)
list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
"plugin_registrar.cc"
)
list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
list(APPEND CPP_WRAPPER_SOURCES_APP
"flutter_engine.cc"
"flutter_view_controller.cc"
)
list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
# Wrapper sources needed for a plugin.
add_library(flutter_wrapper_plugin STATIC
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_PLUGIN}
)
apply_standard_settings(flutter_wrapper_plugin)
set_target_properties(flutter_wrapper_plugin PROPERTIES
POSITION_INDEPENDENT_CODE ON)
set_target_properties(flutter_wrapper_plugin PROPERTIES
CXX_VISIBILITY_PRESET hidden)
target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
target_include_directories(flutter_wrapper_plugin PUBLIC
"${WRAPPER_ROOT}/include"
)
add_dependencies(flutter_wrapper_plugin flutter_assemble)
# Wrapper sources needed for the runner.
add_library(flutter_wrapper_app STATIC
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_APP}
)
apply_standard_settings(flutter_wrapper_app)
target_link_libraries(flutter_wrapper_app PUBLIC flutter)
target_include_directories(flutter_wrapper_app PUBLIC
"${WRAPPER_ROOT}/include"
)
add_dependencies(flutter_wrapper_app flutter_assemble)
# === Flutter tool backend ===
# _phony_ is a non-existent file to force this command to run every time,
# since currently there's no way to get a full input/output list from the
# flutter tool.
set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
add_custom_command(
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
${CPP_WRAPPER_SOURCES_APP}
${PHONY_OUTPUT}
COMMAND ${CMAKE_COMMAND} -E env
${FLUTTER_TOOL_ENVIRONMENT}
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
windows-x64 $<CONFIG>
VERBATIM
)
add_custom_target(flutter_assemble DEPENDS
"${FLUTTER_LIBRARY}"
${FLUTTER_LIBRARY_HEADERS}
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_PLUGIN}
${CPP_WRAPPER_SOURCES_APP}
)

View File

@ -0,0 +1,14 @@
//
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
}

View File

@ -0,0 +1,15 @@
//
// Generated file. Do not edit.
//
// clang-format off
#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_
#include <flutter/plugin_registry.h>
// Registers Flutter plugins.
void RegisterPlugins(flutter::PluginRegistry* registry);
#endif // GENERATED_PLUGIN_REGISTRANT_

View File

@ -0,0 +1,24 @@
#
# Generated file, do not edit.
#
list(APPEND FLUTTER_PLUGIN_LIST
flutter_secure_storage_windows
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
)
set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
endforeach(ffi_plugin)

View File

@ -0,0 +1,40 @@
cmake_minimum_required(VERSION 3.14)
project(runner LANGUAGES CXX)
# Define the application target. To change its name, change BINARY_NAME in the
# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
# work.
#
# Any new source files that you add to the application should be added here.
add_executable(${BINARY_NAME} WIN32
"flutter_window.cpp"
"main.cpp"
"utils.cpp"
"win32_window.cpp"
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
"Runner.rc"
"runner.exe.manifest"
)
# Apply the standard set of build settings. This can be removed for applications
# that need different build settings.
apply_standard_settings(${BINARY_NAME})
# Add preprocessor definitions for the build version.
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"")
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}")
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}")
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}")
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}")
# Disable Windows macros that collide with C++ standard library functions.
target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
# Add dependency libraries and include directories. Add any application-specific
# dependencies here.
target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib")
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
# Run the Flutter tool portions of the build. This must not be removed.
add_dependencies(${BINARY_NAME} flutter_assemble)

View File

@ -0,0 +1,121 @@
// Microsoft Visual C++ generated resource script.
//
#pragma code_page(65001)
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// English (United States) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Icon
//
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_APP_ICON ICON "resources\\app_icon.ico"
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD)
#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD
#else
#define VERSION_AS_NUMBER 1,0,0,0
#endif
#if defined(FLUTTER_VERSION)
#define VERSION_AS_STRING FLUTTER_VERSION
#else
#define VERSION_AS_STRING "1.0.0"
#endif
VS_VERSION_INFO VERSIONINFO
FILEVERSION VERSION_AS_NUMBER
PRODUCTVERSION VERSION_AS_NUMBER
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
#ifdef _DEBUG
FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0x0L
#endif
FILEOS VOS__WINDOWS32
FILETYPE VFT_APP
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904e4"
BEGIN
VALUE "CompanyName", "com.example" "\0"
VALUE "FileDescription", "flutter_secure_storage_windows_example" "\0"
VALUE "FileVersion", VERSION_AS_STRING "\0"
VALUE "InternalName", "flutter_secure_storage_windows_example" "\0"
VALUE "LegalCopyright", "Copyright (C) 2023 com.example. All rights reserved." "\0"
VALUE "OriginalFilename", "flutter_secure_storage_windows_example.exe" "\0"
VALUE "ProductName", "flutter_secure_storage_windows_example" "\0"
VALUE "ProductVersion", VERSION_AS_STRING "\0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1252
END
END
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED

View File

@ -0,0 +1,66 @@
#include "flutter_window.h"
#include <optional>
#include "flutter/generated_plugin_registrant.h"
FlutterWindow::FlutterWindow(const flutter::DartProject& project)
: project_(project) {}
FlutterWindow::~FlutterWindow() {}
bool FlutterWindow::OnCreate() {
if (!Win32Window::OnCreate()) {
return false;
}
RECT frame = GetClientArea();
// The size here must match the window dimensions to avoid unnecessary surface
// creation / destruction in the startup path.
flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
frame.right - frame.left, frame.bottom - frame.top, project_);
// Ensure that basic setup of the controller was successful.
if (!flutter_controller_->engine() || !flutter_controller_->view()) {
return false;
}
RegisterPlugins(flutter_controller_->engine());
SetChildContent(flutter_controller_->view()->GetNativeWindow());
flutter_controller_->engine()->SetNextFrameCallback([&]() {
this->Show();
});
return true;
}
void FlutterWindow::OnDestroy() {
if (flutter_controller_) {
flutter_controller_ = nullptr;
}
Win32Window::OnDestroy();
}
LRESULT
FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept {
// Give Flutter, including plugins, an opportunity to handle window messages.
if (flutter_controller_) {
std::optional<LRESULT> result =
flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
lparam);
if (result) {
return *result;
}
}
switch (message) {
case WM_FONTCHANGE:
flutter_controller_->engine()->ReloadSystemFonts();
break;
}
return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
}

View File

@ -0,0 +1,33 @@
#ifndef RUNNER_FLUTTER_WINDOW_H_
#define RUNNER_FLUTTER_WINDOW_H_
#include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h>
#include <memory>
#include "win32_window.h"
// A window that does nothing but host a Flutter view.
class FlutterWindow : public Win32Window {
public:
// Creates a new FlutterWindow hosting a Flutter view running |project|.
explicit FlutterWindow(const flutter::DartProject& project);
virtual ~FlutterWindow();
protected:
// Win32Window:
bool OnCreate() override;
void OnDestroy() override;
LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
LPARAM const lparam) noexcept override;
private:
// The project to run.
flutter::DartProject project_;
// The Flutter instance hosted by this window.
std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
};
#endif // RUNNER_FLUTTER_WINDOW_H_

View File

@ -0,0 +1,43 @@
#include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h>
#include <windows.h>
#include "flutter_window.h"
#include "utils.h"
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
_In_ wchar_t *command_line, _In_ int show_command) {
// Attach to console when present (e.g., 'flutter run') or create a
// new console when running with a debugger.
if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
CreateAndAttachConsole();
}
// Initialize COM, so that it is available for use in the library and/or
// plugins.
::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
flutter::DartProject project(L"data");
std::vector<std::string> command_line_arguments =
GetCommandLineArguments();
project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
FlutterWindow window(project);
Win32Window::Point origin(10, 10);
Win32Window::Size size(1280, 720);
if (!window.Create(L"flutter_secure_storage_windows_example", origin, size)) {
return EXIT_FAILURE;
}
window.SetQuitOnClose(true);
::MSG msg;
while (::GetMessage(&msg, nullptr, 0, 0)) {
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
::CoUninitialize();
return EXIT_SUCCESS;
}

View File

@ -0,0 +1,16 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by Runner.rc
//
#define IDI_APP_ICON 101
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 102
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1001
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
</windowsSettings>
</application>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 and Windows 11 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
</application>
</compatibility>
</assembly>

View File

@ -0,0 +1,64 @@
#include "utils.h"
#include <flutter_windows.h>
#include <io.h>
#include <stdio.h>
#include <windows.h>
#include <iostream>
void CreateAndAttachConsole() {
if (::AllocConsole()) {
FILE *unused;
if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
_dup2(_fileno(stdout), 1);
}
if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
_dup2(_fileno(stdout), 2);
}
std::ios::sync_with_stdio();
FlutterDesktopResyncOutputStreams();
}
}
std::vector<std::string> GetCommandLineArguments() {
// Convert the UTF-16 command line arguments to UTF-8 for the Engine to use.
int argc;
wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
if (argv == nullptr) {
return std::vector<std::string>();
}
std::vector<std::string> command_line_arguments;
// Skip the first argument as it's the binary name.
for (int i = 1; i < argc; i++) {
command_line_arguments.push_back(Utf8FromUtf16(argv[i]));
}
::LocalFree(argv);
return command_line_arguments;
}
std::string Utf8FromUtf16(const wchar_t* utf16_string) {
if (utf16_string == nullptr) {
return std::string();
}
int target_length = ::WideCharToMultiByte(
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
-1, nullptr, 0, nullptr, nullptr);
std::string utf8_string;
if (target_length == 0 || target_length > utf8_string.max_size()) {
return utf8_string;
}
utf8_string.resize(target_length);
int converted_length = ::WideCharToMultiByte(
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
-1, utf8_string.data(),
target_length, nullptr, nullptr);
if (converted_length == 0) {
return std::string();
}
return utf8_string;
}

View File

@ -0,0 +1,19 @@
#ifndef RUNNER_UTILS_H_
#define RUNNER_UTILS_H_
#include <string>
#include <vector>
// Creates a console for the process, and redirects stdout and stderr to
// it for both the runner and the Flutter library.
void CreateAndAttachConsole();
// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string
// encoded in UTF-8. Returns an empty std::string on failure.
std::string Utf8FromUtf16(const wchar_t* utf16_string);
// Gets the command line arguments passed in as a std::vector<std::string>,
// encoded in UTF-8. Returns an empty std::vector<std::string> on failure.
std::vector<std::string> GetCommandLineArguments();
#endif // RUNNER_UTILS_H_

View File

@ -0,0 +1,288 @@
#include "win32_window.h"
#include <dwmapi.h>
#include <flutter_windows.h>
#include "resource.h"
namespace {
/// Window attribute that enables dark mode window decorations.
///
/// Redefined in case the developer's machine has a Windows SDK older than
/// version 10.0.22000.0.
/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
#endif
constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
/// Registry key for app theme preference.
///
/// A value of 0 indicates apps should use dark mode. A non-zero or missing
/// value indicates apps should use light mode.
constexpr const wchar_t kGetPreferredBrightnessRegKey[] =
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme";
// The number of Win32Window objects that currently exist.
static int g_active_window_count = 0;
using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
// Scale helper to convert logical scaler values to physical using passed in
// scale factor
int Scale(int source, double scale_factor) {
return static_cast<int>(source * scale_factor);
}
// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
// This API is only needed for PerMonitor V1 awareness mode.
void EnableFullDpiSupportIfAvailable(HWND hwnd) {
HMODULE user32_module = LoadLibraryA("User32.dll");
if (!user32_module) {
return;
}
auto enable_non_client_dpi_scaling =
reinterpret_cast<EnableNonClientDpiScaling*>(
GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
if (enable_non_client_dpi_scaling != nullptr) {
enable_non_client_dpi_scaling(hwnd);
}
FreeLibrary(user32_module);
}
} // namespace
// Manages the Win32Window's window class registration.
class WindowClassRegistrar {
public:
~WindowClassRegistrar() = default;
// Returns the singleton registar instance.
static WindowClassRegistrar* GetInstance() {
if (!instance_) {
instance_ = new WindowClassRegistrar();
}
return instance_;
}
// Returns the name of the window class, registering the class if it hasn't
// previously been registered.
const wchar_t* GetWindowClass();
// Unregisters the window class. Should only be called if there are no
// instances of the window.
void UnregisterWindowClass();
private:
WindowClassRegistrar() = default;
static WindowClassRegistrar* instance_;
bool class_registered_ = false;
};
WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
const wchar_t* WindowClassRegistrar::GetWindowClass() {
if (!class_registered_) {
WNDCLASS window_class{};
window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
window_class.lpszClassName = kWindowClassName;
window_class.style = CS_HREDRAW | CS_VREDRAW;
window_class.cbClsExtra = 0;
window_class.cbWndExtra = 0;
window_class.hInstance = GetModuleHandle(nullptr);
window_class.hIcon =
LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
window_class.hbrBackground = 0;
window_class.lpszMenuName = nullptr;
window_class.lpfnWndProc = Win32Window::WndProc;
RegisterClass(&window_class);
class_registered_ = true;
}
return kWindowClassName;
}
void WindowClassRegistrar::UnregisterWindowClass() {
UnregisterClass(kWindowClassName, nullptr);
class_registered_ = false;
}
Win32Window::Win32Window() {
++g_active_window_count;
}
Win32Window::~Win32Window() {
--g_active_window_count;
Destroy();
}
bool Win32Window::Create(const std::wstring& title,
const Point& origin,
const Size& size) {
Destroy();
const wchar_t* window_class =
WindowClassRegistrar::GetInstance()->GetWindowClass();
const POINT target_point = {static_cast<LONG>(origin.x),
static_cast<LONG>(origin.y)};
HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
double scale_factor = dpi / 96.0;
HWND window = CreateWindow(
window_class, title.c_str(), WS_OVERLAPPEDWINDOW,
Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
Scale(size.width, scale_factor), Scale(size.height, scale_factor),
nullptr, nullptr, GetModuleHandle(nullptr), this);
if (!window) {
return false;
}
UpdateTheme(window);
return OnCreate();
}
bool Win32Window::Show() {
return ShowWindow(window_handle_, SW_SHOWNORMAL);
}
// static
LRESULT CALLBACK Win32Window::WndProc(HWND const window,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept {
if (message == WM_NCCREATE) {
auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
SetWindowLongPtr(window, GWLP_USERDATA,
reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
auto that = static_cast<Win32Window*>(window_struct->lpCreateParams);
EnableFullDpiSupportIfAvailable(window);
that->window_handle_ = window;
} else if (Win32Window* that = GetThisFromHandle(window)) {
return that->MessageHandler(window, message, wparam, lparam);
}
return DefWindowProc(window, message, wparam, lparam);
}
LRESULT
Win32Window::MessageHandler(HWND hwnd,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept {
switch (message) {
case WM_DESTROY:
window_handle_ = nullptr;
Destroy();
if (quit_on_close_) {
PostQuitMessage(0);
}
return 0;
case WM_DPICHANGED: {
auto newRectSize = reinterpret_cast<RECT*>(lparam);
LONG newWidth = newRectSize->right - newRectSize->left;
LONG newHeight = newRectSize->bottom - newRectSize->top;
SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
return 0;
}
case WM_SIZE: {
RECT rect = GetClientArea();
if (child_content_ != nullptr) {
// Size and position the child window.
MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
rect.bottom - rect.top, TRUE);
}
return 0;
}
case WM_ACTIVATE:
if (child_content_ != nullptr) {
SetFocus(child_content_);
}
return 0;
case WM_DWMCOLORIZATIONCOLORCHANGED:
UpdateTheme(hwnd);
return 0;
}
return DefWindowProc(window_handle_, message, wparam, lparam);
}
void Win32Window::Destroy() {
OnDestroy();
if (window_handle_) {
DestroyWindow(window_handle_);
window_handle_ = nullptr;
}
if (g_active_window_count == 0) {
WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
}
}
Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
return reinterpret_cast<Win32Window*>(
GetWindowLongPtr(window, GWLP_USERDATA));
}
void Win32Window::SetChildContent(HWND content) {
child_content_ = content;
SetParent(content, window_handle_);
RECT frame = GetClientArea();
MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
frame.bottom - frame.top, true);
SetFocus(child_content_);
}
RECT Win32Window::GetClientArea() {
RECT frame;
GetClientRect(window_handle_, &frame);
return frame;
}
HWND Win32Window::GetHandle() {
return window_handle_;
}
void Win32Window::SetQuitOnClose(bool quit_on_close) {
quit_on_close_ = quit_on_close;
}
bool Win32Window::OnCreate() {
// No-op; provided for subclasses.
return true;
}
void Win32Window::OnDestroy() {
// No-op; provided for subclasses.
}
void Win32Window::UpdateTheme(HWND const window) {
DWORD light_mode;
DWORD light_mode_size = sizeof(light_mode);
LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey,
kGetPreferredBrightnessRegValue,
RRF_RT_REG_DWORD, nullptr, &light_mode,
&light_mode_size);
if (result == ERROR_SUCCESS) {
BOOL enable_dark_mode = light_mode == 0;
DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE,
&enable_dark_mode, sizeof(enable_dark_mode));
}
}

View File

@ -0,0 +1,102 @@
#ifndef RUNNER_WIN32_WINDOW_H_
#define RUNNER_WIN32_WINDOW_H_
#include <windows.h>
#include <functional>
#include <memory>
#include <string>
// A class abstraction for a high DPI-aware Win32 Window. Intended to be
// inherited from by classes that wish to specialize with custom
// rendering and input handling
class Win32Window {
public:
struct Point {
unsigned int x;
unsigned int y;
Point(unsigned int x, unsigned int y) : x(x), y(y) {}
};
struct Size {
unsigned int width;
unsigned int height;
Size(unsigned int width, unsigned int height)
: width(width), height(height) {}
};
Win32Window();
virtual ~Win32Window();
// Creates a win32 window with |title| that is positioned and sized using
// |origin| and |size|. New windows are created on the default monitor. Window
// sizes are specified to the OS in physical pixels, hence to ensure a
// consistent size this function will scale the inputted width and height as
// as appropriate for the default monitor. The window is invisible until
// |Show| is called. Returns true if the window was created successfully.
bool Create(const std::wstring& title, const Point& origin, const Size& size);
// Show the current window. Returns true if the window was successfully shown.
bool Show();
// Release OS resources associated with window.
void Destroy();
// Inserts |content| into the window tree.
void SetChildContent(HWND content);
// Returns the backing Window handle to enable clients to set icon and other
// window properties. Returns nullptr if the window has been destroyed.
HWND GetHandle();
// If true, closing this window will quit the application.
void SetQuitOnClose(bool quit_on_close);
// Return a RECT representing the bounds of the current client area.
RECT GetClientArea();
protected:
// Processes and route salient window messages for mouse handling,
// size change and DPI. Delegates handling of these to member overloads that
// inheriting classes can handle.
virtual LRESULT MessageHandler(HWND window,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept;
// Called when CreateAndShow is called, allowing subclass window-related
// setup. Subclasses should return false if setup fails.
virtual bool OnCreate();
// Called when Destroy is called.
virtual void OnDestroy();
private:
friend class WindowClassRegistrar;
// OS callback called by message pump. Handles the WM_NCCREATE message which
// is passed when the non-client area is being created and enables automatic
// non-client DPI scaling so that the non-client area automatically
// responsponds to changes in DPI. All other messages are handled by
// MessageHandler.
static LRESULT CALLBACK WndProc(HWND const window,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept;
// Retrieves a class instance pointer for |window|
static Win32Window* GetThisFromHandle(HWND const window) noexcept;
// Update the window frame's theme to match the system theme.
static void UpdateTheme(HWND const window);
bool quit_on_close_ = false;
// window handle for top level window.
HWND window_handle_ = nullptr;
// window handle for hosted content.
HWND child_content_ = nullptr;
};
#endif // RUNNER_WIN32_WINDOW_H_

View File

@ -0,0 +1,3 @@
export 'src/flutter_secure_storage_windows_stub.dart'
if (dart.library.ffi) 'src/flutter_secure_storage_windows_ffi.dart'
show FlutterSecureStorageWindows;

View File

@ -0,0 +1,404 @@
import 'dart:async';
import 'dart:convert';
import 'dart:ffi';
import 'dart:io';
import 'package:ffi/ffi.dart';
import 'package:flutter/foundation.dart' show debugPrint, visibleForTesting;
import 'package:flutter/services.dart';
import 'package:flutter_secure_storage_platform_interface/flutter_secure_storage_platform_interface.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';
import 'package:win32/win32.dart';
@visibleForTesting
extension OptionsExtension on Map<String, String> {
bool get useBackwardCompatibility =>
this['useBackwardCompatibility'] != 'false';
}
class FlutterSecureStorageWindows extends FlutterSecureStoragePlatform {
final FlutterSecureStoragePlatform _backwardCompatible;
final MapStorage _storage;
FlutterSecureStorageWindows()
: this._(
MethodChannelFlutterSecureStorage(),
DpapiJsonFileMapStorage(),
);
FlutterSecureStorageWindows._(
this._backwardCompatible,
this._storage,
);
/// Registers this plugin.
static void registerWith() {
FlutterSecureStoragePlatform.instance = FlutterSecureStorageWindows();
}
@override
Future<bool> containsKey({
required String key,
required Map<String, String> options,
}) async {
final map = await _storage.load(options);
if (map.containsKey(key)) {
return true;
}
if (options.useBackwardCompatibility) {
return _backwardCompatible.containsKey(key: key, options: options);
}
return false;
}
@override
Future<void> delete({
required String key,
required Map<String, String> options,
}) async {
final map = await _storage.load(options);
final initialSize = map.length;
map.remove(key);
if (map.length != initialSize) {
await _storage.save(map, options);
}
if (options.useBackwardCompatibility) {
await _backwardCompatible.delete(key: key, options: options);
}
}
@override
Future<void> deleteAll({required Map<String, String> options}) async {
await _storage.clear(options);
if (options.useBackwardCompatibility) {
await _backwardCompatible.deleteAll(options: options);
}
}
@override
Future<String?> read({
required String key,
required Map<String, String> options,
}) async {
final map = await _storage.load(options);
var result = map[key];
if (options.useBackwardCompatibility) {
if (result == null) {
final compatible =
await _backwardCompatible.read(key: key, options: options);
if (compatible != null) {
// Write back now, so the value should be retrieved from JSON file next.
result = map[key] = compatible;
await _storage.save(map, options);
}
}
// Clear old entry.
await _backwardCompatible.delete(key: key, options: options);
}
return result;
}
@override
Future<Map<String, String>> readAll({
required Map<String, String> options,
}) async {
final map = await _storage.load(options);
if (!options.useBackwardCompatibility) {
// Just return a map.
return map;
}
final compatible = await _backwardCompatible.readAll(options: options);
if (compatible.isEmpty) {
return map;
}
for (final entry in compatible.entries) {
map.putIfAbsent(entry.key, () => entry.value);
}
// Write back now, so the value should be retrieved from JSON file next.
await _storage.save(map, options);
// Clear old entries.
await _backwardCompatible.deleteAll(options: options);
return map;
}
@override
Future<void> write({
required String key,
required String value,
required Map<String, String> options,
}) async {
final map = await _storage.load(options);
map[key] = value;
await _storage.save(map, options);
if (options.useBackwardCompatibility) {
// Clear old entry.
_backwardCompatible.delete(key: key, options: options);
}
}
// @override
// Future<bool> isCupertinoProtectedDataAvailable() => Future.value(true);
//
// @override
// Stream<bool> get onCupertinoProtectedDataAvailabilityChanged =>
// Stream.value(true);
}
@visibleForTesting
FlutterSecureStorageWindows createFlutterSecureStorageWindows(
FlutterSecureStoragePlatform backwardCompatible,
MapStorage mapStorage,
) =>
FlutterSecureStorageWindows._(backwardCompatible, mapStorage);
@visibleForTesting
abstract class MapStorage {
FutureOr<Map<String, String>> load(Map<String, String> options);
FutureOr<void> save(Map<String, String> data, Map<String, String> options);
FutureOr<void> clear(Map<String, String> options);
}
@visibleForTesting
const String encryptedJsonFileName = 'flutter_secure_storage.dat';
@visibleForTesting
class DpapiJsonFileMapStorage extends MapStorage {
DpapiJsonFileMapStorage();
FutureOr<String> _getJsonFilePath() async {
final appDataDirectory = await getApplicationSupportDirectory();
return path.canonicalize(
path.join(
appDataDirectory.path,
encryptedJsonFileName,
),
);
}
@override
FutureOr<Map<String, String>> load(Map<String, String> options) async {
final file = File(await _getJsonFilePath());
if (!(await file.exists())) {
return {};
}
late final Uint8List encryptedText;
try {
encryptedText = await file.readAsBytes();
} on FileSystemException catch (e) {
// Another process has been deleted a file or parent directory
// since previous File.exists() call.
// We can ignore it.
debugPrint(
'Reading file has been deleted by another process. $e',
);
return {};
}
late final String plainText;
try {
plainText = using((alloc) {
final Pointer<Uint8> pEncryptedText = alloc(encryptedText.length);
pEncryptedText
.asTypedList(encryptedText.length)
.setAll(0, encryptedText);
// Specify size of the struct explicitly.
final Pointer<CRYPT_INTEGER_BLOB> encryptedTextBlob =
alloc.allocate(sizeOf<CRYPT_INTEGER_BLOB>());
encryptedTextBlob.ref.cbData = encryptedText.length;
encryptedTextBlob.ref.pbData = pEncryptedText;
// Specify size of the struct explicitly.
final Pointer<CRYPT_INTEGER_BLOB> plainTextBlob =
alloc.allocate(sizeOf<CRYPT_INTEGER_BLOB>());
if (CryptUnprotectData(
encryptedTextBlob,
nullptr,
nullptr,
nullptr,
nullptr,
0,
plainTextBlob,
) ==
0) {
throw WindowsException(
GetLastError(),
message: 'Failure on CryptUnprotectData()',
);
}
if (plainTextBlob.ref.pbData.address == NULL) {
throw WindowsException(
ERROR_OUTOFMEMORY,
message: 'Failure on CryptUnprotectData()',
);
}
try {
return utf8.decoder.convert(
plainTextBlob.ref.pbData.asTypedList(plainTextBlob.ref.cbData),
);
} finally {
if (plainTextBlob.ref.pbData.address != NULL) {
if (LocalFree(plainTextBlob.ref.pbData).address != NULL) {
debugPrint(
'load: Failed to LocalFree with: 0x${GetLastError().toHexString(32)}',
);
}
}
}
});
} on FormatException catch (e) {
// A file content should be malformed.
debugPrint(
'Failed to decrypt data: $e Delete corrupt file: ${file.path}',
);
await file.delete();
rethrow;
} on WindowsException catch (e) {
// A file content should be malformed.
debugPrint(
'Failed to decrypt data: $e Delete corrupt file: ${file.path}',
);
await file.delete();
rethrow;
}
final dynamic decoded;
try {
decoded = jsonDecode(plainText);
} on FormatException catch (e) {
// A file content should be malformed.
debugPrint(
'Failed to parse JSON: $e Delete corrupt file: ${file.path}',
);
await file.delete();
rethrow;
}
if (decoded is! Map) {
debugPrint(
'Failed to parse JSON: Not an object. Delete corrupt file: ${file.path}',
);
await file.delete();
throw const FormatException('JSON is not an object.');
}
return {
for (final e
in decoded.entries.where((x) => x.key is String && x.value is String))
e.key as String: e.value as String,
};
}
@override
FutureOr<void> save(
Map<String, String> data,
Map<String, String> options,
) async {
final file = File(await _getJsonFilePath());
final json = jsonEncode(data);
final plainText = utf8.encode(json);
await using<FutureOr<void>>((alloc) async {
final Pointer<Uint8> pPlainText = alloc(plainText.length);
pPlainText.asTypedList(plainText.length).setAll(0, plainText);
// Specify size of the struct explicitly.
final Pointer<CRYPT_INTEGER_BLOB> plainTextBlob =
alloc.allocate(sizeOf<CRYPT_INTEGER_BLOB>());
plainTextBlob.ref.cbData = plainText.length;
plainTextBlob.ref.pbData = pPlainText;
// Specify size of the struct explicitly.
final Pointer<CRYPT_INTEGER_BLOB> encryptedTextBlob =
alloc.allocate(sizeOf<CRYPT_INTEGER_BLOB>());
if (CryptProtectData(
plainTextBlob,
nullptr,
nullptr,
nullptr,
nullptr,
0,
encryptedTextBlob,
) ==
0) {
throw WindowsException(
GetLastError(),
message: 'Failure on CryptProtectData()',
);
}
if (encryptedTextBlob.ref.pbData.address == NULL) {
throw WindowsException(
ERROR_OUTOFMEMORY,
message: 'Failure on CryptProtectData()',
);
}
try {
final encryptedText = encryptedTextBlob.ref.pbData
.asTypedList(encryptedTextBlob.ref.cbData);
// Loop to handle race condition.
while (true) {
try {
await (await file.create(recursive: true))
.writeAsBytes(encryptedText, flush: true);
// If success, finish loop.
break;
} on FileSystemException catch (e) {
// Another process has been deleted a file or parent directory
// since previous File.create() call.
// We will retry writing.
debugPrint(
'Reading file has been deleted by another process. $e',
);
}
}
} finally {
if (encryptedTextBlob.ref.pbData.address != NULL) {
if (LocalFree(encryptedTextBlob.ref.pbData).address != NULL) {
debugPrint(
'save: Failed to LocalFree with: 0x${GetLastError().toHexString(32)}',
);
}
}
}
});
}
@override
FutureOr<void> clear(Map<String, String> options) async {
final file = File(await _getJsonFilePath());
if (await file.exists()) {
try {
await file.delete();
} on FileSystemException catch (e) {
// Another process has been deleted a file or parent directory
// since previous File.exists() call.
// We can ignore it.
debugPrint(
'Deleting file has been deleted by another process. $e',
);
}
}
}
}

View File

@ -0,0 +1,58 @@
import 'package:flutter_secure_storage_platform_interface/flutter_secure_storage_platform_interface.dart';
/// A stub implementation to avoid extra transitive dependencies
/// on non-Windows platforms including web.
class FlutterSecureStorageWindows extends FlutterSecureStoragePlatform {
/// Cannot be instantiated.
FlutterSecureStorageWindows()
: assert(false, 'Cannot instantiate this class.');
/// Registers this plugin.
static void registerWith() {
FlutterSecureStoragePlatform.instance = FlutterSecureStorageWindows();
}
@override
Future<bool> containsKey({
required String key,
required Map<String, String> options,
}) =>
Future.value(false);
@override
Future<void> delete({
required String key,
required Map<String, String> options,
}) =>
Future.value();
@override
Future<void> deleteAll({required Map<String, String> options}) =>
Future.value();
@override
Future<String?> read({
required String key,
required Map<String, String> options,
}) =>
Future.value();
@override
Future<Map<String, String>> readAll({required Map<String, String> options}) =>
Future.value({});
@override
Future<void> write({
required String key,
required String value,
required Map<String, String> options,
}) =>
Future.value();
// @override
// Future<bool> isCupertinoProtectedDataAvailable() => Future.value(true);
//
// @override
// Stream<bool> get onCupertinoProtectedDataAvailabilityChanged =>
// Stream.value(true);
}

View File

@ -0,0 +1,30 @@
name: flutter_secure_storage_windows
description: Windows implementation of flutter_secure_storage. Please use flutter_secure_storage instead of this package.
repository: https://github.com/mogol/flutter_secure_storage
version: 3.1.2
environment:
sdk: '>=2.12.0 <4.0.0'
flutter: ">=2.0.0"
dependencies:
ffi: ^2.0.0
flutter:
sdk: flutter
flutter_secure_storage_platform_interface: ^1.1.1
path: ^1.8.0
path_provider: ^2.0.0
win32: ^5.0.0
dev_dependencies:
flutter_test:
sdk: flutter
lint: ^2.0.0
flutter:
plugin:
implements: flutter_secure_storage
platforms:
windows:
pluginClass: FlutterSecureStorageWindowsPlugin
dartPluginClass: FlutterSecureStorageWindows

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,29 @@
cmake_minimum_required(VERSION 3.15)
set(PROJECT_NAME "flutter_secure_storage_windows")
project(${PROJECT_NAME} LANGUAGES CXX)
# This value is used when generating builds using this plugin, so it must
# not be changed
set(PLUGIN_NAME "flutter_secure_storage_windows_plugin")
add_library(${PLUGIN_NAME} SHARED
"flutter_secure_storage_windows_plugin.cpp"
)
apply_standard_settings(${PLUGIN_NAME})
set_target_properties(${PLUGIN_NAME} PROPERTIES
CXX_VISIBILITY_PRESET hidden)
target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL)
target_include_directories(${PLUGIN_NAME} INTERFACE
"${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin)
# List of absolute paths to libraries that should be bundled with the plugin
set(flutter_secure_storage_bundled_libraries
""
PARENT_SCOPE
)
if(NOT DEFINED STORAGE_PREFIX)
add_compile_definitions(SECURE_STORAGE_KEY_PREFIX="${BINARY_NAME}_VGhpcyBpcyB0aGUgcHJlZml4IGZv_")
else()
add_compile_definitions(SECURE_STORAGE_KEY_PREFIX="${STORAGE_PREFIX}_VGhpcyBpcyB0aGUgcHJlZml4IGZv_")
endif()

View File

@ -0,0 +1,941 @@
#include "include/flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h"
// This must be included before many other Windows headers.
#include <windows.h>
#include <wincred.h>
#include <ShlObj_core.h>
#include <sys/stat.h>
#include <errno.h>
#include <direct.h>
#include <bcrypt.h>
// For getPlatformVersion; remove unless needed for your plugin implementation.
#include <VersionHelpers.h>
#include <flutter/method_channel.h>
#include <flutter/plugin_registrar_windows.h>
#include <flutter/standard_method_codec.h>
#include <map>
#include <memory>
#include <sstream>
#include <iostream>
#include <fstream>
#include <string>
#include <regex>
#pragma comment(lib, "version.lib")
#pragma comment(lib, "bcrypt.lib")
namespace
{
class FlutterSecureStorageWindowsPlugin : public flutter::Plugin
{
public:
static void RegisterWithRegistrar(flutter::PluginRegistrarWindows *registrar);
FlutterSecureStorageWindowsPlugin();
virtual ~FlutterSecureStorageWindowsPlugin();
private:
// Called when a method is called on this plugin's channel from Dart.
void HandleMethodCall(
const flutter::MethodCall<flutter::EncodableValue> &method_call,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
// Retrieves the value passed to the given param.
std::optional<std::string> GetStringArg(
const std::string &param,
const flutter::EncodableMap *args);
// Derive the key for a value given a method argument map.
std::optional<std::string> FlutterSecureStorageWindowsPlugin::GetValueKey(const flutter::EncodableMap *args);
// Removes prefix of the given storage key.
//
// The prefix (defined by ELEMENT_PREFERENCES_KEY_PREFIX) is added automatically when writing to storage,
// to distinguish values that are written by this plugin from values that are not.
std::string RemoveKeyPrefix(const std::string &key);
// Gets the string name for the given int error code
std::string GetErrorString(const DWORD &error_code);
// Get string name of ntstatus
std::string NtStatusToString(const CHAR* operation, NTSTATUS status);
DWORD GetApplicationSupportPath(std::wstring& path);
std::wstring SanitizeDirString(std::wstring string);
bool PathExists(const std::wstring& path);
bool MakePath(const std::wstring& path);
PBYTE GetEncryptionKey();
// Stores the given value under the given key.
void Write(const std::string &key, const std::string &val);
std::optional<std::string> Read(const std::string &key);
flutter::EncodableMap ReadAll();
void Delete(const std::string &key);
void DeleteAll();
bool ContainsKey(const std::string &key);
};
const std::string ELEMENT_PREFERENCES_KEY_PREFIX = SECURE_STORAGE_KEY_PREFIX;
const int ELEMENT_PREFERENCES_KEY_PREFIX_LENGTH = (sizeof SECURE_STORAGE_KEY_PREFIX) - 1;
std::wstring Utf8ToWide(const std::string& value) {
if (value.empty()) {
return std::wstring();
}
const int size = MultiByteToWideChar(CP_UTF8, 0, value.c_str(), -1, nullptr, 0);
if (size <= 0) {
throw GetLastError();
}
std::wstring wide(static_cast<size_t>(size), L'\0');
const int converted = MultiByteToWideChar(CP_UTF8, 0, value.c_str(), -1, wide.data(), size);
if (converted <= 0) {
throw GetLastError();
}
wide.resize(static_cast<size_t>(converted - 1));
return wide;
}
std::string WideToUtf8(const wchar_t* value) {
if (value == nullptr || value[0] == L'\0') {
return std::string();
}
const int size = WideCharToMultiByte(CP_UTF8, 0, value, -1, nullptr, 0, nullptr, nullptr);
if (size <= 0) {
throw GetLastError();
}
std::string utf8(static_cast<size_t>(size), '\0');
const int converted = WideCharToMultiByte(CP_UTF8, 0, value, -1, utf8.data(), size, nullptr, nullptr);
if (converted <= 0) {
throw GetLastError();
}
utf8.resize(static_cast<size_t>(converted - 1));
return utf8;
}
std::wstring CredentialFilter() {
return Utf8ToWide(ELEMENT_PREFERENCES_KEY_PREFIX + '*');
}
static inline void rtrim(std::wstring& s) {
s.erase(std::find_if(s.rbegin(), s.rend(), [](wchar_t ch) {
return !std::isspace(ch);
}).base(), s.end());
}
// static
void FlutterSecureStorageWindowsPlugin::RegisterWithRegistrar(
flutter::PluginRegistrarWindows *registrar)
{
auto channel =
std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
registrar->messenger(), "plugins.it_nomads.com/flutter_secure_storage",
&flutter::StandardMethodCodec::GetInstance());
auto plugin = std::make_unique<FlutterSecureStorageWindowsPlugin>();
channel->SetMethodCallHandler(
[plugin_pointer = plugin.get()](const auto &call, auto result)
{
plugin_pointer->HandleMethodCall(call, std::move(result));
});
registrar->AddPlugin(std::move(plugin));
}
FlutterSecureStorageWindowsPlugin::FlutterSecureStorageWindowsPlugin() {}
FlutterSecureStorageWindowsPlugin::~FlutterSecureStorageWindowsPlugin() {}
std::optional<std::string> FlutterSecureStorageWindowsPlugin::GetValueKey(const flutter::EncodableMap *args)
{
auto key = this->GetStringArg("key", args);
if (key.has_value())
return ELEMENT_PREFERENCES_KEY_PREFIX + key.value();
return std::nullopt;
}
std::string FlutterSecureStorageWindowsPlugin::RemoveKeyPrefix(const std::string& key)
{
return key.substr(ELEMENT_PREFERENCES_KEY_PREFIX_LENGTH);
}
std::optional<std::string> FlutterSecureStorageWindowsPlugin::GetStringArg(
const std::string &param,
const flutter::EncodableMap *args)
{
auto p = args->find(param);
if (p == args->end())
return std::nullopt;
return std::get<std::string>(p->second);
}
std::string FlutterSecureStorageWindowsPlugin::GetErrorString(const DWORD &error_code)
{
switch (error_code)
{
case ERROR_NO_SUCH_LOGON_SESSION:
return "ERROR_NO_SUCH_LOGIN_SESSION";
case ERROR_INVALID_FLAGS:
return "ERROR_INVALID_FLAGS";
case ERROR_BAD_USERNAME:
return "ERROR_BAD_USERNAME";
case SCARD_E_NO_READERS_AVAILABLE:
return "SCARD_E_NO_READERS_AVAILABLE";
case SCARD_E_NO_SMARTCARD:
return "SCARD_E_NO_SMARTCARD";
case SCARD_W_REMOVED_CARD:
return "SCARD_W_REMOVED_CARD";
case SCARD_W_WRONG_CHV:
return "SCARD_W_WRONG_CHV";
case ERROR_INVALID_PARAMETER:
return "ERROR_INVALID_PARAMETER";
default:
return "UNKNOWN_ERROR";
}
}
std::string FlutterSecureStorageWindowsPlugin::NtStatusToString(const CHAR* operation, NTSTATUS status)
{
std::ostringstream oss;
oss << operation << ", 0x" << std::hex << status;
switch (status)
{
case 0xc0000000:
oss << " (STATUS_SUCCESS)";
break;
case 0xC0000008:
oss << " (STATUS_INVALID_HANDLE)";
break;
case 0xc000000d:
oss << " (STATUS_INVALID_PARAMETER)";
break;
case 0xc00000bb:
oss << " (STATUS_NOT_SUPPORTED)";
break;
case 0xC0000225:
oss << " (STATUS_NOT_FOUND)";
break;
}
return oss.str();
}
DWORD FlutterSecureStorageWindowsPlugin::GetApplicationSupportPath(std::wstring &path)
{
std::wstring companyName;
std::wstring productName;
TCHAR nameBuffer[MAX_PATH + 1]{};
char* infoBuffer;
DWORD versionInfoSize;
DWORD resVal;
UINT queryLen;
LPVOID queryVal;
LPWSTR appdataPath;
std::wostringstream stream;
SHGetKnownFolderPath(FOLDERID_RoamingAppData,KF_FLAG_DEFAULT,NULL,&appdataPath);
if (nameBuffer == NULL) {
return ERROR_OUTOFMEMORY;
}
resVal = GetModuleFileName(NULL,nameBuffer,MAX_PATH);
if (resVal == 0) {
return GetLastError();
}
versionInfoSize = GetFileVersionInfoSize(nameBuffer, NULL);
if (versionInfoSize != 0) {
infoBuffer = (char*) calloc(versionInfoSize,sizeof(char));
if (infoBuffer == NULL) {
return ERROR_OUTOFMEMORY;
}
if (GetFileVersionInfo(nameBuffer, 0, versionInfoSize, infoBuffer) == 0) {
free(infoBuffer);
infoBuffer = NULL;
}
else {
if (VerQueryValue(infoBuffer, TEXT("\\StringFileInfo\\040904e4\\CompanyName"), &queryVal, &queryLen) != 0) {
companyName = SanitizeDirString(std::wstring((const TCHAR*)queryVal));
}
else if (VerQueryValue(infoBuffer, TEXT("\\StringFileInfo\\040904b0\\CompanyName"), &queryVal, &queryLen) != 0) {
companyName = SanitizeDirString(std::wstring((const TCHAR*)queryVal));
}
else {
companyName = L"placeholder_company";
}
if (VerQueryValue(infoBuffer, TEXT("\\StringFileInfo\\040904e4\\ProductName"), &queryVal, &queryLen) != 0) {
productName = SanitizeDirString(std::wstring((const TCHAR*)queryVal));
}
else if (VerQueryValue(infoBuffer, TEXT("\\StringFileInfo\\040904b0\\ProductName"), &queryVal, &queryLen) != 0) {
productName = SanitizeDirString(std::wstring((const TCHAR*)queryVal));
}
else {
productName = L"placeholder_product";
}
}
stream << appdataPath << "\\" << companyName << "\\" << productName;
path = stream.str();
}
else {
return GetLastError();
}
return ERROR_SUCCESS;
}
std::wstring FlutterSecureStorageWindowsPlugin::SanitizeDirString(std::wstring string)
{
std::wstring illegalChars = L"\\/:?\"<>|";
for (auto it = string.begin(); it < string.end(); ++it) {
if (illegalChars.find(*it) != std::wstring::npos) {
*it = L'_';
}
}
rtrim(string);
return string;
}
bool FlutterSecureStorageWindowsPlugin::PathExists(const std::wstring& path)
{
struct _stat info;
if (_wstat(path.c_str(), &info) != 0) {
return false;
}
return (info.st_mode & _S_IFDIR) != 0;
}
bool FlutterSecureStorageWindowsPlugin::MakePath(const std::wstring& path)
{
int ret = _wmkdir(path.c_str());
if (ret == 0) {
return true;
}
switch (errno) {
case ENOENT:
{
size_t pos = path.find_last_of('/');
if (pos == std::wstring::npos)
pos = path.find_last_of('\\');
if (pos == std::wstring::npos)
return false;
if (!MakePath(path.substr(0, pos)))
return false;
}
return 0 == _wmkdir(path.c_str());
case EEXIST:
return PathExists(path);
default:
return false;
}
}
PBYTE FlutterSecureStorageWindowsPlugin::GetEncryptionKey()
{
const size_t KEY_SIZE = 16;
DWORD credError = 0;
PBYTE AesKey;
PCREDENTIALW pcred;
std::wstring target_name = Utf8ToWide("key_" + ELEMENT_PREFERENCES_KEY_PREFIX);
AesKey = (PBYTE)HeapAlloc(GetProcessHeap(), 0, KEY_SIZE);
if (NULL == AesKey) {
return NULL;
}
bool ok = CredReadW(target_name.c_str(), CRED_TYPE_GENERIC, 0, &pcred);
if (ok) {
if (pcred->CredentialBlobSize != KEY_SIZE) {
CredFree(pcred);
CredDeleteW(target_name.c_str(), CRED_TYPE_GENERIC, 0);
goto NewKey;
}
memcpy(AesKey, pcred->CredentialBlob, KEY_SIZE);
CredFree(pcred);
return AesKey;
}
credError = GetLastError();
if (credError != ERROR_NOT_FOUND) {
return NULL;
}
NewKey:
if (BCryptGenRandom(NULL, AesKey, KEY_SIZE, BCRYPT_USE_SYSTEM_PREFERRED_RNG) != ERROR_SUCCESS) {
return NULL;
}
CREDENTIALW cred = { 0 };
cred.Type = CRED_TYPE_GENERIC;
cred.TargetName = const_cast<LPWSTR>(target_name.c_str());
cred.CredentialBlobSize = KEY_SIZE;
cred.CredentialBlob = AesKey;
cred.Persist = CRED_PERSIST_LOCAL_MACHINE;
ok = CredWriteW(&cred, 0);
if (!ok) {
std::cerr << "Failed to write encryption key" << std::endl;
return NULL;
}
return AesKey;
}
void FlutterSecureStorageWindowsPlugin::HandleMethodCall(
const flutter::MethodCall<flutter::EncodableValue> &method_call,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result)
{
auto method = method_call.method_name();
const auto *args = std::get_if<flutter::EncodableMap>(method_call.arguments());
std::wstring path;
if (GetApplicationSupportPath(path) != ERROR_SUCCESS) {
result->Error("Exception occurred", "GetApplicationSupportPath");
return;
}
try
{
if (method == "write")
{
auto key = this->GetValueKey(args);
auto val = this->GetStringArg("value", args);
if (key.has_value())
{
if (val.has_value())
this->Write(key.value(), val.value());
else
this->Delete(key.value());
result->Success();
}
else
{
result->Error("Exception occurred", "write");
}
}
else if (method == "read")
{
auto key = this->GetValueKey(args);
if (key.has_value())
{
auto val = this->Read(key.value());
if (val.has_value())
result->Success(flutter::EncodableValue(val.value()));
else
result->Success();
}
else
{
result->Error("Exception occurred", "read");
}
}
else if (method == "readAll")
{
auto creds = this->ReadAll();
result->Success(flutter::EncodableValue(creds));
}
else if (method == "delete")
{
auto key = this->GetValueKey(args);
if (key.has_value())
{
this->Delete(key.value());
result->Success();
}
else
{
result->Error("Exception occurred", "delete");
}
}
else if (method == "deleteAll")
{
this->DeleteAll();
result->Success();
}
else if (method == "containsKey")
{
auto key = this->GetValueKey(args);
if (key.has_value())
{
auto contains_key = this->ContainsKey(key.value());
result->Success(flutter::EncodableValue(contains_key));
}
else
{
result->Error("Exception occurred", "containsKey");
}
}
else
{
result->NotImplemented();
}
}
catch (DWORD e)
{
auto str_code = this->GetErrorString(e);
result->Error("Exception encountered: " + str_code, method);
}
}
void FlutterSecureStorageWindowsPlugin::Write(const std::string &key, const std::string &val)
{
//The recommended size for AES-GCM IV is 12 bytes
const DWORD NONCE_SIZE = 12;
const DWORD KEY_SIZE = 16;
NTSTATUS status;
BCRYPT_ALG_HANDLE algo = NULL;
BCRYPT_KEY_HANDLE keyHandle = NULL;
DWORD bytesWritten = 0,
ciphertextSize = 0;
PBYTE ciphertext = NULL,
iv = (PBYTE)HeapAlloc(GetProcessHeap(), 0, NONCE_SIZE),
encryptionKey = GetEncryptionKey();
BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authInfo{};
BCRYPT_AUTH_TAG_LENGTHS_STRUCT authTagLengths{};
std::basic_ofstream<BYTE> fs;
std::wstring appSupportPath;
std::string error;
if (iv == NULL) {
error = "IV HeapAlloc Failed";
goto err;
}
if (encryptionKey == NULL) {
error = "encryptionKey is NULL";
goto err;
}
status = BCryptOpenAlgorithmProvider(&algo, BCRYPT_AES_ALGORITHM, NULL, 0);
if (!BCRYPT_SUCCESS(status)) {
error = NtStatusToString("BCryptOpenAlgorithmProvider", status);
goto err;
}
status = BCryptSetProperty(algo, BCRYPT_CHAINING_MODE, (PUCHAR)BCRYPT_CHAIN_MODE_GCM, sizeof(BCRYPT_CHAIN_MODE_GCM), 0);
if (!BCRYPT_SUCCESS(status)) {
error = NtStatusToString("BCryptSetProperty", status);
goto err;
}
status = BCryptGetProperty(algo, BCRYPT_AUTH_TAG_LENGTH, (PBYTE)&authTagLengths, sizeof(BCRYPT_AUTH_TAG_LENGTHS_STRUCT), &bytesWritten, 0);
if (!BCRYPT_SUCCESS(status)) {
error = NtStatusToString("BCryptGetProperty", status);
goto err;
}
BCRYPT_INIT_AUTH_MODE_INFO(authInfo);
authInfo.pbNonce = (PUCHAR)HeapAlloc(GetProcessHeap(), 0, NONCE_SIZE);
if (authInfo.pbNonce == NULL) {
error = "pbNonce HeapAlloc Failed";
goto err;
}
authInfo.cbNonce = NONCE_SIZE;
status = BCryptGenRandom(NULL, iv, authInfo.cbNonce, BCRYPT_USE_SYSTEM_PREFERRED_RNG);
if (!BCRYPT_SUCCESS(status)) {
error = NtStatusToString("BCryptGenRandom", status);
goto err;
}
//copy the original IV into the authInfo, we can't write the IV directly into the authInfo because it will change after calling BCryptEncrypt and we still need to write the IV to file
memcpy(authInfo.pbNonce, iv, authInfo.cbNonce);
//We do not use additional authenticated data
authInfo.pbAuthData = NULL;
authInfo.cbAuthData = 0;
//Make space for the authentication tag
authInfo.pbTag = (PUCHAR)HeapAlloc(GetProcessHeap(), 0, authTagLengths.dwMaxLength);
if (authInfo.pbTag == NULL) {
error = "pbTag HeapAlloc Failed";
goto err;
}
authInfo.cbTag = authTagLengths.dwMaxLength;
status = BCryptGenerateSymmetricKey(algo, &keyHandle, NULL, 0, encryptionKey, KEY_SIZE, 0);
if (!BCRYPT_SUCCESS(status)) {
error = NtStatusToString("BCryptGenerateSymmetricKey", status);
goto err;
}
//First call to BCryptEncrypt to get size of ciphertext
status = BCryptEncrypt(keyHandle, (PUCHAR)val.c_str(), (ULONG)val.length() + 1, (PVOID)&authInfo, NULL, 0, NULL, 0, &bytesWritten, 0);
if (!BCRYPT_SUCCESS(status)) {
error = NtStatusToString("BCryptEncrypt1", status);
goto err;
}
ciphertextSize = bytesWritten;
ciphertext = (PBYTE)HeapAlloc(GetProcessHeap(), 0, ciphertextSize);
if (ciphertext == NULL) {
error = "CipherText HeapAlloc failed";
goto err;
}
//Actual encryption
status = BCryptEncrypt(keyHandle, (PUCHAR)val.c_str(), (ULONG)val.length() + 1, (PVOID)&authInfo, NULL, 0, ciphertext, ciphertextSize, &bytesWritten, 0);
if (!BCRYPT_SUCCESS(status)) {
error = NtStatusToString("BCryptEncrypt2", status);
goto err;
}
GetApplicationSupportPath(appSupportPath);
if (!PathExists(appSupportPath)) {
MakePath(appSupportPath);
}
fs = std::basic_ofstream<BYTE>(appSupportPath + L"\\" + std::wstring(key.begin(), key.end()) + L".secure", std::ios::binary | std::ios::trunc);
if (!fs) {
error = "Failed to open output stream";
goto err;
}
fs.write(iv, NONCE_SIZE);
fs.write(authInfo.pbTag, authInfo.cbTag);
fs.write(ciphertext, ciphertextSize);
fs.close();
HeapFree(GetProcessHeap(), 0, iv);
HeapFree(GetProcessHeap(), 0, encryptionKey);
HeapFree(GetProcessHeap(), 0, authInfo.pbNonce);
HeapFree(GetProcessHeap(), 0, authInfo.pbTag);
HeapFree(GetProcessHeap(), 0, ciphertext);
return;
err:
if (iv) {
HeapFree(GetProcessHeap(), 0, iv);
}
if (encryptionKey) {
HeapFree(GetProcessHeap(), 0, encryptionKey);
}
if (authInfo.pbNonce) {
HeapFree(GetProcessHeap(), 0, authInfo.pbNonce);
}
if (authInfo.pbTag) {
HeapFree(GetProcessHeap(), 0, authInfo.pbTag);
}
if (ciphertext) {
HeapFree(GetProcessHeap(), 0, ciphertext);
}
throw std::runtime_error(error);
}
std::optional<std::string> FlutterSecureStorageWindowsPlugin::Read(const std::string &key)
{
const DWORD NONCE_SIZE = 12;
const DWORD KEY_SIZE = 16;
NTSTATUS status;
BCRYPT_ALG_HANDLE algo = NULL;
BCRYPT_KEY_HANDLE keyHandle = NULL;
BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authInfo{};
BCRYPT_AUTH_TAG_LENGTHS_STRUCT authTagLengths{};
PBYTE encryptionKey = GetEncryptionKey(),
ciphertext = NULL,
fileBuffer = NULL,
plaintext = NULL;
DWORD plaintextSize = 0,
bytesWritten = 0,
ciphertextSize = 0;
std::wstring appSupportPath;
std::basic_ifstream<BYTE> fs;
std::streampos fileSize;
std::optional<std::string> returnVal = std::nullopt;
if (encryptionKey == NULL) {
std::cerr << "encryptionKey is NULL" << std::endl;
goto cleanup;
}
GetApplicationSupportPath(appSupportPath);
if (!PathExists(appSupportPath)) {
MakePath(appSupportPath);
}
//Read full file into a buffer
fs = std::basic_ifstream<BYTE>(appSupportPath + L"\\" + std::wstring(key.begin(), key.end()) + L".secure", std::ios::binary);
if (!fs.good()) {
//Backwards comp.
PCREDENTIALW pcred;
std::wstring target_name = Utf8ToWide(key);
bool ok = CredReadW(target_name.c_str(), CRED_TYPE_GENERIC, 0, &pcred);
if (ok)
{
auto val = std::string((char*)pcred->CredentialBlob);
CredFree(pcred);
returnVal = val;
}
goto cleanup;
}
fs.unsetf(std::ios::skipws);
fs.seekg(0, std::ios::end);
fileSize = fs.tellg();
fs.seekg(0, std::ios::beg);
fileBuffer = (PBYTE)HeapAlloc(GetProcessHeap(), 0, fileSize);
if (NULL == fileBuffer) {
std::cerr << "fileBuffer HeapAlloc failed" << std::endl;
goto cleanup;
}
fs.read(fileBuffer, fileSize);
fs.close();
status = BCryptOpenAlgorithmProvider(&algo, BCRYPT_AES_ALGORITHM, NULL, 0);
if (!BCRYPT_SUCCESS(status)) {
std::cerr << NtStatusToString("BCryptOpenAlgorithmProvider", status) << std::endl;
goto cleanup;
}
status = BCryptSetProperty(algo, BCRYPT_CHAINING_MODE, (PUCHAR)BCRYPT_CHAIN_MODE_GCM, sizeof(BCRYPT_CHAIN_MODE_GCM), 0);
if (!BCRYPT_SUCCESS(status)) {
std::cerr << NtStatusToString("BCryptOpenAlgorithmProvider", status) << std::endl;
goto cleanup;
}
status = BCryptGetProperty(algo, BCRYPT_AUTH_TAG_LENGTH, (PBYTE)&authTagLengths, sizeof(BCRYPT_AUTH_TAG_LENGTHS_STRUCT), &bytesWritten, 0);
if (!BCRYPT_SUCCESS(status)) {
std::cerr << NtStatusToString("BCryptGetProperty", status) << std::endl;
goto cleanup;
}
BCRYPT_INIT_AUTH_MODE_INFO(authInfo);
authInfo.pbNonce = (PUCHAR)HeapAlloc(GetProcessHeap(), 0, NONCE_SIZE);
if (authInfo.pbNonce == NULL) {
std::cerr << "pbNonce HeapAlloc Failed" << std::endl;
goto cleanup;
}
authInfo.cbNonce = NONCE_SIZE;
//Check if file is at least long enough for iv and authentication tag
if (fileSize <= static_cast<long long>(NONCE_SIZE) + authTagLengths.dwMaxLength) {
std::cerr << "File is too small" << std::endl;
goto cleanup;
}
authInfo.pbTag = (PUCHAR)HeapAlloc(GetProcessHeap(), 0, authTagLengths.dwMaxLength);
if (authInfo.pbTag == NULL) {
std::cerr << "pbTag HeapAlloc Failed" << std::endl;
goto cleanup;
}
ciphertextSize = (DWORD)fileSize - NONCE_SIZE - authTagLengths.dwMaxLength;
ciphertext = (PBYTE)HeapAlloc(GetProcessHeap(), 0, ciphertextSize);
if (ciphertext == NULL) {
std::cerr << "ciphertext HeapAlloc failed" << std::endl;
goto cleanup;
}
//Copy different parts needed for decryption from filebuffer
#pragma warning(push)
#pragma warning(disable:6385)
memcpy(authInfo.pbNonce, fileBuffer, NONCE_SIZE);
#pragma warning(pop)
memcpy(authInfo.pbTag, &fileBuffer[NONCE_SIZE], authTagLengths.dwMaxLength);
memcpy(ciphertext, &fileBuffer[NONCE_SIZE + authTagLengths.dwMaxLength], ciphertextSize);
authInfo.cbTag = authTagLengths.dwMaxLength;
status = BCryptGenerateSymmetricKey(algo, &keyHandle, NULL, 0, encryptionKey, KEY_SIZE, 0);
if (!BCRYPT_SUCCESS(status)) {
std::cerr << NtStatusToString("BCryptGenerateSymmetricKey", status) << std::endl;
goto cleanup;
}
//First call is to determine size of plaintext
status = BCryptDecrypt(keyHandle, ciphertext, ciphertextSize, (PVOID)&authInfo, NULL, 0, NULL, 0, &bytesWritten, 0);
if (!BCRYPT_SUCCESS(status)) {
std::cerr << NtStatusToString("BCryptDecrypt1", status) << std::endl;
goto cleanup;
}
plaintextSize = bytesWritten;
plaintext = (PBYTE)HeapAlloc(GetProcessHeap(), 0, plaintextSize);
if (NULL == plaintext) {
std::cerr << "plaintext HeapAlloc failed" << std::endl;
goto cleanup;
}
//Actuual decryption
status = BCryptDecrypt(keyHandle, ciphertext, ciphertextSize, (PVOID)&authInfo, NULL, 0, plaintext, plaintextSize, &bytesWritten, 0);
if (!BCRYPT_SUCCESS(status)) {
std::cerr << NtStatusToString("BCryptDecrypt2", status) << std::endl;
goto cleanup;
}
returnVal = (char*)plaintext;
cleanup:
if (encryptionKey) {
HeapFree(GetProcessHeap(), 0, encryptionKey);
}
if (ciphertext) {
HeapFree(GetProcessHeap(), 0, ciphertext);
}
if (plaintext) {
HeapFree(GetProcessHeap(), 0, plaintext);
}
if (fileBuffer) {
HeapFree(GetProcessHeap(), 0, fileBuffer);
}
if (authInfo.pbNonce) {
HeapFree(GetProcessHeap(), 0, authInfo.pbNonce);
}
if (authInfo.pbTag) {
HeapFree(GetProcessHeap(), 0, authInfo.pbTag);
}
return returnVal;
}
flutter::EncodableMap FlutterSecureStorageWindowsPlugin::ReadAll()
{
WIN32_FIND_DATA searchRes;
HANDLE hFile;
std::wstring appSupportPath;
GetApplicationSupportPath(appSupportPath);
if (!PathExists(appSupportPath)) {
MakePath(appSupportPath);
}
hFile = FindFirstFile((appSupportPath + L"\\*.secure").c_str(), &searchRes);
if (hFile == INVALID_HANDLE_VALUE) {
return flutter::EncodableMap();
}
flutter::EncodableMap creds;
do {
std::wstring fileName(searchRes.cFileName);
size_t pos = fileName.find(L".secure");
fileName.erase(pos, 7);
char* out = new char[fileName.length() + 1];
size_t charsConverted = 0;
wcstombs_s(&charsConverted, out, fileName.length() + 1, fileName.c_str(), fileName.length() + 1);
std::optional<std::string> val = this->Read(out);
auto key = this->RemoveKeyPrefix(out);
if (val.has_value()) {
creds[key] = val.value();
continue;
}
} while (FindNextFile(hFile, &searchRes) != 0);
//Backwards comp.
PCREDENTIALW* pcreds;
DWORD cred_count = 0;
std::wstring credential_filter = CredentialFilter();
bool ok = CredEnumerateW(credential_filter.c_str(), 0, &cred_count, &pcreds);
if (!ok)
{
return creds;
}
for (DWORD i = 0; i < cred_count; i++)
{
auto pcred = pcreds[i];
std::string target_name = WideToUtf8(pcred->TargetName);
auto val = std::string((char*)pcred->CredentialBlob);
auto key = this->RemoveKeyPrefix(target_name);
//If the key exists then data was already read from a file, which implies that the data read from the credential system is outdated
if (creds.find(key) == creds.end()) {
creds[key] = val;
}
}
CredFree(pcreds);
return creds;
}
void FlutterSecureStorageWindowsPlugin::Delete(const std::string &key)
{
std::wstring appSupportPath;
GetApplicationSupportPath(appSupportPath);
auto wstr = std::wstring(key.begin(), key.end());
BOOL ok = DeleteFile((appSupportPath + L"\\" + wstr + L".secure").c_str());
if (!ok) {
DWORD error = GetLastError();
if (error != ERROR_FILE_NOT_FOUND && error != ERROR_PATH_NOT_FOUND) {
throw error;
}
}
//Backwards comp.
ok = CredDeleteW(wstr.c_str(), CRED_TYPE_GENERIC, 0);
if (!ok)
{
auto error = GetLastError();
// Silently ignore if we try to delete a key that doesn't exist
if (error == ERROR_NOT_FOUND)
return;
throw error;
}
}
void FlutterSecureStorageWindowsPlugin::DeleteAll()
{
WIN32_FIND_DATA searchRes;
HANDLE hFile;
std::wstring appSupportPath;
GetApplicationSupportPath(appSupportPath);
if (!PathExists(appSupportPath)) {
MakePath(appSupportPath);
}
hFile = FindFirstFile((appSupportPath + L"\\*.secure").c_str(), &searchRes);
if (hFile == INVALID_HANDLE_VALUE) {
return;
}
do {
std::wstring fileName(searchRes.cFileName);
BOOL ok = DeleteFile((appSupportPath + L"\\" + fileName).c_str());
if (!ok) {
DWORD error = GetLastError();
if (error != ERROR_FILE_NOT_FOUND) {
throw error;
}
}
} while (FindNextFile(hFile, &searchRes) != 0);
//Backwards comp.
PCREDENTIALW* pcreds;
DWORD cred_count = 0;
std::wstring credential_filter = CredentialFilter();
bool read_ok = CredEnumerateW(credential_filter.c_str(), 0, &cred_count, &pcreds);
if (!read_ok)
{
auto error = GetLastError();
if (error == ERROR_NOT_FOUND)
// No credentials to delete
return;
throw error;
}
for (DWORD i = 0; i < cred_count; i++)
{
auto pcred = pcreds[i];
auto target_name = pcred->TargetName;
bool delete_ok = CredDeleteW(target_name, CRED_TYPE_GENERIC, 0);
if (!delete_ok)
{
throw GetLastError();
}
}
CredFree(pcreds);
}
bool FlutterSecureStorageWindowsPlugin::ContainsKey(const std::string &key)
{
std::wstring appSupportPath;
GetApplicationSupportPath(appSupportPath);
std::wstring wstr = std::wstring(key.begin(), key.end());
if (INVALID_FILE_ATTRIBUTES == GetFileAttributes((appSupportPath + L"\\" + wstr + L".secure").c_str())) {
//Backwards comp.
PCREDENTIALW pcred;
std::wstring target_name = Utf8ToWide(key);
bool ok = CredReadW(target_name.c_str(), CRED_TYPE_GENERIC, 0, &pcred);
if (ok) return true;
auto error = GetLastError();
if (error == ERROR_NOT_FOUND)
return false;
throw error;
}
return true;
}
} // namespace
void FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
FlutterDesktopPluginRegistrarRef registrar)
{
FlutterSecureStorageWindowsPlugin::RegisterWithRegistrar(
flutter::PluginRegistrarManager::GetInstance()
->GetRegistrar<flutter::PluginRegistrarWindows>(registrar));
}

View File

@ -0,0 +1,23 @@
#ifndef FLUTTER_PLUGIN_FLUTTER_SECURE_STORAGE_WINDOWS_PLUGIN_H_
#define FLUTTER_PLUGIN_FLUTTER_SECURE_STORAGE_WINDOWS_PLUGIN_H_
#include <flutter_plugin_registrar.h>
#ifdef FLUTTER_PLUGIN_IMPL
#define FLUTTER_PLUGIN_EXPORT __declspec(dllexport)
#else
#define FLUTTER_PLUGIN_EXPORT __declspec(dllimport)
#endif
#if defined(__cplusplus)
extern "C" {
#endif
FLUTTER_PLUGIN_EXPORT void FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
FlutterDesktopPluginRegistrarRef registrar);
#if defined(__cplusplus)
} // extern "C"
#endif
#endif // FLUTTER_PLUGIN_FLUTTER_SECURE_STORAGE_WINDOWS_PLUGIN_H_