mirror of
https://github.com/localsend/localsend.git
synced 2026-08-07 07:14:52 +00:00
feat: path sanitizing in core
This commit is contained in:
@@ -2,3 +2,4 @@
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
/build/
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// This file is automatically generated, so please do not edit it.
|
||||
// @generated by `flutter_rust_bridge`@ 2.12.0.
|
||||
|
||||
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
|
||||
|
||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
||||
import 'package:localsend_isolates/rust/frb_generated.dart';
|
||||
|
||||
/// Rewrites `name` into a file name that is legal on the current platform,
|
||||
/// replacing illegal characters with `_`.
|
||||
String sanitizeFileName({required String name}) => RustLib.instance.api.crateApiFilenameSanitizeFileName(name: name);
|
||||
|
||||
/// Whether `name` is a legal file name on the current platform, i.e. whether
|
||||
/// [sanitize_file_name] would leave it untouched.
|
||||
bool isValidFileName({required String name}) => RustLib.instance.api.crateApiFilenameIsValidFileName(name: name);
|
||||
@@ -10,6 +10,7 @@ import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
||||
import 'package:localsend_isolates/rust/api/cancel.dart';
|
||||
import 'package:localsend_isolates/rust/api/crypto.dart';
|
||||
import 'package:localsend_isolates/rust/api/discovery.dart';
|
||||
import 'package:localsend_isolates/rust/api/filename.dart';
|
||||
import 'package:localsend_isolates/rust/api/http.dart';
|
||||
import 'package:localsend_isolates/rust/api/logging.dart';
|
||||
import 'package:localsend_isolates/rust/api/model.dart';
|
||||
@@ -74,7 +75,7 @@ class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
|
||||
String get codegenVersion => '2.12.0';
|
||||
|
||||
@override
|
||||
int get rustContentHash => -895476497;
|
||||
int get rustContentHash => -1120530143;
|
||||
|
||||
static const kDefaultExternalLibraryLoaderConfig = ExternalLibraryLoaderConfig(
|
||||
stem: 'rust_lib_localsend_app',
|
||||
@@ -271,6 +272,10 @@ abstract class RustLibApi extends BaseApi {
|
||||
|
||||
Stream<RsHashFileEvent> crateApiCryptoHashFile({String? path, int? fileDescriptor, Uint8List? bytes, required RsCancellationToken cancelToken});
|
||||
|
||||
bool crateApiFilenameIsValidFileName({required String name});
|
||||
|
||||
String crateApiFilenameSanitizeFileName({required String name});
|
||||
|
||||
Future<RsDiscovery> crateApiDiscoveryStartDiscovery({
|
||||
required String group,
|
||||
required int port,
|
||||
@@ -1895,6 +1900,56 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
argNames: ['sink', 'path', 'fileDescriptor', 'bytes', 'cancelToken'],
|
||||
);
|
||||
|
||||
@override
|
||||
bool crateApiFilenameIsValidFileName({required String name}) {
|
||||
return handler.executeSync(
|
||||
SyncTask(
|
||||
callFfi: () {
|
||||
final serializer = SseSerializer(generalizedFrbRustBinding);
|
||||
sse_encode_String(name, serializer);
|
||||
return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 52)!;
|
||||
},
|
||||
codec: SseCodec(
|
||||
decodeSuccessData: sse_decode_bool,
|
||||
decodeErrorData: null,
|
||||
),
|
||||
constMeta: kCrateApiFilenameIsValidFileNameConstMeta,
|
||||
argValues: [name],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta get kCrateApiFilenameIsValidFileNameConstMeta => const TaskConstMeta(
|
||||
debugName: 'is_valid_file_name',
|
||||
argNames: ['name'],
|
||||
);
|
||||
|
||||
@override
|
||||
String crateApiFilenameSanitizeFileName({required String name}) {
|
||||
return handler.executeSync(
|
||||
SyncTask(
|
||||
callFfi: () {
|
||||
final serializer = SseSerializer(generalizedFrbRustBinding);
|
||||
sse_encode_String(name, serializer);
|
||||
return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 53)!;
|
||||
},
|
||||
codec: SseCodec(
|
||||
decodeSuccessData: sse_decode_String,
|
||||
decodeErrorData: null,
|
||||
),
|
||||
constMeta: kCrateApiFilenameSanitizeFileNameConstMeta,
|
||||
argValues: [name],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta get kCrateApiFilenameSanitizeFileNameConstMeta => const TaskConstMeta(
|
||||
debugName: 'sanitize_file_name',
|
||||
argNames: ['name'],
|
||||
);
|
||||
|
||||
@override
|
||||
Future<RsDiscovery> crateApiDiscoveryStartDiscovery({
|
||||
required String group,
|
||||
@@ -1930,7 +1985,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
sse_encode_String(certPem, serializer);
|
||||
sse_encode_String(privateKeyPem, serializer);
|
||||
sse_encode_u_64(timeoutMs, serializer);
|
||||
pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 52, port: port_);
|
||||
pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 54, port: port_);
|
||||
},
|
||||
codec: SseCodec(
|
||||
decodeSuccessData: sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsDiscovery,
|
||||
@@ -2005,7 +2060,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
sse_encode_opt_String(pin, serializer);
|
||||
sse_encode_opt_box_autoadd_web_send_params(webSend, serializer);
|
||||
sse_encode_opt_String(showToken, serializer);
|
||||
pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 53, port: port_);
|
||||
pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 55, port: port_);
|
||||
},
|
||||
codec: SseCodec(
|
||||
decodeSuccessData: sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRsHttpServer,
|
||||
@@ -2031,7 +2086,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
final serializer = SseSerializer(generalizedFrbRustBinding);
|
||||
sse_encode_String(cert, serializer);
|
||||
sse_encode_String(publicKey, serializer);
|
||||
pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 54, port: port_);
|
||||
pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 56, port: port_);
|
||||
},
|
||||
codec: SseCodec(
|
||||
decodeSuccessData: sse_decode_unit,
|
||||
|
||||
@@ -11,6 +11,7 @@ import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_io.dart';
|
||||
import 'package:localsend_isolates/rust/api/cancel.dart';
|
||||
import 'package:localsend_isolates/rust/api/crypto.dart';
|
||||
import 'package:localsend_isolates/rust/api/discovery.dart';
|
||||
import 'package:localsend_isolates/rust/api/filename.dart';
|
||||
import 'package:localsend_isolates/rust/api/http.dart';
|
||||
import 'package:localsend_isolates/rust/api/logging.dart';
|
||||
import 'package:localsend_isolates/rust/api/model.dart';
|
||||
|
||||
@@ -13,6 +13,7 @@ import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_web.dart';
|
||||
import 'package:localsend_isolates/rust/api/cancel.dart';
|
||||
import 'package:localsend_isolates/rust/api/crypto.dart';
|
||||
import 'package:localsend_isolates/rust/api/discovery.dart';
|
||||
import 'package:localsend_isolates/rust/api/filename.dart';
|
||||
import 'package:localsend_isolates/rust/api/http.dart';
|
||||
import 'package:localsend_isolates/rust/api/logging.dart';
|
||||
import 'package:localsend_isolates/rust/api/model.dart';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:gal/gal.dart';
|
||||
import 'package:legalize/legalize.dart';
|
||||
import 'package:localsend_isolates/rust/api/filename.dart' as rust_filename;
|
||||
import 'package:localsend_isolates/util/android_channel.dart' as android_channel;
|
||||
import 'package:localsend_isolates/util/content_uri_helper.dart';
|
||||
import 'package:localsend_isolates/util/file_path_helper.dart';
|
||||
@@ -183,7 +183,7 @@ Future<(String, String?, String)> digestFilePathAndPrepareDirectory({
|
||||
return (destinationUri, documentUri, p.basename(fileName));
|
||||
}
|
||||
|
||||
final actualFileName = legalizeFilename(p.basename(fileName), os: Platform.operatingSystem);
|
||||
final actualFileName = rust_filename.sanitizeFileName(name: p.basename(fileName));
|
||||
final fileNameParts = p.split(fileName);
|
||||
final dir = p.joinAll([parentDirectory, ...fileNameParts.take(fileNameParts.length - 1)]);
|
||||
|
||||
|
||||
@@ -344,14 +344,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
legalize:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: legalize
|
||||
sha256: bc3068aa4f14588575c8b5ba2a9e608c242dad325e7f7c56fedd68adba33526a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -17,7 +17,6 @@ dependencies:
|
||||
flutter_rust_bridge: 2.12.0
|
||||
freezed_annotation: 3.1.0
|
||||
gal: 2.3.2
|
||||
legalize: 1.2.2
|
||||
logging: 1.3.0
|
||||
mime: 2.0.0
|
||||
path: 1.9.1
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
use flutter_rust_bridge::frb;
|
||||
use localsend::util::filename;
|
||||
|
||||
/// Rewrites `name` into a file name that is legal on the current platform,
|
||||
/// replacing illegal characters with `_`.
|
||||
#[frb(sync)]
|
||||
pub fn sanitize_file_name(name: String) -> String {
|
||||
filename::sanitize(&name, filename::Rules::current())
|
||||
}
|
||||
|
||||
/// Whether `name` is a legal file name on the current platform, i.e. whether
|
||||
/// [sanitize_file_name] would leave it untouched.
|
||||
#[frb(sync)]
|
||||
pub fn is_valid_file_name(name: String) -> bool {
|
||||
filename::is_valid(&name, filename::Rules::current())
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
pub mod cancel;
|
||||
pub mod crypto;
|
||||
pub mod discovery;
|
||||
pub mod filename;
|
||||
pub mod http;
|
||||
pub mod logging;
|
||||
pub mod model;
|
||||
|
||||
@@ -44,7 +44,7 @@ flutter_rust_bridge::frb_generated_boilerplate!(
|
||||
default_rust_auto_opaque = RustAutoOpaqueMoi,
|
||||
);
|
||||
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.12.0";
|
||||
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -895476497;
|
||||
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -1120530143;
|
||||
|
||||
// Section: executor
|
||||
|
||||
@@ -3042,6 +3042,68 @@ fn wire__crate__api__crypto__hash_file_impl(
|
||||
},
|
||||
)
|
||||
}
|
||||
fn wire__crate__api__filename__is_valid_file_name_impl(
|
||||
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
||||
rust_vec_len_: i32,
|
||||
data_len_: i32,
|
||||
) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse {
|
||||
FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::<flutter_rust_bridge::for_generated::SseCodec, _>(
|
||||
flutter_rust_bridge::for_generated::TaskInfo {
|
||||
debug_name: "is_valid_file_name",
|
||||
port: None,
|
||||
mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync,
|
||||
},
|
||||
move || {
|
||||
let message = unsafe {
|
||||
flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(
|
||||
ptr_,
|
||||
rust_vec_len_,
|
||||
data_len_,
|
||||
)
|
||||
};
|
||||
let mut deserializer =
|
||||
flutter_rust_bridge::for_generated::SseDeserializer::new(message);
|
||||
let api_name = <String>::sse_decode(&mut deserializer);
|
||||
deserializer.end();
|
||||
transform_result_sse::<_, ()>((move || {
|
||||
let output_ok =
|
||||
Result::<_, ()>::Ok(crate::api::filename::is_valid_file_name(api_name))?;
|
||||
Ok(output_ok)
|
||||
})())
|
||||
},
|
||||
)
|
||||
}
|
||||
fn wire__crate__api__filename__sanitize_file_name_impl(
|
||||
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
||||
rust_vec_len_: i32,
|
||||
data_len_: i32,
|
||||
) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse {
|
||||
FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::<flutter_rust_bridge::for_generated::SseCodec, _>(
|
||||
flutter_rust_bridge::for_generated::TaskInfo {
|
||||
debug_name: "sanitize_file_name",
|
||||
port: None,
|
||||
mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync,
|
||||
},
|
||||
move || {
|
||||
let message = unsafe {
|
||||
flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(
|
||||
ptr_,
|
||||
rust_vec_len_,
|
||||
data_len_,
|
||||
)
|
||||
};
|
||||
let mut deserializer =
|
||||
flutter_rust_bridge::for_generated::SseDeserializer::new(message);
|
||||
let api_name = <String>::sse_decode(&mut deserializer);
|
||||
deserializer.end();
|
||||
transform_result_sse::<_, ()>((move || {
|
||||
let output_ok =
|
||||
Result::<_, ()>::Ok(crate::api::filename::sanitize_file_name(api_name))?;
|
||||
Ok(output_ok)
|
||||
})())
|
||||
},
|
||||
)
|
||||
}
|
||||
fn wire__crate__api__discovery__start_discovery_impl(
|
||||
port_: flutter_rust_bridge::for_generated::MessagePort,
|
||||
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
||||
@@ -5044,9 +5106,9 @@ fn pde_ffi_dispatcher_primary_impl(
|
||||
data_len,
|
||||
),
|
||||
51 => wire__crate__api__crypto__hash_file_impl(port, ptr, rust_vec_len, data_len),
|
||||
52 => wire__crate__api__discovery__start_discovery_impl(port, ptr, rust_vec_len, data_len),
|
||||
53 => wire__crate__api__server__start_server_impl(port, ptr, rust_vec_len, data_len),
|
||||
54 => wire__crate__api__crypto__verify_cert_impl(port, ptr, rust_vec_len, data_len),
|
||||
54 => wire__crate__api__discovery__start_discovery_impl(port, ptr, rust_vec_len, data_len),
|
||||
55 => wire__crate__api__server__start_server_impl(port, ptr, rust_vec_len, data_len),
|
||||
56 => wire__crate__api__crypto__verify_cert_impl(port, ptr, rust_vec_len, data_len),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
@@ -5063,6 +5125,8 @@ fn pde_ffi_dispatcher_sync_impl(
|
||||
6 => wire__crate__api__cancel__RsCancellationToken_cancel_impl(ptr, rust_vec_len, data_len),
|
||||
45 => wire__crate__api__cancel__create_cancellation_token_impl(ptr, rust_vec_len, data_len),
|
||||
46 => wire__crate__api__http__create_client_impl(ptr, rust_vec_len, data_len),
|
||||
52 => wire__crate__api__filename__is_valid_file_name_impl(ptr, rust_vec_len, data_len),
|
||||
53 => wire__crate__api__filename__sanitize_file_name_impl(ptr, rust_vec_len, data_len),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:localsend_isolates/rust/frb_generated.dart';
|
||||
import 'package:localsend_isolates/src/task/server/file_saver.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
void main() {
|
||||
late Directory tempDir;
|
||||
|
||||
setUpAll(() {
|
||||
RustLib.initMock(api: _MockRustLibApi());
|
||||
});
|
||||
|
||||
setUp(() {
|
||||
tempDir = Directory.systemTemp.createTempSync('file_saver_test');
|
||||
});
|
||||
@@ -57,3 +62,12 @@ void main() {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// The sanitizer lives in the Rust library, which is not loaded in unit tests.
|
||||
class _MockRustLibApi implements RustLibApi {
|
||||
@override
|
||||
String crateApiFilenameSanitizeFileName({required String name}) => name;
|
||||
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => throw UnsupportedError('Not mocked: ${invocation.memberName}');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user