mirror of
https://github.com/localsend/localsend.git
synced 2026-08-07 07:14:52 +00:00
feat: add option to disable checksum verification
This commit is contained in:
@@ -39,6 +39,11 @@ pub struct ServerConfigV2 {
|
||||
/// Optional PIN that senders must provide via the `pin` query parameter.
|
||||
pub pin: Option<String>,
|
||||
|
||||
/// Whether the SHA-256 checksums that senders provide for their files are
|
||||
/// verified after receiving. When disabled, received files are not hashed
|
||||
/// and a mismatch is not detected.
|
||||
pub verify_checksums: bool,
|
||||
|
||||
/// Channel on which the server emits events that must be handled by the application.
|
||||
pub event_tx: mpsc::Sender<ServerEventV2>,
|
||||
}
|
||||
@@ -48,6 +53,9 @@ pub(crate) struct V2State {
|
||||
/// Optional PIN required for prepare-upload requests.
|
||||
pub(crate) pin: Option<String>,
|
||||
|
||||
/// Whether sender-provided SHA-256 checksums are verified after receiving.
|
||||
pub(crate) verify_checksums: bool,
|
||||
|
||||
/// Channel on which server events are emitted to the application.
|
||||
pub(crate) event_tx: mpsc::Sender<ServerEventV2>,
|
||||
|
||||
@@ -95,6 +103,7 @@ impl AppState {
|
||||
let v2 = v2_config.map(|config| {
|
||||
Arc::new(V2State {
|
||||
pin: config.pin,
|
||||
verify_checksums: config.verify_checksums,
|
||||
event_tx: config.event_tx,
|
||||
session: Mutex::new(None),
|
||||
pin_attempts: Mutex::new(LruCache::new(NonZeroUsize::new(200).unwrap())),
|
||||
|
||||
@@ -385,7 +385,10 @@ pub(crate) async fn upload(
|
||||
let mut upload_guard = UploadGuard::new(v2.clone(), session_id.clone(), file_id.clone());
|
||||
|
||||
let file_size = file_dto.size;
|
||||
let expected_sha256 = file_dto.sha256.clone();
|
||||
let expected_sha256 = match v2.verify_checksums {
|
||||
true => file_dto.sha256.clone(),
|
||||
false => None,
|
||||
};
|
||||
let timestamps = match &file_dto.metadata {
|
||||
Some(metadata) => FileTimestamps {
|
||||
modified: metadata.modified_time(),
|
||||
|
||||
@@ -76,6 +76,7 @@ async fn start_register_server(
|
||||
None,
|
||||
Some(ServerConfigV2 {
|
||||
pin: None,
|
||||
verify_checksums: true,
|
||||
event_tx,
|
||||
}),
|
||||
None,
|
||||
|
||||
@@ -37,6 +37,16 @@ async fn start_test_server(
|
||||
pin: Option<String>,
|
||||
accept: bool,
|
||||
save_dir: Option<PathBuf>,
|
||||
) -> TestServer {
|
||||
start_test_server_with_verification(pin, accept, save_dir, true).await
|
||||
}
|
||||
|
||||
/// Like [start_test_server], but allows disabling the checksum verification.
|
||||
async fn start_test_server_with_verification(
|
||||
pin: Option<String>,
|
||||
accept: bool,
|
||||
save_dir: Option<PathBuf>,
|
||||
verify_checksums: bool,
|
||||
) -> TestServer {
|
||||
let _ = tracing_subscriber::fmt().with_test_writer().try_init();
|
||||
let port = free_port();
|
||||
@@ -125,7 +135,11 @@ async fn start_test_server(
|
||||
token: "server-fingerprint".to_string(),
|
||||
},
|
||||
None,
|
||||
Some(ServerConfigV2 { pin, event_tx }),
|
||||
Some(ServerConfigV2 {
|
||||
pin,
|
||||
verify_checksums,
|
||||
event_tx,
|
||||
}),
|
||||
None,
|
||||
stop_rx,
|
||||
)
|
||||
@@ -499,6 +513,45 @@ async fn test_upload_with_mismatched_sha256() {
|
||||
assert_status(result, 422);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_upload_mismatched_sha256_with_verification_disabled() {
|
||||
let server = start_test_server_with_verification(None, true, None, false).await;
|
||||
let client = LsHttpClientV2::try_new_without_cert().unwrap();
|
||||
|
||||
let bytes = b"hello".to_vec();
|
||||
let mut file = file_dto("file-a", "a.bin", bytes.len() as u64);
|
||||
file.sha256 = Some(sha256_hex(b"something else"));
|
||||
|
||||
let response = client
|
||||
.prepare_upload(
|
||||
ProtocolType::Http,
|
||||
"127.0.0.1",
|
||||
server.port,
|
||||
None,
|
||||
prepare_upload_request(&[file]),
|
||||
None,
|
||||
CancellationToken::new(),
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.response
|
||||
.unwrap();
|
||||
|
||||
// The mismatch goes unnoticed because the received bytes are not hashed.
|
||||
upload_bytes(
|
||||
&client,
|
||||
server.port,
|
||||
&response.session_id,
|
||||
"file-a",
|
||||
&response.files["file-a"],
|
||||
&bytes,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(server.received.lock().await["file-a"], bytes);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_upload_retry_after_mismatched_sha256() {
|
||||
let server = start_test_server(None, true, None).await;
|
||||
@@ -927,6 +980,7 @@ async fn test_prepare_upload_aborted_by_sender_disconnect() {
|
||||
None,
|
||||
Some(ServerConfigV2 {
|
||||
pin: None,
|
||||
verify_checksums: true,
|
||||
event_tx,
|
||||
}),
|
||||
None,
|
||||
@@ -1029,6 +1083,7 @@ async fn test_prepare_upload_cancelled_by_session_less_cancel() {
|
||||
None,
|
||||
Some(ServerConfigV2 {
|
||||
pin: None,
|
||||
verify_checksums: true,
|
||||
event_tx,
|
||||
}),
|
||||
None,
|
||||
@@ -1164,6 +1219,7 @@ async fn test_prepare_upload_aborted_by_sender_disconnect_tls() {
|
||||
None,
|
||||
Some(ServerConfigV2 {
|
||||
pin: None,
|
||||
verify_checksums: true,
|
||||
event_tx,
|
||||
}),
|
||||
None,
|
||||
|
||||
@@ -123,6 +123,7 @@ async fn start_tls_server_with_web(identity: &Identity, web: Option<WebConfig>)
|
||||
None,
|
||||
Some(ServerConfigV2 {
|
||||
pin: None,
|
||||
verify_checksums: true,
|
||||
event_tx,
|
||||
}),
|
||||
web,
|
||||
|
||||
@@ -140,6 +140,7 @@ async fn start_test_server(
|
||||
None,
|
||||
Some(ServerConfigV2 {
|
||||
pin: None,
|
||||
verify_checksums: true,
|
||||
event_tx: v2_event_tx,
|
||||
}),
|
||||
web_config,
|
||||
@@ -340,6 +341,7 @@ async fn test_upload_page() {
|
||||
None,
|
||||
Some(ServerConfigV2 {
|
||||
pin: None,
|
||||
verify_checksums: true,
|
||||
event_tx: v2_event_tx,
|
||||
}),
|
||||
Some(WebConfig {
|
||||
|
||||
@@ -34,6 +34,7 @@ Future<RsHttpServer> startServer({
|
||||
DeviceType? deviceType,
|
||||
required String fingerprint,
|
||||
String? pin,
|
||||
required bool verifyChecksums,
|
||||
WebParams? web,
|
||||
String? showToken,
|
||||
}) => RustLib.instance.api.crateApiServerStartServer(
|
||||
@@ -45,6 +46,7 @@ Future<RsHttpServer> startServer({
|
||||
deviceType: deviceType,
|
||||
fingerprint: fingerprint,
|
||||
pin: pin,
|
||||
verifyChecksums: verifyChecksums,
|
||||
web: web,
|
||||
showToken: showToken,
|
||||
);
|
||||
|
||||
@@ -302,6 +302,7 @@ abstract class RustLibApi extends BaseApi {
|
||||
DeviceType? deviceType,
|
||||
required String fingerprint,
|
||||
String? pin,
|
||||
required bool verifyChecksums,
|
||||
WebParams? web,
|
||||
String? showToken,
|
||||
});
|
||||
@@ -2043,6 +2044,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
DeviceType? deviceType,
|
||||
required String fingerprint,
|
||||
String? pin,
|
||||
required bool verifyChecksums,
|
||||
WebParams? web,
|
||||
String? showToken,
|
||||
}) {
|
||||
@@ -2058,6 +2060,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
sse_encode_opt_box_autoadd_device_type(deviceType, serializer);
|
||||
sse_encode_String(fingerprint, serializer);
|
||||
sse_encode_opt_String(pin, serializer);
|
||||
sse_encode_bool(verifyChecksums, serializer);
|
||||
sse_encode_opt_box_autoadd_web_params(web, serializer);
|
||||
sse_encode_opt_String(showToken, serializer);
|
||||
pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 55, port: port_);
|
||||
@@ -2067,7 +2070,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
decodeErrorData: sse_decode_AnyhowException,
|
||||
),
|
||||
constMeta: kCrateApiServerStartServerConstMeta,
|
||||
argValues: [port, tls, alias, version, deviceModel, deviceType, fingerprint, pin, web, showToken],
|
||||
argValues: [port, tls, alias, version, deviceModel, deviceType, fingerprint, pin, verifyChecksums, web, showToken],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
@@ -2075,7 +2078,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
|
||||
TaskConstMeta get kCrateApiServerStartServerConstMeta => const TaskConstMeta(
|
||||
debugName: 'start_server',
|
||||
argNames: ['port', 'tls', 'alias', 'version', 'deviceModel', 'deviceType', 'fingerprint', 'pin', 'web', 'showToken'],
|
||||
argNames: ['port', 'tls', 'alias', 'version', 'deviceModel', 'deviceType', 'fingerprint', 'pin', 'verifyChecksums', 'web', 'showToken'],
|
||||
);
|
||||
|
||||
@override
|
||||
|
||||
@@ -30,6 +30,10 @@ class HttpServerStartTask implements BaseHttpServerTask {
|
||||
/// Optional PIN that senders must provide to start an upload session.
|
||||
final String? pin;
|
||||
|
||||
/// Whether the SHA-256 checksums that senders provide for their files are
|
||||
/// verified after receiving.
|
||||
final bool verifyChecksums;
|
||||
|
||||
/// Serves the web pages: the download page (web send) and/or the upload page.
|
||||
/// `null` disables the web pages.
|
||||
final WebParams? web;
|
||||
@@ -40,6 +44,7 @@ class HttpServerStartTask implements BaseHttpServerTask {
|
||||
|
||||
HttpServerStartTask({
|
||||
required this.pin,
|
||||
required this.verifyChecksums,
|
||||
required this.web,
|
||||
required this.showToken,
|
||||
});
|
||||
@@ -398,6 +403,7 @@ Future<void> setupHttpServerIsolate(
|
||||
deviceType: syncState.deviceInfo.deviceType.toRust(),
|
||||
fingerprint: syncState.securityContext.certificateHash,
|
||||
pin: startTask.pin,
|
||||
verifyChecksums: startTask.verifyChecksums,
|
||||
web: startTask.web,
|
||||
showToken: startTask.showToken,
|
||||
);
|
||||
|
||||
@@ -256,6 +256,10 @@ class IsolateHttpUploadCancelAction extends ReduxAction<IsolateController, Paren
|
||||
class IsolateHttpServerStartAction extends ReduxActionWithResult<IsolateController, ParentIsolateState, Stream<HttpServerEvent>> {
|
||||
final String? pin;
|
||||
|
||||
/// Whether the SHA-256 checksums that senders provide for their files are
|
||||
/// verified after receiving.
|
||||
final bool verifyChecksums;
|
||||
|
||||
/// Serves the web pages: the download page (web send) and/or the upload page.
|
||||
/// `null` disables the web pages.
|
||||
final WebParams? web;
|
||||
@@ -266,6 +270,7 @@ class IsolateHttpServerStartAction extends ReduxActionWithResult<IsolateControll
|
||||
|
||||
IsolateHttpServerStartAction({
|
||||
required this.pin,
|
||||
required this.verifyChecksums,
|
||||
required this.web,
|
||||
required this.showToken,
|
||||
});
|
||||
@@ -282,6 +287,7 @@ class IsolateHttpServerStartAction extends ReduxActionWithResult<IsolateControll
|
||||
connection.sendWrappedTaskAndListenStream(
|
||||
task: HttpServerStartTask(
|
||||
pin: pin,
|
||||
verifyChecksums: verifyChecksums,
|
||||
web: web,
|
||||
showToken: showToken,
|
||||
),
|
||||
|
||||
@@ -22,6 +22,7 @@ class HttpServerService {
|
||||
required DeviceType? deviceType,
|
||||
required String fingerprint,
|
||||
required String? pin,
|
||||
required bool verifyChecksums,
|
||||
required WebParams? web,
|
||||
required String? showToken,
|
||||
}) async {
|
||||
@@ -38,6 +39,7 @@ class HttpServerService {
|
||||
deviceType: deviceType,
|
||||
fingerprint: fingerprint,
|
||||
pin: pin,
|
||||
verifyChecksums: verifyChecksums,
|
||||
web: web,
|
||||
showToken: showToken,
|
||||
);
|
||||
|
||||
@@ -183,6 +183,7 @@ pub async fn start_server(
|
||||
device_type: Option<DeviceType>,
|
||||
fingerprint: String,
|
||||
pin: Option<String>,
|
||||
verify_checksums: bool,
|
||||
web: Option<WebParams>,
|
||||
show_token: Option<String>,
|
||||
) -> anyhow::Result<RsHttpServer> {
|
||||
@@ -243,7 +244,11 @@ pub async fn start_server(
|
||||
token: fingerprint,
|
||||
},
|
||||
internal_config,
|
||||
Some(ServerConfigV2 { pin, event_tx }),
|
||||
Some(ServerConfigV2 {
|
||||
pin,
|
||||
verify_checksums,
|
||||
event_tx,
|
||||
}),
|
||||
web_config,
|
||||
stop_rx,
|
||||
)
|
||||
|
||||
@@ -3201,6 +3201,7 @@ fn wire__crate__api__server__start_server_impl(
|
||||
<Option<crate::api::model::DeviceType>>::sse_decode(&mut deserializer);
|
||||
let api_fingerprint = <String>::sse_decode(&mut deserializer);
|
||||
let api_pin = <Option<String>>::sse_decode(&mut deserializer);
|
||||
let api_verify_checksums = <bool>::sse_decode(&mut deserializer);
|
||||
let api_web = <Option<crate::api::server::WebParams>>::sse_decode(&mut deserializer);
|
||||
let api_show_token = <Option<String>>::sse_decode(&mut deserializer);
|
||||
deserializer.end();
|
||||
@@ -3216,6 +3217,7 @@ fn wire__crate__api__server__start_server_impl(
|
||||
api_device_type,
|
||||
api_fingerprint,
|
||||
api_pin,
|
||||
api_verify_checksums,
|
||||
api_web,
|
||||
api_show_token,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user