Myslel jsem, že napíšu krátkou (pro mě je to krátká) „odpověď“, abych mohl shrnout své body.
Některé "Osvědčené postupy" při vytváření systému ukládání souborů. Úložiště souborů je široká kategorie, takže vaše najeté kilometry se mohou u některých lišit. Berte je jako návrh toho, co jsem zjistil, že funguje dobře.
Názvy souborů Neukládejte soubor s názvem, který mu dal koncový uživatel. Mohou a budou používat všechny druhy mizerných postav, které vám ztrpčují život. Některé mohou být stejně špatné jako '
jednoduché uvozovky, což na linuxu v podstatě dělá to, že není možné číst nebo dokonce smazat soubor ( přímo ). Některé věci se mohou zdát jednoduché jako prostor, ale v závislosti na tom, kde jej používáte, a na operačním systému na vašem serveru můžete skončit s
one%20two.txt
nebo one+two.txt
nebo one two.txt
což může nebo nemusí ve vašich odkazech vytvářet nejrůznější problémy.
Nejlepší je vytvořit hash, něco jako sha1
to může být tak jednoduché jako {user_id}{orgianl_name}
Uživatelské jméno snižuje pravděpodobnost kolizí s názvy souborů jiných uživatelů.
Raději dělám file_hash('sha1', $contents)
tímto způsobem, pokud někdo nahraje stejný soubor více než jednou, můžete to zachytit (obsah je stejný, hash je stejný). Pokud však očekáváte, že budete mít velké soubory, možná budete chtít provést nějaké benchmarking, abyste zjistili, jaký typ výkonu má. Většinou zpracovávám malé soubory, takže to funguje dobře.-poznámka- že s časovým razítkem lze soubor stále uložit, protože celý název je jiný, ale je docela dobře vidět a lze jej ověřit v databázi.
Bez ohledu na to, co děláte, bych před ním uvedl časové razítko time().'-'.$filename
. To je užitečná informace, protože se jedná o absolutní čas, kdy byl soubor vytvořen.
Pokud jde o název, který uživatel zadá souboru. Stačí to uložit do záznamu databáze. Tímto způsobem jim můžete ukázat jméno, které očekávají, ale použijte jméno, o kterém víte, že je pro odkazy vždy bezpečné.
$filename ='nějaký mizerný^ souborane.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";
Výstupy
Ext: .jpg
Hash: bb9d2c2c7c73bb8248537a701870e35742b41c02
Timestamp: 1511853063
Hashname: 1511853063-bb9d2c2c7c73bb8248537a701870e35742b41c02.jpg
Můžete to zkusit zde
Cesty nikdy neukládejte celou cestu k souboru. V databázi potřebujete pouze hash z vytvoření hashovaného názvu. "Kořenová" cesta ke složce, ve které je soubor uložen, by měla být provedena v PHP. To má několik výhod.
- zabraňuje přenosu adresářů. Protože neprojdete žádnou částí cesty kolem vás, nemusíte se tolik bát, že by někdo uklouzl
\..\..
tam a jít tam, kam by neměli. Špatným příkladem by bylo, když někdo přepíše.htpassword
soubor nahráním souboru s názvem to s adresářem transverse v něm. - Má jednotnější vzhled odkazů, jednotnou velikost a jednotnou sadu znaků.
https://en.wikipedia.org/wiki/Directory_traversal_attack
- Údržba. Cesty se mění, servery se mění. Požadavky na změnu vašeho systému. Pokud potřebujete tyto soubory přemístit, ale uložili jste k nim úplnou úplnou cestu do databáze, zaseklo jste se lepením všeho dohromady pomocí
symlinks
nebo aktualizaci všech vašich záznamů.
Existují určité výjimky. Pokud je chcete ukládat do měsíční složky nebo podle uživatelského jména. Tuto část cesty byste mohli uložit do samostatného pole. Ale i v takovém případě byste jej mohli sestavit dynamicky na základě dat uložených v záznamu. Zjistil jsem, že je nejlepší uložit si co nejméně informací o cestě. A vytvoří konfiguraci nebo konstantu, kterou můžete použít na všech místech, kde potřebujete zadat cestu k souboru.
Také path
a link
jsou velmi odlišné, takže uložením pouze názvu jej můžete propojit z jakékoli stránky PHP, kterou chcete, aniž byste museli odečítat data z cesty. Vždy mi připadalo jednodušší přidat k názvu souboru než odečíst z cesty.
Databáze (jen některé návrhy, použití se může lišit )Jako vždy se u dat zeptejte sami sebe, kdo, co, kde, kdy
- id -
int
automatické zvýšení primárního klíče - user_id -
int
cizí klíč, kdo nahrál - hash -
char[40] *sha1*, unique
co hash - hasname -
varchar
{timestampl}-{hash}.{ext} kde název souborů na pevném disku - název souboru -
varchar
původní jméno zadané uživatelem, tímto způsobem jim můžeme ukázat jméno, které očekávají (pokud je to důležité) - stav -
enum[public,private,deleted,pending.. etc]
stav souboru, v závislosti na vašem případu použití možná budete muset zkontrolovat soubory, možná jsou některé soukromé, může je vidět pouze uživatel, možná jsou některé veřejné atd. - status_date -
timestamp|datetime
čas, kdy se stav změnil. - create_date -
timestamp|datetime
kdy v době, kdy byl soubor vytvořen, je preferováno časové razítko, protože to některé věci usnadňuje, ale v tom případě by mělo být stejné časové razítko použité v názvu hashna. - typ -
varchar
- typ mime, může být užitečné pro nastavení typu mime při stahování atd.
Pokud očekáváte, že různí uživatelé nahrají stejný soubor a použijete file_hash
můžete vytvořit hash
pole kombinovaného jedinečného indexu user_id
a hash
tímto způsobem by došlo ke konfliktu pouze v případě, že by stejný uživatel nahrál stejný soubor. Můžete to také udělat na základě časového razítka a hash, v závislosti na vašich potřebách.
To je základní věc, na kterou jsem mohl myslet, není to absolutní, jen některé oblasti, o kterých jsem si myslel, že by byly užitečné.
Je užitečné mít hash samotný, pokud jej uložíte samostatně, můžete jej uložit do CHAR(40)
pro sha1 (zabírá méně místa v DB než VARCHAR
) a nastavte řazení na UTF8_bin
který je binární. Díky tomu se při vyhledávání na něm rozlišují velká a malá písmena. Přestože existuje jen malá možnost kolize hashů, přidává to o něco větší ochranu, protože hashe jsou velká a malá písmena.
hashname
můžete vždy vytvořit za běhu, pokud rozšíření uložíte, a časové razítko odděleně. Pokud se přistihnete, že vytváříte věci znovu a znovu, možná je budete chtít uložit do DB, abyste si zjednodušili práci v PHP.
Líbí se mi, že do odkazu vložím hash, žádné rozšíření, nic, takže moje odkazy vypadají takto.
http://www.example.com/download/ad87109bfff0765f4dd8cf4943b04d16a4070fea
Opravdu jednoduché, skutečné obecné, bezpečné v adresách URL vždy stejné velikosti atd..
hashname
protože tento "soubor" by byl takto
1511848005-ad87109bfff0765f4dd8cf4943b04d16a4070fea.jpg
Pokud máte konflikty se stejným souborem a jiným uživatelem (což jsem uvedl výše). Vždy můžete do odkazu přidat část časového razítka, user_id nebo obojí. Pokud použijete user_id, může být užitečné zapsat do levého pole nuly. Někteří uživatelé mohou mít například ID:1
a některé mohou být ID:234
takže můžete nechat pole na 4 místech a udělat z nich 0001
a 0234
. Pak to přidejte do hashe, což je téměř nepostřehnutelné:
1511848005-ad87109bfff0765f4dd8cf4943b04d16a4070fea0234.jpg
Zde je důležité, že protože sha1
je vždy 40
a id je vždy 4
můžeme tyto dva oddělit přesně a snadno. A tímto způsobem to stále můžete hledat jedinečně. Existuje mnoho různých možností, ale hodně záleží na vašich potřebách.
Přístup Jako je stahování. Vždy byste měli soubor vypsat pomocí PHP, nedávejte jim přímý přístup k souboru. Nejlepším způsobem je ukládat soubory mimo webroot (nad public_html
nebo www
složka). Pak v PHP můžete nastavit záhlaví na správný typ av podstatě načíst soubor. Funguje to prakticky u všeho kromě videa. Nezpracovávám videa, takže to je téma mimo moji zkušenost. Ale považuji za nejlepší si to představit, protože všechna data souboru jsou text, jsou to záhlaví, která z tohoto textu dělají obrázek nebo soubor Excel nebo pdf.
Velkou výhodou toho, že jim neposkytnete přímý přístup k souboru, je, že pokud máte členský web, nebo nechcete, aby byl váš obsah přístupný bez přihlášení, můžete před poskytnutím obsahu snadno zkontrolovat v PHP, zda jsou přihlášeni. A protože je soubor mimo webroot, nemají k němu přístup žádným jiným způsobem.
Nejdůležitější je vybrat něco konzistentního, co je stále dostatečně flexibilní, aby zvládlo všechny vaše potřeby.
Jsem si jistý, že dokážu vymyslet další, ale pokud máte nějaký návrh, neváhejte se vyjádřit.
ZÁKLADNÍ PRŮBĚH PROCESU
- Uživatel odešle formulář (
enctype="multipart/form-data"
)
https://www.w3schools.com/tags/att_form_enctype.asp
- Server obdrží příspěvek z formuláře Super Globals
$_POST
a$_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)
]
];
-
Zkontrolujte chyby
if(!$_FILES['fielname']['error'])
-
Dezinfikovat zobrazovaný název
$filename = htmlentities($str, ENT_NOQUOTES, "UTF-8");
-
Uložte soubor, vytvořte DB záznam ( PSUDO-CODE )
Takhle:
$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
-
Vytvořte odkaz (liší se v závislosti na směrování), jednoduchým způsobem je vytvořit odkaz takto
http://www.example.com/download?file={$hash}
ale je to ošklivější nežhttp://www.example.com/download/{$hash}
-
uživatel klikne na odkaz přejde na stránku stahování.
získejte INPUT a vyhledejte záznam
$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
atd....
Na zdraví!