Ik dacht dat ik een kort (voor mij is dit kort) "antwoord" zou schrijven, zodat ik mijn punten kon samenvatten.
Enkele "Best Practices" bij het maken van een bestandsopslagsysteem. Bestandsopslag is een brede categorie, dus uw kilometerstand kan voor sommige hiervan variëren. Neem ze als suggestie van wat ik heb gevonden goed werkt.
Bestandsnamen Sla het bestand niet op met de naam die een eindgebruiker eraan heeft gegeven. Ze kunnen en zullen allerlei waardeloze karakters gebruiken die je het leven zuur maken. Sommige kunnen zo slecht zijn als '
enkele aanhalingstekens, wat het op linux in feite zo maakt dat het onmogelijk is om het bestand te lezen of zelfs te verwijderen ( rechtstreeks ). Sommige dingen kunnen eenvoudig lijken, zoals een spatie, maar afhankelijk van waar je het gebruikt en het besturingssysteem op je server, zou je kunnen eindigen met
one%20two.txt
of one+two.txt
of one two.txt
die al dan niet allerlei problemen in uw links kunnen veroorzaken.
Je kunt het beste een hash maken, zoiets als sha1
dit kan zo simpel zijn als {user_id}{orgianl_name}
De gebruikersnaam maakt het minder waarschijnlijk dat er botsingen ontstaan met bestandsnamen van andere gebruikers.
Ik doe liever file_hash('sha1', $contents)
op die manier als iemand hetzelfde bestand meer dan één keer uploadt, kun je dat opvangen (de inhoud is hetzelfde, de hash is hetzelfde). Maar als u grote bestanden verwacht, wilt u er misschien wat benchmarking op doen om te zien wat voor soort prestaties het heeft. Ik werk meestal met kleine bestanden, dus daarvoor werkt het prima.-let op- dat met de tijdstempel het bestand nog steeds kan worden opgeslagen omdat de volledige naam anders is, maar het maakt het vrij gemakkelijk te zien en het kan worden geverifieerd in de database.
Wat je ook doet, ik zou het vooraf laten gaan met een tijdstempel time().'-'.$filename
. Dit is nuttige informatie om te hebben, omdat het de absolute tijd is waarop het bestand is gemaakt.
Wat betreft de naam die een gebruiker aan het bestand geeft. Sla dat gewoon op in het databaserecord. Op deze manier kun je ze de naam laten zien die ze verwachten, maar gebruik een naam waarvan je weet dat deze altijd veilig is voor links.
$filename ='some crapy^ 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";
Uitgangen
Ext: .jpg
Hash: bb9d2c2c7c73bb8248537a701870e35742b41c02
Timestamp: 1511853063
Hashname: 1511853063-bb9d2c2c7c73bb8248537a701870e35742b41c02.jpg
Je kunt het hier proberen
Paden sla nooit het volledige pad naar het bestand op. Het enige dat u in de database nodig hebt, is de hash van het maken van de gehashte naam. Het "root"-pad naar de map waarin het bestand is opgeslagen, moet in PHP worden gedaan. Dit heeft verschillende voordelen.
- voorkomt directory-overdracht. Omdat je geen enkel deel van het pad om je heen passeert, hoef je je niet zoveel zorgen te maken dat iemand een
\..\..
uitglijdt daar en op plaatsen waar ze niet horen. Een slecht voorbeeld hiervan is dat iemand een.htpassword
. overschrijft bestand door een bestand met de naam te uploaden met de map transversaal erin. - Heeft uniformer ogende links, uniforme grootte, uniforme reeks tekens.
https://en.wikipedia.org/wiki/Directory_traversal_attack
- Onderhoud. Paden veranderen, Servers veranderen. Eisen aan uw systeem veranderen. Als je die bestanden moet verplaatsen, maar je hebt het absolute volledige pad ernaartoe in de DB opgeslagen, zit je vast aan het lijmen van alles met
symlinks
of het bijwerken van al uw gegevens.
Hierop zijn enkele uitzonderingen. Als u ze wilt opslaan in een maandelijkse map of op gebruikersnaam. Dat deel van het pad zou je in een apart veld kunnen opslaan. Maar zelfs in dat geval zou je het dynamisch kunnen bouwen op basis van gegevens die in het record zijn opgeslagen. Ik heb ontdekt dat het het beste is om zo min mogelijk padinformatie op te slaan. En ze maken een configuratie of een constante die je kunt gebruiken op alle plaatsen waar je het pad naar het bestand moet zetten.
Ook het path
en de link
zijn heel verschillend, dus door alleen de naam op te slaan, kunt u deze koppelen vanaf elke gewenste PHP-pagina zonder dat u gegevens van het pad hoeft af te trekken. Ik heb het altijd gemakkelijker gevonden om toe te voegen aan de bestandsnaam dan om af te trekken van een pad.
Database (slechts enkele suggesties, het gebruik kan variëren) Zoals altijd met gegevens, vraag jezelf af, wie, wat, waar, wanneer
- id -
int
primaire sleutel automatisch verhogen - user_id -
int
externe sleutel, wie heb het geüpload - hash -
char[40] *sha1*, unique
wat de hasj - hashnaam -
varchar
{timestampl}-{hash}.{ext} waar de bestandsnaam op de harde schijf - bestandsnaam -
varchar
de originele naam die door de gebruiker is gegeven, op die manier kunnen we ze de naam laten zien die ze verwachten (als dat belangrijk is) - status -
enum[public,private,deleted,pending.. etc]
status van het bestand, afhankelijk van uw gebruikssituatie, moet u de bestanden mogelijk bekijken, of misschien zijn sommige privé, alleen de gebruiker kan ze zien, misschien zijn sommige openbaar enz. - status_date -
timestamp|datetime
keer dat de status is gewijzigd. - create_date -
timestamp|datetime
wanneer op het moment dat het bestand is gemaakt, heeft een tijdstempel de voorkeur omdat dit sommige dingen gemakkelijker maakt, maar in dat geval zou het hetzelfde tijdstempel moeten zijn in de hashnaam. - typ -
varchar
- mime-type, kan handig zijn voor het instellen van het mime-type bij het downloaden enz.
Als u verwacht dat verschillende gebruikers hetzelfde bestand uploaden en u de file_hash
. gebruikt je kunt de hash
. maken veld een gecombineerde unieke index van de user_id
en de hash
op deze manier zou het alleen conflicteren als dezelfde gebruiker hetzelfde bestand zou uploaden. Je kunt het ook doen op basis van de tijdstempel en hash, afhankelijk van je behoeften.
Dat zijn de basisdingen die ik kon bedenken, dit is niet absoluut, alleen enkele velden waarvan ik dacht dat ze nuttig zouden zijn.
Het is handig om de hash op zichzelf te hebben, als je het op zichzelf opslaat, kun je het opslaan in een CHAR(40)
for sha1 (neemt minder ruimte in beslag in de DB dan VARCHAR
) en stel de sortering in op UTF8_bin
wat binair is. Dit maakt zoekopdrachten erop hoofdlettergevoelig. Hoewel er weinig kans is op een hash-botsing, voegt dit net iets meer bescherming toe omdat hashes hoofdletters en kleine letters zijn.
Je kunt altijd de hashname
build bouwen on-the-fly als u de extensie opslaat en de tijdstempel van elkaar gescheiden. Als je merkt dat je keer op keer dingen maakt, wil je het misschien gewoon in de DB opslaan om het werk in PHP te vereenvoudigen.
Ik vind het leuk om gewoon de hash in de link te plaatsen, geen extensie, dus mijn links zien er zo uit.
http://www.example.com/download/ad87109bfff0765f4dd8cf4943b04d16a4070fea
Echt eenvoudig, echt generiek, veilig in URL's altijd dezelfde grootte enz.
De hashname
want dit "bestand" zou zo zijn
1511848005-ad87109bfff0765f4dd8cf4943b04d16a4070fea.jpg
Als je conflicten hebt met hetzelfde bestand en een andere gebruiker (die ik hierboven heb genoemd). Je kunt altijd het tijdstempelgedeelte toevoegen aan de link, de user_id of beide. Als u de user_id gebruikt, kan het handig zijn om deze met nullen te vullen. Sommige gebruikers hebben bijvoorbeeld ID:1
en sommige kunnen ID:234
. zijn dus je zou het op 4 plaatsen kunnen achterlaten en ze 0001
kunnen maken en 0234
. Voeg dat dan toe aan de hash, wat bijna onmerkbaar is:
1511848005-ad87109bfff0765f4dd8cf4943b04d16a4070fea0234.jpg
Het belangrijkste hier is dat omdat sha1
is altijd 40
en de id is altijd 4
we kunnen de twee nauwkeurig en gemakkelijk scheiden. En zo kun je het toch uniek opzoeken. Er zijn veel verschillende opties, maar zoveel hangt af van uw behoeften.
Toegang Zoals downloaden. Je moet het bestand altijd met PHP uitvoeren, geef ze geen directe toegang tot het bestand. De beste manier is om de bestanden buiten de webroot op te slaan (boven de public_html
, of www
map). Vervolgens kun je in PHP de headers op het juiste type instellen en het bestand in feite uitlezen. Dit werkt voor vrijwel alles behalve video. Ik behandel geen video's, dus dat is een onderwerp dat buiten mijn ervaring ligt. Maar ik vind het het beste om erover na te denken, aangezien alle bestandsgegevens tekst zijn, het zijn de koppen die van die tekst een afbeelding maken, of een Excel-bestand of een pdf.
Het grote voordeel om ze geen directe toegang tot het bestand te geven, is dat als je een lidmaatschapssite hebt, of niet wilt dat je inhoud toegankelijk is zonder een login, je gemakkelijk in PHP kunt controleren of ze zijn ingelogd voordat je ze de inhoud geeft. En aangezien het bestand zich buiten de webroot bevindt, kunnen ze er op geen enkele andere manier toegang toe krijgen.
Het belangrijkste is om iets consistents te kiezen, dat nog steeds flexibel genoeg is om aan al je behoeften te voldoen.
Ik weet zeker dat ik er nog meer kan bedenken, maar als je suggesties hebt, reageer dan gerust.
BASIS PROCESSTROOM
- Gebruiker dient formulier in (
enctype="multipart/form-data"
)
https://www.w3schools.com/tags/att_form_enctype.asp
- Server ontvangt het bericht van het formulier Super Globals
$_POST
en de$_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)
]
];
-
Controleer op fouten
if(!$_FILES['fielname']['error'])
-
Sanitize weergavenaam
$filename = htmlentities($str, ENT_NOQUOTES, "UTF-8");
-
Bestand opslaan, DB-record maken ( PSUDO-CODE )
Zoals dit:
$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
-
Maak een link (varieert op basis van routering), de eenvoudige manier is om je link als volgt te doen
http://www.example.com/download?file={$hash}
maar het is lelijker danhttp://www.example.com/download/{$hash}
-
gebruiker klikt link gaat naar downloadpagina.
haal INPUT op en zoek record 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
Enz....
Proost!