'POST만 허용됩니다'], 405); } $input = get_json_input(); // 안전 가드: uploads 디렉토리 밖으로 못 나가게 function is_safe_uploads_path($path) { $real_uploads = realpath(UPLOADS_DIR); $real_target = realpath($path); if ($real_uploads === false) return false; if ($real_target === false) return false; return strpos($real_target, $real_uploads) === 0; } $deleted = []; $failed = []; // 1) URL 리스트로 개별 파일 삭제 if (isset($input['urls']) && is_array($input['urls'])) { foreach ($input['urls'] as $url) { $url = trim($url); if ($url === '') continue; // uploads/ 로 시작하는 상대 경로만 허용 if (!preg_match('#^uploads/#', $url)) { $failed[] = ['url' => $url, 'reason' => 'invalid path']; continue; } // 외부 URL은 건너뛰기 (http://, https:// 등) if (preg_match('#^https?://#i', $url)) { continue; } // 절대 경로 만들기 $relative = preg_replace('#^uploads/#', '', $url); $full_path = UPLOADS_DIR . '/' . $relative; if (!file_exists($full_path)) { $failed[] = ['url' => $url, 'reason' => 'not found']; continue; } if (!is_safe_uploads_path($full_path)) { $failed[] = ['url' => $url, 'reason' => 'unsafe path']; continue; } if (@unlink($full_path)) { $deleted[] = $url; } else { $failed[] = ['url' => $url, 'reason' => 'unlink failed']; } } } // 2) 폴더 삭제 (프로젝트 폴더 통째로) if (isset($input['folder'])) { $folder = trim($input['folder']); if ($folder !== '' && !preg_match('#[/\\\\]#', $folder)) { $folder_path = UPLOADS_DIR . '/' . $folder; if (is_dir($folder_path) && is_safe_uploads_path($folder_path)) { // 폴더 안의 파일들 삭제 $files = glob($folder_path . '/*'); foreach ($files as $f) { if (is_file($f)) { if (@unlink($f)) { $deleted[] = $folder . '/' . basename($f); } else { $failed[] = ['url' => $folder . '/' . basename($f), 'reason' => 'unlink failed']; } } } // 빈 폴더는 삭제 @rmdir($folder_path); } } } json_response([ 'success' => true, 'deleted_count' => count($deleted), 'failed_count' => count($failed), 'deleted' => $deleted, 'failed' => $failed ]);