Arif

WFN Üye
Konular
638
Mesajlar
638
Çözüm
0
Ticaret Puanı
0
Üyelik
23 Gün
Konum
Bilecik
Web Sitesi
Var
Alanı
SEO Uzmanı
1/1
Konu sahibi
İndirme işlemi için PHP’de dosyanın yolunu doğrulayıp uygun HTTP başlıklarını göndermeniz, ardından dosya içeriğini readfile() ile yanıt olarak aktarmanız yeterlidir. İstemci dosyayı indirmeye başladığında PHP betiği başka çıktı üretmemelidir; aksi durumda dosya bozulabilir veya tarayıcı beklenen indirme yanıtını alamayabilir.

PHP ile güvenli dosya indirme​


Aşağıdaki örnekte, ziyaretçinin doğrudan dosya yolu göndermesine izin verilmez. Kullanıcı yalnızca uygulamanın izin verdiği bir dosya adıyla eşleştirilir. Bu yaklaşım, ../ gibi yollarla uygulama dışındaki dosyaların okunmasını önlemeye yardımcı olur.

Örnek klasör yapısı şöyledir:

Kod:
/proje
    /downloads
        rapor.pdf
        sozlesme.docx
    indir.php

indir.php dosyasına şu kodu yerleştirebilirsiniz:

PHP:
<?php

$dosyalar = [
    'rapor' => [
        'yol' => __DIR__ . '/downloads/rapor.pdf',
        'ad'  => 'rapor.pdf',
        'tür'  => 'application/pdf',
    ],
    'sozlesme' => [
        'yol' => __DIR__ . '/downloads/sozlesme.docx',
        'ad'  => 'sozlesme.docx',
        'tür'  => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    ],
];

$kod = $_GET['dosya'] ?? '';

if (!isset($dosyalar[$kod])) {
    http_response_code(404);
    exit('Dosya bulunamadı.');
}

$dosya = $dosyalar[$kod];

if (!is_file($dosya['yol']) || !is_readable($dosya['yol'])) {
    http_response_code(404);
    exit('Dosya mevcut değil veya okunamıyor.');
}

$boyut = filesize($dosya['yol']);

header('Content-Description: File Transfer');
header('Content-Type: ' . $dosya['tür']);
header('Content-Disposition: attachment; filename="' . $dosya['ad'] . '"');
header('Content-Length: ' . $boyut);
header('Cache-Control: no-cache, no-store, must-revalidate');
header('Pragma: no-cache');
header('Expires: 0');

readfile($dosya['yol']);
exit;

Bu dosyayı çalıştırdığınızda indir.php?dosya=rapor adresine gelen istek, downloads/rapor.pdf dosyasını tarayıcıya gönderir. Content-Disposition: attachment başlığı tarayıcıya içeriği sayfada göstermeye çalışmak yerine indirme olarak ele almasını söyler. İndirilen dosyanın adı ise filename bölümünden gelir.

Koddan önce herhangi bir HTML, boşluk, hata çıktısı veya echo kullanılmamalıdır. PHP bir HTTP başlığı gönderdikten sonra gövdeye fazladan metin eklerse indirme yanıtı beklenmedik hâle gelebilir. Bu nedenle dosya indirme betiğini HTML şablonundan ayrı tutmak genellikle daha güvenlidir.

İndirme bağlantısı nasıl oluşturulur?​


Kullanıcıya aşağıdaki gibi bir bağlantı verebilirsiniz:

HTML:
<a href="indir.php?dosya=rapor">Raporu indir</a>

Bağlantıya tıklandığında PHP, rapor anahtarını izin verilen dosya listesinde arar. Anahtar bulunursa dosyayı gönderir; bulunamazsa 404 yanıtı verir. Bu nedenle bağlantıda gerçek sunucu yolu olan /var/www/... gibi bir değer kullanmak yerine uygulamanın tanıdığı bir anahtar kullanılır.

Aynı yöntemi bir düğmeyle de kullanabilirsiniz:

HTML:
<a href="indir.php?dosya=sozlesme">Sözleşmeyi indir</a>

Dosya tarayıcıda açılmak yerine indirilmelidir. Tarayıcının dosyayı doğrudan göstermesi isteniyorsa Content-Disposition başlığındaki attachment yerine inline kullanılabilir; ancak bu davranış dosya türüne ve tarayıcının desteğine bağlıdır.

Bir dosya yolu doğrudan URL’den alınacaksa​


Bazen dosya adları veritabanında tutulur ve indirme adresine bir kimlik gönderilir. Bu durumda kullanıcıdan gelen değeri doğrudan dosya yoluna eklemek güvenli değildir:

PHP:
$dosya = $_GET['dosya'];
readfile(__DIR__ . '/downloads/' . $dosya);

Bu kullanım, uygun denetim yapılmadığında ../ içeren yolların denenmesine ve uygulamanın erişebildiği başka dosyaların okunmasına yol açabilir. Daha güvenli yöntem, önce bir kimlik almak, bu kimliği veritabanında aramak ve veritabanından gelen dosya yolunun izin verilen ana dizin içinde kaldığını doğrulamaktır.

Basit bir dosya adı doğrulaması gerekiyorsa basename() yalnızca dosya adını ayırmaya yardımcı olabilir; tek başına yetkilendirme sağlamaz. Kullanıcının hangi dosyayı indirmeye yetkili olduğu ayrıca kontrol edilmelidir.

Örneğin dosya yolu veritabanından geldikten sonra şu kontroller uygulanabilir:

PHP:
$anaDizin = realpath(__DIR__ . '/downloads');
$dosyaYolu = realpath($veritabanindanGelenYol);

if ($anaDizin === false || $dosyaYolu === false) {
    http_response_code(404);
    exit('Dosya bulunamadı.');
}

