GitGram — index.php — GitGram
Hobbes_OS2_Archive / main / v1.12 / index.php8,698 B↓ Raw
<?php
define('HOBBES', true);
require_once __DIR__ . '/config.php';
require_once __DIR__ . '/includes/functions.php';
require_once __DIR__ . '/includes/storage.php';
require_once __DIR__ . '/includes/auth.php';
require_once __DIR__ . '/includes/search.php';
require_once __DIR__ . '/includes/markdown.php';

// ── Shared hosting: verify data directory is NOT web-accessible ───────────────
// If this check is reachable, .htaccess on data/ is working.
// (No action needed here — the data/.htaccess handles blocking.)

// Start session; then check for HTTP Basic Auth (wget/curl support)
auth_start();
auth_try_basic();

// ── Parse URL ─────────────────────────────────────────────────────────────────
// Support ?_r=path fallback for hosts where mod_rewrite is not available.
// e.g. index.php?_r=setup  →  treated as /setup
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
if ($uri === '/index.php' && !empty($_GET['_r'])) {
    $uri = '/' . ltrim(preg_replace('#[^a-zA-Z0-9/_-]#', '', $_GET['_r']), '/');
}
$uri   = '/' . trim($uri, '/');
$parts = explode('/', trim($uri, '/'));
$route_params = [];

// ── First-run gate: redirect everything to /setup if no admin exists ──────────
$always_allow = ['login', 'logout', 'register', 'setup', 'check'];
$first_seg    = $parts[0] ?? '';

$is_setup_done = !empty(glob(USERS_DIR . '/*.json'));
if (!$is_setup_done && $first_seg !== 'setup' && $first_seg !== 'check') {
    redirect('/setup');
}

// ── Access gate ───────────────────────────────────────────────────────────────
// Non-OS/2, non-authenticated visitors may see the landing page and auth pages.
// Browsing, searching, and downloading require an OS/2 browser or an account.
// Exception: /download/.../.json metadata sidecars and /catalog.json are always public.
$browse_only_routes = ['browse', 'search', 'file', 'files', 'download'];
$is_public_meta = ($first_seg === 'download' && str_ends_with($uri, '.json'));
if (!$is_public_meta && in_array($first_seg, $browse_only_routes, true) && !can_access()) {
    // For download routes: send a 401 challenge so wget/curl know to use credentials.
    // Browsers get the normal redirect to the home page.
    if ($first_seg === 'download') {
        $settings_tmp = settings_load();
        header('WWW-Authenticate: Basic realm="' . addslashes($settings_tmp['site_name']) . '"');
        http_response_code(401);
        header('Content-Type: text/plain; charset=UTF-8');
        die("401 Unauthorized\n\nThis archive requires authentication.\n\nUsage:\n  curl -u USERNAME:PASSWORD " . $_SERVER['REQUEST_URI'] . "\n  wget --user=USERNAME --password=PASSWORD " . $_SERVER['REQUEST_URI'] . "\n");
    }
    flash('info', 'An account is required to browse and download files. Please log in or register.');
    redirect('/');
}

// ── Router ─────────────────────────────────────────────────────────────────────
$page = null;

