/apps/checklist/index.html 출력 * https://abcd.com/?todo -> /apps/todo/index.html 출력 * * 보안 메모: 라우트는 화이트리스트로만 매핑. 디렉토리 트래버설 방지. */ // 응답 헤더 header('Content-Type: text/html; charset=utf-8'); /** @var array $ROUTES 라우트 키 → 서브디렉토리 경로(루트 기준, 마지막 슬래시 없음) */ $ROUTES = [ // 오타 케이스도 허용 'cheklist' => 'apps/remember_todo', 'checklist' => 'apps/remember_todo', 'todo' => 'apps/remember_todo', 'test' => 'apps/dist', ]; /** * 첫 번째 쿼리 "키"를 반환. * ?foo, ?foo=bar, ?foo&x=1 형태 모두 지원. * 값은 무시하고 키만 사용. * * @return string|null */ function first_query_key(): ?string { if (!empty($_GET)) { $keys = array_keys($_GET); if (!empty($keys[0])) { return strtolower((string)$keys[0]); } } $qs = $_SERVER['QUERY_STRING'] ?? ''; if ($qs !== '') { $first = explode('&', $qs)[0]; $key = explode('=', $first, 2)[0]; if ($key !== '') return strtolower($key); } return null; } /** * 안에 가 없으면 삽입. * 상대경로 리소스들이 서브디렉토리 기준으로 로드되도록 보정. * * @param string $html * @param string $baseHref '/apps/checklist/' 형식(꼭 슬래시로 끝남) * @return string */ function ensure_base_tag(string $html, string $baseHref): string { if (preg_match('~ 직후에 삽입 return preg_replace('~]*)>~i', '', $html, 1); } $key = first_query_key(); $dir = $key !== null && isset($ROUTES[$key]) ? $ROUTES[$key] : null; if ($dir !== null) { $dir = trim($dir, "/"); $file = __DIR__ . "/" . $dir . "/index.html"; if (!is_file($file)) { http_response_code(404); echo "

404 Not Found

경로를 찾을 수 없습니다.

"; exit; } $html = file_get_contents($file); // base 보정 (상대 경로 자원 정상 로딩) $baseHref = "/" . $dir . "/"; $html = ensure_base_tag($html, $baseHref); echo $html; exit; } // 기본: 루트의 index.html이 있으면 그대로 보여주기 $rootIndex = __DIR__ . "/index.html"; if (is_file($rootIndex)) { readfile($rootIndex); exit; } // 최종 fallback http_response_code(404); echo "

404 Not Found

루트 index.html이 없습니다.

";