PHP Dosya İşlemleri: Okuma, Yazma ve Güvenli Yükleme

PHP Dosya İşlemleri: Okuma, Yazma ve Güvenli Yükleme

4 dakika okuma

Dosya işlemleri basit görünür, ancak iki noktada sık hata yapılır: büyük dosyaların tamamının belleğe alınması ve kullanıcıdan gelen dosyaların yeterince denetlenmeden saklanması. Bu yazıda okuma ve yazma yöntemleri ile yükleme güvenliği birlikte ele alınıyor.

Dosya işlemleri aynı zamanda yavaş işlemlerdir. Disk erişimi bellek erişiminden kat kat uzun sürdüğü için, döngü içinde tekrarlanan okuma ve yazma çağrıları istek süresini hızla büyütür. Tekrarlanan erişim yerine veriyi tek seferde alıp bellekte işlemek, çoğu durumda en basit iyileştirmedir.

Dosya okuma

Küçük dosyalar tek çağrıyla okunabilir. Boyut büyüdüğünde ise dosyanın tamamı belleğe alınacağı için satır satır okuma tercih edilmelidir:

PHP14 satır
// küçük dosya
$icerik = file_get_contents('ayar.json');
$ayar = json_decode($icerik, true);

// büyük dosya: bellek sabit kalır
$fp = fopen('kayit.log', 'r');
if ($fp === false) {
    throw new RuntimeException('Dosya açılamadı');
}

while (($satir = fgets($fp)) !== false) {
    isle(trim($satir));
}
fclose($fp);

Dönüş değerleri kontrol edilmeden kullanılmamalıdır. Dosya yoksa ya da okuma izni verilmemişse fonksiyonlar false döner ve bu değer sessizce boş metin gibi davranarak hatanın kaynağını gizler.

Dosya yazma ve kilitleme

Aynı dosyaya birden fazla istek aynı anda yazabilir. Kilit kullanılmadığında satırlar iç içe geçer ve dosya bozulur. LOCK_EX bayrağı yazma sırasında dosyayı kilitler:

PHP4 satır
file_put_contents('sayac.txt', $deger, LOCK_EX);

// sona ekleme
file_put_contents('islem.log', $satir . PHP_EOL, FILE_APPEND | LOCK_EX);

Yazma işleminin bir kısmının tamamlanıp bir kısmının başarısız olması riskliyse, önce geçici dosyaya yazıp sonra yeniden adlandırmak daha güvenlidir. Yeniden adlandırma aynı dosya sisteminde atomik kabul edilir.

Dizin işlemleri

PHP13 satır
if (!is_dir($dizin)) {
    mkdir($dizin, 0755, true);
}

// belirli uzantıdaki dosyalar
$resimler = glob($dizin . '/*.{jpg,png}', GLOB_BRACE);

// dizin içeriği
foreach (scandir($dizin) as $ad) {
    if ($ad === '.' || $ad === '..') {
        continue;
    }
}

Dizin oluştururken üçüncü parametrenin true verilmesi, ara dizinlerin de oluşturulmasını sağlar. İzinler gereğinden geniş verilmemelidir; yazma izni yalnızca gerçekten gereken dizinlerde bulunmalıdır.

Yüklenen dosyaların denetimi

Yükleme işleminde en sık yapılan hata, dosya türünü uzantıya ya da tarayıcının bildirdiği tür bilgisine bakarak belirlemek. İkisi de kullanıcı kontrolündedir. Tür, dosyanın içeriğinden okunmalıdır:

PHP14 satır
$dosya = $_FILES['gorsel'] ?? null;

if ($dosya === null || $dosya['error'] !== UPLOAD_ERR_OK) {
    throw new RuntimeException('Yükleme başarısız');
}

$finfo = new finfo(FILEINFO_MIME_TYPE);
$tur = $finfo->file($dosya['tmp_name']);

$izinli = ['image/jpeg' => 'jpg', 'image/png' => 'png', 'image/webp' => 'webp'];

if (!isset($izinli[$tur])) {
    throw new RuntimeException('Desteklenmeyen tür');
}

Dosya adı kullanıcıdan alınmaz, sunucuda üretilir. Böylece hem çakışma hem de ad üzerinden yapılan dizin geçişi denemeleri engellenir:

PHP6 satır
$ad = bin2hex(random_bytes(16)) . '.' . $izinli[$tur];
$hedef = $yuklemeDizini . '/' . $ad;

if (!move_uploaded_file($dosya['tmp_name'], $hedef)) {
    throw new RuntimeException('Dosya taşınamadı');
}

