file_signature 1.0.1
file_signature: ^1.0.1 copied to clipboard
A pure Dart security utility that validates files by checking Magic Byte signatures. Prevents rename attacks (e.g. .exe as .png) on Mobile, Web, and Desktop.
import 'package:file_signature/file_signature.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:file_picker/file_picker.dart';
void main() {
runApp(const MaterialApp(home: FileSignatureDemo()));
}
class FileSignatureDemo extends StatefulWidget {
const FileSignatureDemo({super.key});
@override
State<FileSignatureDemo> createState() => _FileSignatureDemoState();
}
class _FileSignatureDemoState extends State<FileSignatureDemo> {
String _status = "Pick a file to test security.";
Color _statusColor = Colors.grey;
// SCENARIO 1: The "Everyday" Check (Image Picker)
Future<void> _pickImage() async {
final picker = ImagePicker();
final xFile = await picker.pickImage(source: ImageSource.gallery);
if (xFile == null) return;
setState(() => _status = "Scanning ${xFile.name}...");
try {
// THE GUARD CLAUSE
// We only allow PNG and JPEG. If you rename .exe to .png, this throws.
await FileSignature.guard(
xFile,
allowed: [FileFormat.png, FileFormat.jpeg],
);
// If we reach here, it's safe!
_updateStatus("✅ Verified Safe Image (PNG/JPG)", Colors.green);
} on SecurityException catch (e) {
_updateStatus("⛔ BLOCKED: ${e.message}", Colors.red);
} catch (e) {
_updateStatus("Error: $e", Colors.orange);
}
}
// SCENARIO 2: The "Senior" Check (Stream Interceptor)
Future<void> _pickAnyFile() async {
// Pick any file (even huge videos)
FilePickerResult? result = await FilePicker.platform.pickFiles(
withReadStream: true, // Critical for Web/Large files
);
if (result == null) return;
final file = result.files.first;
final stream = file.readStream;
if (stream == null) {
_updateStatus("Error: Could not read file stream", Colors.orange);
return;
}
setState(() => _status = "Scanning Stream of ${file.name}...");
try {
// THE INTERCEPTOR
// This peeks at the stream without consuming it.
// We accept PDF and ZIP for this demo.
final safeStream = FileSignature.guardStream(
stream,
allowed: [FileFormat.pdf, FileFormat.zip],
);
// Simulate an upload by "draining" the stream
// In a real app, you would pass 'safeStream' to Dio or http.post
await safeStream.drain();
_updateStatus("✅ Stream Verified & Uploaded (PDF/ZIP)", Colors.green);
} catch (e) {
_updateStatus("⛔ STREAM KILLED: $e", Colors.red);
}
}
void _updateStatus(String msg, Color color) {
if (!mounted) return;
setState(() {
_status = msg;
_statusColor = color;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("File Signature Security Demo")),
body: Center(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.security, size: 80, color: Colors.blue),
const SizedBox(height: 20),
Text(
_status,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: _statusColor,
),
),
const SizedBox(height: 40),
ElevatedButton.icon(
icon: const Icon(Icons.image),
label: const Text("Test Image Picker (Guard XFile)"),
onPressed: _pickImage,
),
const SizedBox(height: 10),
ElevatedButton.icon(
icon: const Icon(Icons.cloud_upload),
label: const Text("Test Large File (Guard Stream)"),
onPressed: _pickAnyFile,
),
const SizedBox(height: 20),
const Text(
"Try renaming a .exe to .png and uploading it!",
style: TextStyle(color: Colors.grey),
),
],
),
),
),
);
}
}