// Home
if ($uri === '/') {
    $page = 'home';

// Browse: /browse or /browse/{slug}
} elseif ($uri === '/browse') {
    $page = 'browse';
} elseif (preg_match('#^/browse/([a-z0-9-]+)$#', $uri, $m)) {
    $route_params['slug'] = $m[1];
    $page = 'browse';

// File detail: /files/{cat/path}/{filename}
} elseif (preg_match('#^/files/(.+)$#', $uri, $m)) {
    $route_params['path'] = $m[1];
    $page = 'file';

// File edit: /file/edit/{id}  (admin, ID-based is fine)
} elseif (preg_match('#^/file/edit/([a-zA-Z0-9_]+)$#', $uri, $m)) {
    $route_params['id'] = $m[1];
    $page = 'file/edit';

// Legacy: /file/{id} — 301 redirect to canonical URL
} elseif (preg_match('#^/file/([a-zA-Z0-9_]+)$#', $uri, $m)) {
    $meta = file_meta_load($m[1]);
    if ($meta && !empty($meta['approved'])) {
        header('Location: ' . file_url($meta), true, 301);
        exit;
    }
    http_response_code(404);
    $page = '404';

// File metadata JSON sidecar (public): /download/{cat/path}/{filename}.json
} elseif (preg_match('#^/download/(.+)\.json$#', $uri, $m)) {
    $route_params['path'] = $m[1];
    $page = 'download_meta';

// Download: /download/{cat/path}/{filename}
} elseif (preg_match('#^/download/(.+)$#', $uri, $m)) {
    $route_params['path'] = $m[1];
    $page = 'download';

// Public catalog for mirrors
} elseif ($uri === '/catalog.json') {
    $page = 'catalog';

// Mirror info page
} elseif ($uri === '/mirror') {
    $page = 'mirror';

// Search
} elseif ($uri === '/search') {
    $page = 'search';

// Upload
} elseif ($uri === '/upload') {
    $page = 'upload';

// Pool: /pool or /pool/{action}/{id}
} elseif ($uri === '/pool') {
    $page = 'pool';
} elseif (preg_match('#^/pool/([a-z_]+)(?:/([a-zA-Z0-9_]+))?$#', $uri, $m)) {
    $route_params['action'] = $m[1];
    $route_params['id']     = $m[2] ?? '';
    $page = 'pool';

// Auth
} elseif ($uri === '/login') {
    $page = 'login';
} elseif ($uri === '/logout') {
    $page = 'logout';
} elseif ($uri === '/register') {
    $page = 'register';
} elseif (preg_match('#^/register/invite(?:/([a-f0-9]+))?$#', $uri, $m)) {
    $route_params['code'] = $m[1] ?? null;
    $page = 'register';

// Invite management
} elseif ($uri === '/invite') {
    $page = 'invite';

// Profile
} elseif ($uri === '/profile') {
    $page = 'profile';

// Admin routes
} elseif ($uri === '/admin' || $uri === '/admin/') {
    $page = 'admin/index';
} elseif ($uri === '/admin/settings') {
    $page = 'admin/settings';
} elseif ($uri === '/admin/css') {
    $page = 'admin/css';
} elseif ($uri === '/admin/users') {
    $page = 'admin/users';
} elseif (preg_match('#^/admin/user-limits/([a-zA-Z0-9_-]+)$#', $uri, $m)) {
    $route_params['username'] = $m[1];
    $page = 'admin/user_limits';
} elseif ($uri === '/admin/categories') {
    $page = 'admin/categories';
} elseif ($uri === '/admin/landing') {
    $page = 'admin/landing';
} elseif ($uri === '/admin/splash') {
    $page = 'admin/splash';
} elseif ($uri === '/admin/meta-merge') {
    $page = 'admin/meta_merge';
} elseif ($uri === '/admin/bulk-delete') {
    $page = 'admin/bulk_delete';
} elseif (preg_match('#^/admin/file-delete/([a-zA-Z0-9_]+)$#', $uri, $m) && $_SERVER['REQUEST_METHOD'] === 'POST') {
    require_role('admin');
    csrf_check();
    $fm      = file_meta_load($m[1]);
    $cat_id  = $fm['category'] ?? '';
    if ($fm) {
        $path = file_physical_path($fm);
        if (file_exists($path)) {
            @unlink($path);
            $dir = dirname($path);
            if ($dir !== UPLOADS_DIR && is_dir($dir) && count(glob($dir . '/*')) === 0) @rmdir($dir);
        }
        search_remove_file($fm['id']);
        file_meta_delete($fm['id']);
        flash('success', 'File "' . h($fm['original_name']) . '" permanently deleted.');
    } else {
        flash('error', 'File not found.');
    }
    $cat = $cat_id ? category_by_id($cat_id) : null;
    redirect($cat ? '/browse/' . $cat['slug'] : '/browse');
} elseif (in_array($uri, ['/admin/rebuild-index', '/admin/rebuild-catalog'], true)) {
    // Legacy routes — actions now handled in pages/admin/index.php
    redirect('/admin');
} elseif ($uri === '/admin/repair') {
    $page = 'admin/repair';
} elseif ($uri === '/admin/reports') {
    $page = 'admin/reports';
} elseif ($uri === '/admin/mirror') {
    $page = 'admin/mirror';

// Setup (first-run)
} elseif ($uri === '/setup') {
    $page = 'setup';

// 404
} else {
    http_response_code(404);
    $page = '404';
}

// ── Dispatch ───────────────────────────────────────────────────────────────────
$page_file = ROOT_DIR . '/pages/' . $page . '.php';
if ($page && file_exists($page_file)) {
    include $page_file;
} elseif ($page === '404') {
    define('HOBBES_TEMPLATE_OPEN', true);
    $page_title = '404 Not Found';
    $_page      = '';
    include ROOT_DIR . '/templates/header.php';
    echo '<div class="panel"><div class="panel-title">404 Not Found</div>';
    echo '<div class="panel-body"><p>The page you requested was not found.</p>';
    echo '<p><a href="/">Return to home</a></p></div></div>';
    include ROOT_DIR . '/templates/footer.php';
} else {
    http_response_code(500);
    echo 'Internal error: page not found.';
}
Ready
GitGram