Yol denetimi ve dizin geçişi

Kullanıcıdan gelen bir değer dosya yoluna eklendiğinde, iki nokta üst üste kullanılarak üst dizinlere çıkılabilir. Çözüm, yolu çözümleyip beklenen dizinin içinde kalıp kalmadığını doğrulamaktır:

PHP7 satır
$istenen = realpath($yuklemeDizini . '/' . basename($_GET['dosya'] ?? ''));
$kok = realpath($yuklemeDizini);

if ($istenen === false || !str_starts_with($istenen, $kok)) {
    http_response_code(404);
    exit;
}

Yüklenen dosyaların web kökünün dışında saklanması ek bir koruma sağlar. Dosyalar doğrudan adresle değil, yetki kontrolü yapan bir betik üzerinden sunulur.

İzinler ve sahiplik

Web sunucusu süreci hangi kullanıcı adına çalışıyorsa, dosya izinleri o kullanıcıya göre ayarlanmalıdır. Yaygın hata, sorun çıkmasın diye tüm dizine tam yetki vermektir. Bu ayar, sunucuya sızan bir saldırganın dosyaları değiştirmesini de serbest bırakır.

Genel kural şudur: dizinler için 755, dosyalar için 644 yeterlidir. Yazma izni yalnızca yükleme ve önbellek dizinlerinde bulunmalı, uygulama kodunun bulunduğu dizinler yazmaya kapalı olmalıdır.

PHP3 satır
// yalnızca gereken dizinde yazma izni
chmod($yuklemeDizini, 0755);
chmod($hedefDosya, 0644);

Geçici dosyalar ve temizlik

Ara işlemler için üretilen dosyalar işletim sisteminin geçici dizininde tutulmalıdır. Bu dizin hem doğru izinlere sahiptir hem de düzenli olarak temizlenir. Dosya adının elle üretilmesi yerine hazır fonksiyon kullanmak, aynı anda çalışan isteklerin birbirinin dosyasını ezmesini engeller:

PHP8 satır
$gecici = tempnam(sys_get_temp_dir(), 'rapor_');

try {
    file_put_contents($gecici, $veri);
    isle($gecici);
} finally {
    unlink($gecici);
}

Silme işleminin finally bloğunda yapılması, hata durumunda bile dosyanın kalmamasını sağlar. Uzun süre çalışan sistemlerde biriken geçici dosyalar disk dolmasının sessiz nedenlerinden biridir.

Kontrol listesi

  • Büyük dosyalar satır satır okunur.
  • Dosya işlemlerinin dönüş değerleri kontrol edilir.
  • Eşzamanlı yazmada kilit kullanılır.
  • Yüklenen dosyanın türü içerikten okunur, uzantıya güvenilmez.
  • Dosya adı sunucuda üretilir.
  • Kullanıcıdan gelen yol realpath ile doğrulanır.

PHP eğitim serisi

Bu yazı, temel sözdiziminden üretim ortamına kadar ilerleyen 25 bölümlük serinin bir parçası. Bölümler sırayla okunacak biçimde hazırlandı, ancak her biri tek başına da kullanılabilir.

Temeller

  1. Kurulum ve temel sözdizimi
  2. Döngüler ve akış kontrolü
  3. Diziler
  4. Fonksiyonlar
  5. String fonksiyonları
  6. Süper globaller
  7. Form işleme
  8. Dosya işlemleri (bu yazı)

Dilin ayrıntıları

  1. Tip sistemi ve strict_types
  2. Hata ve istisna yönetimi
  3. Düzenli ifadeler
  4. Tarih ve saat işlemleri

Nesne yönelimli PHP

  1. Sınıf ve nesne
  2. Kalıtım, arayüz ve trait
  3. Statik üyeler, sabitler ve enum
  4. Namespace, Composer ve autoload

Veri ve güvenlik

  1. PDO ile veritabanı işlemleri
  2. Oturum yönetimi ve kimlik doğrulama
  3. Güvenli çıktı ve XSS
  4. JSON ile veri değişimi

Dış dünya ve üretim

  1. cURL ile dış API çağırma
  2. Kendi REST API'ni yazmak
  3. PHPUnit ile birim test
  4. Hata ayıklama ve performans ölçümü
  5. Üretime alma ve güvenlik

Yorumlar

Bu form reCAPTCHA ile korunur; Google Gizlilik Politikası ve Hizmet Şartları geçerlidir.

İlk yorumu sen yap.

İlgili Yazılar