$anaDizin = rtrim($anaDizin, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;

if (strncmp($dosyaYolu, $anaDizin, strlen($anaDizin)) !== 0) {
    http_response_code(403);
    exit('Bu dosyaya erişilemez.');
}

if (!is_file($dosyaYolu) || !is_readable($dosyaYolu)) {
    http_response_code(404);
    exit('Dosya okunamıyor.');
}

Burada realpath() sembolik yolları çözerek gerçek yolu verir. Gerçek yol izin verilen indirme diziniyle başlamıyorsa dosya gönderilmez. Bu kontrol, dosyanın yalnızca adını denetlemekten daha anlamlıdır; yine de erişim yetkisi, oturum ve dosyanın kime ait olduğu ayrıca uygulanmalıdır.

Başlıkları dosya türüne göre ayarlama​


Content-Type, gönderilen içeriğin türünü belirtir. Her dosyada application/octet-stream kullanılabilir; bu genel bir ikili veri türüdür ve tarayıcıya içeriği yorumlamaya çalışmaması gerektiğini bildirir. Ancak bilinen dosya türlerinde doğru MIME türünü vermek daha açıklayıcıdır.
Dosya türüÖrnek Content-TypeTipik davranış
PDFapplication/pdfattachment ile indirme, inline ile tarayıcıda görüntüleme mümkün olabilir
ZIPapplication/zipGenellikle indirme olarak ele alınır
PNGimage/pngTarayıcı görüntüleyebilir; attachment kullanılırsa indirilir
Genel ikili dosyaapplication/octet-streamTarayıcı içeriği yorumlamadan indirmeye yönelir
Dosya uzantısını kullanıcıdan gelen değere göre belirlemek yerine, izin verilen dosya kaydında sabit tutmak daha güvenlidir. Böylece kullanıcı rapor.pdf.exe gibi farklı bir ad göndererek uygulamanın beklemediği bir dosyayı seçemez.

İndirme sonucunu nasıl kontrol edersiniz?​


İşlemden sonra şu sonuçları birbirinden ayırın:
  • Dosya indirme penceresi açılıyor ve dosya doğru adla kaydediliyorsa PHP yanıtı temel olarak doğru çalışıyor demektir. Bu durum, dosyanın herkese açık olması gerektiğini kanıtlamaz; yetkisiz erişim kontrolü ayrıca yapılmalıdır.
  • 404 yanıtı alıyorsanız gönderilen anahtarın izin verilen listede bulunup bulunmadığını ve fiziksel dosyanın beklenen klasörde gerçekten yer alıp almadığını kontrol edin.
  • 403 yanıtı alıyorsanız uygulama dosyayı bilerek erişime kapatıyordur. Kullanıcının oturumunu, dosyaya erişim hakkını ve dosya yolunun izin verilen dizin içinde kalıp kalmadığını inceleyin.
  • İndirilen dosya bozuksa PHP dosyasının başında veya sonunda görünmeyen boşluk, UTF-8 BOM, hata mesajı ya da echo çıktısı olup olmadığını kontrol edin. Ayrıca gönderilen Content-Length değerinin dosyanın gerçek boyutuyla eşleştiğini doğrulayın.
  • Tarayıcı dosyayı açıyor, indirmiyorsa Content-Disposition: attachment başlığının gerçekten gönderildiğini ve sunucuda daha sonra farklı bir başlıkla değiştirilmediğini kontrol edin.
Sunucuda PHP hata gösterimi açıksa hata mesajları dosya yanıtına karışabilir. Geliştirme sırasında hatayı günlüklerde inceleyin; üretim ortamında kullanıcıya doğrudan hata ayrıntısı basmak yerine genel bir hata yanıtı verin.

Büyük dosyalarda dikkat edilmesi gerekenler​


readfile() çoğu normal dosya için yeterlidir. Ancak çok büyük dosyalarda PHP işlem süresi, bellek, web sunucusu ve istemci bağlantısı için tanımlanmış sınırlar devreye girebilir. Dosyayı önce belleğe almak yerine readfile() gibi akış tabanlı bir yöntem kullanmak daha uygundur; file_get_contents() ile tüm dosyayı bir değişkene almak gereksiz bellek tüketebilir.

İndirme sırasında erişim kontrolü veya özel kayıt tutma gerekmiyorsa büyük ve herkese açık dosyaları doğrudan web sunucusunun sunması daha verimli olabilir. Dosya özel olacaksa PHP üzerinden yetkilendirme yapıp dosyayı sunucuya aktarmak veya erişimi süreli, imzalı bir bağlantıyla sınırlamak tercih edilebilir. Bu seçim, dosyanın gizliliğine ve sunucu mimarisine bağlıdır.

Dosya indirme adresi oturum gerektiriyorsa, indirme betiği dosya içeriğini göndermeden önce oturumu kontrol etmelidir:

PHP:
<?php

session_start();

if (empty($_SESSION['kullanici_id'])) {
    http_response_code(401);
    exit('İndirme için giriş yapmalısınız.');
}

// Bundan sonra dosya kaydı ve yetki kontrolü yapılır.

Giriş yapmış olmak tek başına dosyaya erişim hakkı anlamına gelmez. Kullanıcının ilgili dosyaya sahip olup olmadığı veya dosya için gerekli role sahip bulunup bulunmadığı ayrıca kontrol edilmelidir.
 

Sende şimdi bize katılmak ister misin?

Kayıt ol

Bize katılım kolay ve ücretsizdir!

Foruma git ?

Bu konuyu görüntüleyen kullanıcılar

Bu konuyu şu anda görüntüleyen üye bulunmuyor.
Robotlar:
  • Anthropic