Add stealer parser
Signed-off-by: Andrea Pavone <info@andreapavone.com>
This commit is contained in:
@ -3,14 +3,29 @@
|
|||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Services\StealerParser;
|
use App\Services\StealerParser;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class FileParserController extends Controller
|
class FileParserController extends Controller
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @param Request $request
|
||||||
|
* @return JsonResponse
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
public function stealer(Request $request) {
|
public function stealer(Request $request) {
|
||||||
$file = $request->file('stealerFile');
|
$file = $request->file('file');
|
||||||
|
|
||||||
|
if(!$file->isValid()){
|
||||||
|
throw new \Exception('Invalid file');
|
||||||
|
}
|
||||||
|
|
||||||
$content = file_get_contents($file->path());
|
$content = file_get_contents($file->path());
|
||||||
|
|
||||||
|
if(empty($content)) {
|
||||||
|
throw new \Exception('Empty File');
|
||||||
|
}
|
||||||
|
|
||||||
//TODO: Evaluate to deatch this execution from the web request
|
//TODO: Evaluate to deatch this execution from the web request
|
||||||
$credentials = (new StealerParser($content))->parse();
|
$credentials = (new StealerParser($content))->parse();
|
||||||
|
|
||||||
|
|||||||
162
resources/js/Components/FileUpload.tsx
Normal file
162
resources/js/Components/FileUpload.tsx
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
import React, { useState, useRef } from 'react';
|
||||||
|
import { Upload, AlertCircle, FileText, X, Check } from 'lucide-react';
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/Components/ui/card";
|
||||||
|
import { Button } from "@/Components/ui/button";
|
||||||
|
import { Alert, AlertDescription } from "@/Components/ui/alert";
|
||||||
|
|
||||||
|
export default function FileUpload({ onFileUpload, maxSize = 5 }: {
|
||||||
|
onFileUpload: (file: File) => void;
|
||||||
|
maxSize?: number;
|
||||||
|
}) {
|
||||||
|
const [dragActive, setDragActive] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
const handleDrag = (e: React.DragEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
if (e.type === "dragenter" || e.type === "dragover") {
|
||||||
|
setDragActive(true);
|
||||||
|
} else if (e.type === "dragleave") {
|
||||||
|
setDragActive(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateFile = (file: File): boolean => {
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
// Check file type
|
||||||
|
if (file.type !== 'text/plain' && !file.name.endsWith('.txt')) {
|
||||||
|
setError('Only .txt files are allowed');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check file size
|
||||||
|
if (file.size > maxSize * 1024 * 1024) {
|
||||||
|
setError(`File size must be less than ${maxSize}MB`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDrop = (e: React.DragEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
setDragActive(false);
|
||||||
|
|
||||||
|
const file = e.dataTransfer.files?.[0];
|
||||||
|
if (file && validateFile(file)) {
|
||||||
|
setSelectedFile(file);
|
||||||
|
onFileUpload(file);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const file = e.target.files?.[0];
|
||||||
|
if (file && validateFile(file)) {
|
||||||
|
setSelectedFile(file);
|
||||||
|
onFileUpload(file);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
inputRef.current?.click();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemove = () => {
|
||||||
|
setSelectedFile(null);
|
||||||
|
setError(null);
|
||||||
|
if (inputRef.current) {
|
||||||
|
inputRef.current.value = '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Upload Text File</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Drag and drop a .txt file or click to browse
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"relative flex flex-col items-center justify-center w-full h-64 p-6 border-2 border-dashed rounded-lg transition-colors",
|
||||||
|
dragActive ? "border-primary bg-primary/5" : "border-muted-foreground/25",
|
||||||
|
selectedFile && "border-success bg-success/5"
|
||||||
|
)}
|
||||||
|
onDragEnter={handleDrag}
|
||||||
|
onDragLeave={handleDrag}
|
||||||
|
onDragOver={handleDrag}
|
||||||
|
onDrop={handleDrop}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
ref={inputRef}
|
||||||
|
type="file"
|
||||||
|
accept=".txt"
|
||||||
|
onChange={handleChange}
|
||||||
|
className="hidden"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{selectedFile ? (
|
||||||
|
<div className="flex flex-col items-center gap-2">
|
||||||
|
<div className="flex items-center gap-2 text-success">
|
||||||
|
<Check className="w-8 h-8" />
|
||||||
|
<FileText className="w-8 h-8" />
|
||||||
|
</div>
|
||||||
|
<p className="text-sm font-medium">{selectedFile.name}</p>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{(selectedFile.size / 1024).toFixed(1)} KB
|
||||||
|
</p>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="mt-2"
|
||||||
|
onClick={handleRemove}
|
||||||
|
>
|
||||||
|
<X className="w-4 h-4 mr-2" />
|
||||||
|
Remove file
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex flex-col items-center gap-4">
|
||||||
|
<Upload className="w-8 h-8 text-muted-foreground" />
|
||||||
|
<div className="flex flex-col items-center gap-1">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
Choose a file
|
||||||
|
</Button>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
or drag and drop it here
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Only .txt files up to {maxSize}MB are supported
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<Alert variant="destructive" className="mt-4">
|
||||||
|
<AlertCircle className="h-4 w-4" />
|
||||||
|
<AlertDescription>{error}</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -7,18 +7,26 @@ import { TextSearchIcon } from 'lucide-react';
|
|||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
TableCaption,
|
|
||||||
TableCell,
|
TableCell,
|
||||||
TableFooter,
|
|
||||||
TableHead,
|
TableHead,
|
||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@/Components/ui/table"
|
} from "@/Components/ui/table"
|
||||||
|
import FileUpload from "@/Components/FileUpload";
|
||||||
import {Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle} from "@/Components/ui/card";
|
import {Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle} from "@/Components/ui/card";
|
||||||
|
|
||||||
|
type ParsedFile = {
|
||||||
|
url: string,
|
||||||
|
username: string,
|
||||||
|
password: string,
|
||||||
|
application: string,
|
||||||
|
}
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const [securitySummaryResponse, setSecuritySummaryResponse] = useState<SecuritySummaryResponseType | null>(null);
|
const [securitySummaryResponse, setSecuritySummaryResponse] = useState<SecuritySummaryResponseType | null>(null);
|
||||||
|
|
||||||
|
const [parsedFile, setParsedFile] = useState<null| ParsedFile[]>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
axios.get<SecuritySummaryResponseType>('/api/v1/security/summary')
|
axios.get<SecuritySummaryResponseType>('/api/v1/security/summary')
|
||||||
.then(response => {
|
.then(response => {
|
||||||
@ -29,6 +37,29 @@ export default function Home() {
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleFileUpload = async (file: File) => {
|
||||||
|
// Gestisci il file qui
|
||||||
|
// Esempio: invia il file al server
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.post('/api/v1/parse/stealer', formData, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if(response.data.status === 'success'){
|
||||||
|
setParsedFile(response.data.data);
|
||||||
|
console.log('data', response.data.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Upload failed:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto p-4 space-y-6">
|
<div className="container mx-auto p-4 space-y-6">
|
||||||
@ -76,10 +107,36 @@ export default function Home() {
|
|||||||
<CardDescription></CardDescription>
|
<CardDescription></CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
--todo
|
<FileUpload
|
||||||
|
|
||||||
|
onFileUpload={handleFileUpload}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<h3 className="text-2xl mt-6">Parse Output</h3>
|
||||||
|
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Application</TableHead>
|
||||||
|
<TableHead>URL</TableHead>
|
||||||
|
<TableHead>Username</TableHead>
|
||||||
|
<TableHead>Password</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{parsedFile?.map((value, index) => (
|
||||||
|
<TableRow key={index}>
|
||||||
|
<TableCell>{value.application}</TableCell>
|
||||||
|
<TableCell>{value.url}</TableCell>
|
||||||
|
<TableCell>{value.username}</TableCell>
|
||||||
|
<TableCell>{value.password}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user