[projects/buildlogs] Escaping, pdo etc.

arekm arekm at pld-linux.org
Mon Apr 20 16:14:57 CEST 2026


commit f7fe2a12964780d21b5dbb6d9e70bf835a676d2c
Author: Arkadiusz Miśkiewicz <arekm at maven.pl>
Date:   Sat Apr 11 03:22:19 2026 +0200

    Escaping, pdo etc.

 helpers/buildlogs-inotify-mover.sh  |  16 +-
 helpers/buildlogs-mover.sh          |  72 ++--
 helpers/install-buildlogs-tree.sh   |  25 +-
 index.php                           | 704 ++++++++++++++++++++++--------------
 pld-buildlogs/scripts/addlog.php    | 120 ++++--
 pld-buildlogs/scripts/migration.php | 141 +++++---
 6 files changed, 653 insertions(+), 425 deletions(-)
---
diff --git a/helpers/buildlogs-inotify-mover.sh b/helpers/buildlogs-inotify-mover.sh
index 3b0d324..e7db89f 100644
--- a/helpers/buildlogs-inotify-mover.sh
+++ b/helpers/buildlogs-inotify-mover.sh
@@ -1,14 +1,14 @@
 #!/bin/sh
+set -eu
 
 root="/home/services/ftpd/buildlogs"
 
-if test -f /etc/buildlogs-mover.conf ; then
-	. /etc/buildlogs-mover.conf
+if [ -f /etc/buildlogs-mover.conf ]; then
+    . /etc/buildlogs-mover.conf
 fi
 
-
-inotifywait -q -m -r -e move $root/*/*/.new | \
-        while read path change file; do
-                [ "$change" != "MOVED_TO" ] && continue
-                /bin/su - ftp -s /bin/sh -c "/home/services/buildlogs/buildlogs-mover.sh"
-        done
+inotifywait -q -m -r -e move "$root"/*/*/.new | \
+while IFS= read -r path change file; do
+    [ "$change" != "MOVED_TO" ] && continue
+    /bin/su - ftp -s /bin/sh -c "/home/services/buildlogs/buildlogs-mover.sh"
+done
diff --git a/helpers/buildlogs-mover.sh b/helpers/buildlogs-mover.sh
index bd917ba..32595ea 100644
--- a/helpers/buildlogs-mover.sh
+++ b/helpers/buildlogs-mover.sh
@@ -13,52 +13,54 @@
 # Note that we look for root/*/*/.new/*.info, so don't place any additional
 # directories there.
 
+set -eu
+
 root="/home/services/ftpd/buildlogs"
 ADDLOG="/home/services/httpd/html/pld-buildlogs/scripts/addlog.php"
 
-if test -f /etc/buildlogs-mover.conf ; then
-  . /etc/buildlogs-mover.conf
+if [ -f /etc/buildlogs-mover.conf ]; then
+    . /etc/buildlogs-mover.conf
 fi
 
-for n in $root/*/*/.new ; do
-  if test ! -d $n ; then
-    echo "$n doesn't exists or ain't directory"
+# Ensure at least one .new dir exists. If the glob doesn't match anything,
+# find will return no results and the script exits gracefully.
+set +e
+first=$(find "$root" -mindepth 3 -maxdepth 3 -type d -name '.new' | head -n 1)
+set -e
+if [ -z "$first" ]; then
+    echo "no .new directories found under $root" >&2
     exit 1
-  fi
-  break # don't check all
-done
+fi
 
-handle_info () {
-  info="$1"
-  info_val="$(cat "$info" 2>/dev/null)"
-  if echo "$info_val" | grep -q '^END$' ; then
+handle_info() {
+    info="$1"
+    info_val=$(cat "$info" 2>/dev/null) || return 0
+    echo "$info_val" | grep -q '^END$' || return 0
     status=$(echo "$info_val" | grep '^Status:' | sed -e 's/.*: *//')
-    case $status in
-      OK ) s=OK ;;
-      FAIL* ) s=FAIL ;;
-      * )
-        # this script is run from cron, so this should go through mail 
-	# to admin
-        echo "bad buildlog status: $status in $info:" 1>&2
-	echo "#v+" 1>&2
-	echo "$info_val" 1>&2
-	echo "#v-" 1>&2
-	rm "$info" 2>/dev/null
-	return
-        ;;
+    case "$status" in
+        OK)    s=OK ;;
+        FAIL*) s=FAIL ;;
+        *)
+            {
+                echo "bad buildlog status: $status in $info:"
+                echo "#v+"
+                echo "$info_val"
+                echo "#v-"
+            } >&2
+            rm -f "$info"
+            return 0
+            ;;
     esac
-    archdir="$(dirname "$(dirname "$info")")"
+    archdir=$(dirname "$(dirname "$info")")
     file=$(basename "$info" .info)
-    if test -f "$archdir/.new/$file"; then
-	    mv -f "$archdir/.new/$file" "$archdir/$s/$file"
-	    $ADDLOG "$archdir/$s/$file"
-	    rm "$info" 2>/dev/null
+    if [ -f "$archdir/.new/$file" ]; then
+        mv -f "$archdir/.new/$file" "$archdir/$s/$file"
+        "$ADDLOG" "$archdir/$s/$file"
+        rm -f "$info"
     fi
-  fi
 }
 
-for info in $root/*/*/.new/*.info ; do
-  if test -f "$info" ; then
-    handle_info "$info"
-  fi
+find "$root" -mindepth 4 -maxdepth 4 -type f -name '*.info' -path '*/.new/*' | \
+while IFS= read -r info; do
+    [ -f "$info" ] && handle_info "$info"
 done
diff --git a/helpers/install-buildlogs-tree.sh b/helpers/install-buildlogs-tree.sh
index 3873710..ef176ce 100644
--- a/helpers/install-buildlogs-tree.sh
+++ b/helpers/install-buildlogs-tree.sh
@@ -1,20 +1,21 @@
 #!/bin/sh
 # create directories needed for buildlogs
 
-if [ "$1" = "" ] ; then
-  echo "usage: $0 dir"
-  exit 1
+set -eu
+
+if [ -z "${1:-}" ]; then
+    echo "usage: $0 dir" >&2
+    exit 1
 fi
 
-mk () 
-{
-  for d in "$@" ; do
-    mkdir -p $d/{.new,OK,prevOK,FAIL}
-  done
+mk() {
+    for d in "$@"; do
+        mkdir -p "$d/.new" "$d/OK" "$d/prevOK" "$d/FAIL"
+    done
 }
 
-cd "$1" || exit 1
+cd "$1"
 
-mk ac/{i{3,5,6}86,athlon,sparc,ppc,alpha,SRPMS}
-mk ra/{i{3,5,6}86,sparc,ppc,alpha,SRPMS}
-mk nest/{i{3,5,6}86,alpha,ppc,athlon,SRPMS}
+mk ac/i386 ac/i586 ac/i686 ac/athlon ac/sparc ac/ppc ac/alpha ac/SRPMS
+mk ra/i386 ra/i586 ra/i686 ra/sparc ra/ppc ra/alpha ra/SRPMS
+mk nest/i386 nest/i586 nest/i686 nest/alpha nest/ppc nest/athlon nest/SRPMS
diff --git a/index.php b/index.php
index 8d01e7b..c3fd3b1 100644
--- a/index.php
+++ b/index.php
@@ -1,117 +1,191 @@
 <?php
-if (!function_exists("ob_gzhandler"))
-  die("ob_gzhandler function is missing - install php zlib module");
+declare(strict_types=1);
 
+if (!function_exists("ob_gzhandler")) {
+    http_response_code(500);
+    die("ob_gzhandler function is missing - install php zlib module");
+}
 ob_start("ob_gzhandler", 1);
+
+header("X-Content-Type-Options: nosniff");
+header("X-Frame-Options: DENY");
+header("Referrer-Policy: no-referrer-when-downgrade");
+header(
+    "Content-Security-Policy: "
+    . "default-src 'self'; "
+    . "img-src 'self' data:; "
+    . "style-src 'self' 'unsafe-inline'; "
+    . "script-src 'self' 'unsafe-inline'; "
+    . "object-src 'none'; "
+    . "frame-ancestors 'none'"
+);
+
+function h(?string $s): string {
+    return htmlspecialchars($s ?? '', ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5, 'UTF-8');
+}
+
+function validate_identifier(?string $v): ?string {
+    if ($v === null || $v === '') return null;
+    if (strlen($v) > 64) return null;
+    if (!preg_match('/^[A-Za-z0-9][A-Za-z0-9_.+-]*$/', $v)) return null;
+    return $v;
+}
+
+function safe_log_path(string $root, string $relative): ?string {
+    $root_real = realpath($root);
+    if ($root_real === false) return null;
+    $full = $root_real . '/' . $relative;
+    $full_real = realpath($full);
+    if ($full_real === false) return null;
+    if (strncmp($full_real, $root_real . '/', strlen($root_real) + 1) !== 0) {
+        return null;
+    }
+    return $full_real;
+}
+
+function like_escape(string $s): string {
+    // Escape SQL LIKE wildcards. The LIKE clause must specify ESCAPE '\\'.
+    return str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $s);
+}
+
+function one_item(string $h, string $t): void {
+    echo "<tr><td bgcolor=\"#ccccff\">$h:</td>".
+         "<td bgcolor=\"#cccccc\">$t</td></tr>";
+}
+
+function href_tag(string $url_attr, string $body): string {
+    return '<a href="' . $url_attr . '">' . $body . '</a>';
+}
+
 $buildlogs_server = "buildlogs.pld-linux.org";
 $url = "index.php";
-$fail_or_ok = array( "FAIL", "OK" );
-/*
-$database = 'sqlite:/home/services/ftp/buildlogs2.db';
-$root_directory = "/home/services/ftp/pub/pld-buildlogs";
-*/
+$fail_or_ok = ["FAIL", "OK"];
 
 // $database, $root_directory and others are taken from buildlogs.inc
 include('buildlogs.inc');
 
