sql >> Databáze >  >> RDS >> Mysql

php, jak propojit soubor ze souborového serveru s těmito informacemi z databáze

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

  1. Uživatel odešle formulář (enctype="multipart/form-data" )

https://www.w3schools.com/tags/att_form_enctype.asp

  1. 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)
    ]
 ];
  1. Zkontrolujte chyby if(!$_FILES['fielname']['error'])

  2. Dezinfikovat zobrazovaný název $filename = htmlentities($str, ENT_NOQUOTES, "UTF-8");

  3. 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

  1. 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}

  2. 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í!




  1. Upřesnění výsledků vyhledávání na základě filtrů

  2. Jak vytvořit 'dvoustranný' jedinečný index na dvou polích?

  3. Oprava poškozeného kódování UTF-8

  4. Seznam jazyků podporovaných databází Oracle