Jeg regnede med, at jeg ville skrive et kort (for mig er dette kort) "svar" bare så jeg kunne opsummere mine pointer.
Nogle "bedste fremgangsmåder", når du opretter et fillagringssystem. Fillagring er en bred kategori, så din kilometertal kan variere for nogle af disse. Tag dem bare som et forslag til, hvad jeg fandt, der fungerer godt.
Filnavne Gem ikke filen med det navn, som en slutbruger giver den. De kan og vil bruge alle slags lorte karakterer, der vil gøre dit liv surt. Nogle kan være så dårlige som '
enkelte citater, hvilket på linux dybest set gør det umuligt at læse, eller endda slette filen (direkte). Nogle ting kan virke simple som et mellemrum, men afhængigt af hvor du bruger det og operativsystemet på din server kan du ende med
one%20two.txt
eller one+two.txt
eller one two.txt
som måske eller måske ikke skaber alle slags problemer i dine links.
Den bedste ting at gøre er at oprette en hash, noget som sha1
dette kan være så simpelt som {user_id}{orgianl_name}
Brugernavnet gør det mindre sandsynligt for kollisioner med andre brugeres filnavne.
Jeg foretrækker at lave file_hash('sha1', $contents)
på den måde, hvis nogen uploader den samme fil mere, så når du kan fange det (indholdet er det samme, hashen er den samme). Men hvis du forventer at have store filer, vil du måske lave nogle bench-markering på den for at se, hvilken type ydeevne den har. Jeg håndterer mest små filer, så det fungerer fint til det.-bemærk- at med tidsstemplet kan filen stadig gemmes, fordi det fulde navn er anderledes, men det gør det ret nemt at se, og det kan verificeres i databasen.
Uanset hvad du gør, ville jeg præfikse det med et tidsstempel time().'-'.$filename
. Dette er nyttig information at have, fordi det er det absolutte tidspunkt, hvor filen blev oprettet.
Hvad angår navnet, giver en bruger filen. Bare gem det i databaseposten. På denne måde kan du vise dem det navn, de forventer, men brug et navn, som du ved altid er sikkert for links.
$filename ='noget lort^ fileane.jpg';
$ext = strrchr($filename, '.');
echo "\nExt: {$ext}\n";
$hash = sha1('some crapy^ fileane.jpg');
echo "Hash: {$hash}\n";
$time = time();
echo "Timestamp: {$time}\n";
$hashname = $time.'-'.$hash.$ext;
echo "Hashname: $hashname\n";
Udgange
Ext: .jpg
Hash: bb9d2c2c7c73bb8248537a701870e35742b41c02
Timestamp: 1511853063
Hashname: 1511853063-bb9d2c2c7c73bb8248537a701870e35742b41c02.jpg
Du kan prøve det her
Stier gem aldrig den fulde sti til filen. Alt hvad du behøver i databasen er hashen fra oprettelsen af det hash-navn. "Root"-stien til den mappe, filen er gemt i, skal udføres i PHP. Dette har flere fordele.
- forhindrer katalogoverførsel. Fordi du ikke passerer nogen del af stien rundt om dig, behøver du ikke bekymre dig så meget om, at nogen smutter en
\..\..
derinde og går steder, de ikke burde. Et dårligt eksempel på dette ville være nogen, der overskriver et.htpassword
fil ved at uploade en fil ved navn den med mappe på tværs. - Har mere ensartede links, ensartet størrelse, ensartet sæt af tegn.
https://en.wikipedia.org/wiki/Directory_traversal_attack
- Vedligeholdelse. Stier ændrer sig, servere ændrer sig. Kravene til dit system ændrer sig. Hvis du har brug for at flytte disse filer, men du har gemt den absolutte fulde sti til dem i DB'en, sidder du fast ved at lime alt sammen med
symlinks
eller opdatere alle dine optegnelser.
Der er nogle undtagelser fra dette. Hvis du vil gemme dem i en månedlig mappe eller efter brugernavn. Du kan gemme den del af stien i et separat felt. Men selv i det tilfælde kan du bygge det dynamisk baseret på data gemt i posten. Jeg har fundet ud af, at det er bedst at gemme så lidt stioplysninger som muligt. Og de laver en konfiguration eller en konstant, som du kan bruge alle de steder, du skal bruge for at sætte stien til filen.
Også path
og link
er meget forskellige, så ved kun at gemme navnet kan du linke det fra hvilken PHP-side du ønsker uden at skulle trække data fra stien. Jeg har altid fundet det nemmere at tilføje til filnavnet og derefter trække fra en sti.
Database (kun nogle forslag, brugen kan variere) Som altid med data spørg dig selv, hvem, hvad, hvor, hvornår
- id -
int
primær nøgle automatisk stigning - bruger_id -
int
fremmednøgle, hvem uploadede den - hash -
char[40] *sha1*, unique
hvad hashen - hashnavn -
varchar
{timestampl}-{hash}.{ext} hvor filnavnet på harddisken - filnavn -
varchar
det originale navn givet af brugeren, på den måde kan vi vise dem det navn, de forventer (hvis det er vigtigt) - status -
enum[public,private,deleted,pending.. etc]
status for filen, afhængigt af dit brugstilfælde, skal du muligvis gennemgå filerne, eller måske er nogle private, kun brugeren kan se dem, måske er nogle offentlige osv. - status_dato -
timestamp|datetime
tidspunkt, hvor status blev ændret. - create_date -
timestamp|datetime
hvornår tidspunkt, hvor filen blev oprettet, foretrækkes et tidsstempel, da det gør nogle ting nemmere, men det bør i så fald være det samme tidsstempel, som bruges i hashnavnet. - type -
varchar
- mime-type, kan være nyttig til at indstille mime-typen ved download osv.
Hvis du forventer, at forskellige brugere uploader den samme fil, og du bruger file_hash
du kan lave hash
felt et kombineret unikt indeks for user_id
og hash
på denne måde ville det kun være i konflikt, hvis den samme bruger uploadede den samme fil. Du kan også gøre det baseret på tidsstemplet og hash, afhængigt af dine behov.
Det er de grundlæggende ting, jeg kunne komme i tanke om, dette er ikke et absolut, bare nogle felter, jeg troede ville være nyttige.
Det er nyttigt at have hashen for sig selv, hvis du gemmer den for sig selv, kan du gemme den i en CHAR(40)
for sha1 (optager mindre plads i DB'en end VARCHAR
) og indstil sorteringen til UTF8_bin
som er binært. Dette gør søgninger på det store og små bogstaver. Selvom der er ringe mulighed for en hash-kollision, tilføjer dette bare en smule mere beskyttelse, fordi hashes er store og små bogstaver.
Du kan altid bygge hashname
on the fly, hvis du gemmer udvidelsen, og tidsstemplet adskilt. Hvis du finder dig selv at skabe ting igen og igen, vil du måske bare gemme det i DB'en for at forenkle arbejdet i PHP.
Jeg kan godt lide bare at sætte hashen i linket, ingen udvidelse ingen noget, så mine links ser sådan ud.
http://www.example.com/download/ad87109bfff0765f4dd8cf4943b04d16a4070fea
Rigtig enkel, ægte generisk, sikker i urls altid samme størrelse osv..
hashname
for denne "fil" ville være sådan her
1511848005-ad87109bfff0765f4dd8cf4943b04d16a4070fea.jpg
Hvis du har konflikter med den samme fil og en anden bruger (som jeg nævnte ovenfor). Du kan altid tilføje tidsstemplet til linket, user_id eller begge dele. Hvis du bruger user_id'et, kan det være nyttigt at venstre indsætte det med nuller. For eksempel kan nogle brugere have ID:1
og nogle kan være ID:234
så du kunne lade den ligge på 4 steder og gøre dem til 0001
og 0234
. Føj derefter det til hashen, hvilket næsten ikke er til at bemærke:
1511848005-ad87109bfff0765f4dd8cf4943b04d16a4070fea0234.jpg
Det vigtige her er, at fordi sha1
er altid 40
og id'et er altid 4
vi kan adskille de to præcist og nemt. Og på denne måde kan du stadig slå det unikt op. Der er mange forskellige muligheder, men så meget afhænger af dine behov.
Adgang Såsom at downloade. Du bør altid udlæse filen med PHP, giv dem ikke direkte adgang til filen. Den bedste måde er at gemme filerne uden for webroot (over public_html
). eller www
mappe). Så i PHP kan du indstille overskrifterne til den korrekte type og grundlæggende læse filen op. Dette virker til stort set alt undtagen video. Jeg håndterer ikke videoer, så det er et emne uden for min erfaring. Men jeg synes, det er bedst at tænke på det, da alle fildata er tekst, det er de overskrifter, der gør teksten til et billede eller en excel-fil eller en pdf.
Den store fordel ved ikke at give dem direkte adgang til filen er, at hvis du har et medlemsside, eller hvis du ikke vil have dit indhold tilgængeligt uden login, kan du nemt tjekke i PHP, om de er logget ind, før du giver dem indholdet. Og da filen er uden for webroot, kan de ikke få adgang til den på anden måde.
Det vigtigste er at vælge noget konsekvent, som stadig er fleksibelt nok til at klare alle dine behov.
Jeg er sikker på, at jeg kan komme med mere, men hvis du har nogle forslag, er du velkommen til at kommentere.
GRUNDLÆGGENDE PROCESFLOW
- Brugeren indsender formular (
enctype="multipart/form-data"
)
https://www.w3schools.com/tags/att_form_enctype.asp
- Serveren modtager indlægget fra formularen Super Globals
$_POST
og$_FILES
http://php.net/manual/en/reserved.variables.files .php
$_FILES = [
'fieldname' => [
'name' => "MyFile.txt" // (comes from the browser, so treat as tainted)
'type' => "text/plain" // (not sure where it gets this from - assume the browser, so treat as tainted)
'tmp_name' => "/tmp/php/php1h4j1o" // (could be anywhere on your system, depending on your config settings, but the user has no control, so this isn't tainted)
'error' => "0" //UPLOAD_ERR_OK (= 0)
'size' => "123" // (the size in bytes)
]
];
-
Tjek for fejl
if(!$_FILES['fielname']['error'])
-
Rengør visningsnavnet
$filename = htmlentities($str, ENT_NOQUOTES, "UTF-8");
-
Gem fil, opret DB-record ( PSUDO-CODE )
Sådan:
$path = __DIR__.'/uploads/'; //for exmaple
$time = time();
$hash = hash_file('sha1',$_FILES['fielname']['tmp_name']);
$type = $_FILES['fielname']['type'];
$hashname = $time.'-'.$hash.strrchr($_FILES['fielname']['name'], '.');
$status = 'pending';
if(!move_uploaded_file ($_FILES['fielname']['tmp_name'], $path.$hashname )){
//failed
//do somehing for errors.
die();
}
//store record in db
http://php.net/manual/en/function.move -uploaded-file.php
-
Opret link (varierer baseret på routing), den enkle måde er at lave dit link på denne måde
http://www.example.com/download?file={$hash}
men det er grimmere endhttp://www.example.com/download/{$hash}
-
bruger klikker på linket går til downloadsiden.
få INPUT og slå posten op
$hash = $_GET['file'];
$stmt = $PDO->prepare("SELECT * FROM attachments WHERE hash = :hash LIMIT 1");
$stmt->execute([":hash" => $hash]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
print_r($row);
http://php.net/manual/en/intro.pdo.php
Osv...
Skål!