-/* It should be set */
-
-$langs["en_US"]["charset"]="ISO-8859-1";
-$langs["pl_PL"]["charset"]="ISO-8859-2";
-
-$lang="en_US";
-$lang_detected="";
-if (isset($_SERVER["HTTP_ACCEPT_LANGUAGE"]))
-{
-  $rows=explode(";",$_SERVER["HTTP_ACCEPT_LANGUAGE"]);
-  $rows=explode(",",$rows[0]);
-  $lang_detected=rtrim($rows[0]);
-} else if (preg_match("/opera/i",$_SERVER["HTTP_USER_AGENT"]))
-{
-  $lang_detected=preg_replace("/.*\[(.*)\].*/i","\\1",$_SERVER["HTTP_USER_AGENT"]);
+session_set_cookie_params([
+    'lifetime' => 0,
+    'path' => '/',
+    'httponly' => true,
+    'samesite' => 'Lax',
+    'secure' => !empty($_SERVER['HTTPS']),
+]);
+session_start();
+
+$langs = [
+    'en_US' => ['charset' => 'UTF-8'],
+    'pl_PL' => ['charset' => 'UTF-8'],
+];
+
+$lang = 'en_US';
+$lang_detected = '';
+if (isset($_SERVER["HTTP_ACCEPT_LANGUAGE"])) {
+    $rows = explode(";", $_SERVER["HTTP_ACCEPT_LANGUAGE"]);
+    $rows = explode(",", $rows[0]);
+    $lang_detected = rtrim($rows[0]);
+} elseif (isset($_SERVER["HTTP_USER_AGENT"]) && preg_match("/opera/i", $_SERVER["HTTP_USER_AGENT"])) {
+    $lang_detected = preg_replace("/.*\[(.*)\].*/i", "\\1", $_SERVER["HTTP_USER_AGENT"]);
 }
+$lang_detected = preg_replace("/^pl$/i", "pl_PL", $lang_detected);
 
-// FIXME - some array
-$lang_detected=preg_replace("/^pl$/i","pl_PL",$lang_detected);
-
-if (isset($lang_detected) && isset($langs[$lang_detected]))
-{
-  $lang=$lang_detected;
+if ($lang_detected !== '' && isset($langs[$lang_detected])) {
+    $lang = $lang_detected;
 }
 
-if (isset($_GET["lang"]))$_SESSION["lang"]=$_GET["lang"];
-if (isset($_SESSION["lang"]))$lang=$_SESSION["lang"];
+if (isset($_GET["lang"]) && isset($langs[$_GET["lang"]])) {
+    $_SESSION["lang"] = $_GET["lang"];
+}
+if (isset($_SESSION["lang"]) && isset($langs[$_SESSION["lang"]])) {
+    $lang = $_SESSION["lang"];
+}
 
 putenv("LANG=$lang");
-setlocale(LC_ALL,$lang);
-bindtextdomain("messages","locale");
+setlocale(LC_ALL, $lang);
+bindtextdomain("messages", "locale");
 textdomain("messages");
 
-if (isset($_GET["dist"]) && isset($_GET["arch"]))
-{
-	$dist = $_GET["dist"];
-	$dist = basename(htmlspecialchars($dist, ENT_QUOTES, 'UTF-8'));
-	$arch = $_GET["arch"];
-	$arch = basename(htmlspecialchars($arch, ENT_QUOTES, 'UTF-8'));
+// Centralized input parsing. Every user-facing variable is validated here.
+$dist = validate_identifier($_GET["dist"] ?? $_POST["dist"] ?? null);
+if ($dist !== null) {
+    $dist = basename($dist);
+    $dist = validate_identifier($dist);
 }
 
-if (isset($_POST["dist"])) {
-	$dist = $_POST["dist"];
-	$dist = basename(htmlspecialchars($dist, ENT_QUOTES, 'UTF-8'));
+$arch = validate_identifier($_GET["arch"] ?? $_POST["arch"] ?? null);
+if ($arch !== null) {
+    $arch = basename($arch);
+    $arch = validate_identifier($arch);
+    if ($arch === 'src') {
+        $arch = 'SRPMS';
+    }
 }
 
-if (isset($_POST["arch"])) {
-	$arch = $_POST["arch"];
-	$arch = basename(htmlspecialchars($arch, ENT_QUOTES, 'UTF-8'));
+$name = null;
+$name_url = '';
+if (isset($_GET["name"])) {
+    $raw_name = basename((string)$_GET["name"]);
+    $name = validate_identifier($raw_name);
+    if ($name !== null) {
+        $name_url = urlencode($name);
+    }
 }
 
-if (isset($_GET["name"])) {
-	$name_url = urlencode($_GET["name"]);
-	$name = $_GET["name"];
-	$name = basename(htmlspecialchars($name, ENT_QUOTES, 'UTF-8'));
-}
-
-if (isset($_GET["ok"]))$ok=(int)$_GET["ok"];
-else $ok="";
-if (isset($_GET["ns"]))$ns=(int)$_GET["ns"];
-else $ns="";
-if (isset($_GET["cnt"]))$cnt=(int)$_GET["cnt"];
-else $cnt = 50;
-if (isset($_GET["action"])) {
-	$action = $_GET["action"];
-	$action = htmlspecialchars($action, ENT_QUOTES, 'UTF-8');
-} else
-	$action="";
-if (isset($_GET["off"]))$off=(int)$_GET["off"];
-else $off = 0;
+$id = null;
 if (isset($_GET["id"])) {
-	$id = $_GET["id"];
-	$id = htmlspecialchars($id, ENT_QUOTES, 'UTF-8');
+    $raw_id = basename((string)$_GET["id"]);
+    $id = validate_identifier($raw_id);
 }
 
-if (isset($_POST["str"])) {
-	$str = $_POST["str"];
-	$str = htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
+$ok_raw = $_GET["ok"] ?? null;
+if ($ok_raw === null || $ok_raw === '') {
+    $ok = '';
+} else {
+    $ok = (int)$ok_raw;
+    if ($ok !== 0 && $ok !== 1) {
+        $ok = 0;
+    }
 }
-if (isset($_POST["action"])) {
-	$action = $_POST["action"];
-	$action = htmlspecialchars($action, ENT_QUOTES, 'UTF-8');
+
+$ns_raw = $_GET["ns"] ?? null;
+if ($ns_raw === null || $ns_raw === '') {
+    $ns = '';
+} else {
+    $ns = (int)$ns_raw;
+    if ($ns < 0 || $ns > 2) {
+        $ns = 0;
+    }
 }
 
-if (isset($arch) && $arch == "src")
-	$arch = "SRPMS";
+$cnt = isset($_GET["cnt"]) ? (int)$_GET["cnt"] : 50;
+if ($cnt < 1) $cnt = 1;
+if ($cnt > 500) $cnt = 500;
+
+$off = isset($_GET["off"]) ? (int)$_GET["off"] : 0;
+if ($off < 0) $off = 0;
+
+$ACTION_WHITELIST = ['', 'text', 'tail', 'qa', 'qatxt', 'sqa', 'adv_search'];
+$action_raw = $_POST["action"] ?? $_GET["action"] ?? '';
+$action = in_array($action_raw, $ACTION_WHITELIST, true) ? $action_raw : '';
+
+$str = '';
+if (isset($_POST["str"])) {
+    $str_raw = (string)$_POST["str"];
+    if (strlen($str_raw) <= 200 && mb_check_encoding($str_raw, 'UTF-8')
+        && !preg_match('/[\x00-\x1F\x7F]/', $str_raw)) {
+        $str = $str_raw;
+    }
+}
 
 function myheader()
 {
-echo '<' . '?xml version="1.0" encoding="' . _("ISO-8859-1") .'"?' . ">\n";
-echo '<' . '?xml-stylesheet href="#internalStyle" type="text/css"?' . ">\n";
+    header("Content-Type: text/html; charset=UTF-8");
 ?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
-    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
+<!DOCTYPE html>
+<html lang="en">
  <head>
   <title>PLD Build Logs</title>
-  <?php echo '<meta http-equiv="Content-type" content="text/html; charset=' . _("ISO-8859-1") .'"/>' ."\n";?>
+  <meta charset="UTF-8">
   <style type="text/css"><!--
 A { text-decoration: none; }
 A:hover { text-decoration: underline; }
@@ -189,22 +263,24 @@ function list_logs()
 		'cnt' => $cnt);
 	$big_url = $url . '?' . http_build_query($query_data);
 
+	$dist_arch = h($dist . '/' . $arch);
 	if ($ok == 1) {
-		echo "<h1>"._("Listing of")." $dist/$arch/OK "
-			."(<a href=\"$big_url&ok=0\">"._("fail")."</a>)</h1>\n";
+		echo "<h1>"._("Listing of")." $dist_arch/OK "
+			."(<a href=\"" . h($big_url) . "&ok=0\">"._("fail")."</a>)</h1>\n";
 	} else {
-		echo "<h1>"._("Listing of")." $dist/$arch/FAIL "
-			."(<a href=\"$big_url&ok=1\">"._("ok")."</a>)</h1>\n";
+		echo "<h1>"._("Listing of")." $dist_arch/FAIL "
+			."(<a href=\"" . h($big_url) . "&ok=1\">"._("ok")."</a>)</h1>\n";
 	}
 
 	echo "<div align=\"center\"><table cols=\"4\" border=\"0\" cellspacing=\"1\" ".
 		"cellpadding=\"3\" bgcolor=\"#000000\" width=\"90%\">\n";
+	$big_url_esc = h($big_url);
 	echo "<tr><th bgcolor=\"#CCCCFF\" align=\"right\" width=\"1%\">"._("No.")."</th>".
 		 "<th bgcolor=\"#CCCCFF\" align=\"left\" width=\"80%\">"._("Log File").
-			"[<a href=\"$big_url&ns=1\">"._("sort")."</a>]</th>".
+			"[<a href=\"$big_url_esc&ns=1\">"._("sort")."</a>]</th>".
 		 "<th bgcolor=\"#CCCCFF\" align=\"right\" width=\"15%\">"._("Size")."</th> ".
 		 "<th bgcolor=\"#CCCCFF\" align=\"left\">"._("Age").
-			 "[<a href=\"$big_url&ns=0\">"._("sort")."</a>]</th>".
+			 "[<a href=\"$big_url_esc&ns=0\">"._("sort")."</a>]</th>".
 		 "</tr>";
 
 	if ($ns != 1) $ns = 0;
@@ -227,12 +303,12 @@ function list_logs()
 	$now = time();
 	$i = $off;
 	$stmt = $dbh->prepare($query);
-	$stmt->bindParam(':dist', $dist, PDO::PARAM_STR);
-	$stmt->bindParam(':arch', $arch, PDO::PARAM_STR);
-	$stmt->bindParam(':ok', $ok, PDO::PARAM_INT);
-	$stmt->bindParam(':limitnr', $cnt, PDO::PARAM_INT);
-	$stmt->bindParam(':offset', $off, PDO::PARAM_INT);
-	$stmt->execute([$dist, $arch, $ok, $cnt, $off]);
+	$stmt->bindValue(':dist', $dist, PDO::PARAM_STR);
+	$stmt->bindValue(':arch', $arch, PDO::PARAM_STR);
+	$stmt->bindValue(':ok', $ok, PDO::PARAM_INT);
+	$stmt->bindValue(':limitnr', $cnt, PDO::PARAM_INT);
+	$stmt->bindValue(':offset', $off, PDO::PARAM_INT);
+	$stmt->execute();
 	while ($row = $stmt->fetch()) {
 		$name = $row["name"];
 		$id = $row["id"];
@@ -249,14 +325,14 @@ function list_logs()
 			$t /= 60;
 			if ($t >= 24) {
 				$t /= 24;
-				$t = round($t);
+				$t = (int)round($t);
 				$t = $t . " " . ngettext("day","days",$t);
 			} else {
-				$t = round($t);
+				$t = (int)round($t);
 				$t = $t . " " . ngettext("hour","hours",$t);
 			}
 		} else {
-			$t = round($t);
+			$t = (int)round($t);
 			$t = $t . " " . ngettext("minute","minutes",$t);
 		}
 		$url_data = array(
@@ -265,13 +341,13 @@ function list_logs()
 			'ok' => $ok,
 			'name' => $name_url,
 			'id' => $id);
-		$u = $url . '?' . http_build_query($url_data);
+		$u = h($url . '?' . http_build_query($url_data));
 		echo "<tr><td bgcolor=\"#CCCCCC\" align=\"right\">".($i+1).".</td>".
-		     "<td bgcolor=\"#CCCCCC\"><a href=\"$u\">".htmlspecialchars($f, ENT_QUOTES, 'UTF-8')."</a> ".
+		     "<td bgcolor=\"#CCCCCC\"><a href=\"$u\">" . h($f) . "</a> ".
 		     "[<a href=\"$u&action=text\">"._("text")."</a> | ".
 		      "<a href=\"$u&action=tail\">"._("tail")."</a>]".
 		     "</td><td bgcolor=\"#CCCCCC\" align=\"right\">".
-		     "$s</td><td bgcolor=\"#CCCCCC\">$t</td></tr>\n";
+		     h((string)$s)."</td><td bgcolor=\"#CCCCCC\">$t</td></tr>\n";
 		$i++;
 	}
 	$count = $i - $off;
@@ -286,9 +362,9 @@ function list_logs()
 
 	if ($off > 0) {
 		$noff = $off - $count;
-		if ($noff < 0)	
+		if ($noff < 0)
 			$noff = 0;
-		$hrefurl = "<a href=\"$big_url&off=$noff\">";
+		$hrefurl = "<a href=\"" . h($big_url) . "&off=$noff\">";
 		echo "$hrefurl$backarr</a></td><td align=\"left\">$hrefurl$back</a>";
 	} else {
 		echo "$backarr</td><td align=\"left\">$back";
@@ -296,8 +372,8 @@ function list_logs()
 
 	echo "</td>\n<td align=\"center\">";
 
-	if (isset($dist) && isset($arch)) {
-		echo "[<a href=\"$big_url&action=qa\">"._("View <quot>rpm -qa</quot> of builder")."</a>]";
+	if ($dist !== null && $arch !== null) {
+		echo "[<a href=\"" . h($big_url) . "&action=qa\">"._("View <quot>rpm -qa</quot> of builder")."</a>]";
 	} else {
 		echo " ";
 	}
@@ -305,9 +381,9 @@ function list_logs()
 	echo "</td>\n<td align=right>";
 	if ($cnt == $count) {
 		$noff = $off + $cnt;
-		if ($noff < 0)	
+		if ($noff < 0)
 			$noff = 0;
-		$hrefurl = "<a href=\"$big_url&off=$noff\">";
+		$hrefurl = "<a href=\"" . h($big_url) . "&off=$noff\">";
 		echo "$hrefurl$forward</a></td><td align=right width=1%>$hrefurl$forwardarr</a>";
 	} else {
 		echo "$forward</td><td align=right width=1%>$forwardarr";
@@ -356,50 +432,52 @@ function dump_log($tail)
 
 	$df = preg_replace("/.*\/([^\/]*)$/", "\\1", $f);
 	$df = preg_replace("/\.(bz2|gz)$/", "", $df);
-	list($name, $id) = explode(',', $df);
+	$parts = explode(',', $df, 2);
+	$name = $parts[0];
+	$id = $parts[1] ?? '';
 
+	$name_h = h($name);
 	if ($name != 'command') {
-		$gitweb_url = "http://git.pld-linux.org/?p=packages/$name.git;a=summary";
-		$name = "<a href=\"$gitweb_url\">$name</a>";
+		$gitweb_url = "http://git.pld-linux.org/?p=packages/" . rawurlencode($name) . ";a=summary";
+		$name_h = '<a href="' . h($gitweb_url) . '">' . h($name) . '</a>';
 	}
-	echo "<h1>$name <small>$id</small></h1>";
+	echo "<h1>$name_h <small>" . h($id) . "</small></h1>";
 
 	echo "<table border=\"0\" cellpadding=\"3\" cellspacing=\"1\" bgcolor=\"#000000\" width=\"100%\">";
 
-	function one_item($h, $t) {
-		echo "<tr><td bgcolor=\"#ccccff\">$h:</td>".
-		         "<td bgcolor=\"#cccccc\">$t</td></tr>";
-	}
-
-	function href($h, $c) {
-		return "<a href=\"$h\">$c</a>";
-	}
-
 	one_item(_("Status"), ($ok == 1 ?
 				"<font color=\"green\"><b>"._("OK")."</b></font>" :
 				"<font color=\"red\"><b>"._("Failed")."</b></a>"));
+	$source_url = "https://" . $buildlogs_server . "/pld/" . $f;
 	one_item(_("Source URL"),
-		 href("https://$buildlogs_server/pld/$f",
-		      "https://$buildlogs_server/pld/$f"));
+		 href_tag(h($source_url), h($source_url)));
 
 	$big_url = "$url?dist=$dist&arch=$arch&ok=$ok&ns=$ns&cnt=$cnt";
 	$bu = "$big_url&off=$off";
 
+	$bu_attr = h($bu);
+	$name_url_esc = h((string)$name_url);
+	$id_esc = h(rawurlencode((string)$id));
 	one_item(_("text/plain URL"),
-	         href("$bu&name=$name_url&id=$id&action=text",
+	         href_tag("{$bu_attr}&name={$name_url_esc}&id={$id_esc}&action=text",
 		      _("View!")));
 	if ($tail) {
 		one_item(_("full text"),
-	        	 href("$bu&name=$name_url&id=$id",
+	        	 href_tag("{$bu_attr}&name={$name_url_esc}&id={$id_esc}",
 			      _("View!")));
 	}
 
-	if (isset($dist) && isset($arch)) {
-		one_item(_("rpm -qa of builder"), href("$bu&action=qa", _("View!")));
+	if ($dist !== null && $arch !== null) {
+		one_item(_("rpm -qa of builder"), href_tag("{$bu_attr}&action=qa", _("View!")));
 	} else {
 		one_item(_("rpm -qa of builder"), _("Not available"));
-        }
-	one_item("Date", date("Y/m/d H:i:s", filemtime("$root_directory/$f")));
+	}
+	$full_path = safe_log_path($root_directory, $f);
+	if ($full_path !== null && is_file($full_path)) {
+		one_item("Date", date("Y/m/d H:i:s", filemtime($full_path)));
+	} else {
+		one_item("Date", _("unknown"));
+	}
 	/*
 	echo "<tr><td>Here:</td><td>" .
 		"<a href=\"$url?idx=$idx&ok=$ok&id=$id\">".
@@ -412,10 +490,27 @@ function dump_log($tail)
 	# what can I say beside PHP suxx? how the fuck should I create
 	# bidirectional pipe? gotta use wget
 
+	if ($full_path === null) {
+		echo "<p>"._("Log not found")."</p>";
+		echo "</table>";
+		return;
+	}
 	$filter = get_filter($f);
 
-	$cmd = "$filter '$root_directory/$f'";
-	$fd = popen($cmd, "r");
+	$descriptors = [
+		0 => ['pipe', 'r'],
+		1 => ['pipe', 'w'],
+		2 => ['pipe', 'w'],
+	];
+	$proc = proc_open([$filter, $full_path], $descriptors, $pipes);
+	if (!is_resource($proc)) {
+		echo "<p>"._("Failed to spawn decompressor")."</p>";
+		echo "</table>";
+		return;
+	}
+	fclose($pipes[0]);
+	fclose($pipes[2]);
+	$fd = $pipes[1];
 	$toc = array();
 	$err = array();
 	$first_cut = false;
@@ -429,7 +524,7 @@ function dump_log($tail)
 		$toc_elem = false;
 		$err_elem = false;
 
-		$s = htmlspecialchars($s);
+		$s = h($s);
 		// highlight errors
 		if (preg_match("/(?:fail|error(s|\sCS\d+)?):/i", $s)) {
 			$first_cut = true;
@@ -493,7 +588,8 @@ function dump_log($tail)
 				$err[] = $err_elem;
 		}
 	}
-	pclose($fd);
+	fclose($fd);
+	proc_close($proc);
 
 	// no errors found, no processing found but we are in tail mode
 	if ($tail && !$first_cut_done && $out_buf_size > 100) {
@@ -547,27 +643,45 @@ function dump_text()
 	global $root_directory;
 	global $buildlogs_server;
 
-	header("Content-type: text/plain");
+	header("Content-Type: text/plain; charset=UTF-8");
 
 	$f = file_name();
 	if ($f == false)
 		return;
 
+	$full_path = safe_log_path($root_directory, $f);
+	if ($full_path === null || !is_file($full_path)) {
+		http_response_code(404);
+		echo "# log not found\n";
+		return;
+	}
+
 	echo "# src  : https://$buildlogs_server/pld/$f\n";
-	echo "# date   : " .
-			date("Y/m/d H:i:s", filemtime("$root_directory/$f")) . "\n";
+	echo "# date   : " . date("Y/m/d H:i:s", filemtime($full_path)) . "\n";
 
 	$filter = get_filter($f);
 
-	$cmd = "$filter '$root_directory/$f'";
-	$fd = popen($cmd, "r");
-	fpassthru($fd);
-	pclose($fd);
+	$descriptors = [
+		0 => ['pipe', 'r'],
+		1 => ['pipe', 'w'],
+		2 => ['pipe', 'w'],
+	];
+	$proc = proc_open([$filter, $full_path], $descriptors, $pipes);
+	if (!is_resource($proc)) {
+		http_response_code(500);
+		echo "# failed to spawn decompressor\n";
+		return;
+	}
+	fclose($pipes[0]);
+	fclose($pipes[2]);
+	fpassthru($pipes[1]);
+	fclose($pipes[1]);
+	proc_close($proc);
 }
 
 function list_archs()
 {
-	global $addr, $url, $cnt,$ok,$ns;
+	global $addr, $url, $cnt, $ok, $ns, $dist, $arch;
 
 	if (!isset($cnt))
 		$cnt = 50;
@@ -576,23 +690,24 @@ function list_archs()
 
 	echo "<table width=\"100%\" border=\"0\">\n";
 	echo "<tr><td bgcolor=\"#cccccc\" nowrap=\"nowrap\">"._("Failed")."</td><td bgcolor=\"#cccccc\">"._("Ok")."</td></tr>\n";
-	foreach ($addr as $dist => $ddist) {
+	foreach ($addr as $d => $ddist) {
 		echo "<tr><td colspan=2 nowrap=\"nowrap\"><hr/></td></tr>\n";
-		foreach ($ddist as $arch) {
+		foreach ($ddist as $a) {
+			$da = h($d . '/' . $a);
+			$fail_qs = h("$url?dist=$d&arch=$a&ok=0&cnt=$cnt");
+			$ok_qs   = h("$url?dist=$d&arch=$a&ok=1&cnt=$cnt");
 			echo "<tr><td nowrap=\"nowrap\">".
-				"<a href=\"$url?dist=$dist&arch=$arch&ok=0&cnt=$cnt\">
-				$dist/$arch</a></td><td nowrap=\"nowrap\">".
-				"[<a href=\"$url?dist=$dist&arch=$arch&ok=1&cnt=$cnt\">OK</a>]</td>".
-				#"<td>[<a href=\"$url?idx=$i&action=qa\">qa</a>]</td>".
+				"<a href=\"$fail_qs\">$da</a></td><td nowrap=\"nowrap\">".
+				"[<a href=\"$ok_qs\">OK</a>]</td>".
 				"</tr>\n";
 		}
 	}
 	echo "</table><hr />\n";
 	
 	echo "<div align=\"center\">";
-	echo "<a href=\"$big_url&action=adv_search\">"._("Advanced Search")."</a><br />\n";
-	
-	echo "<a href=\"$url\">main()</a><hr />\n";
+	echo "<a href=\"" . h($big_url) . "&action=adv_search\">"._("Advanced Search")."</a><br />\n";
+
+	echo "<a href=\"" . h($url) . "\">main()</a><hr />\n";
 	echo "<a href=\"http://www.pld-linux.org/\"><img src=\"powpld.png\" ".
 	     "alt=\""._("Powered by PLD Linux")."\" border=\"0\" /></a><br />\n" .
 	     "<small>(c) 2002-". date("Y") . " ". 
@@ -632,13 +747,13 @@ function list_archs()
 	echo "</small></div>";
 
 	echo "<div align=\"center\"><small>";
-		echo "Your IP: " . $_SERVER['REMOTE_ADDR'];
+		echo "Your IP: " . h($_SERVER['REMOTE_ADDR'] ?? '');
 	echo "</small></div>";
 
-	if (isset($dist) && isset($arch)) {
+	if ($dist !== null && $arch !== null) {
 	echo "<form action=\"index.php\" method=\"post\">";
-	echo "<input type=\"hidden\" name=\"dist\" value=\"$dist\" />";
-	echo "<input type=\"hidden\" name=\"arch\" value=\"$arch\" />";
+	echo "<input type=\"hidden\" name=\"dist\" value=\"" . h($dist) . "\" />";
+	echo "<input type=\"hidden\" name=\"arch\" value=\"" . h($arch) . "\" />";
 	echo "<input type=\"hidden\" name=\"action\" value=\"sqa\" />";
 	echo "<input type=\"text\" size=\"14\" name=\"str\" /><br />";
 	echo "<input type=\"submit\" name=\"submit\" value=\""._("Search rpmqa!")."\" />";
@@ -648,12 +763,22 @@ function list_archs()
 
 function get_qa()
 {
-    global $dist, $arch;
+	global $dist, $arch;
 
-	if (!isset($dist) || !isset($arch))
+	if ($dist === null || $arch === null) {
 		return false;
-	$addr = "http://ftp1.pld-linux.org/dists/$dist/.stat/builder/$dist/rpmqa-$arch.txt";
-    return fopen("$addr", "r");
+	}
+	$addr = "https://ftp1.pld-linux.org/dists/" . rawurlencode($dist)
+		. "/.stat/builder/" . rawurlencode($dist)
+		. "/rpmqa-" . rawurlencode($arch) . ".txt";
+	$ctx = stream_context_create([
+		'http' => [
+			'method' => 'GET',
+			'timeout' => 30,
+			'ignore_errors' => false,
+		],
+	]);
+	return @fopen($addr, 'r', false, $ctx);
 }
 
 function search_qa()
@@ -661,23 +786,26 @@ function search_qa()
 	global $url, $str, $dist, $arch;
 
 	$f = get_qa();
-	echo "<h1>"._("Search results for")." '$str' "._("in")." $dist/$arch</h1>";
+	echo "<h1>"._("Search results for")." '" . h($str) . "' "._("in")." "
+		. h($dist . '/' . $arch) . "</h1>";
 
 	start_pre();
 
-	if ($f == 0) {
-	 	echo _("Sorry, cannot open.");
+	if ($f === false) {
+		echo h(_("Sorry, cannot open."));
 	} else {
-		while (($s = fgets($f, 1000)) != false) {
-			if (stristr($s, "Query done at:")) {
-				echo "rpmqa database from " . strstr($s, ":") . "\n";
+		while (($s = fgets($f, 1000)) !== false) {
+			if (stristr($s, "Query done at:") !== false) {
+				echo "rpmqa database from " . h(strstr($s, ":")) . "\n";
 				continue;
 			}
 
-			if (stristr($s, $str))
-				echo $s;
+			if ($str !== '' && stristr($s, $str) !== false) {
+				echo h($s);
+			}
 		}
 		echo "\n/* EOF */";
+		fclose($f);
 	}
 	end_pre();
 }
@@ -687,22 +815,29 @@ function dump_qa($plain)
 	global $url, $dist, $arch;
 
 	$f = get_qa();
+	$da = h($dist . '/' . $arch);
 
 	if ($plain) {
-		header("Content-type: text/plain");
+		header("Content-Type: text/plain; charset=UTF-8");
 		echo _("# rpm -qa of")." $dist/$arch\n";
 	} else {
-		echo "<h1>"._("rpm -qa of")." $a</h1>";
-		echo "<a href=\"$url?dist=$dist&arch=$arch&action=qatxt\">"._("text/plain version")."</a>";
+		echo "<h1>"._("rpm -qa of")." $da</h1>";
+		$qa_url = h($url . '?' . http_build_query([
+			'dist'   => $dist,
+			'arch'   => $arch,
+			'action' => 'qatxt',
+		]));
+		echo "<a href=\"$qa_url\">"._("text/plain version")."</a>";
 		start_pre();
 	}
 
-	if ($f == 0) {
-	 	echo _("Sorry, cannot open.");
+	if ($f === false) {
+		echo h(_("Sorry, cannot open."));
 	} else {
-		while (($s = fgets($f, 1000)) != false) {
-			echo $s;
+		while (($s = fgets($f, 1000)) !== false) {
+			echo $plain ? $s : h($s);
 		}
+		fclose($f);
 	}
 
 	if (!$plain)
@@ -724,12 +859,11 @@ function adv_search()
        "e.checked = document.forms[0].all.checked;\n".
        "}\n }\n -->\n </script>\n";
 
-/* Shut up warnings */
-  if (!isset($_POST["n2"])) $_POST["n2"] = "";
-  if (!isset($_POST["age1"])) $_POST["age1"] = "";
-  if (!isset($_POST["age2"])) $_POST["age2"] = "";
-  if (!isset($_POST["size1"])) $_POST["size1"] = "";
-  if (!isset($_POST["size2"])) $_POST["size2"] = "";
+  $p_n2    = (string)($_POST["n2"]    ?? '');
+  $p_age1  = (string)($_POST["age1"]  ?? '');
+  $p_age2  = (string)($_POST["age2"]  ?? '');
+  $p_size1 = (string)($_POST["size1"] ?? '');
+  $p_size2 = (string)($_POST["size2"] ?? '');
 
   echo "<form action=\"index.php?action=adv_search\" method=\"post\">";
 
@@ -737,19 +871,19 @@ function adv_search()
   echo "<table border=\"0\">\n";
   echo "<tr>\n";
   echo "<td>"._("Package name")."</td>\n";
-  echo "<td><input type=\"text\" size=\"20\" name=\"n2\" value=\"". $_POST["name"] ."\"/></td>\n";
+  echo "<td><input type=\"text\" size=\"20\" name=\"n2\" value=\"" . h($p_n2) . "\"/></td>\n";
   echo "</tr>\n";
 
   echo "<tr>\n";
   echo "<td>"._("Days")."</td>\n";
-  echo "<td>"._("From").": <input type=\"text\" size=\"20\" name=\"age1\" value=\"". $_POST["age1"] ."\" /></td>\n";
-  echo "<td>"._("To").": <input type=\"text\" size=\"20\" name=\"age2\" value=\"". $_POST["age2"] ."\" /></td>\n";
+  echo "<td>"._("From").": <input type=\"text\" size=\"20\" name=\"age1\" value=\"" . h($p_age1) . "\" /></td>\n";
+  echo "<td>"._("To").": <input type=\"text\" size=\"20\" name=\"age2\" value=\"" . h($p_age2) . "\" /></td>\n";
   echo "</tr>\n";
 
   echo "<tr>\n";
   echo "<td>"._("Size")."</td>\n";
-  echo "<td>"._("From").": <input type=\"text\" size=\"20\" name=\"size1\" value=\"". $_POST["size1"] ."\" /></td>\n";
-  echo "<td>"._("To").": <input type=\"text\" size=\"20\" name=\"size2\" value=\"". $_POST["size2"] ."\" /></td>\n";
+  echo "<td>"._("From").": <input type=\"text\" size=\"20\" name=\"size1\" value=\"" . h($p_size1) . "\" /></td>\n";
+  echo "<td>"._("To").": <input type=\"text\" size=\"20\" name=\"size2\" value=\"" . h($p_size2) . "\" /></td>\n";
   echo "</tr>\n";
 
   echo "<tr>\n";
@@ -762,23 +896,16 @@ function adv_search()
   echo "</tr>\n";
 
   $i = 1;
-  foreach ($addr as $dist => $ddist) {
-    foreach ($ddist as $arch) {
+  foreach ($addr as $d => $ddist) {
+    foreach ($ddist as $a) {
     echo "<tr>\n";
-    $name="as0_".$i;
-    if (!isset($_POST["$name"])) {
-    	$check = " ";
-    } else {
-    	$check=" checked='checked'";
-    }
-    echo "<td><input name=\"$name\" id=\"$name\" type=\"checkbox\"$check /><label for=\"$name\">". "$dist/$arch" ."</label></td>\n";
-    $name="as1_".$i;
-    if (!isset($_POST["$n2"])) {
-    	$check = " ";
-    } else {
-    	$check=" checked='checked'";
-    }
-    echo "<td><input name=\"$name\" id=\"$name\" type=\"checkbox\"$check /><label for=\"$name\">". "$dist/$arch" ."</label></td>\n";
+    $label = h($d . '/' . $a);
+    $name = "as0_" . $i;
+    $check = isset($_POST[$name]) ? " checked='checked'" : " ";
+    echo "<td><input name=\"$name\" id=\"$name\" type=\"checkbox\"$check /><label for=\"$name\">$label</label></td>\n";
+    $name = "as1_" . $i;
+    $check = isset($_POST[$name]) ? " checked='checked'" : " ";
+    echo "<td><input name=\"$name\" id=\"$name\" type=\"checkbox\"$check /><label for=\"$name\">$label</label></td>\n";
     echo "</tr>\n";
     $i++;
     }
@@ -790,129 +917,146 @@ function adv_search()
 
   echo "</table>\n";
 
-//	if (isset($_POST["name"]) || isset($_POST["age1"]) || isset($_POST["age2"]) ||
-//	  isset($_POST["size1"]) || isset($_POST["size2"])
-  if (($_POST["n2"]!="") || ($_POST["age1"]!="") || ($_POST["age2"]!="") ||
-    ($_POST["size1"]!="") || ($_POST["size2"]!=""))
+  if ($p_n2 !== '' || $p_age1 !== '' || $p_age2 !== '' || $p_size1 !== '' || $p_size2 !== '')
   {
-	$query = "SELECT log_id, dist, arch, ok, name, size, mtime, id FROM logs WHERE 1 ";
-	if ($_POST["n2"] != "") {
-		$n = addslashes($_POST["n2"]);
-		$query .= "AND name LIKE '$n%' ";
+	$conditions = ['1=1'];
+	$params = [];
+	if ($p_n2 !== '') {
+		$conditions[] = "name LIKE :name_prefix ESCAPE '\\'";
+		$params[':name_prefix'] = like_escape($p_n2) . '%';
 	}
 	$now = time();
-
-	if ($_POST["age1"] != "") {
-		$age = $now - (int)$_POST["age1"] * 24 * 3600;
-		$query .= "AND mtime > $age ";
+	if ($p_age1 !== '') {
+		$conditions[] = 'mtime > :age1';
+		$params[':age1'] = $now - ((int)$p_age1) * 24 * 3600;
 	}
-
-	if ($_POST["age2"] != "") {
-		$age = $now - (int)$_POST["age2"] * 24 * 3600;
-		$query .= "AND mtime < $age ";
+	if ($p_age2 !== '') {
+		$conditions[] = 'mtime < :age2';
+		$params[':age2'] = $now - ((int)$p_age2) * 24 * 3600;
 	}
-
-	if ($_POST["size1"] != "") {
-		$size = (int)$_POST["size1"];
-		$query .= "AND size > $size ";
+	if ($p_size1 !== '') {
+		$conditions[] = 'size > :size1';
+		$params[':size1'] = (int)$p_size1;
 	}
-
-	if ($_POST["size2"] != "") {
-		$size = (int)$_POST["size2"];
-		$query .= "AND size < $size ";
+	if ($p_size2 !== '') {
+		$conditions[] = 'size < :size2';
+		$params[':size2'] = (int)$p_size2;
 	}
 
-	$or = "AND (";
-  $i = 1;
-  foreach ($addr as $dist => $ddist) {
-    foreach ($ddist as $arch) {
-      for ($j = 0; $j < 2; $j++) {
-			  if (isset($_POST["as" . $j . "_" .$i])) {
-				$query .= "$or (dist = '$dist' AND arch = '$arch' AND ok = $j)";
-				$or = " OR ";
+	$dao_clauses = [];
+	$ii = 1;
+	foreach ($addr as $d => $ddist) {
+		foreach ($ddist as $a) {
+			for ($j = 0; $j < 2; $j++) {
+				if (isset($_POST["as" . $j . "_" . $ii])) {
+					$pd = ":d{$ii}_{$j}";
+					$pa = ":a{$ii}_{$j}";
+					$dao_clauses[] = "(dist = $pd AND arch = $pa AND ok = $j)";
+					$params[$pd] = $d;
+					$params[$pa] = $a;
+				}
 			}
+			$ii++;
 		}
-    $i++;
-    }
 	}
-	if ($or == " OR ") $query .= ")";
-//	if (!isset($cnt)) $cnt = 50;
-//	if (!isset($off)) $off = 0;
-	if (!isset($ns)) $ns = 0;
-	switch ($ns) {
-		case 0:
-			$query .= " ORDER BY mtime DESC";
-			break;
-		case 1:
-			$query .= " ORDER BY name";
-			break;
-		case 2:
-			$query .= " ORDER BY dist, arch, name";
-			break;
-	}
-	$query .= " LIMIT $cnt OFFSET $off ";
+	if ($dao_clauses) {
+		$conditions[] = '(' . implode(' OR ', $dao_clauses) . ')';
+	}
+
+	$order_whitelist = [
+		0 => 'mtime DESC',
+		1 => 'name',
+		2 => 'dist, arch, name',
+	];
+	$ns_int = is_int($ns) ? $ns : (int)$ns;
+	$order = $order_whitelist[$ns_int] ?? 'mtime DESC';
+
+	$sql = 'SELECT log_id, dist, arch, ok, name, size, mtime, id FROM logs WHERE '
+		. implode(' AND ', $conditions)
+		. ' ORDER BY ' . $order
+		. ' LIMIT :limitnr OFFSET :offset';
 
 	try {
-		$dbh = new PDO("$database");
+		$dbh = new PDO($database);
+		$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+		$dbh->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
 	} catch (PDOException $e) {
 		mydie("new PDO: " . $e->getMessage());
 	}
-	$result = $dbh->query("$query")->fetchAll();
+	$stmt = $dbh->prepare($sql);
+	foreach ($params as $k => $v) {
+		$stmt->bindValue($k, $v);
+	}
+	$stmt->bindValue(':limitnr', $cnt, PDO::PARAM_INT);
+	$stmt->bindValue(':offset', $off, PDO::PARAM_INT);
+	$stmt->execute();
+	$result = $stmt->fetchAll();
 
-	if ($result == FALSE) {
-		echo _("Nothing found");
+	if (empty($result)) {
+		echo h(_("Nothing found"));
 	} else {
+		$big_url_esc = h($big_url);
 		echo "<table border=\"0\" cellspacing=\"1\" ".
 			"cellpadding=3 bgcolor=\"#000000\" width=\"90%\">\n";
 		echo "<tr><th bgcolor=\"#CCCCFF\" align=\"left\" width=\"10%\">"._("Builder").
-	     		"[<a href=\"$big_url&ns=2\">"._("sort")."</a>]</th>";
+			"[<a href=\"$big_url_esc&ns=2\">"._("sort")."</a>]</th>";
 		echo "<th bgcolor=\"#CCCCFF\" align=\"left\" width=\"60%\">"._("Log File").
-			"[<a href=\"$big_url&ns=1\">"._("sort")."</a>]</th>".
-	         	"<th bgcolor=\"#CCCCFF\" align=\"right\" width=\"15%\">"._("Size")."</th> ".
-			 "<th bgcolor=\"#CCCCFF\" align=\"left\">"._("Age").
-			 "[<a href=\"$big_url&ns=0\">"._("sort")."</a>]</th>".
-			 "</th></tr>";
+			"[<a href=\"$big_url_esc&ns=1\">"._("sort")."</a>]</th>".
+			"<th bgcolor=\"#CCCCFF\" align=\"right\" width=\"15%\">"._("Size")."</th> ".
+			"<th bgcolor=\"#CCCCFF\" align=\"left\">"._("Age").
+			"[<a href=\"$big_url_esc&ns=0\">"._("sort")."</a>]</th>".
+			"</th></tr>";
 		$i = $off;
-//		for ($i = $off; $i < $off + $count; $i++) {
 		foreach ($result as $row) {
-      $dist = $row["dist"];
-      $arch = $row["arch"];
-      $name = $row["name"];
-      $name_url = urlencode($name);
-      $id = $row["id"];
-			$f = $name;
-			$t = $now - $row["mtime"];
-			$s = $row["size"];
-			$t /= 60;
+			$r_dist = (string)$row["dist"];
+			$r_arch = (string)$row["arch"];
+			$r_name = (string)$row["name"];
+			$r_id   = (string)$row["id"];
+			$r_ok   = (int)$row["ok"];
+			$r_size = (int)$row["size"];
+			$r_mtime = (int)$row["mtime"];
+
+			$t = ($now - $r_mtime) / 60;
 			if ($t >= 60) {
 				$t /= 60;
 				if ($t >= 24) {
 					$t /= 24;
 					$t = round($t);
-					$t = $t . " " . ngettext("day","days",$t);
+					$t_str = $t . " " . ngettext("day","days",(int)$t);
 				} else {
 					$t = round($t);
-					$t = $t . " " . ngettext("hour","hours",$t);
+					$t_str = $t . " " . ngettext("hour","hours",(int)$t);
 				}
 			} else {
 				$t = round($t);
-				$t = $t . " " . ngettext("minute","minutes",$t);
+				$t_str = $t . " " . ngettext("minute","minutes",(int)$t);
 			}
-			
-//                $big_url = "$url?idx=$i&ok=$j&ns=$ns&cnt=$cnt";
-      $ok = $row["ok"];
-			$u = "$url?dist=$dist&arch=$arch&name=$name_url&ok=$ok&id=$id";
-			$b = "$url?dist=$dist&arch=$arch&ok=$ok&ns=$ns&off=$off&cnt=$cnt";
 
-			$builder = "$dist/$arch/". $fail_or_ok[$ok];
+			$u = h($url . '?' . http_build_query([
+				'dist' => $r_dist,
+				'arch' => $r_arch,
+				'name' => $r_name,
+				'ok'   => $r_ok,
+				'id'   => $r_id,
+			]));
+			$b = h($url . '?' . http_build_query([
+				'dist' => $r_dist,
+				'arch' => $r_arch,
+				'ok'   => $r_ok,
+				'ns'   => is_int($ns) ? $ns : 0,
+				'off'  => $off,
+				'cnt'  => $cnt,
+			]));
+			$builder = h($r_dist . '/' . $r_arch . '/' . $fail_or_ok[$r_ok]);
+
 			echo "<tr>";
 			echo "<td bgcolor=\"#CCCCCC\"><a href=\"$b\">$builder</a></td>";
-			echo "<td bgcolor=\"#CCCCCC\"><a href=\"$u\">$f</a> ".
-		     	"[<a href=\"$u&action=text\">"._("text")."</a> | ".
-		      	"<a href=\"$u&action=tail\">"._("tail")."</a>]".
-		     	"</td><td bgcolor=\"#CCCCCC\" align=\"right\">".
-		     	"$s</td><td bgcolor=\"#CCCCCC\">$t</td></tr>\n";
-		     	$i++;
+			echo "<td bgcolor=\"#CCCCCC\"><a href=\"$u\">" . h($r_name) . "</a> ".
+				"[<a href=\"$u&action=text\">"._("text")."</a> | ".
+				"<a href=\"$u&action=tail\">"._("tail")."</a>]".
+				"</td><td bgcolor=\"#CCCCCC\" align=\"right\">".
+				h((string)$r_size)."</td><td bgcolor=\"#CCCCCC\">$t_str</td></tr>\n";
+			$i++;
 		}
 		echo "</table></div>\n";
 
diff --git a/pld-buildlogs/scripts/addlog.php b/pld-buildlogs/scripts/addlog.php
index 24dd189..8fc4488 100644
--- a/pld-buildlogs/scripts/addlog.php
+++ b/pld-buildlogs/scripts/addlog.php
@@ -1,55 +1,97 @@
 #!/usr/bin/php.cli
 <?php
-// $Revision: 1.3 $, $Date: 2009/01/20 07:37:40 $
-/*
-$database = 'sqlite:/home/services/httpd/html/pld-buildlogs/db/buildlogs2.db';
-$root_directory = '/home/services/ftp/pub/pld-buildlogs';
-// $database and $root_directory are taken from buildlogs.inc .
-// parameter: argv[1] - full path to the log file.
-*/
-
-$result = array("FAIL" => 0, "OK" => 1);
+declare(strict_types=1);
+
+$result = ["FAIL" => 0, "OK" => 1];
 include('buildlogs.inc');
 
 if (!isset($argv[1])) {
-	die("Usage: $argv[0] full_path_to_the_log\n");
+    fwrite(STDERR, "Usage: {$argv[0]} full_path_to_the_log\n");
+    exit(1);
+}
+
+$path = $argv[1];
+
+if (!preg_match("|^" . preg_quote($root_directory, '|')
+        . "/([^/]+)/([^/]+)/([^/]+)/(.+)\\.bz2$|", $path, $matches)) {
+    exit(0);
 }
-if (!preg_match("|^$root_directory/(.*)/(.*)/(.*)/(.*)\.bz2$|", $argv[1], $matches))
-	exit(0);
 
-if (preg_match("/^(.*),(.*)$/", $matches[4], $m2)) {
-    $name = $m2[1];
-    $id = $m2[2];
+$dist_raw = $matches[1];
+$arch_raw = $matches[2];
+$status   = $matches[3];
+$basename = $matches[4];
+
+if (preg_match('/^(.*),(.*)$/', $basename, $m2)) {
+    $name_raw = $m2[1];
+    $id_raw = $m2[2];
 } else {
-    $name = $matches[4];
-    $id = "";
+    $name_raw = $basename;
+    $id_raw = '';
 }
-$dist = $matches[1];
-$arch = $matches[2];
-$ok = $result[$matches[3]];
-$size = filesize($argv[1]);
-$mtime = filemtime($argv[1]);
 
-#print "name $name, id $id, dist $dist, arch $arch, ok $ok, size, $size, mtime $mtime\n";
+$identifier_re = '/^[A-Za-z0-9][A-Za-z0-9_.+-]*$/';
+if (!preg_match($identifier_re, $dist_raw)
+    || !preg_match($identifier_re, $arch_raw)
+    || !preg_match($identifier_re, $name_raw)
+    || ($id_raw !== '' && !preg_match($identifier_re, $id_raw))
+    || !isset($result[$status])) {
+    fwrite(STDERR, "skip: invalid identifier in {$path}\n");
+    exit(0);
+}
+
+$ok = $result[$status];
+$size = filesize($path);
+$mtime = filemtime($path);
+if ($size === false || $mtime === false) {
+    fwrite(STDERR, "stat failed: {$path}\n");
+    exit(1);
+}
 
 try {
-	$dbh = new PDO("$database");
+    $dbh = new PDO($database);
+    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+    $dbh->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
 } catch (PDOException $e) {
-	die ($e->getMessage());
+    fwrite(STDERR, "PDO connect: " . $e->getMessage() . "\n");
+    exit(1);
 }
-$result = $dbh->query("SELECT log_id FROM logs WHERE dist = '$dist' AND arch = '$arch' AND name = '$name'"
-." AND id = '$id' LIMIT 1")->fetchAll();
-if (count($result) == 1) {
-	foreach ($result as $row) {
-		$query = "UPDATE logs SET ok = $ok, size = $size, mtime = $mtime WHERE log_id = $row[log_id]";
-		break;
-	}
+
+$sel = $dbh->prepare(
+    'SELECT log_id FROM logs WHERE dist = :dist AND arch = :arch '
+    . 'AND name = :name AND id = :id LIMIT 1'
+);
+$sel->execute([
+    ':dist' => $dist_raw,
+    ':arch' => $arch_raw,
+    ':name' => $name_raw,
+    ':id'   => $id_raw,
+]);
+$row = $sel->fetch();
+
+if ($row !== false) {
+    $upd = $dbh->prepare(
+        'UPDATE logs SET ok = :ok, size = :size, mtime = :mtime '
+        . 'WHERE log_id = :log_id'
+    );
+    $upd->execute([
+        ':ok'     => $ok,
+        ':size'   => $size,
+        ':mtime'  => $mtime,
+        ':log_id' => $row['log_id'],
+    ]);
 } else {
-	$query = "INSERT INTO logs(dist, arch, ok, name, size, mtime, id) "
-	. "VALUES('$dist', '$arch', $ok, '$name', $size, $mtime, '$id')";
-}
-$ile = $dbh->exec("$query");
-if ($ile != 1) {
-	print_r($dbh->errorInfo());
+    $ins = $dbh->prepare(
+        'INSERT INTO logs(dist, arch, ok, name, size, mtime, id) '
+        . 'VALUES(:dist, :arch, :ok, :name, :size, :mtime, :id)'
+    );
+    $ins->execute([
+        ':dist'  => $dist_raw,
+        ':arch'  => $arch_raw,
+        ':ok'    => $ok,
+        ':name'  => $name_raw,
+        ':size'  => $size,
+        ':mtime' => $mtime,
+        ':id'    => $id_raw,
+    ]);
 }
-?>
diff --git a/pld-buildlogs/scripts/migration.php b/pld-buildlogs/scripts/migration.php
index 20faa00..b2deedb 100644
--- a/pld-buildlogs/scripts/migration.php
+++ b/pld-buildlogs/scripts/migration.php
@@ -1,68 +1,107 @@
 #!/usr/bin/php.cli
 <?php
-// $Revision: 1.3 $, $Date: 2009/01/20 07:49:34 $
-/*
-$root_directory = '/home/services/ftp/pub/pld-buildlogs';
-$database = 'sqlite:/home/services/httpd/html/pld-buildlogs/db/buildlogs2.db';
-// $root_directory and $database are taken from buildlogs.inc
-*/
+declare(strict_types=1);
+
 include('buildlogs.inc');
 
 if (file_exists($database_file)) {
-	unlink($database_file);
+    unlink($database_file);
 }
 
-$query = " CREATE TABLE LOGS(log_id INTEGER PRIMARY KEY, dist TEXT, arch TEXT, ok INTEGER, name TEXT, "
-	. "size INTEGER, mtime INTEGER, id TEXT DEFAULT ''); CREATE INDEX IF NOT EXISTS dao_index ON LOGS(dist, arch, ok);";
 try {
-	$dbhandle = new PDO("$database");
+    $dbh = new PDO($database);
+    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+    $dbh->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
 } catch (PDOException $e) {
-	die("new PDO: ". $e->getMessage());
+    fwrite(STDERR, "PDO connect: " . $e->getMessage() . "\n");
+    exit(1);
 }
 
-$result = array("FAIL", "OK");
+$dbh->query(
+    "CREATE TABLE logs(log_id INTEGER PRIMARY KEY, "
+    . "dist TEXT, arch TEXT, ok INTEGER, name TEXT, "
+    . "size INTEGER, mtime INTEGER, id TEXT DEFAULT '')"
+);
+$dbh->query("CREATE INDEX IF NOT EXISTS dao_index ON logs(dist, arch, ok)");
+
+$ins = $dbh->prepare(
+    'INSERT INTO logs(dist, arch, ok, name, size, mtime, id) '
+    . 'VALUES(:dist, :arch, :ok, :name, :size, :mtime, :id)'
+);
+
+$identifier_re = '/^[A-Za-z0-9][A-Za-z0-9_.+-]*$/';
+$result = ["FAIL", "OK"];
+
+$dbh->beginTransaction();
+$inserted = 0;
+$skipped = 0;
 
 $dh = opendir($root_directory);
-if (!$dh) {
-	die("opendir $root_directory");
+if ($dh === false) {
+    fwrite(STDERR, "opendir $root_directory failed\n");
+    exit(1);
 }
-while ($dist = readdir($dh)) {
-	if ($dist[0] == '.') continue;
-	if (!is_dir("$root_directory/$dist")) continue;
-	$ah = opendir("$root_directory/$dist");
-	if (!$ah) die("opendir $dist");
-	while ($arch = readdir($ah)) {
-		if ($arch[0] == '.') continue;
-		if (!is_dir("$root_directory/$dist/$arch")) continue;
-		for ($ok = 0; $ok < 2; $ok++) {
-			$directory = "$root_directory/$dist/$arch/" . $result[$ok];
-			$sh = opendir($directory);
-			if (!$sh) continue;
-			while ($file = readdir($sh)) {
-				if (preg_match("/^(.*)\.bz2$/", $file, $match)) {
-					if (preg_match("/(.*),(.*)/", $match[1], $m2)) {
-						$name = $m2[1];
-						$id = $m2[2];
-					} else {
-						$name = $match[1];
-						$id = "";
-					}
-					$f = "$directory/" . $match[0];
-					$size = filesize($f);
-					$mtime = filemtime($f);
-$query .= " INSERT INTO logs(dist, arch, ok, name, size, mtime, id) "
-. "VALUES('$dist', '$arch', $ok, '$name', $size, $mtime, '$id');";
-				}
-				
-			}
-			closedir($sh);
-		}
-	}
-	closedir($ah);
+while (($dist = readdir($dh)) !== false) {
+    if ($dist[0] === '.') continue;
+    if (!is_dir("$root_directory/$dist")) continue;
+    if (!preg_match($identifier_re, $dist)) {
+        $skipped++;
+        continue;
+    }
+    $ah = opendir("$root_directory/$dist");
+    if ($ah === false) {
+        fwrite(STDERR, "opendir $dist failed\n");
+        continue;
+    }
+    while (($arch = readdir($ah)) !== false) {
+        if ($arch[0] === '.') continue;
+        if (!is_dir("$root_directory/$dist/$arch")) continue;
+        if (!preg_match($identifier_re, $arch)) {
+            $skipped++;
+            continue;
+        }
+        for ($ok = 0; $ok < 2; $ok++) {
+            $directory = "$root_directory/$dist/$arch/" . $result[$ok];
+            $sh = @opendir($directory);
+            if ($sh === false) continue;
+            while (($file = readdir($sh)) !== false) {
+                if (!preg_match('/^(.*)\.bz2$/', $file, $match)) continue;
+                if (preg_match('/^(.*),(.*)$/', $match[1], $m2)) {
+                    $name = $m2[1];
+                    $id = $m2[2];
+                } else {
+                    $name = $match[1];
+                    $id = '';
+                }
+                if (!preg_match($identifier_re, $name)
+                    || ($id !== '' && !preg_match($identifier_re, $id))) {
+                    $skipped++;
+                    continue;
+                }
+                $f = "$directory/" . $match[0];
+                $size = filesize($f);
+                $mtime = filemtime($f);
+                if ($size === false || $mtime === false) {
+                    $skipped++;
+                    continue;
+                }
+                $ins->execute([
+                    ':dist'  => $dist,
+                    ':arch'  => $arch,
+                    ':ok'    => $ok,
+                    ':name'  => $name,
+                    ':size'  => $size,
+                    ':mtime' => $mtime,
+                    ':id'    => $id,
+                ]);
+                $inserted++;
+            }
+            closedir($sh);
+        }
+    }
+    closedir($ah);
 }
 closedir($dh);
 
-$dbhandle->beginTransaction();
-$dbhandle->exec("$query");
-$dbhandle->commit();
-?>
+$dbh->commit();
+fwrite(STDERR, "done: inserted $inserted, skipped $skipped invalid entries\n");
================================================================

---- gitweb:

http://git.pld-linux.org/gitweb.cgi/projects/buildlogs.git/commitdiff/166f87ba777bdf92412dccf4b6e825ff1e0b4f99



More information about the pld-cvs-commit mailing list