[aur-dev] [PATCH v2 00/10] Use Git repositories to store AUR packages
This is a reroll of the series I submitted in June. I would like to merge this into master soon and start testing stuff, so any reviews are highly appreciated. Lukas Fleischer (10): Use raw scanner mode in config_get() Add support for adding SSH public keys to profiles Add basic Git authentication/authorization scripts Add update hook template Use Git repositories to store packages Add public clone URLs to package details Add sshd setup instructions Honor the database socket setting in Git scripts git-update.py: Update repository description Remove legacy scripts INSTALL | 108 +- conf/config.proto | 18 +- schema/aur-schema.sql | 1 + scripts/cleanup | 46 - .../0001-Patch-sshd-for-the-AUR.patch | 152 ++ scripts/git-integration/aurinfo.py | 204 ++ scripts/git-integration/git-auth.py | 42 + scripts/git-integration/git-serve.py | 108 ++ scripts/git-integration/git-update.py | 217 +++ scripts/git-integration/sshd_config | 6 + scripts/uploadbuckets.sh | 58 - upgrading/4.0.0.txt | 5 + web/html/account.php | 5 +- web/html/pkgsubmit.php | 489 ----- web/lib/Archive/PEAR.php | 1063 ----------- web/lib/Archive/PEAR5.php | 33 - web/lib/Archive/Tar.php | 1993 -------------------- web/lib/acctfuncs.inc.php | 78 +- web/lib/aurjson.class.php | 1 - web/lib/confparser.inc.php | 2 +- web/lib/pkgbasefuncs.inc.php | 39 - web/lib/pkgfuncs.inc.php | 31 +- web/lib/routing.inc.php | 1 - web/template/account_edit_form.php | 5 + web/template/header.php | 1 - web/template/pkg_details.php | 15 +- web/template/pkgbase_details.php | 15 +- 27 files changed, 877 insertions(+), 3859 deletions(-) delete mode 100755 scripts/cleanup create mode 100644 scripts/git-integration/0001-Patch-sshd-for-the-AUR.patch create mode 100644 scripts/git-integration/aurinfo.py create mode 100755 scripts/git-integration/git-auth.py create mode 100755 scripts/git-integration/git-serve.py create mode 100755 scripts/git-integration/git-update.py create mode 100644 scripts/git-integration/sshd_config delete mode 100755 scripts/uploadbuckets.sh create mode 100644 upgrading/4.0.0.txt delete mode 100644 web/html/pkgsubmit.php delete mode 100644 web/lib/Archive/PEAR.php delete mode 100644 web/lib/Archive/PEAR5.php delete mode 100644 web/lib/Archive/Tar.php -- 2.2.1
We do not want PHP to parse the values for us. Use raw scanner mode to avoid issues with certain values, such as regular expressions. Signed-off-by: Lukas Fleischer <archlinux@cryptocrack.de> --- web/lib/confparser.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/lib/confparser.inc.php b/web/lib/confparser.inc.php index 41ee581..3977911 100644 --- a/web/lib/confparser.inc.php +++ b/web/lib/confparser.inc.php @@ -4,7 +4,7 @@ function config_get($section, $key) { global $AUR_CONFIG; if (!isset($AUR_CONFIG)) { - $AUR_CONFIG = parse_ini_file("../../conf/config", true); + $AUR_CONFIG = parse_ini_file("../../conf/config", true, INI_SCANNER_RAW); } return $AUR_CONFIG[$section][$key]; -- 2.2.1
Users can now add an SSH public key on the account edit page. This will later be used to authenticate users via SSH. Signed-off-by: Lukas Fleischer <archlinux@cryptocrack.de> --- schema/aur-schema.sql | 1 + upgrading/4.0.0.txt | 5 +++ web/html/account.php | 5 ++- web/lib/acctfuncs.inc.php | 78 ++++++++++++++++++++++++++++++++++---- web/template/account_edit_form.php | 5 +++ 5 files changed, 85 insertions(+), 9 deletions(-) create mode 100644 upgrading/4.0.0.txt diff --git a/schema/aur-schema.sql b/schema/aur-schema.sql index 9c57683..dfd158f 100644 --- a/schema/aur-schema.sql +++ b/schema/aur-schema.sql @@ -33,6 +33,7 @@ CREATE TABLE Users ( LangPreference VARCHAR(5) NOT NULL DEFAULT 'en', IRCNick VARCHAR(32) NOT NULL DEFAULT '', PGPKey VARCHAR(40) NULL DEFAULT NULL, + SSHPubKey VARCHAR(4096) NULL DEFAULT NULL, LastLogin BIGINT UNSIGNED NOT NULL DEFAULT 0, LastLoginIPAddress INTEGER UNSIGNED NOT NULL DEFAULT 0, InactivityTS BIGINT UNSIGNED NOT NULL DEFAULT 0, diff --git a/upgrading/4.0.0.txt b/upgrading/4.0.0.txt new file mode 100644 index 0000000..543fbac --- /dev/null +++ b/upgrading/4.0.0.txt @@ -0,0 +1,5 @@ +1. Add a field for the SSH public key to the Users table: + +---- +ALTER TABLE Users ADD COLUMN SSHPubKey VARCHAR(4096) NULL DEFAULT NULL; +---- diff --git a/web/html/account.php b/web/html/account.php index c1a1cd7..3dc8ef0 100644 --- a/web/html/account.php +++ b/web/html/account.php @@ -59,7 +59,7 @@ if (isset($_COOKIE["AURSID"])) { display_account_form("UpdateAccount", $row["Username"], $row["AccountTypeID"], $row["Suspended"], $row["Email"], "", "", $row["RealName"], $row["LangPreference"], - $row["IRCNick"], $row["PGPKey"], + $row["IRCNick"], $row["PGPKey"], $row["SSHPubKey"], $row["InactivityTS"] ? 1 : 0, $row["ID"]); } else { print __("You do not have permission to edit this account."); @@ -98,7 +98,8 @@ if (isset($_COOKIE["AURSID"])) { in_request("U"), in_request("T"), in_request("S"), in_request("E"), in_request("P"), in_request("C"), in_request("R"), in_request("L"), in_request("I"), - in_request("K"), in_request("J"), in_request("ID")); + in_request("K"), in_request("PK"), in_request("J"), + in_request("ID")); } } else { if (has_credential(CRED_ACCOUNT_SEARCH)) { diff --git a/web/lib/acctfuncs.inc.php b/web/lib/acctfuncs.inc.php index 2d8dbaf..20ac081 100644 --- a/web/lib/acctfuncs.inc.php +++ b/web/lib/acctfuncs.inc.php @@ -53,13 +53,14 @@ function html_format_pgp_fingerprint($fingerprint) { * @param string $L The language preference of the displayed user * @param string $I The IRC nickname of the displayed user * @param string $K The PGP key fingerprint of the displayed user + * @param string $PK The SSH public key of the displayed user * @param string $J The inactivity status of the displayed user * @param string $UID The user ID of the displayed user * * @return void */ -function display_account_form($A,$U="",$T="",$S="", - $E="",$P="",$C="",$R="",$L="",$I="",$K="",$J="", $UID=0) { +function display_account_form($A,$U="",$T="",$S="",$E="",$P="",$C="",$R="", + $L="",$I="",$K="",$PK="",$J="", $UID=0) { global $SUPPORTED_LANGS; include("account_edit_form.php"); @@ -82,13 +83,14 @@ function display_account_form($A,$U="",$T="",$S="", * @param string $L The language preference of the user * @param string $I The IRC nickname of the user * @param string $K The PGP fingerprint of the user + * @param string $PK The SSH public key of the user * @param string $J The inactivity status of the user * @param string $UID The user ID of the modified account * * @return string|void Return void if successful, otherwise return error */ -function process_account_form($TYPE,$A,$U="",$T="",$S="",$E="", - $P="",$C="",$R="",$L="",$I="",$K="",$J="",$UID=0) { +function process_account_form($TYPE,$A,$U="",$T="",$S="",$E="",$P="",$C="", + $R="",$L="",$I="",$K="",$PK="",$J="",$UID=0) { global $SUPPORTED_LANGS; $error = ''; @@ -146,6 +148,15 @@ function process_account_form($TYPE,$A,$U="",$T="",$S="",$E="", $error = __("The PGP key fingerprint is invalid."); } + if (!$error && !empty($PK)) { + if (valid_ssh_pubkey($PK)) { + $tokens = explode(" ", $PK); + $PK = $tokens[0] . " " . $tokens[1]; + } else { + $error = __("The SSH public key is invalid."); + } + } + if (isset($_COOKIE['AURSID'])) { $atype = account_from_sid($_COOKIE['AURSID']); if (($atype == "User" && $T > 1) || ($atype == "Trusted User" && $T > 2)) { @@ -192,11 +203,29 @@ function process_account_form($TYPE,$A,$U="",$T="",$S="",$E="", "<strong>", htmlspecialchars($E,ENT_QUOTES), "</strong>"); } } + if (!$error) { + /* + * Check whether the SSH public key is available. + * TODO: Fix race condition. + */ + $q = "SELECT COUNT(*) FROM Users "; + $q.= "WHERE SSHPubKey = " . $dbh->quote($PK); + if ($TYPE == "edit") { + $q.= " AND ID != " . intval($UID); + } + $result = $dbh->query($q); + $row = $result->fetch(PDO::FETCH_NUM); + + if ($row[0]) { + $error = __("The SSH public key, %s%s%s, is already in use.", + "<strong>", htmlspecialchars($PK, ENT_QUOTES), "</strong>"); + } + } if ($error) { print "<ul class='errorlist'><li>".$error."</li></ul>\n"; display_account_form($A, $U, $T, $S, $E, "", "", - $R, $L, $I, $K, $J, $UID); + $R, $L, $I, $K, $PK, $J, $UID); return; } @@ -218,11 +247,13 @@ function process_account_form($TYPE,$A,$U="",$T="",$S="",$E="", $L = $dbh->quote($L); $I = $dbh->quote($I); $K = $dbh->quote(str_replace(" ", "", $K)); + $PK = $dbh->quote($PK); $q = "INSERT INTO Users (AccountTypeID, Suspended, "; $q.= "InactivityTS, Username, Email, Passwd, Salt, "; - $q.= "RealName, LangPreference, IRCNick, PGPKey) "; + $q.= "RealName, LangPreference, IRCNick, PGPKey, "; + $q.= "SSHPubKey) "; $q.= "VALUES (1, 0, 0, $U, $E, $P, $salt, $R, $L, "; - $q.= "$I, $K)"; + $q.= "$I, $K, $PK)"; $result = $dbh->exec($q); if (!$result) { print __("Error trying to create account, %s%s%s.", @@ -290,6 +321,7 @@ function process_account_form($TYPE,$A,$U="",$T="",$S="",$E="", $q.= ", LangPreference = " . $dbh->quote($L); $q.= ", IRCNick = " . $dbh->quote($I); $q.= ", PGPKey = " . $dbh->quote(str_replace(" ", "", $K)); + $q.= ", SSHPubKey = " . $dbh->quote($PK); $q.= ", InactivityTS = " . $inactivity_ts; $q.= " WHERE ID = ".intval($UID); $result = $dbh->exec($q); @@ -800,6 +832,38 @@ function valid_pgp_fingerprint($fingerprint) { } /** + * Determine if the SSH public key is valid + * + * @param string $pubkey SSH public key to check + * + * @return bool True if the SSH public key is valid, otherwise false + */ +function valid_ssh_pubkey($pubkey) { + $valid_prefixes = array( + "ssh-rsa", "ssh-dss", "ecdsa-sha2-nistp256", + "ecdsa-sha2-nistp384", "ecdsa-sha2-nistp521", "ssh-ed25519" + ); + + $has_valid_prefix = false; + foreach ($valid_prefixes as $prefix) { + if (strpos($pubkey, $prefix . " ") === 0) { + $has_valid_prefix = true; + break; + } + } + if (!$has_valid_prefix) { + return false; + } + + $tokens = explode(" ", $pubkey); + if (empty($tokens[1])) { + return false; + } + + return (base64_encode(base64_decode($tokens[1], true)) == $tokens[1]); +} + +/** * Determine if the user account has been suspended * * @param string $id The ID of user to check if suspended diff --git a/web/template/account_edit_form.php b/web/template/account_edit_form.php index 17dd937..996c207 100644 --- a/web/template/account_edit_form.php +++ b/web/template/account_edit_form.php @@ -98,6 +98,11 @@ </p> <p> + <label for="id_ssh"><?= __("SSH Public Key") ?>:</label> + <textarea name="PK" id="id_ssh" rows="5" cols="30"><?= htmlspecialchars($PK) ?></textarea> + </p> + + <p> <label for="id_language"><?= __("Language") ?>:</label> <select name="L" id="id_language"> <?php -- 2.2.1
This adds two scripts to be used together with Git over SSH: * git-auth.py is supposed to be used as AuthorizedKeysCommand. It checks whether the public key belongs to any AUR user and invokes git-serve.py, passing the name of the corresponding user as a command line argument, if any. * git-serve.py is a wrapper around git-shell(1) that checks whether the user passed as command line argument has access to the Git repository that a push operation writes to. Signed-off-by: Lukas Fleischer <archlinux@cryptocrack.de> --- conf/config.proto | 11 ++++ scripts/git-integration/git-auth.py | 41 ++++++++++++++ scripts/git-integration/git-serve.py | 104 +++++++++++++++++++++++++++++++++++ 3 files changed, 156 insertions(+) create mode 100755 scripts/git-integration/git-auth.py create mode 100755 scripts/git-integration/git-serve.py diff --git a/conf/config.proto b/conf/config.proto index f00b352..13cafe0 100644 --- a/conf/config.proto +++ b/conf/config.proto @@ -26,3 +26,14 @@ max_rpc_results = 5000 aur_request_ml = aur-requests@archlinux.org request_idle_time = 1209600 auto_orphan_age = 15552000 + +[auth] +key-prefixes = ssh-rsa ssh-dss ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 ssh-ed25519 +username-regex = [a-zA-Z0-9]+[.\-_]?[a-zA-Z0-9]+$ +git-serve-cmd = /srv/http/aur/scripts/git-integration/git-serve.py +ssh-options = no-port-forwarding,no-X11-forwarding,no-pty + +[serve] +repo-base = /pub/git/ +repo-regex = [a-z0-9][a-z0-9.+_-]*$ +git-shell-cmd = /usr/bin/git-shell diff --git a/scripts/git-integration/git-auth.py b/scripts/git-integration/git-auth.py new file mode 100755 index 0000000..8701d5e --- /dev/null +++ b/scripts/git-integration/git-auth.py @@ -0,0 +1,41 @@ +#!/usr/bin/python3 + +import configparser +import mysql.connector +import os +import re + +config = configparser.RawConfigParser() +config.read(os.path.dirname(os.path.realpath(__file__)) + "/../../conf/config") + +aur_db_host = config.get('database', 'host') +aur_db_name = config.get('database', 'name') +aur_db_user = config.get('database', 'user') +aur_db_pass = config.get('database', 'password') + +key_prefixes = config.get('auth', 'key-prefixes').split() +username_regex = config.get('auth', 'username-regex') +git_serve_cmd = config.get('auth', 'git-serve-cmd') +ssh_opts = config.get('auth', 'ssh-options') + +pubkey = os.environ.get("SSH_KEY") +valid_prefixes = tuple(p + " " for p in key_prefixes) +if pubkey is None or not pubkey.startswith(valid_prefixes): + exit(1) + +db = mysql.connector.connect(host=aur_db_host, user=aur_db_user, + passwd=aur_db_pass, db=aur_db_name, + buffered=True) + +cur = db.cursor() +cur.execute("SELECT Username FROM Users WHERE SSHPubKey = %s " + + "AND Suspended = 0", (pubkey,)) + +if cur.rowcount != 1: + exit(1) + +user = cur.fetchone()[0] +if not re.match(username_regex, user): + exit(1) + +print('command="%s %s",%s %s' % (git_serve_cmd, user, ssh_opts, pubkey)) diff --git a/scripts/git-integration/git-serve.py b/scripts/git-integration/git-serve.py new file mode 100755 index 0000000..08132f2 --- /dev/null +++ b/scripts/git-integration/git-serve.py @@ -0,0 +1,104 @@ +#!/usr/bin/python3 + +import configparser +import mysql.connector +import os +import pygit2 +import re +import shlex +import sys + +config = configparser.RawConfigParser() +config.read(os.path.dirname(os.path.realpath(__file__)) + "/../../conf/config") + +aur_db_host = config.get('database', 'host') +aur_db_name = config.get('database', 'name') +aur_db_user = config.get('database', 'user') +aur_db_pass = config.get('database', 'password') + +repo_base_path = config.get('serve', 'repo-base') +repo_regex = config.get('serve', 'repo-regex') +git_shell_cmd = config.get('serve', 'git-shell-cmd') + +def repo_path_validate(path): + if not path.startswith(repo_base_path): + return False + if not path.endswith('.git/'): + return False + repo = path[len(repo_base_path):-5] + return re.match(repo_regex, repo) + +def repo_path_get_pkgbase(path): + pkgbase = path.rstrip('/').rpartition('/')[2] + if pkgbase.endswith('.git'): + pkgbase = pkgbase[:-4] + return pkgbase + +def setup_repo(repo, user): + if not re.match(repo_regex, repo): + die('invalid repository name: %s' % (repo)) + + db = mysql.connector.connect(host=aur_db_host, user=aur_db_user, + passwd=aur_db_pass, db=aur_db_name) + cur = db.cursor() + + cur.execute("SELECT COUNT(*) FROM PackageBases WHERE Name = %s ", [repo]) + if cur.fetchone()[0] > 0: + die('package base already exists: %s' % (repo)) + + cur.execute("SELECT ID FROM Users WHERE Username = %s ", [user]) + userid = cur.fetchone()[0] + if userid == 0: + die('unknown user: %s' % (user)) + + cur.execute("INSERT INTO PackageBases (Name, SubmittedTS, ModifiedTS, " + + "SubmitterUID) VALUES (%s, UNIX_TIMESTAMP(), " + + "UNIX_TIMESTAMP(), %s)", [repo, userid]) + + db.commit() + db.close() + + repo_path = repo_base_path + '/' + repo + '.git/' + pygit2.init_repository(repo_path, True) + +def check_permissions(pkgbase, user): + db = mysql.connector.connect(host=aur_db_host, user=aur_db_user, + passwd=aur_db_pass, db=aur_db_name, + buffered=True) + cur = db.cursor() + + cur.execute("SELECT COUNT(*) FROM PackageBases INNER JOIN Users " + + "ON Users.ID = PackageBases.MaintainerUID OR " + + "PackageBases.MaintainerUID IS NULL WHERE " + + "Name = %s AND Username = %s", [pkgbase, user]) + return cur.fetchone()[0] > 0 + +def die(msg): + sys.stderr.write("%s\n" % (msg)) + exit(1) + +user = sys.argv[1] +cmd = os.environ.get("SSH_ORIGINAL_COMMAND") +if not cmd: + die('no command specified') +cmdargv = shlex.split(cmd) +action = cmdargv[0] + +if action == 'git-upload-pack' or action == 'git-receive-pack': + path = cmdargv[1] + if not repo_path_validate(path): + die('invalid path: %s' % (path)) + pkgbase = repo_path_get_pkgbase(path) + if action == 'git-receive-pack': + if not check_permissions(pkgbase, user): + die('permission denied: %s' % (user)) + os.environ["AUR_USER"] = user + os.environ["AUR_GIT_DIR"] = path + os.environ["AUR_PKGBASE"] = pkgbase + os.execl(git_shell_cmd, git_shell_cmd, '-c', cmd) +elif action == 'setup-repo': + if len(cmdargv) < 2: + die('missing repository name') + setup_repo(cmdargv[1], user) +else: + die('invalid command: %s' % (action)) -- 2.2.1
This adds a script that can be used as an update hook to check all commits for validity and to regenerate the package details page before updating a named ref. Signed-off-by: Lukas Fleischer <archlinux@cryptocrack.de> --- conf/config.proto | 1 + scripts/git-integration/aurinfo.py | 204 ++++++++++++++++++++++++++++++++ scripts/git-integration/git-serve.py | 2 + scripts/git-integration/git-update.py | 212 ++++++++++++++++++++++++++++++++++ 4 files changed, 419 insertions(+) create mode 100644 scripts/git-integration/aurinfo.py create mode 100755 scripts/git-integration/git-update.py diff --git a/conf/config.proto b/conf/config.proto index 13cafe0..969164a 100644 --- a/conf/config.proto +++ b/conf/config.proto @@ -36,4 +36,5 @@ ssh-options = no-port-forwarding,no-X11-forwarding,no-pty [serve] repo-base = /pub/git/ repo-regex = [a-z0-9][a-z0-9.+_-]*$ +git-update-hook = /srv/http/aur/scripts/git-integration/git-update.py git-shell-cmd = /usr/bin/git-shell diff --git a/scripts/git-integration/aurinfo.py b/scripts/git-integration/aurinfo.py new file mode 100644 index 0000000..d9b9372 --- /dev/null +++ b/scripts/git-integration/aurinfo.py @@ -0,0 +1,204 @@ +#!/usr/bin/env python + +from copy import copy, deepcopy +import pprint +import sys + +class Attr(object): + def __init__(self, name, is_multivalued=False, allow_arch_extensions=False): + self.name = name + self.is_multivalued = is_multivalued + self.allow_arch_extensions = allow_arch_extensions + +PKGBUILD_ATTRIBUTES = { + 'arch': Attr('arch', True), + 'backup': Attr('backup', True), + 'changelog': Attr('changelog', False), + 'checkdepends': Attr('checkdepends', True), + 'conflicts': Attr('conflicts', True, True), + 'depends': Attr('depends', True, True), + 'epoch': Attr('epoch', False), + 'groups': Attr('groups', True), + 'install': Attr('install', False), + 'license': Attr('license', True), + 'makedepends': Attr('makedepends', True, True), + 'md5sums': Attr('md5sums', True, True), + 'noextract': Attr('noextract', True), + 'optdepends': Attr('optdepends', True, True), + 'options': Attr('options', True), + 'pkgname': Attr('pkgname', False), + 'pkgrel': Attr('pkgrel', False), + 'pkgver': Attr('pkgver', False), + 'provides': Attr('provides', True, True), + 'replaces': Attr('replaces', True, True), + 'sha1sums': Attr('sha1sums', True, True), + 'sha224sums': Attr('sha224sums', True, True), + 'sha256sums': Attr('sha256sums', True, True), + 'sha384sums': Attr('sha384sums', True, True), + 'sha512sums': Attr('sha512sums', True, True), + 'source': Attr('source', True, True), + 'url': Attr('url', False), + 'validpgpkeys': Attr('validpgpkeys', True), +} + +def find_attr(attrname): + # exact match + attr = PKGBUILD_ATTRIBUTES.get(attrname, None) + if attr: + return attr + + # prefix match + # XXX: this could break in the future if PKGBUILD(5) ever + # introduces a key which is a subset of another. + for k in PKGBUILD_ATTRIBUTES.keys(): + if attrname.startswith(k + '_'): + return PKGBUILD_ATTRIBUTES[k] + +def IsMultiValued(attrname): + attr = find_attr(attrname) + return attr and attr.is_multivalued + +class AurInfo(object): + def __init__(self): + self._pkgbase = {} + self._packages = {} + + def GetPackageNames(self): + return self._packages.keys() + + def GetMergedPackage(self, pkgname): + package = deepcopy(self._pkgbase) + package['pkgname'] = pkgname + for k, v in self._packages.get(pkgname).items(): + package[k] = deepcopy(v) + return package + + def AddPackage(self, pkgname): + self._packages[pkgname] = {} + return self._packages[pkgname] + + def SetPkgbase(self, pkgbasename): + self._pkgbase = {'pkgname' : pkgbasename} + return self._pkgbase + + +class StderrECatcher(object): + def Catch(self, lineno, error): + print('ERROR[%d]: %s' % (lineno, error), file=sys.stderr) + + +class CollectionECatcher(object): + def __init__(self): + self._errors = [] + + def Catch(self, lineno, error): + self._errors.append((lineno, error)) + + def HasErrors(self): + return len(self._errors) > 0 + + def Errors(self): + return copy(self._errors) + + +def ParseAurinfoFromIterable(iterable, ecatcher=None): + aurinfo = AurInfo() + + if ecatcher is None: + ecatcher = StderrECatcher() + + current_package = None + lineno = 0 + + for line in iterable: + lineno += 1 + + if line.startswith('#'): + continue + + if not line.strip(): + # end of package + current_package = None + continue + + if not line.startswith('\t'): + # start of new package + try: + key, value = map(str.strip, line.split('=', 1)) + except ValueError: + ecatcher.Catch(lineno, 'unexpected header format in section=%s' % + current_package['pkgname']) + continue + + if key == 'pkgbase': + current_package = aurinfo.SetPkgbase(value) + else: + current_package = aurinfo.AddPackage(value) + else: + # package attribute + if current_package is None: + ecatcher.Catch(lineno, 'package attribute found outside of ' + 'a package section') + continue + + try: + key, value = map(str.strip, line.split('=', 1)) + except ValueError: + ecatcher.Catch(lineno, 'unexpected attribute format in ' + 'section=%s' % current_package['pkgname']) + + if IsMultiValued(key): + if not current_package.get(key): + current_package[key] = [] + if value: + current_package[key].append(value) + else: + if not current_package.get(key): + current_package[key] = value + else: + ecatcher.Catch(lineno, 'overwriting attribute ' + '%s: %s -> %s' % (key, current_package[key], + value)) + + return aurinfo + + +def ParseAurinfo(filename='.AURINFO', ecatcher=None): + with open(filename) as f: + return ParseAurinfoFromIterable(f, ecatcher) + + +def ValidateAurinfo(filename='.AURINFO'): + ecatcher = CollectionECatcher() + ParseAurinfo(filename, ecatcher) + errors = ecatcher.Errors() + for error in errors: + print('error on line %d: %s' % error, file=sys.stderr) + return not errors + + +if __name__ == '__main__': + pp = pprint.PrettyPrinter(indent=4) + + if len(sys.argv) == 1: + print('error: not enough arguments') + sys.exit(1) + elif len(sys.argv) == 2: + action = sys.argv[1] + filename = '.AURINFO' + else: + action, filename = sys.argv[1:3] + + if action == 'parse': + aurinfo = ParseAurinfo() + for pkgname in aurinfo.GetPackageNames(): + print(">>> merged package: %s" % pkgname) + pp.pprint(aurinfo.GetMergedPackage(pkgname)) + print() + elif action == 'validate': + sys.exit(not ValidateAurinfo(filename)) + else: + print('unknown action: %s' % action) + sys.exit(1) + +# vim: set et ts=4 sw=4: diff --git a/scripts/git-integration/git-serve.py b/scripts/git-integration/git-serve.py index 08132f2..e621677 100755 --- a/scripts/git-integration/git-serve.py +++ b/scripts/git-integration/git-serve.py @@ -18,6 +18,7 @@ aur_db_pass = config.get('database', 'password') repo_base_path = config.get('serve', 'repo-base') repo_regex = config.get('serve', 'repo-regex') +git_update_hook = config.get('serve', 'git-update-hook') git_shell_cmd = config.get('serve', 'git-shell-cmd') def repo_path_validate(path): @@ -60,6 +61,7 @@ def setup_repo(repo, user): repo_path = repo_base_path + '/' + repo + '.git/' pygit2.init_repository(repo_path, True) + os.symlink(git_update_hook, repo_path + 'hooks/update') def check_permissions(pkgbase, user): db = mysql.connector.connect(host=aur_db_host, user=aur_db_user, diff --git a/scripts/git-integration/git-update.py b/scripts/git-integration/git-update.py new file mode 100755 index 0000000..c20eede --- /dev/null +++ b/scripts/git-integration/git-update.py @@ -0,0 +1,212 @@ +#!/usr/bin/python3 + +from copy import copy, deepcopy +import configparser +import mysql.connector +import os +import pygit2 +import re +import sys + +import aurinfo + +config = configparser.RawConfigParser() +config.read(os.path.dirname(os.path.realpath(__file__)) + "/../../conf/config") + +aur_db_host = config.get('database', 'host') +aur_db_name = config.get('database', 'name') +aur_db_user = config.get('database', 'user') +aur_db_pass = config.get('database', 'password') + +def save_srcinfo(srcinfo, db, cur, user): + # Obtain package base ID and previous maintainer. + pkgbase = srcinfo._pkgbase['pkgname'] + cur.execute("SELECT ID, MaintainerUID FROM PackageBases " + "WHERE Name = %s", [pkgbase]) + (pkgbase_id, maintainer_uid) = cur.fetchone() + was_orphan = not maintainer_uid + + # Obtain the user ID of the new maintainer. + cur.execute("SELECT ID FROM Users WHERE Username = %s", [user]) + user_id = int(cur.fetchone()[0]) + + # Update package base details and delete current packages. + cur.execute("UPDATE PackageBases SET ModifiedTS = UNIX_TIMESTAMP(), " + + "MaintainerUID = %s, PackagerUID = %s, " + + "OutOfDateTS = NULL WHERE ID = %s", + [user_id, user_id, pkgbase_id]) + cur.execute("DELETE FROM Packages WHERE PackageBaseID = %s", + [pkgbase_id]) + + for pkgname in srcinfo.GetPackageNames(): + pkginfo = srcinfo.GetMergedPackage(pkgname) + + if 'epoch' in pkginfo and pkginfo['epoch'] > 0: + ver = '%d:%s-%s' % (pkginfo['epoch'], pkginfo['pkgver'], + pkginfo['pkgrel']) + else: + ver = '%s-%s' % (pkginfo['pkgver'], pkginfo['pkgrel']) + + # Create a new package. + cur.execute("INSERT INTO Packages (PackageBaseID, Name, " + + "Version, Description, URL) " + + "VALUES (%s, %s, %s, %s, %s)", + [pkgbase_id, pkginfo['pkgname'], ver, + pkginfo['pkgdesc'], pkginfo['url']]) + db.commit() + pkgid = cur.lastrowid + + # Add package sources. + for source in pkginfo['source']: + cur.execute("INSERT INTO PackageSources (PackageID, Source) " + + "VALUES (%s, %s)", [pkgid, source]) + + # Add package dependencies. + for deptype in ('depends', 'makedepends', + 'checkdepends', 'optdepends'): + if not deptype in pkginfo: + continue + cur.execute("SELECT ID FROM DependencyTypes WHERE Name = %s", + [deptype]) + deptypeid = cur.fetchone()[0] + for dep in pkginfo[deptype]: + depname = re.sub(r'(<|=|>).*', '', dep) + depcond = dep[len(depname):] + cur.execute("INSERT INTO PackageDepends (PackageID, " + + "DepTypeID, DepName, DepCondition) " + + "VALUES (%s, %s, %s, %s)", [pkgid, deptypeid, + depname, depcond]) + + # Add package relations (conflicts, provides, replaces). + for reltype in ('conflicts', 'provides', 'replaces'): + if not reltype in pkginfo: + continue + cur.execute("SELECT ID FROM RelationTypes WHERE Name = %s", + [reltype]) + reltypeid = cur.fetchone()[0] + for rel in pkginfo[reltype]: + relname = re.sub(r'(<|=|>).*', '', rel) + relcond = rel[len(relname):] + cur.execute("INSERT INTO PackageRelations (PackageID, " + + "RelTypeID, RelName, RelCondition) " + + "VALUES (%s, %s, %s, %s)", [pkgid, reltypeid, + relname, relcond]) + + # Add package licenses. + if 'license' in pkginfo: + for license in pkginfo['license']: + cur.execute("SELECT ID FROM Licenses WHERE Name = %s", + [license]) + if cur.rowcount == 1: + licenseid = cur.fetchone()[0] + else: + cur.execute("INSERT INTO Licenses (Name) VALUES (%s)", + [license]) + db.commit() + licenseid = cur.lastrowid + cur.execute("INSERT INTO PackageLicenses (PackageID, " + + "LicenseID) VALUES (%s, %s)", + [pkgid, licenseid]) + + # Add package groups. + if 'groups' in pkginfo: + for group in pkginfo['groups']: + cur.execute("SELECT ID FROM Groups WHERE Name = %s", + [group]) + if cur.rowcount == 1: + groupid = cur.fetchone()[0] + else: + cur.execute("INSERT INTO Groups (Name) VALUES (%s)", + [group]) + db.commit() + groupid = cur.lastrowid + cur.execute("INSERT INTO PackageGroups (PackageID, " + "GroupID) VALUES (%s, %s)", [pkgid, groupid]) + + # Add user to notification list on adoption. + if was_orphan: + cur.execute("INSERT INTO CommentNotify (PackageBaseID, UserID) " + + "VALUES (%s, %s)", [pkgbase_id, user_id]) + + db.commit() + +def die(msg): + sys.stderr.write("error: %s\n" % (msg)) + exit(1) + +def die_commit(msg, commit): + sys.stderr.write("error: The following error " + + "occurred when parsing commit\n") + sys.stderr.write("error: %s:\n" % (commit)) + sys.stderr.write("error: %s\n" % (msg)) + exit(1) + +if len(sys.argv) != 4: + die("invalid arguments") + +refname = sys.argv[1] +sha1_old = sys.argv[2] +sha1_new = sys.argv[3] + +user = os.environ.get("AUR_USER") +pkgbase = os.environ.get("AUR_PKGBASE") +git_dir = os.environ.get("AUR_GIT_DIR") + +if refname != "refs/heads/master": + die("pushing to a branch other than master is restricted") + +repo = pygit2.Repository(git_dir) +walker = repo.walk(sha1_new, pygit2.GIT_SORT_TOPOLOGICAL) +if sha1_old != "0000000000000000000000000000000000000000": + walker.hide(sha1_old) + +for commit in walker: + if not '.SRCINFO' in commit.tree: + die_commit("missing .SRCINFO", commit.id) + + for treeobj in commit.tree: + if repo[treeobj.id].size > 100000: + die_commit("maximum blob size (100kB) exceeded", commit.id) + + srcinfo_raw = repo[commit.tree['.SRCINFO'].id].data.decode() + srcinfo_raw = srcinfo_raw.split('\n') + ecatcher = aurinfo.CollectionECatcher() + srcinfo = aurinfo.ParseAurinfoFromIterable(srcinfo_raw, ecatcher) + errors = ecatcher.Errors() + if errors: + sys.stderr.write("error: The following errors occurred " + "when parsing .SRCINFO in commit\n") + sys.stderr.write("error: %s:\n" % (commit.id)) + for error in errors: + sys.stderr.write("error: line %d: %s\n" % error) + exit(1) + + srcinfo_pkgbase = srcinfo._pkgbase['pkgname'] + if srcinfo_pkgbase != pkgbase: + die_commit('invalid pkgbase: %s' % (srcinfo_pkgbase), commit.id) + + for pkgname in srcinfo.GetPackageNames(): + pkginfo = srcinfo.GetMergedPackage(pkgname) + + if not re.match(r'[a-z0-9][a-z0-9\.+_-]*$', pkginfo['pkgname']): + die_commit('invalid package name: %s' % (pkginfo['pkgname']), + commit.id) + + if not re.match(r'(?:http|ftp)s?://.*', pkginfo['url']): + die_commit('invalid URL: %s' % (pkginfo['url']), commit.id) + + for field in ('pkgname', 'pkgdesc', 'url'): + if len(pkginfo[field]) > 255: + die_commit('%s field too long: %s' % (field, pkginfo[field]), + commit.id) + +srcinfo_raw = repo[repo[sha1_new].tree['.SRCINFO'].id].data.decode() +srcinfo_raw = srcinfo_raw.split('\n') +srcinfo = aurinfo.ParseAurinfoFromIterable(srcinfo_raw) + +db = mysql.connector.connect(host=aur_db_host, user=aur_db_user, + passwd=aur_db_pass, db=aur_db_name, + buffered=True) +cur = db.cursor() +save_srcinfo(srcinfo, db, cur, user) +db.close() -- 2.2.1
* Remove package submission page from the web interface. * Replace PKGBUILD and tarball links with links to cgit. * Remove the "URLPath" field from RPC replies. Signed-off-by: Lukas Fleischer <archlinux@cryptocrack.de> --- conf/config.proto | 5 +- web/html/pkgsubmit.php | 489 ---------- web/lib/Archive/PEAR.php | 1063 -------------------- web/lib/Archive/PEAR5.php | 33 - web/lib/Archive/Tar.php | 1993 -------------------------------------- web/lib/aurjson.class.php | 1 - web/lib/pkgbasefuncs.inc.php | 39 - web/lib/pkgfuncs.inc.php | 31 +- web/lib/routing.inc.php | 1 - web/template/header.php | 1 - web/template/pkg_details.php | 10 +- web/template/pkgbase_details.php | 10 +- 12 files changed, 12 insertions(+), 3664 deletions(-) delete mode 100644 web/html/pkgsubmit.php delete mode 100644 web/lib/Archive/PEAR.php delete mode 100644 web/lib/Archive/PEAR5.php delete mode 100644 web/lib/Archive/Tar.php diff --git a/conf/config.proto b/conf/config.proto index 969164a..d20b533 100644 --- a/conf/config.proto +++ b/conf/config.proto @@ -6,9 +6,6 @@ name = AUR user = aur password = aur -[paths] -storage = /srv/aur/unsupported/ - [options] username_min_len = 3 username_max_len = 16 @@ -21,7 +18,7 @@ persistent_cookie_timeout = 2592000 max_filesize_uncompressed = 8388608 disable_http_login = 1 aur_location = http://localhost -package_url = /packages/ +cgit_uri = https://git.aur.archlinux.org/ max_rpc_results = 5000 aur_request_ml = aur-requests@archlinux.org request_idle_time = 1209600 diff --git a/web/html/pkgsubmit.php b/web/html/pkgsubmit.php deleted file mode 100644 index 098c3fa..0000000 --- a/web/html/pkgsubmit.php +++ /dev/null @@ -1,489 +0,0 @@ -<?php - -set_include_path(get_include_path() . PATH_SEPARATOR . '../lib'); - -require_once('Archive/Tar.php'); - -include_once("aur.inc.php"); # access AUR common functions -include_once("pkgfuncs.inc.php"); # package functions - -set_lang(); # this sets up the visitor's language -check_sid(); # see if they're still logged in - -$cwd = getcwd(); - -if ($_COOKIE["AURSID"]) { - $uid = uid_from_sid($_COOKIE['AURSID']); -} -else { - $uid = NULL; -} - -if ($uid): - - # Track upload errors - $error = ""; - - if (isset($_REQUEST['pkgsubmit'])) { - - # Make sure authenticated user submitted the package themselves - if (!check_token()) { - $error = __("Invalid token for user action."); - } - - # Before processing, make sure we even have a file - switch($_FILES['pfile']['error']) { - case UPLOAD_ERR_INI_SIZE: - $maxsize = ini_get('upload_max_filesize'); - $error = __("Error - Uploaded file larger than maximum allowed size (%s)", $maxsize); - break; - case UPLOAD_ERR_PARTIAL: - $error = __("Error - File partially uploaded"); - break; - case UPLOAD_ERR_NO_FILE: - $error = __("Error - No file uploaded"); - break; - case UPLOAD_ERR_NO_TMP_DIR: - $error = __("Error - Could not locate temporary upload folder"); - break; - case UPLOAD_ERR_CANT_WRITE: - $error = __("Error - File could not be written"); - break; - } - - # Check whether the file is gzip'ed - if (!$error) { - $fh = fopen($_FILES['pfile']['tmp_name'], 'rb'); - fseek($fh, 0, SEEK_SET); - list(, $magic) = unpack('v', fread($fh, 2)); - - if ($magic != 0x8b1f) { - $error = __("Error - unsupported file format (please submit gzip'ed tarballs generated by makepkg(8) only)."); - } - } - - # Check uncompressed file size (ZIP bomb protection) - $max_filesize_uncompressed = config_get_int('options', 'max_filesize_uncompressed'); - if (!$error && $max_filesize_uncompressed) { - fseek($fh, -4, SEEK_END); - list(, $filesize_uncompressed) = unpack('V', fread($fh, 4)); - - if ($filesize_uncompressed > $max_filesize_uncompressed) { - $error = __("Error - uncompressed file size too large."); - } - } - - # Close file handle before extracting stuff - if (isset($fh) && is_resource($fh)) { - fclose($fh); - } - - if (!$error) { - $tar = new Archive_Tar($_FILES['pfile']['tmp_name']); - - /* Extract PKGBUILD and .SRCINFO into a string. */ - $pkgbuild_raw = $srcinfo_raw = ''; - $dircount = 0; - foreach ($tar->listContent() as $tar_file) { - if ($tar_file['typeflag'] == 0) { - if (strchr($tar_file['filename'], '/') === false) { - $error = __("Error - source tarball may not contain files outside a directory."); - break; - } elseif ($tar_file['mode'] != 0644 && $tar_file['mode'] != 0755) { - $error = __("Error - all files must have permissions of 644 or 755."); - break; - } elseif (substr($tar_file['filename'], -9) == '/PKGBUILD') { - $pkgbuild_raw = $tar->extractInString($tar_file['filename']); - } elseif (substr($tar_file['filename'], -9) == '/.AURINFO' || - substr($tar_file['filename'], -9) == '/.SRCINFO') { - $srcinfo_raw = $tar->extractInString($tar_file['filename']); - } - } elseif ($tar_file['typeflag'] == 5) { - if (substr_count($tar_file['filename'], "/") > 1) { - $error = __("Error - source tarball may not contain nested subdirectories."); - break; - } elseif (++$dircount > 1) { - $error = __("Error - source tarball may not contain more than one directory."); - break; - } elseif ($tar_file['mode'] != 0755) { - $error = __("Error - all directories must have permissions of 755."); - break; - } - } - } - } - - if (!$error && $dircount !== 1) { - $error = __("Error - source tarball may not contain files outside a directory."); - } - - if (empty($pkgbuild_raw) && !$error) { - $error = __("Error trying to unpack upload - PKGBUILD does not exist."); - } - - if (empty($srcinfo_raw)) { - $srcinfo_raw = ''; - if (!$error) { - $error = __("The source package does not contain any meta data. Please use `makepkg --source` from pacman 4.2.0 or newer to create AUR source packages."); - } - } - - /* Parse .SRCINFO and extract meta data. */ - $pkgbase_info = array(); - $pkginfo = array(); - $section_info = array(); - foreach (explode("\n", $srcinfo_raw) as $line) { - $line = ltrim($line); - if (empty($line) || $line[0] == '#') { - continue; - } - list($key, $value) = explode(' = ', $line, 2); - $tokens = explode('_', $key, 2); - $key = $tokens[0]; - if (count($tokens) > 1) { - $arch = $tokens[1]; - } else { - $arch = NULL; - } - switch ($key) { - case 'pkgbase': - case 'pkgname': - if (!empty($section_info)) { - if (isset($section_info['pkgbase'])) { - $pkgbase_info = $section_info; - } elseif (isset($section_info['pkgname'])) { - $pkginfo[] = array_pkgbuild_merge($pkgbase_info, $section_info); - } - } - $section_info = array( - 'license' => array(), - 'groups' => array(), - 'depends' => array(), - 'makedepends' => array(), - 'checkdepends' => array(), - 'optdepends' => array(), - 'source' => array(), - 'conflicts' => array(), - 'provides' => array(), - 'replaces' => array() - ); - /* Fall-through case. */ - case 'epoch': - case 'pkgdesc': - case 'pkgver': - case 'pkgrel': - case 'url': - $section_info[$key] = $value; - break; - case 'license': - case 'groups': - $section_info[$key][] = $value; - break; - case 'depends': - case 'makedepends': - case 'checkdepends': - case 'optdepends': - case 'conflicts': - case 'provides': - case 'replaces': - case 'source': - $section_info[$key][$arch][] = $value; - break; - } - } - - if (!empty($section_info)) { - if (isset($section_info['pkgbase'])) { - $pkgbase_info = $section_info; - } elseif (isset($section_info['pkgname'])) { - $pkginfo[] = array_pkgbuild_merge($pkgbase_info, $section_info); - } - } - - /* Validate package base name. */ - if (!$error) { - $pkgbase_name = $pkgbase_info['pkgbase']; - if (!preg_match("/^[a-z0-9][a-z0-9\.+_-]*$/D", $pkgbase_name)) { - $error = __("Invalid name: only lowercase letters are allowed."); - } - - /* Check whether the package base already exists. */ - $base_id = pkgbase_from_name($pkgbase_name); - } - - foreach ($pkginfo as $key => $pi) { - /* Bail out early if an error has occurred. */ - if ($error) { - break; - } - - /* Validate package names. */ - $pkg_name = $pi['pkgname']; - if (!preg_match("/^[a-z0-9][a-z0-9\.+_-]*$/D", $pkg_name)) { - $error = __("Invalid name: only lowercase letters are allowed."); - break; - } - - /* Determine the full package versions with epoch. */ - if (isset($pi['epoch']) && (int)$pi['epoch'] > 0) { - $pkginfo[$key]['full-version'] = sprintf('%d:%s-%s', $pi['epoch'], $pi['pkgver'], $pi['pkgrel']); - } else { - $pkginfo[$key]['full-version'] = sprintf('%s-%s', $pi['pkgver'], $pi['pkgrel']); - } - - /* Check for http:// or other protocols in the URL. */ - $parsed_url = parse_url($pi['url']); - if (!$parsed_url['scheme']) { - $error = __("Package URL is missing a protocol (ie. http:// ,ftp://)"); - break; - } - - /* - * The DB schema imposes limitations on number of - * allowed characters. Print error message when these - * limitations are exceeded. - */ - if (strlen($pi['pkgname']) > 64) { - $error = __("Error - Package name cannot be greater than %d characters", 64); - break; - } - if (strlen($pi['url']) > 255) { - $error = __("Error - Package URL cannot be greater than %d characters", 255); - break; - } - if (strlen($pi['pkgdesc']) > 255) { - $error = __("Error - Package description cannot be greater than %d characters", 255); - break; - } - foreach ($pi['license'] as $lic) { - if (strlen($lic > 64)) { - $error = __("Error - Package license cannot be greater than %d characters", 64); - break; - } - } - if (strlen($pkginfo[$key]['full-version']) > 32) { - $error = __("Error - Package version cannot be greater than %d characters", 32); - break; - } - - /* Check if package name is blacklisted. */ - if (!$base_id && pkg_name_is_blacklisted($pi['pkgname']) && !can_submit_blacklisted(account_from_sid($_COOKIE["AURSID"]))) { - $error = __( "%s is on the package blacklist, please check if it's available in the official repos.", $pi['pkgname']); - break; - } - } - - if (isset($pkgbase_name)) { - $incoming_pkgdir = config_get('paths', 'storage') . substr($pkgbase_name, 0, 2) . "/" . $pkgbase_name; - } - - /* Upload PKGBUILD and tarball. */ - if (!$error && !can_submit_pkgbase($pkgbase_name, $_COOKIE["AURSID"])) { - $error = __( "You are not allowed to overwrite the %s%s%s package.", "<strong>", $pkgbase_name, "</strong>"); - } - - if (!$error) { - foreach ($pkginfo as $pi) { - if (!can_submit_pkg($pi['pkgname'], $base_id)) { - $error = __( "You are not allowed to overwrite the %s%s%s package.", "<strong>", $pi['pkgname'], "</strong>"); - break; - } - } - } - - if (!$error) { - /* - * Blow away the existing directory and its contents. - */ - if (file_exists($incoming_pkgdir)) { - rm_tree($incoming_pkgdir); - } - - /* - * The mode is masked by the current umask, so not as - * scary as it looks. - */ - if (!mkdir($incoming_pkgdir, 0777, true)) { - $error = __( "Could not create directory %s.", $incoming_pkgdir); - } - - if (!chdir($incoming_pkgdir)) { - $error = __("Could not change directory to %s.", $incoming_pkgdir); - } - - file_put_contents('PKGBUILD', $pkgbuild_raw); - move_uploaded_file($_FILES['pfile']['tmp_name'], $pkgbase_name . '.tar.gz'); - } - - /* Update the backend database. */ - if (!$error) { - begin_atomic_commit(); - - /* - * Check the category to use, "1" meaning "none" (or - * "keep category" for existing packages). - */ - if (isset($_POST['category'])) { - $category_id = max(1, intval($_POST['category'])); - } else { - $category_id = 1; - } - - if ($base_id) { - /* - * This is an overwrite of an existing package - * base, the database ID needs to be preserved - * so that any votes are retained. - */ - $was_orphan = (pkgbase_maintainer_uid($base_id) === NULL); - - pkgbase_update($base_id, $pkgbase_info['pkgbase'], $uid); - - if ($category_id > 1) { - pkgbase_update_category($base_id, $category_id); - } - - pkgbase_delete_packages($base_id); - } else { - /* This is a brand new package. */ - $was_orphan = true; - $base_id = pkgbase_create($pkgbase_name, $category_id, $uid); - } - - foreach ($pkginfo as $pi) { - $pkgid = pkg_create($base_id, $pi['pkgname'], $pi['full-version'], $pi['pkgdesc'], $pi['url']); - - foreach ($pi['license'] as $lic) { - $licid = pkg_create_license($lic); - pkg_add_lic($pkgid, $licid); - } - - foreach ($pi['groups'] as $grp) { - $grpid = pkg_create_group($grp); - pkg_add_grp($pkgid, $grpid); - } - - foreach (array('depends', 'makedepends', 'checkdepends', 'optdepends') as $deptype) { - foreach ($pi[$deptype] as $deparch => $depgrp) { - foreach ($depgrp as $dep) { - $deppkgname = preg_replace("/(<|=|>).*/", "", $dep); - $depcondition = str_replace($deppkgname, "", $dep); - pkg_add_dep($pkgid, $deptype, $deppkgname, $depcondition, $deparch); - } - } - } - - foreach (array('conflicts', 'provides', 'replaces') as $reltype) { - foreach ($pi[$reltype] as $relarch => $relgrp) { - foreach ($relgrp as $rel) { - $relpkgname = preg_replace("/(<|=|>).*/", "", $rel); - $relcondition = str_replace($relpkgname, "", $rel); - pkg_add_rel($pkgid, $reltype, $relpkgname, $relcondition, $relarch); - } - } - } - - foreach ($pi['source'] as $srcarch => $srcgrp) { - foreach ($srcgrp as $src) { - pkg_add_src($pkgid, $src, $srcarch); - } - } - } - - /* - * If we just created this package, or it was an orphan - * and we auto-adopted, add submitting user to the - * notification list. - */ - if ($was_orphan) { - pkgbase_notify(array($base_id), true); - } - - end_atomic_commit(); - - header('Location: ' . get_pkgbase_uri($pkgbase_info['pkgbase'])); - } - - chdir($cwd); - } - -html_header("Submit"); - -?> - -<div class="box"> - <h2><?= __("Submit"); ?></h2> - <p><?= __("Upload your source packages here. Create source packages with `makepkg --source`.") ?></p> - -<?php - if (empty($_REQUEST['pkgsubmit']) || $error): - # User is not uploading, or there were errors uploading - then - # give the visitor the default upload form - if (ini_get("file_uploads")): - - $pkgbase_categories = pkgbase_categories(); -?> - -<?php if ($error): ?> - <ul class="errorlist"><li><?= $error ?></li></ul> -<?php endif; ?> - -<form action="<?= get_uri('/submit/'); ?>" method="post" enctype="multipart/form-data"> - <fieldset> - <div> - <input type="hidden" name="pkgsubmit" value="1" /> - <input type="hidden" name="token" value="<?= htmlspecialchars($_COOKIE['AURSID']) ?>" /> - </div> - <p> - <label for="id_category"><?= __("Package Category"); ?>:</label> - <select id="id_category" name="category"> - <option value="1"><?= __("Select Category"); ?></option> - <?php - foreach ($pkgbase_categories as $num => $cat): - print '<option value="' . $num . '"'; - if (isset($_POST['category']) && $_POST['category'] == $cat): - print ' selected="selected"'; - endif; - print '>' . $cat . '</option>'; - endforeach; - ?> - </select> - </p> - <p> - <label for="id_file"><?= __("Upload package file"); ?>:</label> - <input id="id_file" type="file" name="pfile" size='30' /> - </p> - <p> - <label></label> - <input class="button" type="submit" value="<?= __("Upload"); ?>" /> - </p> - </fieldset> -</form> -</div> -<?php - else: - print __("Sorry, uploads are not permitted by this server."); -?> - -<br /> -</div> -<?php - endif; - endif; -else: - # Visitor is not logged in - html_header("Submit"); - print __("You must create an account before you can upload packages."); -?> - -<br /> - -<?php -endif; -?> - - - -<?php -html_footer(AUR_VERSION); - diff --git a/web/lib/Archive/PEAR.php b/web/lib/Archive/PEAR.php deleted file mode 100644 index 2aa8525..0000000 --- a/web/lib/Archive/PEAR.php +++ /dev/null @@ -1,1063 +0,0 @@ -<?php -/** - * PEAR, the PHP Extension and Application Repository - * - * PEAR class and PEAR_Error class - * - * PHP versions 4 and 5 - * - * @category pear - * @package PEAR - * @author Sterling Hughes <sterling@php.net> - * @author Stig Bakken <ssb@php.net> - * @author Tomas V.V.Cox <cox@idecnet.com> - * @author Greg Beaver <cellog@php.net> - * @copyright 1997-2010 The Authors - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: PEAR.php 313023 2011-07-06 19:17:11Z dufuz $ - * @link http://pear.php.net/package/PEAR - * @since File available since Release 0.1 - */ - -/**#@+ - * ERROR constants - */ -define('PEAR_ERROR_RETURN', 1); -define('PEAR_ERROR_PRINT', 2); -define('PEAR_ERROR_TRIGGER', 4); -define('PEAR_ERROR_DIE', 8); -define('PEAR_ERROR_CALLBACK', 16); -/** - * WARNING: obsolete - * @deprecated - */ -define('PEAR_ERROR_EXCEPTION', 32); -/**#@-*/ -define('PEAR_ZE2', (function_exists('version_compare') && - version_compare(zend_version(), "2-dev", "ge"))); - -if (substr(PHP_OS, 0, 3) == 'WIN') { - define('OS_WINDOWS', true); - define('OS_UNIX', false); - define('PEAR_OS', 'Windows'); -} else { - define('OS_WINDOWS', false); - define('OS_UNIX', true); - define('PEAR_OS', 'Unix'); // blatant assumption -} - -$GLOBALS['_PEAR_default_error_mode'] = PEAR_ERROR_RETURN; -$GLOBALS['_PEAR_default_error_options'] = E_USER_NOTICE; -$GLOBALS['_PEAR_destructor_object_list'] = array(); -$GLOBALS['_PEAR_shutdown_funcs'] = array(); -$GLOBALS['_PEAR_error_handler_stack'] = array(); - -@ini_set('track_errors', true); - -/** - * Base class for other PEAR classes. Provides rudimentary - * emulation of destructors. - * - * If you want a destructor in your class, inherit PEAR and make a - * destructor method called _yourclassname (same name as the - * constructor, but with a "_" prefix). Also, in your constructor you - * have to call the PEAR constructor: $this->PEAR();. - * The destructor method will be called without parameters. Note that - * at in some SAPI implementations (such as Apache), any output during - * the request shutdown (in which destructors are called) seems to be - * discarded. If you need to get any debug information from your - * destructor, use error_log(), syslog() or something similar. - * - * IMPORTANT! To use the emulated destructors you need to create the - * objects by reference: $obj =& new PEAR_child; - * - * @category pear - * @package PEAR - * @author Stig Bakken <ssb@php.net> - * @author Tomas V.V. Cox <cox@idecnet.com> - * @author Greg Beaver <cellog@php.net> - * @copyright 1997-2006 The PHP Group - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 - * @link http://pear.php.net/package/PEAR - * @see PEAR_Error - * @since Class available since PHP 4.0.2 - * @link http://pear.php.net/manual/en/core.pear.php#core.pear.pear - */ -class PEAR -{ - /** - * Whether to enable internal debug messages. - * - * @var bool - * @access private - */ - var $_debug = false; - - /** - * Default error mode for this object. - * - * @var int - * @access private - */ - var $_default_error_mode = null; - - /** - * Default error options used for this object when error mode - * is PEAR_ERROR_TRIGGER. - * - * @var int - * @access private - */ - var $_default_error_options = null; - - /** - * Default error handler (callback) for this object, if error mode is - * PEAR_ERROR_CALLBACK. - * - * @var string - * @access private - */ - var $_default_error_handler = ''; - - /** - * Which class to use for error objects. - * - * @var string - * @access private - */ - var $_error_class = 'PEAR_Error'; - - /** - * An array of expected errors. - * - * @var array - * @access private - */ - var $_expected_errors = array(); - - /** - * Constructor. Registers this object in - * $_PEAR_destructor_object_list for destructor emulation if a - * destructor object exists. - * - * @param string $error_class (optional) which class to use for - * error objects, defaults to PEAR_Error. - * @access public - * @return void - */ - function PEAR($error_class = null) - { - $classname = strtolower(get_class($this)); - if ($this->_debug) { - print "PEAR constructor called, class=$classname\n"; - } - - if ($error_class !== null) { - $this->_error_class = $error_class; - } - - while ($classname && strcasecmp($classname, "pear")) { - $destructor = "_$classname"; - if (method_exists($this, $destructor)) { - global $_PEAR_destructor_object_list; - $_PEAR_destructor_object_list[] = &$this; - if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) { - register_shutdown_function("_PEAR_call_destructors"); - $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true; - } - break; - } else { - $classname = get_parent_class($classname); - } - } - } - - /** - * Destructor (the emulated type of...). Does nothing right now, - * but is included for forward compatibility, so subclass - * destructors should always call it. - * - * See the note in the class desciption about output from - * destructors. - * - * @access public - * @return void - */ - function _PEAR() { - if ($this->_debug) { - printf("PEAR destructor called, class=%s\n", strtolower(get_class($this))); - } - } - - /** - * If you have a class that's mostly/entirely static, and you need static - * properties, you can use this method to simulate them. Eg. in your method(s) - * do this: $myVar = &PEAR::getStaticProperty('myclass', 'myVar'); - * You MUST use a reference, or they will not persist! - * - * @access public - * @param string $class The calling classname, to prevent clashes - * @param string $var The variable to retrieve. - * @return mixed A reference to the variable. If not set it will be - * auto initialised to NULL. - */ - function &getStaticProperty($class, $var) - { - static $properties; - if (!isset($properties[$class])) { - $properties[$class] = array(); - } - - if (!array_key_exists($var, $properties[$class])) { - $properties[$class][$var] = null; - } - - return $properties[$class][$var]; - } - - /** - * Use this function to register a shutdown method for static - * classes. - * - * @access public - * @param mixed $func The function name (or array of class/method) to call - * @param mixed $args The arguments to pass to the function - * @return void - */ - function registerShutdownFunc($func, $args = array()) - { - // if we are called statically, there is a potential - // that no shutdown func is registered. Bug #6445 - if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) { - register_shutdown_function("_PEAR_call_destructors"); - $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true; - } - $GLOBALS['_PEAR_shutdown_funcs'][] = array($func, $args); - } - - /** - * Tell whether a value is a PEAR error. - * - * @param mixed $data the value to test - * @param int $code if $data is an error object, return true - * only if $code is a string and - * $obj->getMessage() == $code or - * $code is an integer and $obj->getCode() == $code - * @access public - * @return bool true if parameter is an error - */ - function isError($data, $code = null) - { - if (!is_a($data, 'PEAR_Error')) { - return false; - } - - if (is_null($code)) { - return true; - } elseif (is_string($code)) { - return $data->getMessage() == $code; - } - - return $data->getCode() == $code; - } - - /** - * Sets how errors generated by this object should be handled. - * Can be invoked both in objects and statically. If called - * statically, setErrorHandling sets the default behaviour for all - * PEAR objects. If called in an object, setErrorHandling sets - * the default behaviour for that object. - * - * @param int $mode - * One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT, - * PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE, - * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION. - * - * @param mixed $options - * When $mode is PEAR_ERROR_TRIGGER, this is the error level (one - * of E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR). - * - * When $mode is PEAR_ERROR_CALLBACK, this parameter is expected - * to be the callback function or method. A callback - * function is a string with the name of the function, a - * callback method is an array of two elements: the element - * at index 0 is the object, and the element at index 1 is - * the name of the method to call in the object. - * - * When $mode is PEAR_ERROR_PRINT or PEAR_ERROR_DIE, this is - * a printf format string used when printing the error - * message. - * - * @access public - * @return void - * @see PEAR_ERROR_RETURN - * @see PEAR_ERROR_PRINT - * @see PEAR_ERROR_TRIGGER - * @see PEAR_ERROR_DIE - * @see PEAR_ERROR_CALLBACK - * @see PEAR_ERROR_EXCEPTION - * - * @since PHP 4.0.5 - */ - function setErrorHandling($mode = null, $options = null) - { - if (isset($this) && is_a($this, 'PEAR')) { - $setmode = &$this->_default_error_mode; - $setoptions = &$this->_default_error_options; - } else { - $setmode = &$GLOBALS['_PEAR_default_error_mode']; - $setoptions = &$GLOBALS['_PEAR_default_error_options']; - } - - switch ($mode) { - case PEAR_ERROR_EXCEPTION: - case PEAR_ERROR_RETURN: - case PEAR_ERROR_PRINT: - case PEAR_ERROR_TRIGGER: - case PEAR_ERROR_DIE: - case null: - $setmode = $mode; - $setoptions = $options; - break; - - case PEAR_ERROR_CALLBACK: - $setmode = $mode; - // class/object method callback - if (is_callable($options)) { - $setoptions = $options; - } else { - trigger_error("invalid error callback", E_USER_WARNING); - } - break; - - default: - trigger_error("invalid error mode", E_USER_WARNING); - break; - } - } - - /** - * This method is used to tell which errors you expect to get. - * Expected errors are always returned with error mode - * PEAR_ERROR_RETURN. Expected error codes are stored in a stack, - * and this method pushes a new element onto it. The list of - * expected errors are in effect until they are popped off the - * stack with the popExpect() method. - * - * Note that this method can not be called statically - * - * @param mixed $code a single error code or an array of error codes to expect - * - * @return int the new depth of the "expected errors" stack - * @access public - */ - function expectError($code = '*') - { - if (is_array($code)) { - array_push($this->_expected_errors, $code); - } else { - array_push($this->_expected_errors, array($code)); - } - return count($this->_expected_errors); - } - - /** - * This method pops one element off the expected error codes - * stack. - * - * @return array the list of error codes that were popped - */ - function popExpect() - { - return array_pop($this->_expected_errors); - } - - /** - * This method checks unsets an error code if available - * - * @param mixed error code - * @return bool true if the error code was unset, false otherwise - * @access private - * @since PHP 4.3.0 - */ - function _checkDelExpect($error_code) - { - $deleted = false; - foreach ($this->_expected_errors as $key => $error_array) { - if (in_array($error_code, $error_array)) { - unset($this->_expected_errors[$key][array_search($error_code, $error_array)]); - $deleted = true; - } - - // clean up empty arrays - if (0 == count($this->_expected_errors[$key])) { - unset($this->_expected_errors[$key]); - } - } - - return $deleted; - } - - /** - * This method deletes all occurences of the specified element from - * the expected error codes stack. - * - * @param mixed $error_code error code that should be deleted - * @return mixed list of error codes that were deleted or error - * @access public - * @since PHP 4.3.0 - */ - function delExpect($error_code) - { - $deleted = false; - if ((is_array($error_code) && (0 != count($error_code)))) { - // $error_code is a non-empty array here; we walk through it trying - // to unset all values - foreach ($error_code as $key => $error) { - $deleted = $this->_checkDelExpect($error) ? true : false; - } - - return $deleted ? true : PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME - } elseif (!empty($error_code)) { - // $error_code comes alone, trying to unset it - if ($this->_checkDelExpect($error_code)) { - return true; - } - - return PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME - } - - // $error_code is empty - return PEAR::raiseError("The expected error you submitted is empty"); // IMPROVE ME - } - - /** - * This method is a wrapper that returns an instance of the - * configured error class with this object's default error - * handling applied. If the $mode and $options parameters are not - * specified, the object's defaults are used. - * - * @param mixed $message a text error message or a PEAR error object - * - * @param int $code a numeric error code (it is up to your class - * to define these if you want to use codes) - * - * @param int $mode One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT, - * PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE, - * PEAR_ERROR_CALLBACK, PEAR_ERROR_EXCEPTION. - * - * @param mixed $options If $mode is PEAR_ERROR_TRIGGER, this parameter - * specifies the PHP-internal error level (one of - * E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR). - * If $mode is PEAR_ERROR_CALLBACK, this - * parameter specifies the callback function or - * method. In other error modes this parameter - * is ignored. - * - * @param string $userinfo If you need to pass along for example debug - * information, this parameter is meant for that. - * - * @param string $error_class The returned error object will be - * instantiated from this class, if specified. - * - * @param bool $skipmsg If true, raiseError will only pass error codes, - * the error message parameter will be dropped. - * - * @access public - * @return object a PEAR error object - * @see PEAR::setErrorHandling - * @since PHP 4.0.5 - */ - function &raiseError($message = null, - $code = null, - $mode = null, - $options = null, - $userinfo = null, - $error_class = null, - $skipmsg = false) - { - // The error is yet a PEAR error object - if (is_object($message)) { - $code = $message->getCode(); - $userinfo = $message->getUserInfo(); - $error_class = $message->getType(); - $message->error_message_prefix = ''; - $message = $message->getMessage(); - } - - if ( - isset($this) && - isset($this->_expected_errors) && - count($this->_expected_errors) > 0 && - count($exp = end($this->_expected_errors)) - ) { - if ($exp[0] == "*" || - (is_int(reset($exp)) && in_array($code, $exp)) || - (is_string(reset($exp)) && in_array($message, $exp)) - ) { - $mode = PEAR_ERROR_RETURN; - } - } - - // No mode given, try global ones - if ($mode === null) { - // Class error handler - if (isset($this) && isset($this->_default_error_mode)) { - $mode = $this->_default_error_mode; - $options = $this->_default_error_options; - // Global error handler - } elseif (isset($GLOBALS['_PEAR_default_error_mode'])) { - $mode = $GLOBALS['_PEAR_default_error_mode']; - $options = $GLOBALS['_PEAR_default_error_options']; - } - } - - if ($error_class !== null) { - $ec = $error_class; - } elseif (isset($this) && isset($this->_error_class)) { - $ec = $this->_error_class; - } else { - $ec = 'PEAR_Error'; - } - - if (intval(PHP_VERSION) < 5) { - // little non-eval hack to fix bug #12147 - include 'PEAR/FixPHP5PEARWarnings.php'; - return $a; - } - - if ($skipmsg) { - $a = new $ec($code, $mode, $options, $userinfo); - } else { - $a = new $ec($message, $code, $mode, $options, $userinfo); - } - - return $a; - } - - /** - * Simpler form of raiseError with fewer options. In most cases - * message, code and userinfo are enough. - * - * @param mixed $message a text error message or a PEAR error object - * - * @param int $code a numeric error code (it is up to your class - * to define these if you want to use codes) - * - * @param string $userinfo If you need to pass along for example debug - * information, this parameter is meant for that. - * - * @access public - * @return object a PEAR error object - * @see PEAR::raiseError - */ - function &throwError($message = null, $code = null, $userinfo = null) - { - if (isset($this) && is_a($this, 'PEAR')) { - $a = &$this->raiseError($message, $code, null, null, $userinfo); - return $a; - } - - $a = &PEAR::raiseError($message, $code, null, null, $userinfo); - return $a; - } - - function staticPushErrorHandling($mode, $options = null) - { - $stack = &$GLOBALS['_PEAR_error_handler_stack']; - $def_mode = &$GLOBALS['_PEAR_default_error_mode']; - $def_options = &$GLOBALS['_PEAR_default_error_options']; - $stack[] = array($def_mode, $def_options); - switch ($mode) { - case PEAR_ERROR_EXCEPTION: - case PEAR_ERROR_RETURN: - case PEAR_ERROR_PRINT: - case PEAR_ERROR_TRIGGER: - case PEAR_ERROR_DIE: - case null: - $def_mode = $mode; - $def_options = $options; - break; - - case PEAR_ERROR_CALLBACK: - $def_mode = $mode; - // class/object method callback - if (is_callable($options)) { - $def_options = $options; - } else { - trigger_error("invalid error callback", E_USER_WARNING); - } - break; - - default: - trigger_error("invalid error mode", E_USER_WARNING); - break; - } - $stack[] = array($mode, $options); - return true; - } - - function staticPopErrorHandling() - { - $stack = &$GLOBALS['_PEAR_error_handler_stack']; - $setmode = &$GLOBALS['_PEAR_default_error_mode']; - $setoptions = &$GLOBALS['_PEAR_default_error_options']; - array_pop($stack); - list($mode, $options) = $stack[sizeof($stack) - 1]; - array_pop($stack); - switch ($mode) { - case PEAR_ERROR_EXCEPTION: - case PEAR_ERROR_RETURN: - case PEAR_ERROR_PRINT: - case PEAR_ERROR_TRIGGER: - case PEAR_ERROR_DIE: - case null: - $setmode = $mode; - $setoptions = $options; - break; - - case PEAR_ERROR_CALLBACK: - $setmode = $mode; - // class/object method callback - if (is_callable($options)) { - $setoptions = $options; - } else { - trigger_error("invalid error callback", E_USER_WARNING); - } - break; - - default: - trigger_error("invalid error mode", E_USER_WARNING); - break; - } - return true; - } - - /** - * Push a new error handler on top of the error handler options stack. With this - * you can easily override the actual error handler for some code and restore - * it later with popErrorHandling. - * - * @param mixed $mode (same as setErrorHandling) - * @param mixed $options (same as setErrorHandling) - * - * @return bool Always true - * - * @see PEAR::setErrorHandling - */ - function pushErrorHandling($mode, $options = null) - { - $stack = &$GLOBALS['_PEAR_error_handler_stack']; - if (isset($this) && is_a($this, 'PEAR')) { - $def_mode = &$this->_default_error_mode; - $def_options = &$this->_default_error_options; - } else { - $def_mode = &$GLOBALS['_PEAR_default_error_mode']; - $def_options = &$GLOBALS['_PEAR_default_error_options']; - } - $stack[] = array($def_mode, $def_options); - - if (isset($this) && is_a($this, 'PEAR')) { - $this->setErrorHandling($mode, $options); - } else { - PEAR::setErrorHandling($mode, $options); - } - $stack[] = array($mode, $options); - return true; - } - - /** - * Pop the last error handler used - * - * @return bool Always true - * - * @see PEAR::pushErrorHandling - */ - function popErrorHandling() - { - $stack = &$GLOBALS['_PEAR_error_handler_stack']; - array_pop($stack); - list($mode, $options) = $stack[sizeof($stack) - 1]; - array_pop($stack); - if (isset($this) && is_a($this, 'PEAR')) { - $this->setErrorHandling($mode, $options); - } else { - PEAR::setErrorHandling($mode, $options); - } - return true; - } - - /** - * OS independant PHP extension load. Remember to take care - * on the correct extension name for case sensitive OSes. - * - * @param string $ext The extension name - * @return bool Success or not on the dl() call - */ - function loadExtension($ext) - { - if (extension_loaded($ext)) { - return true; - } - - // if either returns true dl() will produce a FATAL error, stop that - if ( - function_exists('dl') === false || - ini_get('enable_dl') != 1 || - ini_get('safe_mode') == 1 - ) { - return false; - } - - if (OS_WINDOWS) { - $suffix = '.dll'; - } elseif (PHP_OS == 'HP-UX') { - $suffix = '.sl'; - } elseif (PHP_OS == 'AIX') { - $suffix = '.a'; - } elseif (PHP_OS == 'OSX') { - $suffix = '.bundle'; - } else { - $suffix = '.so'; - } - - return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix); - } -} - -if (PEAR_ZE2) { - include_once 'PEAR5.php'; -} - -function _PEAR_call_destructors() -{ - global $_PEAR_destructor_object_list; - if (is_array($_PEAR_destructor_object_list) && - sizeof($_PEAR_destructor_object_list)) - { - reset($_PEAR_destructor_object_list); - if (PEAR_ZE2) { - $destructLifoExists = PEAR5::getStaticProperty('PEAR', 'destructlifo'); - } else { - $destructLifoExists = PEAR::getStaticProperty('PEAR', 'destructlifo'); - } - - if ($destructLifoExists) { - $_PEAR_destructor_object_list = array_reverse($_PEAR_destructor_object_list); - } - - while (list($k, $objref) = each($_PEAR_destructor_object_list)) { - $classname = get_class($objref); - while ($classname) { - $destructor = "_$classname"; - if (method_exists($objref, $destructor)) { - $objref->$destructor(); - break; - } else { - $classname = get_parent_class($classname); - } - } - } - // Empty the object list to ensure that destructors are - // not called more than once. - $_PEAR_destructor_object_list = array(); - } - - // Now call the shutdown functions - if ( - isset($GLOBALS['_PEAR_shutdown_funcs']) && - is_array($GLOBALS['_PEAR_shutdown_funcs']) && - !empty($GLOBALS['_PEAR_shutdown_funcs']) - ) { - foreach ($GLOBALS['_PEAR_shutdown_funcs'] as $value) { - call_user_func_array($value[0], $value[1]); - } - } -} - -/** - * Standard PEAR error class for PHP 4 - * - * This class is supserseded by {@link PEAR_Exception} in PHP 5 - * - * @category pear - * @package PEAR - * @author Stig Bakken <ssb@php.net> - * @author Tomas V.V. Cox <cox@idecnet.com> - * @author Gregory Beaver <cellog@php.net> - * @copyright 1997-2006 The PHP Group - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 1.9.4 - * @link http://pear.php.net/manual/en/core.pear.pear-error.php - * @see PEAR::raiseError(), PEAR::throwError() - * @since Class available since PHP 4.0.2 - */ -class PEAR_Error -{ - var $error_message_prefix = ''; - var $mode = PEAR_ERROR_RETURN; - var $level = E_USER_NOTICE; - var $code = -1; - var $message = ''; - var $userinfo = ''; - var $backtrace = null; - - /** - * PEAR_Error constructor - * - * @param string $message message - * - * @param int $code (optional) error code - * - * @param int $mode (optional) error mode, one of: PEAR_ERROR_RETURN, - * PEAR_ERROR_PRINT, PEAR_ERROR_DIE, PEAR_ERROR_TRIGGER, - * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION - * - * @param mixed $options (optional) error level, _OR_ in the case of - * PEAR_ERROR_CALLBACK, the callback function or object/method - * tuple. - * - * @param string $userinfo (optional) additional user/debug info - * - * @access public - * - */ - function PEAR_Error($message = 'unknown error', $code = null, - $mode = null, $options = null, $userinfo = null) - { - if ($mode === null) { - $mode = PEAR_ERROR_RETURN; - } - $this->message = $message; - $this->code = $code; - $this->mode = $mode; - $this->userinfo = $userinfo; - - if (PEAR_ZE2) { - $skiptrace = PEAR5::getStaticProperty('PEAR_Error', 'skiptrace'); - } else { - $skiptrace = PEAR::getStaticProperty('PEAR_Error', 'skiptrace'); - } - - if (!$skiptrace) { - $this->backtrace = debug_backtrace(); - if (isset($this->backtrace[0]) && isset($this->backtrace[0]['object'])) { - unset($this->backtrace[0]['object']); - } - } - - if ($mode & PEAR_ERROR_CALLBACK) { - $this->level = E_USER_NOTICE; - $this->callback = $options; - } else { - if ($options === null) { - $options = E_USER_NOTICE; - } - - $this->level = $options; - $this->callback = null; - } - - if ($this->mode & PEAR_ERROR_PRINT) { - if (is_null($options) || is_int($options)) { - $format = "%s"; - } else { - $format = $options; - } - - printf($format, $this->getMessage()); - } - - if ($this->mode & PEAR_ERROR_TRIGGER) { - trigger_error($this->getMessage(), $this->level); - } - - if ($this->mode & PEAR_ERROR_DIE) { - $msg = $this->getMessage(); - if (is_null($options) || is_int($options)) { - $format = "%s"; - if (substr($msg, -1) != "\n") { - $msg .= "\n"; - } - } else { - $format = $options; - } - die(sprintf($format, $msg)); - } - - if ($this->mode & PEAR_ERROR_CALLBACK && is_callable($this->callback)) { - call_user_func($this->callback, $this); - } - - if ($this->mode & PEAR_ERROR_EXCEPTION) { - trigger_error("PEAR_ERROR_EXCEPTION is obsolete, use class PEAR_Exception for exceptions", E_USER_WARNING); - eval('$e = new Exception($this->message, $this->code);throw($e);'); - } - } - - /** - * Get the error mode from an error object. - * - * @return int error mode - * @access public - */ - function getMode() - { - return $this->mode; - } - - /** - * Get the callback function/method from an error object. - * - * @return mixed callback function or object/method array - * @access public - */ - function getCallback() - { - return $this->callback; - } - - /** - * Get the error message from an error object. - * - * @return string full error message - * @access public - */ - function getMessage() - { - return ($this->error_message_prefix . $this->message); - } - - /** - * Get error code from an error object - * - * @return int error code - * @access public - */ - function getCode() - { - return $this->code; - } - - /** - * Get the name of this error/exception. - * - * @return string error/exception name (type) - * @access public - */ - function getType() - { - return get_class($this); - } - - /** - * Get additional user-supplied information. - * - * @return string user-supplied information - * @access public - */ - function getUserInfo() - { - return $this->userinfo; - } - - /** - * Get additional debug information supplied by the application. - * - * @return string debug information - * @access public - */ - function getDebugInfo() - { - return $this->getUserInfo(); - } - - /** - * Get the call backtrace from where the error was generated. - * Supported with PHP 4.3.0 or newer. - * - * @param int $frame (optional) what frame to fetch - * @return array Backtrace, or NULL if not available. - * @access public - */ - function getBacktrace($frame = null) - { - if (defined('PEAR_IGNORE_BACKTRACE')) { - return null; - } - if ($frame === null) { - return $this->backtrace; - } - return $this->backtrace[$frame]; - } - - function addUserInfo($info) - { - if (empty($this->userinfo)) { - $this->userinfo = $info; - } else { - $this->userinfo .= " ** $info"; - } - } - - function __toString() - { - return $this->getMessage(); - } - - /** - * Make a string representation of this object. - * - * @return string a string with an object summary - * @access public - */ - function toString() - { - $modes = array(); - $levels = array(E_USER_NOTICE => 'notice', - E_USER_WARNING => 'warning', - E_USER_ERROR => 'error'); - if ($this->mode & PEAR_ERROR_CALLBACK) { - if (is_array($this->callback)) { - $callback = (is_object($this->callback[0]) ? - strtolower(get_class($this->callback[0])) : - $this->callback[0]) . '::' . - $this->callback[1]; - } else { - $callback = $this->callback; - } - return sprintf('[%s: message="%s" code=%d mode=callback '. - 'callback=%s prefix="%s" info="%s"]', - strtolower(get_class($this)), $this->message, $this->code, - $callback, $this->error_message_prefix, - $this->userinfo); - } - if ($this->mode & PEAR_ERROR_PRINT) { - $modes[] = 'print'; - } - if ($this->mode & PEAR_ERROR_TRIGGER) { - $modes[] = 'trigger'; - } - if ($this->mode & PEAR_ERROR_DIE) { - $modes[] = 'die'; - } - if ($this->mode & PEAR_ERROR_RETURN) { - $modes[] = 'return'; - } - return sprintf('[%s: message="%s" code=%d mode=%s level=%s '. - 'prefix="%s" info="%s"]', - strtolower(get_class($this)), $this->message, $this->code, - implode("|", $modes), $levels[$this->level], - $this->error_message_prefix, - $this->userinfo); - } -} - -/* - * Local Variables: - * mode: php - * tab-width: 4 - * c-basic-offset: 4 - * End: - */ diff --git a/web/lib/Archive/PEAR5.php b/web/lib/Archive/PEAR5.php deleted file mode 100644 index 5cee090..0000000 --- a/web/lib/Archive/PEAR5.php +++ /dev/null @@ -1,33 +0,0 @@ -<?php -/** - * This is only meant for PHP 5 to get rid of certain strict warning - * that doesn't get hidden since it's in the shutdown function - */ -class PEAR5 -{ - /** - * If you have a class that's mostly/entirely static, and you need static - * properties, you can use this method to simulate them. Eg. in your method(s) - * do this: $myVar = &PEAR5::getStaticProperty('myclass', 'myVar'); - * You MUST use a reference, or they will not persist! - * - * @access public - * @param string $class The calling classname, to prevent clashes - * @param string $var The variable to retrieve. - * @return mixed A reference to the variable. If not set it will be - * auto initialised to NULL. - */ - static function &getStaticProperty($class, $var) - { - static $properties; - if (!isset($properties[$class])) { - $properties[$class] = array(); - } - - if (!array_key_exists($var, $properties[$class])) { - $properties[$class][$var] = null; - } - - return $properties[$class][$var]; - } -} diff --git a/web/lib/Archive/Tar.php b/web/lib/Archive/Tar.php deleted file mode 100644 index 32a2ccc..0000000 --- a/web/lib/Archive/Tar.php +++ /dev/null @@ -1,1993 +0,0 @@ -<?php -/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ - -/** - * File::CSV - * - * PHP versions 4 and 5 - * - * Copyright (c) 1997-2008, - * Vincent Blavet <vincent@phpconcept.net> - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * @category File_Formats - * @package Archive_Tar - * @author Vincent Blavet <vincent@phpconcept.net> - * @copyright 1997-2010 The Authors - * @license http://www.opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id$ - * @link http://pear.php.net/package/Archive_Tar - */ - -require_once 'PEAR.php'; - -define('ARCHIVE_TAR_ATT_SEPARATOR', 90001); -define('ARCHIVE_TAR_END_BLOCK', pack("a512", '')); - -/** -* Creates a (compressed) Tar archive -* -* @package Archive_Tar -* @author Vincent Blavet <vincent@phpconcept.net> -* @license http://www.opensource.org/licenses/bsd-license.php New BSD License -* @version $Revision$ -*/ -class Archive_Tar extends PEAR -{ - /** - * @var string Name of the Tar - */ - var $_tarname=''; - - /** - * @var boolean if true, the Tar file will be gzipped - */ - var $_compress=false; - - /** - * @var string Type of compression : 'none', 'gz' or 'bz2' - */ - var $_compress_type='none'; - - /** - * @var string Explode separator - */ - var $_separator=' '; - - /** - * @var file descriptor - */ - var $_file=0; - - /** - * @var string Local Tar name of a remote Tar (http:// or ftp://) - */ - var $_temp_tarname=''; - - /** - * @var string regular expression for ignoring files or directories - */ - var $_ignore_regexp=''; - - /** - * @var object PEAR_Error object - */ - var $error_object=null; - - // {{{ constructor - /** - * Archive_Tar Class constructor. This flavour of the constructor only - * declare a new Archive_Tar object, identifying it by the name of the - * tar file. - * If the compress argument is set the tar will be read or created as a - * gzip or bz2 compressed TAR file. - * - * @param string $p_tarname The name of the tar archive to create - * @param string $p_compress can be null, 'gz' or 'bz2'. This - * parameter indicates if gzip or bz2 compression - * is required. For compatibility reason the - * boolean value 'true' means 'gz'. - * - * @access public - */ - function Archive_Tar($p_tarname, $p_compress = null) - { - $this->PEAR(); - $this->_compress = false; - $this->_compress_type = 'none'; - if (($p_compress === null) || ($p_compress == '')) { - if (@file_exists($p_tarname)) { - if ($fp = @fopen($p_tarname, "rb")) { - // look for gzip magic cookie - $data = fread($fp, 2); - fclose($fp); - if ($data == "\37\213") { - $this->_compress = true; - $this->_compress_type = 'gz'; - // No sure it's enought for a magic code .... - } elseif ($data == "BZ") { - $this->_compress = true; - $this->_compress_type = 'bz2'; - } - } - } else { - // probably a remote file or some file accessible - // through a stream interface - if (substr($p_tarname, -2) == 'gz') { - $this->_compress = true; - $this->_compress_type = 'gz'; - } elseif ((substr($p_tarname, -3) == 'bz2') || - (substr($p_tarname, -2) == 'bz')) { - $this->_compress = true; - $this->_compress_type = 'bz2'; - } - } - } else { - if (($p_compress === true) || ($p_compress == 'gz')) { - $this->_compress = true; - $this->_compress_type = 'gz'; - } else if ($p_compress == 'bz2') { - $this->_compress = true; - $this->_compress_type = 'bz2'; - } else { - $this->_error("Unsupported compression type '$p_compress'\n". - "Supported types are 'gz' and 'bz2'.\n"); - return false; - } - } - $this->_tarname = $p_tarname; - if ($this->_compress) { // assert zlib or bz2 extension support - if ($this->_compress_type == 'gz') - $extname = 'zlib'; - else if ($this->_compress_type == 'bz2') - $extname = 'bz2'; - - if (!extension_loaded($extname)) { - PEAR::loadExtension($extname); - } - if (!extension_loaded($extname)) { - $this->_error("The extension '$extname' couldn't be found.\n". - "Please make sure your version of PHP was built ". - "with '$extname' support.\n"); - return false; - } - } - } - // }}} - - // {{{ destructor - function _Archive_Tar() - { - $this->_close(); - // ----- Look for a local copy to delete - if ($this->_temp_tarname != '') - @unlink($this->_temp_tarname); - $this->_PEAR(); - } - // }}} - - // {{{ create() - /** - * This method creates the archive file and add the files / directories - * that are listed in $p_filelist. - * If a file with the same name exist and is writable, it is replaced - * by the new tar. - * The method return false and a PEAR error text. - * The $p_filelist parameter can be an array of string, each string - * representing a filename or a directory name with their path if - * needed. It can also be a single string with names separated by a - * single blank. - * For each directory added in the archive, the files and - * sub-directories are also added. - * See also createModify() method for more details. - * - * @param array $p_filelist An array of filenames and directory names, or a - * single string with names separated by a single - * blank space. - * - * @return true on success, false on error. - * @see createModify() - * @access public - */ - function create($p_filelist) - { - return $this->createModify($p_filelist, '', ''); - } - // }}} - - // {{{ add() - /** - * This method add the files / directories that are listed in $p_filelist in - * the archive. If the archive does not exist it is created. - * The method return false and a PEAR error text. - * The files and directories listed are only added at the end of the archive, - * even if a file with the same name is already archived. - * See also createModify() method for more details. - * - * @param array $p_filelist An array of filenames and directory names, or a - * single string with names separated by a single - * blank space. - * - * @return true on success, false on error. - * @see createModify() - * @access public - */ - function add($p_filelist) - { - return $this->addModify($p_filelist, '', ''); - } - // }}} - - // {{{ extract() - function extract($p_path='', $p_preserve=false) - { - return $this->extractModify($p_path, '', $p_preserve); - } - // }}} - - // {{{ listContent() - function listContent() - { - $v_list_detail = array(); - - if ($this->_openRead()) { - if (!$this->_extractList('', $v_list_detail, "list", '', '')) { - unset($v_list_detail); - $v_list_detail = 0; - } - $this->_close(); - } - - return $v_list_detail; - } - // }}} - - // {{{ createModify() - /** - * This method creates the archive file and add the files / directories - * that are listed in $p_filelist. - * If the file already exists and is writable, it is replaced by the - * new tar. It is a create and not an add. If the file exists and is - * read-only or is a directory it is not replaced. The method return - * false and a PEAR error text. - * The $p_filelist parameter can be an array of string, each string - * representing a filename or a directory name with their path if - * needed. It can also be a single string with names separated by a - * single blank. - * The path indicated in $p_remove_dir will be removed from the - * memorized path of each file / directory listed when this path - * exists. By default nothing is removed (empty path '') - * The path indicated in $p_add_dir will be added at the beginning of - * the memorized path of each file / directory listed. However it can - * be set to empty ''. The adding of a path is done after the removing - * of path. - * The path add/remove ability enables the user to prepare an archive - * for extraction in a different path than the origin files are. - * See also addModify() method for file adding properties. - * - * @param array $p_filelist An array of filenames and directory names, - * or a single string with names separated by - * a single blank space. - * @param string $p_add_dir A string which contains a path to be added - * to the memorized path of each element in - * the list. - * @param string $p_remove_dir A string which contains a path to be - * removed from the memorized path of each - * element in the list, when relevant. - * - * @return boolean true on success, false on error. - * @access public - * @see addModify() - */ - function createModify($p_filelist, $p_add_dir, $p_remove_dir='') - { - $v_result = true; - - if (!$this->_openWrite()) - return false; - - if ($p_filelist != '') { - if (is_array($p_filelist)) - $v_list = $p_filelist; - elseif (is_string($p_filelist)) - $v_list = explode($this->_separator, $p_filelist); - else { - $this->_cleanFile(); - $this->_error('Invalid file list'); - return false; - } - - $v_result = $this->_addList($v_list, $p_add_dir, $p_remove_dir); - } - - if ($v_result) { - $this->_writeFooter(); - $this->_close(); - } else - $this->_cleanFile(); - - return $v_result; - } - // }}} - - // {{{ addModify() - /** - * This method add the files / directories listed in $p_filelist at the - * end of the existing archive. If the archive does not yet exists it - * is created. - * The $p_filelist parameter can be an array of string, each string - * representing a filename or a directory name with their path if - * needed. It can also be a single string with names separated by a - * single blank. - * The path indicated in $p_remove_dir will be removed from the - * memorized path of each file / directory listed when this path - * exists. By default nothing is removed (empty path '') - * The path indicated in $p_add_dir will be added at the beginning of - * the memorized path of each file / directory listed. However it can - * be set to empty ''. The adding of a path is done after the removing - * of path. - * The path add/remove ability enables the user to prepare an archive - * for extraction in a different path than the origin files are. - * If a file/dir is already in the archive it will only be added at the - * end of the archive. There is no update of the existing archived - * file/dir. However while extracting the archive, the last file will - * replace the first one. This results in a none optimization of the - * archive size. - * If a file/dir does not exist the file/dir is ignored. However an - * error text is send to PEAR error. - * If a file/dir is not readable the file/dir is ignored. However an - * error text is send to PEAR error. - * - * @param array $p_filelist An array of filenames and directory - * names, or a single string with names - * separated by a single blank space. - * @param string $p_add_dir A string which contains a path to be - * added to the memorized path of each - * element in the list. - * @param string $p_remove_dir A string which contains a path to be - * removed from the memorized path of - * each element in the list, when - * relevant. - * - * @return true on success, false on error. - * @access public - */ - function addModify($p_filelist, $p_add_dir, $p_remove_dir='') - { - $v_result = true; - - if (!$this->_isArchive()) - $v_result = $this->createModify($p_filelist, $p_add_dir, - $p_remove_dir); - else { - if (is_array($p_filelist)) - $v_list = $p_filelist; - elseif (is_string($p_filelist)) - $v_list = explode($this->_separator, $p_filelist); - else { - $this->_error('Invalid file list'); - return false; - } - - $v_result = $this->_append($v_list, $p_add_dir, $p_remove_dir); - } - - return $v_result; - } - // }}} - - // {{{ addString() - /** - * This method add a single string as a file at the - * end of the existing archive. If the archive does not yet exists it - * is created. - * - * @param string $p_filename A string which contains the full - * filename path that will be associated - * with the string. - * @param string $p_string The content of the file added in - * the archive. - * @param int $p_datetime A custom date/time (unix timestamp) - * for the file (optional). - * - * @return true on success, false on error. - * @access public - */ - function addString($p_filename, $p_string, $p_datetime = false) - { - $v_result = true; - - if (!$this->_isArchive()) { - if (!$this->_openWrite()) { - return false; - } - $this->_close(); - } - - if (!$this->_openAppend()) - return false; - - // Need to check the get back to the temporary file ? .... - $v_result = $this->_addString($p_filename, $p_string, $p_datetime); - - $this->_writeFooter(); - - $this->_close(); - - return $v_result; - } - // }}} - - // {{{ extractModify() - /** - * This method extract all the content of the archive in the directory - * indicated by $p_path. When relevant the memorized path of the - * files/dir can be modified by removing the $p_remove_path path at the - * beginning of the file/dir path. - * While extracting a file, if the directory path does not exists it is - * created. - * While extracting a file, if the file already exists it is replaced - * without looking for last modification date. - * While extracting a file, if the file already exists and is write - * protected, the extraction is aborted. - * While extracting a file, if a directory with the same name already - * exists, the extraction is aborted. - * While extracting a directory, if a file with the same name already - * exists, the extraction is aborted. - * While extracting a file/directory if the destination directory exist - * and is write protected, or does not exist but can not be created, - * the extraction is aborted. - * If after extraction an extracted file does not show the correct - * stored file size, the extraction is aborted. - * When the extraction is aborted, a PEAR error text is set and false - * is returned. However the result can be a partial extraction that may - * need to be manually cleaned. - * - * @param string $p_path The path of the directory where the - * files/dir need to by extracted. - * @param string $p_remove_path Part of the memorized path that can be - * removed if present at the beginning of - * the file/dir path. - * @param boolean $p_preserve Preserve user/group ownership of files - * - * @return boolean true on success, false on error. - * @access public - * @see extractList() - */ - function extractModify($p_path, $p_remove_path, $p_preserve=false) - { - $v_result = true; - $v_list_detail = array(); - - if ($v_result = $this->_openRead()) { - $v_result = $this->_extractList($p_path, $v_list_detail, - "complete", 0, $p_remove_path, $p_preserve); - $this->_close(); - } - - return $v_result; - } - // }}} - - // {{{ extractInString() - /** - * This method extract from the archive one file identified by $p_filename. - * The return value is a string with the file content, or NULL on error. - * - * @param string $p_filename The path of the file to extract in a string. - * - * @return a string with the file content or NULL. - * @access public - */ - function extractInString($p_filename) - { - if ($this->_openRead()) { - $v_result = $this->_extractInString($p_filename); - $this->_close(); - } else { - $v_result = null; - } - - return $v_result; - } - // }}} - - // {{{ extractList() - /** - * This method extract from the archive only the files indicated in the - * $p_filelist. These files are extracted in the current directory or - * in the directory indicated by the optional $p_path parameter. - * If indicated the $p_remove_path can be used in the same way as it is - * used in extractModify() method. - * - * @param array $p_filelist An array of filenames and directory names, - * or a single string with names separated - * by a single blank space. - * @param string $p_path The path of the directory where the - * files/dir need to by extracted. - * @param string $p_remove_path Part of the memorized path that can be - * removed if present at the beginning of - * the file/dir path. - * @param boolean $p_preserve Preserve user/group ownership of files - * - * @return true on success, false on error. - * @access public - * @see extractModify() - */ - function extractList($p_filelist, $p_path='', $p_remove_path='', $p_preserve=false) - { - $v_result = true; - $v_list_detail = array(); - - if (is_array($p_filelist)) - $v_list = $p_filelist; - elseif (is_string($p_filelist)) - $v_list = explode($this->_separator, $p_filelist); - else { - $this->_error('Invalid string list'); - return false; - } - - if ($v_result = $this->_openRead()) { - $v_result = $this->_extractList($p_path, $v_list_detail, "partial", - $v_list, $p_remove_path, $p_preserve); - $this->_close(); - } - - return $v_result; - } - // }}} - - // {{{ setAttribute() - /** - * This method set specific attributes of the archive. It uses a variable - * list of parameters, in the format attribute code + attribute values : - * $arch->setAttribute(ARCHIVE_TAR_ATT_SEPARATOR, ','); - * - * @param mixed $argv variable list of attributes and values - * - * @return true on success, false on error. - * @access public - */ - function setAttribute() - { - $v_result = true; - - // ----- Get the number of variable list of arguments - if (($v_size = func_num_args()) == 0) { - return true; - } - - // ----- Get the arguments - $v_att_list = &func_get_args(); - - // ----- Read the attributes - $i=0; - while ($i<$v_size) { - - // ----- Look for next option - switch ($v_att_list[$i]) { - // ----- Look for options that request a string value - case ARCHIVE_TAR_ATT_SEPARATOR : - // ----- Check the number of parameters - if (($i+1) >= $v_size) { - $this->_error('Invalid number of parameters for ' - .'attribute ARCHIVE_TAR_ATT_SEPARATOR'); - return false; - } - - // ----- Get the value - $this->_separator = $v_att_list[$i+1]; - $i++; - break; - - default : - $this->_error('Unknow attribute code '.$v_att_list[$i].''); - return false; - } - - // ----- Next attribute - $i++; - } - - return $v_result; - } - // }}} - - // {{{ setIgnoreRegexp() - /** - * This method sets the regular expression for ignoring files and directories - * at import, for example: - * $arch->setIgnoreRegexp("#CVS|\.svn#"); - * - * @param string $regexp regular expression defining which files or directories to ignore - * - * @access public - */ - function setIgnoreRegexp($regexp) - { - $this->_ignore_regexp = $regexp; - } - // }}} - - // {{{ setIgnoreList() - /** - * This method sets the regular expression for ignoring all files and directories - * matching the filenames in the array list at import, for example: - * $arch->setIgnoreList(array('CVS', '.svn', 'bin/tool')); - * - * @param array $list a list of file or directory names to ignore - * - * @access public - */ - function setIgnoreList($list) - { - $regexp = str_replace(array('#', '.', '^', '$'), array('\#', '\.', '\^', '\$'), $list); - $regexp = '#/'.join('$|/', $list).'#'; - $this->setIgnoreRegexp($regexp); - } - // }}} - - // {{{ _error() - function _error($p_message) - { - $this->error_object = &$this->raiseError($p_message); - } - // }}} - - // {{{ _warning() - function _warning($p_message) - { - $this->error_object = &$this->raiseError($p_message); - } - // }}} - - // {{{ _isArchive() - function _isArchive($p_filename=null) - { - if ($p_filename == null) { - $p_filename = $this->_tarname; - } - clearstatcache(); - return @is_file($p_filename) && !@is_link($p_filename); - } - // }}} - - // {{{ _openWrite() - function _openWrite() - { - if ($this->_compress_type == 'gz' && function_exists('gzopen')) - $this->_file = @gzopen($this->_tarname, "wb9"); - else if ($this->_compress_type == 'bz2' && function_exists('bzopen')) - $this->_file = @bzopen($this->_tarname, "w"); - else if ($this->_compress_type == 'none') - $this->_file = @fopen($this->_tarname, "wb"); - else { - $this->_error('Unknown or missing compression type (' - .$this->_compress_type.')'); - return false; - } - - if ($this->_file == 0) { - $this->_error('Unable to open in write mode \'' - .$this->_tarname.'\''); - return false; - } - - return true; - } - // }}} - - // {{{ _openRead() - function _openRead() - { - if (strtolower(substr($this->_tarname, 0, 7)) == 'http://') { - - // ----- Look if a local copy need to be done - if ($this->_temp_tarname == '') { - $this->_temp_tarname = uniqid('tar').'.tmp'; - if (!$v_file_from = @fopen($this->_tarname, 'rb')) { - $this->_error('Unable to open in read mode \'' - .$this->_tarname.'\''); - $this->_temp_tarname = ''; - return false; - } - if (!$v_file_to = @fopen($this->_temp_tarname, 'wb')) { - $this->_error('Unable to open in write mode \'' - .$this->_temp_tarname.'\''); - $this->_temp_tarname = ''; - return false; - } - while ($v_data = @fread($v_file_from, 1024)) - @fwrite($v_file_to, $v_data); - @fclose($v_file_from); - @fclose($v_file_to); - } - - // ----- File to open if the local copy - $v_filename = $this->_temp_tarname; - - } else - // ----- File to open if the normal Tar file - $v_filename = $this->_tarname; - - if ($this->_compress_type == 'gz' && function_exists('gzopen')) - $this->_file = @gzopen($v_filename, "rb"); - else if ($this->_compress_type == 'bz2' && function_exists('bzopen')) - $this->_file = @bzopen($v_filename, "r"); - else if ($this->_compress_type == 'none') - $this->_file = @fopen($v_filename, "rb"); - else { - $this->_error('Unknown or missing compression type (' - .$this->_compress_type.')'); - return false; - } - - if ($this->_file == 0) { - $this->_error('Unable to open in read mode \''.$v_filename.'\''); - return false; - } - - return true; - } - // }}} - - // {{{ _openReadWrite() - function _openReadWrite() - { - if ($this->_compress_type == 'gz') - $this->_file = @gzopen($this->_tarname, "r+b"); - else if ($this->_compress_type == 'bz2') { - $this->_error('Unable to open bz2 in read/write mode \'' - .$this->_tarname.'\' (limitation of bz2 extension)'); - return false; - } else if ($this->_compress_type == 'none') - $this->_file = @fopen($this->_tarname, "r+b"); - else { - $this->_error('Unknown or missing compression type (' - .$this->_compress_type.')'); - return false; - } - - if ($this->_file == 0) { - $this->_error('Unable to open in read/write mode \'' - .$this->_tarname.'\''); - return false; - } - - return true; - } - // }}} - - // {{{ _close() - function _close() - { - //if (isset($this->_file)) { - if (is_resource($this->_file)) { - if ($this->_compress_type == 'gz') - @gzclose($this->_file); - else if ($this->_compress_type == 'bz2') - @bzclose($this->_file); - else if ($this->_compress_type == 'none') - @fclose($this->_file); - else - $this->_error('Unknown or missing compression type (' - .$this->_compress_type.')'); - - $this->_file = 0; - } - - // ----- Look if a local copy need to be erase - // Note that it might be interesting to keep the url for a time : ToDo - if ($this->_temp_tarname != '') { - @unlink($this->_temp_tarname); - $this->_temp_tarname = ''; - } - - return true; - } - // }}} - - // {{{ _cleanFile() - function _cleanFile() - { - $this->_close(); - - // ----- Look for a local copy - if ($this->_temp_tarname != '') { - // ----- Remove the local copy but not the remote tarname - @unlink($this->_temp_tarname); - $this->_temp_tarname = ''; - } else { - // ----- Remove the local tarname file - @unlink($this->_tarname); - } - $this->_tarname = ''; - - return true; - } - // }}} - - // {{{ _writeBlock() - function _writeBlock($p_binary_data, $p_len=null) - { - if (is_resource($this->_file)) { - if ($p_len === null) { - if ($this->_compress_type == 'gz') - @gzputs($this->_file, $p_binary_data); - else if ($this->_compress_type == 'bz2') - @bzwrite($this->_file, $p_binary_data); - else if ($this->_compress_type == 'none') - @fputs($this->_file, $p_binary_data); - else - $this->_error('Unknown or missing compression type (' - .$this->_compress_type.')'); - } else { - if ($this->_compress_type == 'gz') - @gzputs($this->_file, $p_binary_data, $p_len); - else if ($this->_compress_type == 'bz2') - @bzwrite($this->_file, $p_binary_data, $p_len); - else if ($this->_compress_type == 'none') - @fputs($this->_file, $p_binary_data, $p_len); - else - $this->_error('Unknown or missing compression type (' - .$this->_compress_type.')'); - - } - } - return true; - } - // }}} - - // {{{ _readBlock() - function _readBlock() - { - $v_block = null; - if (is_resource($this->_file)) { - if ($this->_compress_type == 'gz') - $v_block = @gzread($this->_file, 512); - else if ($this->_compress_type == 'bz2') - $v_block = @bzread($this->_file, 512); - else if ($this->_compress_type == 'none') - $v_block = @fread($this->_file, 512); - else - $this->_error('Unknown or missing compression type (' - .$this->_compress_type.')'); - } - return $v_block; - } - // }}} - - // {{{ _jumpBlock() - function _jumpBlock($p_len=null) - { - if (is_resource($this->_file)) { - if ($p_len === null) - $p_len = 1; - - if ($this->_compress_type == 'gz') { - @gzseek($this->_file, gztell($this->_file)+($p_len*512)); - } - else if ($this->_compress_type == 'bz2') { - // ----- Replace missing bztell() and bzseek() - for ($i=0; $i<$p_len; $i++) - $this->_readBlock(); - } else if ($this->_compress_type == 'none') - @fseek($this->_file, $p_len*512, SEEK_CUR); - else - $this->_error('Unknown or missing compression type (' - .$this->_compress_type.')'); - - } - return true; - } - // }}} - - // {{{ _writeFooter() - function _writeFooter() - { - if (is_resource($this->_file)) { - // ----- Write the last 0 filled block for end of archive - $v_binary_data = pack('a1024', ''); - $this->_writeBlock($v_binary_data); - } - return true; - } - // }}} - - // {{{ _addList() - function _addList($p_list, $p_add_dir, $p_remove_dir) - { - $v_result=true; - $v_header = array(); - - // ----- Remove potential windows directory separator - $p_add_dir = $this->_translateWinPath($p_add_dir); - $p_remove_dir = $this->_translateWinPath($p_remove_dir, false); - - if (!$this->_file) { - $this->_error('Invalid file descriptor'); - return false; - } - - if (sizeof($p_list) == 0) - return true; - - foreach ($p_list as $v_filename) { - if (!$v_result) { - break; - } - - // ----- Skip the current tar name - if ($v_filename == $this->_tarname) - continue; - - if ($v_filename == '') - continue; - - // ----- ignore files and directories matching the ignore regular expression - if ($this->_ignore_regexp && preg_match($this->_ignore_regexp, '/'.$v_filename)) { - $this->_warning("File '$v_filename' ignored"); - continue; - } - - if (!file_exists($v_filename) && !is_link($v_filename)) { - $this->_warning("File '$v_filename' does not exist"); - continue; - } - - // ----- Add the file or directory header - if (!$this->_addFile($v_filename, $v_header, $p_add_dir, $p_remove_dir)) - return false; - - if (@is_dir($v_filename) && !@is_link($v_filename)) { - if (!($p_hdir = opendir($v_filename))) { - $this->_warning("Directory '$v_filename' can not be read"); - continue; - } - while (false !== ($p_hitem = readdir($p_hdir))) { - if (($p_hitem != '.') && ($p_hitem != '..')) { - if ($v_filename != ".") - $p_temp_list[0] = $v_filename.'/'.$p_hitem; - else - $p_temp_list[0] = $p_hitem; - - $v_result = $this->_addList($p_temp_list, - $p_add_dir, - $p_remove_dir); - } - } - - unset($p_temp_list); - unset($p_hdir); - unset($p_hitem); - } - } - - return $v_result; - } - // }}} - - // {{{ _addFile() - function _addFile($p_filename, &$p_header, $p_add_dir, $p_remove_dir) - { - if (!$this->_file) { - $this->_error('Invalid file descriptor'); - return false; - } - - if ($p_filename == '') { - $this->_error('Invalid file name'); - return false; - } - - // ----- Calculate the stored filename - $p_filename = $this->_translateWinPath($p_filename, false);; - $v_stored_filename = $p_filename; - if (strcmp($p_filename, $p_remove_dir) == 0) { - return true; - } - if ($p_remove_dir != '') { - if (substr($p_remove_dir, -1) != '/') - $p_remove_dir .= '/'; - - if (substr($p_filename, 0, strlen($p_remove_dir)) == $p_remove_dir) - $v_stored_filename = substr($p_filename, strlen($p_remove_dir)); - } - $v_stored_filename = $this->_translateWinPath($v_stored_filename); - if ($p_add_dir != '') { - if (substr($p_add_dir, -1) == '/') - $v_stored_filename = $p_add_dir.$v_stored_filename; - else - $v_stored_filename = $p_add_dir.'/'.$v_stored_filename; - } - - $v_stored_filename = $this->_pathReduction($v_stored_filename); - - if ($this->_isArchive($p_filename)) { - if (($v_file = @fopen($p_filename, "rb")) == 0) { - $this->_warning("Unable to open file '".$p_filename - ."' in binary read mode"); - return true; - } - - if (!$this->_writeHeader($p_filename, $v_stored_filename)) - return false; - - while (($v_buffer = fread($v_file, 512)) != '') { - $v_binary_data = pack("a512", "$v_buffer"); - $this->_writeBlock($v_binary_data); - } - - fclose($v_file); - - } else { - // ----- Only header for dir - if (!$this->_writeHeader($p_filename, $v_stored_filename)) - return false; - } - - return true; - } - // }}} - - // {{{ _addString() - function _addString($p_filename, $p_string, $p_datetime = false) - { - if (!$this->_file) { - $this->_error('Invalid file descriptor'); - return false; - } - - if ($p_filename == '') { - $this->_error('Invalid file name'); - return false; - } - - // ----- Calculate the stored filename - $p_filename = $this->_translateWinPath($p_filename, false);; - - // ----- If datetime is not specified, set current time - if ($p_datetime === false) { - $p_datetime = time(); - } - - if (!$this->_writeHeaderBlock($p_filename, strlen($p_string), - $p_datetime, 384, "", 0, 0)) - return false; - - $i=0; - while (($v_buffer = substr($p_string, (($i++)*512), 512)) != '') { - $v_binary_data = pack("a512", $v_buffer); - $this->_writeBlock($v_binary_data); - } - - return true; - } - // }}} - - // {{{ _writeHeader() - function _writeHeader($p_filename, $p_stored_filename) - { - if ($p_stored_filename == '') - $p_stored_filename = $p_filename; - $v_reduce_filename = $this->_pathReduction($p_stored_filename); - - if (strlen($v_reduce_filename) > 99) { - if (!$this->_writeLongHeader($v_reduce_filename)) - return false; - } - - $v_info = lstat($p_filename); - $v_uid = sprintf("%07s", DecOct($v_info[4])); - $v_gid = sprintf("%07s", DecOct($v_info[5])); - $v_perms = sprintf("%07s", DecOct($v_info['mode'] & 000777)); - - $v_mtime = sprintf("%011s", DecOct($v_info['mtime'])); - - $v_linkname = ''; - - if (@is_link($p_filename)) { - $v_typeflag = '2'; - $v_linkname = readlink($p_filename); - $v_size = sprintf("%011s", DecOct(0)); - } elseif (@is_dir($p_filename)) { - $v_typeflag = "5"; - $v_size = sprintf("%011s", DecOct(0)); - } else { - $v_typeflag = '0'; - clearstatcache(); - $v_size = sprintf("%011s", DecOct($v_info['size'])); - } - - $v_magic = 'ustar '; - - $v_version = ' '; - - if (function_exists('posix_getpwuid')) - { - $userinfo = posix_getpwuid($v_info[4]); - $groupinfo = posix_getgrgid($v_info[5]); - - $v_uname = $userinfo['name']; - $v_gname = $groupinfo['name']; - } - else - { - $v_uname = ''; - $v_gname = ''; - } - - $v_devmajor = ''; - - $v_devminor = ''; - - $v_prefix = ''; - - $v_binary_data_first = pack("a100a8a8a8a12a12", - $v_reduce_filename, $v_perms, $v_uid, - $v_gid, $v_size, $v_mtime); - $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12", - $v_typeflag, $v_linkname, $v_magic, - $v_version, $v_uname, $v_gname, - $v_devmajor, $v_devminor, $v_prefix, ''); - - // ----- Calculate the checksum - $v_checksum = 0; - // ..... First part of the header - for ($i=0; $i<148; $i++) - $v_checksum += ord(substr($v_binary_data_first,$i,1)); - // ..... Ignore the checksum value and replace it by ' ' (space) - for ($i=148; $i<156; $i++) - $v_checksum += ord(' '); - // ..... Last part of the header - for ($i=156, $j=0; $i<512; $i++, $j++) - $v_checksum += ord(substr($v_binary_data_last,$j,1)); - - // ----- Write the first 148 bytes of the header in the archive - $this->_writeBlock($v_binary_data_first, 148); - - // ----- Write the calculated checksum - $v_checksum = sprintf("%06s ", DecOct($v_checksum)); - $v_binary_data = pack("a8", $v_checksum); - $this->_writeBlock($v_binary_data, 8); - - // ----- Write the last 356 bytes of the header in the archive - $this->_writeBlock($v_binary_data_last, 356); - - return true; - } - // }}} - - // {{{ _writeHeaderBlock() - function _writeHeaderBlock($p_filename, $p_size, $p_mtime=0, $p_perms=0, - $p_type='', $p_uid=0, $p_gid=0) - { - $p_filename = $this->_pathReduction($p_filename); - - if (strlen($p_filename) > 99) { - if (!$this->_writeLongHeader($p_filename)) - return false; - } - - if ($p_type == "5") { - $v_size = sprintf("%011s", DecOct(0)); - } else { - $v_size = sprintf("%011s", DecOct($p_size)); - } - - $v_uid = sprintf("%07s", DecOct($p_uid)); - $v_gid = sprintf("%07s", DecOct($p_gid)); - $v_perms = sprintf("%07s", DecOct($p_perms & 000777)); - - $v_mtime = sprintf("%11s", DecOct($p_mtime)); - - $v_linkname = ''; - - $v_magic = 'ustar '; - - $v_version = ' '; - - if (function_exists('posix_getpwuid')) - { - $userinfo = posix_getpwuid($p_uid); - $groupinfo = posix_getgrgid($p_gid); - - $v_uname = $userinfo['name']; - $v_gname = $groupinfo['name']; - } - else - { - $v_uname = ''; - $v_gname = ''; - } - - $v_devmajor = ''; - - $v_devminor = ''; - - $v_prefix = ''; - - $v_binary_data_first = pack("a100a8a8a8a12A12", - $p_filename, $v_perms, $v_uid, $v_gid, - $v_size, $v_mtime); - $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12", - $p_type, $v_linkname, $v_magic, - $v_version, $v_uname, $v_gname, - $v_devmajor, $v_devminor, $v_prefix, ''); - - // ----- Calculate the checksum - $v_checksum = 0; - // ..... First part of the header - for ($i=0; $i<148; $i++) - $v_checksum += ord(substr($v_binary_data_first,$i,1)); - // ..... Ignore the checksum value and replace it by ' ' (space) - for ($i=148; $i<156; $i++) - $v_checksum += ord(' '); - // ..... Last part of the header - for ($i=156, $j=0; $i<512; $i++, $j++) - $v_checksum += ord(substr($v_binary_data_last,$j,1)); - - // ----- Write the first 148 bytes of the header in the archive - $this->_writeBlock($v_binary_data_first, 148); - - // ----- Write the calculated checksum - $v_checksum = sprintf("%06s ", DecOct($v_checksum)); - $v_binary_data = pack("a8", $v_checksum); - $this->_writeBlock($v_binary_data, 8); - - // ----- Write the last 356 bytes of the header in the archive - $this->_writeBlock($v_binary_data_last, 356); - - return true; - } - // }}} - - // {{{ _writeLongHeader() - function _writeLongHeader($p_filename) - { - $v_size = sprintf("%11s ", DecOct(strlen($p_filename))); - - $v_typeflag = 'L'; - - $v_linkname = ''; - - $v_magic = ''; - - $v_version = ''; - - $v_uname = ''; - - $v_gname = ''; - - $v_devmajor = ''; - - $v_devminor = ''; - - $v_prefix = ''; - - $v_binary_data_first = pack("a100a8a8a8a12a12", - '././@LongLink', 0, 0, 0, $v_size, 0); - $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12", - $v_typeflag, $v_linkname, $v_magic, - $v_version, $v_uname, $v_gname, - $v_devmajor, $v_devminor, $v_prefix, ''); - - // ----- Calculate the checksum - $v_checksum = 0; - // ..... First part of the header - for ($i=0; $i<148; $i++) - $v_checksum += ord(substr($v_binary_data_first,$i,1)); - // ..... Ignore the checksum value and replace it by ' ' (space) - for ($i=148; $i<156; $i++) - $v_checksum += ord(' '); - // ..... Last part of the header - for ($i=156, $j=0; $i<512; $i++, $j++) - $v_checksum += ord(substr($v_binary_data_last,$j,1)); - - // ----- Write the first 148 bytes of the header in the archive - $this->_writeBlock($v_binary_data_first, 148); - - // ----- Write the calculated checksum - $v_checksum = sprintf("%06s ", DecOct($v_checksum)); - $v_binary_data = pack("a8", $v_checksum); - $this->_writeBlock($v_binary_data, 8); - - // ----- Write the last 356 bytes of the header in the archive - $this->_writeBlock($v_binary_data_last, 356); - - // ----- Write the filename as content of the block - $i=0; - while (($v_buffer = substr($p_filename, (($i++)*512), 512)) != '') { - $v_binary_data = pack("a512", "$v_buffer"); - $this->_writeBlock($v_binary_data); - } - - return true; - } - // }}} - - // {{{ _readHeader() - function _readHeader($v_binary_data, &$v_header) - { - if (strlen($v_binary_data)==0) { - $v_header['filename'] = ''; - return true; - } - - if (strlen($v_binary_data) != 512) { - $v_header['filename'] = ''; - $this->_error('Invalid block size : '.strlen($v_binary_data)); - return false; - } - - if (!is_array($v_header)) { - $v_header = array(); - } - // ----- Calculate the checksum - $v_checksum = 0; - // ..... First part of the header - for ($i=0; $i<148; $i++) - $v_checksum+=ord(substr($v_binary_data,$i,1)); - // ..... Ignore the checksum value and replace it by ' ' (space) - for ($i=148; $i<156; $i++) - $v_checksum += ord(' '); - // ..... Last part of the header - for ($i=156; $i<512; $i++) - $v_checksum+=ord(substr($v_binary_data,$i,1)); - - if (version_compare(PHP_VERSION,"5.5.0-dev")<0) { - $fmt = "a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/" . - "a8checksum/a1typeflag/a100link/a6magic/a2version/" . - "a32uname/a32gname/a8devmajor/a8devminor/a131prefix"; - } else { - $fmt = "Z100filename/Z8mode/Z8uid/Z8gid/Z12size/Z12mtime/" . - "Z8checksum/Z1typeflag/Z100link/Z6magic/Z2version/" . - "Z32uname/Z32gname/Z8devmajor/Z8devminor/Z131prefix"; - } - $v_data = unpack($fmt, $v_binary_data); - - if (strlen($v_data["prefix"]) > 0) { - $v_data["filename"] = "$v_data[prefix]/$v_data[filename]"; - } - - // ----- Extract the checksum - $v_header['checksum'] = OctDec(trim($v_data['checksum'])); - if ($v_header['checksum'] != $v_checksum) { - $v_header['filename'] = ''; - - // ----- Look for last block (empty block) - if (($v_checksum == 256) && ($v_header['checksum'] == 0)) - return true; - - $this->_error('Invalid checksum for file "'.$v_data['filename'] - .'" : '.$v_checksum.' calculated, ' - .$v_header['checksum'].' expected'); - return false; - } - - // ----- Extract the properties - $v_header['filename'] = $v_data['filename']; - if ($this->_maliciousFilename($v_header['filename'])) { - $this->_error('Malicious .tar detected, file "' . $v_header['filename'] . - '" will not install in desired directory tree'); - return false; - } - $v_header['mode'] = OctDec(trim($v_data['mode'])); - $v_header['uid'] = OctDec(trim($v_data['uid'])); - $v_header['gid'] = OctDec(trim($v_data['gid'])); - $v_header['size'] = OctDec(trim($v_data['size'])); - $v_header['mtime'] = OctDec(trim($v_data['mtime'])); - if (($v_header['typeflag'] = $v_data['typeflag']) == "5") { - $v_header['size'] = 0; - } - $v_header['link'] = trim($v_data['link']); - /* ----- All these fields are removed form the header because - they do not carry interesting info - $v_header[magic] = trim($v_data[magic]); - $v_header[version] = trim($v_data[version]); - $v_header[uname] = trim($v_data[uname]); - $v_header[gname] = trim($v_data[gname]); - $v_header[devmajor] = trim($v_data[devmajor]); - $v_header[devminor] = trim($v_data[devminor]); - */ - - return true; - } - // }}} - - // {{{ _maliciousFilename() - /** - * Detect and report a malicious file name - * - * @param string $file - * - * @return bool - * @access private - */ - function _maliciousFilename($file) - { - if (strpos($file, '/../') !== false) { - return true; - } - if (strpos($file, '../') === 0) { - return true; - } - return false; - } - // }}} - - // {{{ _readLongHeader() - function _readLongHeader(&$v_header) - { - $v_filename = ''; - $n = floor($v_header['size']/512); - for ($i=0; $i<$n; $i++) { - $v_content = $this->_readBlock(); - $v_filename .= $v_content; - } - if (($v_header['size'] % 512) != 0) { - $v_content = $this->_readBlock(); - $v_filename .= trim($v_content); - } - - // ----- Read the next header - $v_binary_data = $this->_readBlock(); - - if (!$this->_readHeader($v_binary_data, $v_header)) - return false; - - $v_filename = trim($v_filename); - $v_header['filename'] = $v_filename; - if ($this->_maliciousFilename($v_filename)) { - $this->_error('Malicious .tar detected, file "' . $v_filename . - '" will not install in desired directory tree'); - return false; - } - - return true; - } - // }}} - - // {{{ _extractInString() - /** - * This method extract from the archive one file identified by $p_filename. - * The return value is a string with the file content, or null on error. - * - * @param string $p_filename The path of the file to extract in a string. - * - * @return a string with the file content or null. - * @access private - */ - function _extractInString($p_filename) - { - $v_result_str = ""; - - While (strlen($v_binary_data = $this->_readBlock()) != 0) - { - if (!$this->_readHeader($v_binary_data, $v_header)) - return null; - - if ($v_header['filename'] == '') - continue; - - // ----- Look for long filename - if ($v_header['typeflag'] == 'L') { - if (!$this->_readLongHeader($v_header)) - return null; - } - - if ($v_header['filename'] == $p_filename) { - if ($v_header['typeflag'] == "5") { - $this->_error('Unable to extract in string a directory ' - .'entry {'.$v_header['filename'].'}'); - return null; - } else { - $n = floor($v_header['size']/512); - for ($i=0; $i<$n; $i++) { - $v_result_str .= $this->_readBlock(); - } - if (($v_header['size'] % 512) != 0) { - $v_content = $this->_readBlock(); - $v_result_str .= substr($v_content, 0, - ($v_header['size'] % 512)); - } - return $v_result_str; - } - } else { - $this->_jumpBlock(ceil(($v_header['size']/512))); - } - } - - return null; - } - // }}} - - // {{{ _extractList() - function _extractList($p_path, &$p_list_detail, $p_mode, - $p_file_list, $p_remove_path, $p_preserve=false) - { - $v_result=true; - $v_nb = 0; - $v_extract_all = true; - $v_listing = false; - - $p_path = $this->_translateWinPath($p_path, false); - if ($p_path == '' || (substr($p_path, 0, 1) != '/' - && substr($p_path, 0, 3) != "../" && !strpos($p_path, ':'))) { - $p_path = "./".$p_path; - } - $p_remove_path = $this->_translateWinPath($p_remove_path); - - // ----- Look for path to remove format (should end by /) - if (($p_remove_path != '') && (substr($p_remove_path, -1) != '/')) - $p_remove_path .= '/'; - $p_remove_path_size = strlen($p_remove_path); - - switch ($p_mode) { - case "complete" : - $v_extract_all = true; - $v_listing = false; - break; - case "partial" : - $v_extract_all = false; - $v_listing = false; - break; - case "list" : - $v_extract_all = false; - $v_listing = true; - break; - default : - $this->_error('Invalid extract mode ('.$p_mode.')'); - return false; - } - - clearstatcache(); - - while (strlen($v_binary_data = $this->_readBlock()) != 0) - { - $v_extract_file = false; - $v_extraction_stopped = 0; - - if (!$this->_readHeader($v_binary_data, $v_header)) - return false; - - if ($v_header['filename'] == '') { - continue; - } - - // ----- Look for long filename - if ($v_header['typeflag'] == 'L') { - if (!$this->_readLongHeader($v_header)) - return false; - } - - if ((!$v_extract_all) && (is_array($p_file_list))) { - // ----- By default no unzip if the file is not found - $v_extract_file = false; - - for ($i=0; $i<sizeof($p_file_list); $i++) { - // ----- Look if it is a directory - if (substr($p_file_list[$i], -1) == '/') { - // ----- Look if the directory is in the filename path - if ((strlen($v_header['filename']) > strlen($p_file_list[$i])) - && (substr($v_header['filename'], 0, strlen($p_file_list[$i])) - == $p_file_list[$i])) { - $v_extract_file = true; - break; - } - } - - // ----- It is a file, so compare the file names - elseif ($p_file_list[$i] == $v_header['filename']) { - $v_extract_file = true; - break; - } - } - } else { - $v_extract_file = true; - } - - // ----- Look if this file need to be extracted - if (($v_extract_file) && (!$v_listing)) - { - if (($p_remove_path != '') - && (substr($v_header['filename'].'/', 0, $p_remove_path_size) - == $p_remove_path)) { - $v_header['filename'] = substr($v_header['filename'], - $p_remove_path_size); - if( $v_header['filename'] == '' ){ - continue; - } - } - if (($p_path != './') && ($p_path != '/')) { - while (substr($p_path, -1) == '/') - $p_path = substr($p_path, 0, strlen($p_path)-1); - - if (substr($v_header['filename'], 0, 1) == '/') - $v_header['filename'] = $p_path.$v_header['filename']; - else - $v_header['filename'] = $p_path.'/'.$v_header['filename']; - } - if (file_exists($v_header['filename'])) { - if ( (@is_dir($v_header['filename'])) - && ($v_header['typeflag'] == '')) { - $this->_error('File '.$v_header['filename'] - .' already exists as a directory'); - return false; - } - if ( ($this->_isArchive($v_header['filename'])) - && ($v_header['typeflag'] == "5")) { - $this->_error('Directory '.$v_header['filename'] - .' already exists as a file'); - return false; - } - if (!is_writeable($v_header['filename'])) { - $this->_error('File '.$v_header['filename'] - .' already exists and is write protected'); - return false; - } - if (filemtime($v_header['filename']) > $v_header['mtime']) { - // To be completed : An error or silent no replace ? - } - } - - // ----- Check the directory availability and create it if necessary - elseif (($v_result - = $this->_dirCheck(($v_header['typeflag'] == "5" - ?$v_header['filename'] - :dirname($v_header['filename'])))) != 1) { - $this->_error('Unable to create path for '.$v_header['filename']); - return false; - } - - if ($v_extract_file) { - if ($v_header['typeflag'] == "5") { - if (!@file_exists($v_header['filename'])) { - if (!@mkdir($v_header['filename'], 0777)) { - $this->_error('Unable to create directory {' - .$v_header['filename'].'}'); - return false; - } - } - } elseif ($v_header['typeflag'] == "2") { - if (@file_exists($v_header['filename'])) { - @unlink($v_header['filename']); - } - if (!@symlink($v_header['link'], $v_header['filename'])) { - $this->_error('Unable to extract symbolic link {' - .$v_header['filename'].'}'); - return false; - } - } else { - if (($v_dest_file = @fopen($v_header['filename'], "wb")) == 0) { - $this->_error('Error while opening {'.$v_header['filename'] - .'} in write binary mode'); - return false; - } else { - $n = floor($v_header['size']/512); - for ($i=0; $i<$n; $i++) { - $v_content = $this->_readBlock(); - fwrite($v_dest_file, $v_content, 512); - } - if (($v_header['size'] % 512) != 0) { - $v_content = $this->_readBlock(); - fwrite($v_dest_file, $v_content, ($v_header['size'] % 512)); - } - - @fclose($v_dest_file); - - if ($p_preserve) { - @chown($v_header['filename'], $v_header['uid']); - @chgrp($v_header['filename'], $v_header['gid']); - } - - // ----- Change the file mode, mtime - @touch($v_header['filename'], $v_header['mtime']); - if ($v_header['mode'] & 0111) { - // make file executable, obey umask - $mode = fileperms($v_header['filename']) | (~umask() & 0111); - @chmod($v_header['filename'], $mode); - } - } - - // ----- Check the file size - clearstatcache(); - if (!is_file($v_header['filename'])) { - $this->_error('Extracted file '.$v_header['filename'] - .'does not exist. Archive may be corrupted.'); - return false; - } - - $filesize = filesize($v_header['filename']); - if ($filesize != $v_header['size']) { - $this->_error('Extracted file '.$v_header['filename'] - .' does not have the correct file size \'' - .$filesize - .'\' ('.$v_header['size'] - .' expected). Archive may be corrupted.'); - return false; - } - } - } else { - $this->_jumpBlock(ceil(($v_header['size']/512))); - } - } else { - $this->_jumpBlock(ceil(($v_header['size']/512))); - } - - /* TBC : Seems to be unused ... - if ($this->_compress) - $v_end_of_file = @gzeof($this->_file); - else - $v_end_of_file = @feof($this->_file); - */ - - if ($v_listing || $v_extract_file || $v_extraction_stopped) { - // ----- Log extracted files - if (($v_file_dir = dirname($v_header['filename'])) - == $v_header['filename']) - $v_file_dir = ''; - if ((substr($v_header['filename'], 0, 1) == '/') && ($v_file_dir == '')) - $v_file_dir = '/'; - - $p_list_detail[$v_nb++] = $v_header; - if (is_array($p_file_list) && (count($p_list_detail) == count($p_file_list))) { - return true; - } - } - } - - return true; - } - // }}} - - // {{{ _openAppend() - function _openAppend() - { - if (filesize($this->_tarname) == 0) - return $this->_openWrite(); - - if ($this->_compress) { - $this->_close(); - - if (!@rename($this->_tarname, $this->_tarname.".tmp")) { - $this->_error('Error while renaming \''.$this->_tarname - .'\' to temporary file \''.$this->_tarname - .'.tmp\''); - return false; - } - - if ($this->_compress_type == 'gz') - $v_temp_tar = @gzopen($this->_tarname.".tmp", "rb"); - elseif ($this->_compress_type == 'bz2') - $v_temp_tar = @bzopen($this->_tarname.".tmp", "r"); - - if ($v_temp_tar == 0) { - $this->_error('Unable to open file \''.$this->_tarname - .'.tmp\' in binary read mode'); - @rename($this->_tarname.".tmp", $this->_tarname); - return false; - } - - if (!$this->_openWrite()) { - @rename($this->_tarname.".tmp", $this->_tarname); - return false; - } - - if ($this->_compress_type == 'gz') { - $end_blocks = 0; - - while (!@gzeof($v_temp_tar)) { - $v_buffer = @gzread($v_temp_tar, 512); - if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) { - $end_blocks++; - // do not copy end blocks, we will re-make them - // after appending - continue; - } elseif ($end_blocks > 0) { - for ($i = 0; $i < $end_blocks; $i++) { - $this->_writeBlock(ARCHIVE_TAR_END_BLOCK); - } - $end_blocks = 0; - } - $v_binary_data = pack("a512", $v_buffer); - $this->_writeBlock($v_binary_data); - } - - @gzclose($v_temp_tar); - } - elseif ($this->_compress_type == 'bz2') { - $end_blocks = 0; - - while (strlen($v_buffer = @bzread($v_temp_tar, 512)) > 0) { - if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) { - $end_blocks++; - // do not copy end blocks, we will re-make them - // after appending - continue; - } elseif ($end_blocks > 0) { - for ($i = 0; $i < $end_blocks; $i++) { - $this->_writeBlock(ARCHIVE_TAR_END_BLOCK); - } - $end_blocks = 0; - } - $v_binary_data = pack("a512", $v_buffer); - $this->_writeBlock($v_binary_data); - } - - @bzclose($v_temp_tar); - } - - if (!@unlink($this->_tarname.".tmp")) { - $this->_error('Error while deleting temporary file \'' - .$this->_tarname.'.tmp\''); - } - - } else { - // ----- For not compressed tar, just add files before the last - // one or two 512 bytes block - if (!$this->_openReadWrite()) - return false; - - clearstatcache(); - $v_size = filesize($this->_tarname); - - // We might have zero, one or two end blocks. - // The standard is two, but we should try to handle - // other cases. - fseek($this->_file, $v_size - 1024); - if (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) { - fseek($this->_file, $v_size - 1024); - } - elseif (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) { - fseek($this->_file, $v_size - 512); - } - } - - return true; - } - // }}} - - // {{{ _append() - function _append($p_filelist, $p_add_dir='', $p_remove_dir='') - { - if (!$this->_openAppend()) - return false; - - if ($this->_addList($p_filelist, $p_add_dir, $p_remove_dir)) - $this->_writeFooter(); - - $this->_close(); - - return true; - } - // }}} - - // {{{ _dirCheck() - - /** - * Check if a directory exists and create it (including parent - * dirs) if not. - * - * @param string $p_dir directory to check - * - * @return bool true if the directory exists or was created - */ - function _dirCheck($p_dir) - { - clearstatcache(); - if ((@is_dir($p_dir)) || ($p_dir == '')) - return true; - - $p_parent_dir = dirname($p_dir); - - if (($p_parent_dir != $p_dir) && - ($p_parent_dir != '') && - (!$this->_dirCheck($p_parent_dir))) - return false; - - if (!@mkdir($p_dir, 0777)) { - $this->_error("Unable to create directory '$p_dir'"); - return false; - } - - return true; - } - - // }}} - - // {{{ _pathReduction() - - /** - * Compress path by changing for example "/dir/foo/../bar" to "/dir/bar", - * rand emove double slashes. - * - * @param string $p_dir path to reduce - * - * @return string reduced path - * - * @access private - * - */ - function _pathReduction($p_dir) - { - $v_result = ''; - - // ----- Look for not empty path - if ($p_dir != '') { - // ----- Explode path by directory names - $v_list = explode('/', $p_dir); - - // ----- Study directories from last to first - for ($i=sizeof($v_list)-1; $i>=0; $i--) { - // ----- Look for current path - if ($v_list[$i] == ".") { - // ----- Ignore this directory - // Should be the first $i=0, but no check is done - } - else if ($v_list[$i] == "..") { - // ----- Ignore it and ignore the $i-1 - $i--; - } - else if ( ($v_list[$i] == '') - && ($i!=(sizeof($v_list)-1)) - && ($i!=0)) { - // ----- Ignore only the double '//' in path, - // but not the first and last / - } else { - $v_result = $v_list[$i].($i!=(sizeof($v_list)-1)?'/' - .$v_result:''); - } - } - } - - if (defined('OS_WINDOWS') && OS_WINDOWS) { - $v_result = strtr($v_result, '\\', '/'); - } - - return $v_result; - } - - // }}} - - // {{{ _translateWinPath() - function _translateWinPath($p_path, $p_remove_disk_letter=true) - { - if (defined('OS_WINDOWS') && OS_WINDOWS) { - // ----- Look for potential disk letter - if ( ($p_remove_disk_letter) - && (($v_position = strpos($p_path, ':')) != false)) { - $p_path = substr($p_path, $v_position+1); - } - // ----- Change potential windows directory separator - if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0,1) == '\\')) { - $p_path = strtr($p_path, '\\', '/'); - } - } - return $p_path; - } - // }}} - -} -?> diff --git a/web/lib/aurjson.class.php b/web/lib/aurjson.class.php index 025adaf..745947e 100644 --- a/web/lib/aurjson.class.php +++ b/web/lib/aurjson.class.php @@ -227,7 +227,6 @@ class AurJSON { while ($row = $result->fetch(PDO::FETCH_ASSOC)) { $resultcount++; $pkgbase_name = $row['PackageBase']; - $row['URLPath'] = $package_url . substr($pkgbase_name, 0, 2) . "/" . $pkgbase_name . "/" . $pkgbase_name . ".tar.gz"; /* * Unfortunately, mysql_fetch_assoc() returns diff --git a/web/lib/pkgbasefuncs.inc.php b/web/lib/pkgbasefuncs.inc.php index 708f861..655856b 100644 --- a/web/lib/pkgbasefuncs.inc.php +++ b/web/lib/pkgbasefuncs.inc.php @@ -915,45 +915,6 @@ function pkgbase_change_category($base_id) { } /** - * Add package base information to the database - * - * @param string $name Name of the new package base - * @param int $category_id Category for the new package base - * @param int $uid User ID of the package uploader - * - * @return int ID of the new package base - */ -function pkgbase_create($name, $category_id, $uid) { - $dbh = DB::connect(); - $q = sprintf("INSERT INTO PackageBases (Name, CategoryID, " . - "SubmittedTS, ModifiedTS, SubmitterUID, MaintainerUID, " . - "PackagerUID) VALUES (%s, %d, UNIX_TIMESTAMP(), " . - "UNIX_TIMESTAMP(), %d, %d, %d)", - $dbh->quote($name), $category_id, $uid, $uid, $uid); - $dbh->exec($q); - return $dbh->lastInsertId(); -} - -/** - * Update package base information for a specific package base - * - * @param string $name Name of the updated package base - * @param int $base_id The package base ID of the affected package - * @param int $uid User ID of the package uploader - * - * @return void - */ -function pkgbase_update($base_id, $name, $uid) { - $dbh = DB::connect(); - $q = sprintf("UPDATE PackageBases SET " . - "Name = %s, ModifiedTS = UNIX_TIMESTAMP(), " . - "MaintainerUID = %d, PackagerUID = %d, OutOfDateTS = NULL " . - "WHERE ID = %d", - $dbh->quote($name), $uid, $uid, $base_id); - $dbh->exec($q); -} - -/** * Change the category a package base belongs to * * @param int $base_id The package base ID to change the category for diff --git a/web/lib/pkgfuncs.inc.php b/web/lib/pkgfuncs.inc.php index 7e7a98f..0c7f2d9 100644 --- a/web/lib/pkgfuncs.inc.php +++ b/web/lib/pkgfuncs.inc.php @@ -184,36 +184,6 @@ function pkg_relations($pkgid) { } /** - * Get the ID of a dependency type given its name - * - * @param string $name The name of the dependency type - * - * @return int The ID of the dependency type - */ -function pkg_dependency_type_id_from_name($name) { - $dbh = DB::connect(); - $q = "SELECT ID FROM DependencyTypes WHERE Name = "; - $q.= $dbh->quote($name); - $result = $dbh->query($q); - return $result->fetch(PDO::FETCH_COLUMN, 0); -} - -/** - * Get the ID of a relation type given its name - * - * @param string $name The name of the relation type - * - * @return int The ID of the relation type - */ -function pkg_relation_type_id_from_name($name) { - $dbh = DB::connect(); - $q = "SELECT ID FROM RelationTypes WHERE Name = "; - $q.= $dbh->quote($name); - $result = $dbh->query($q); - return $result->fetch(PDO::FETCH_COLUMN, 0); -} - -/** * Get the HTML code to display a package dependency link * * @param string $name The name of the dependency @@ -779,6 +749,7 @@ function sanitize_ids($ids) { } /** +<<<<<<< HEAD * Add package information to the database for a specific package * * @param int $base_id ID of the package base diff --git a/web/lib/routing.inc.php b/web/lib/routing.inc.php index 1ee2e35..74ab816 100644 --- a/web/lib/routing.inc.php +++ b/web/lib/routing.inc.php @@ -16,7 +16,6 @@ $ROUTES = array( '/passreset' => 'passreset.php', '/rpc' => 'rpc.php', '/rss' => 'rss.php', - '/submit' => 'pkgsubmit.php', '/tu' => 'tu.php', '/addvote' => 'addvote.php', ); diff --git a/web/template/header.php b/web/template/header.php index 6167fb7..a4958f5 100644 --- a/web/template/header.php +++ b/web/template/header.php @@ -60,7 +60,6 @@ <?php if (has_credential(CRED_PKGREQ_LIST)): ?> <li><a href="<?= get_uri('/requests/') ; ?>"><?= __("Requests"); ?></a></li> <?php endif; ?> - <li><a href="<?= get_uri('/submit/'); ?>"><?= __("Submit"); ?></a></li> <?php if (has_credential(CRED_ACCOUNT_SEARCH)): ?> <li><a href="<?= get_uri('/accounts/') ; ?>"><?= __("Accounts"); ?></a></li> <?php endif; ?> diff --git a/web/template/pkg_details.php b/web/template/pkg_details.php index a8a0b3e..a94042c 100644 --- a/web/template/pkg_details.php +++ b/web/template/pkg_details.php @@ -1,4 +1,7 @@ <?php + +$cgit_uri = config_get('options', 'cgit_uri'); + $uid = uid_from_sid($SID); $pkgid = intval($row['ID']); @@ -21,9 +24,6 @@ $updated_time = ($row["ModifiedTS"] == 0) ? $msg : gmdate("Y-m-d H:i", intval($r $submitted_time = ($row["SubmittedTS"] == 0) ? $msg : gmdate("Y-m-d H:i", intval($row["SubmittedTS"])); $out_of_date_time = ($row["OutOfDateTS"] == 0) ? $msg : gmdate("Y-m-d", intval($row["OutOfDateTS"])); -$package_url = config_get('options', 'package_url'); -$urlpath = $package_url . substr($row['BaseName'], 0, 2) . "/" . $row['BaseName']; - $lics = pkg_licenses($row["ID"]); $grps = pkg_groups($row["ID"]); @@ -79,8 +79,8 @@ $sources = pkg_sources($row["ID"]); <div id="actionlist"> <h4><?= __('Package Actions') ?></h4> <ul class="small"> - <li><a href="<?= $urlpath ?>/PKGBUILD"><?= __('View PKGBUILD') ?></a></li> - <li><a href="<?= $urlpath . '/' . $row['BaseName'] ?>.tar.gz"><?= __('Download tarball') ?></a></li> + <li><a href="<?= $cgit_uri . $row['BaseName'] . '.git' ?>/tree/PKGBUILD"><?= __('View PKGBUILD') ?></a></li> + <li><a href="<?= $cgit_uri . $row['BaseName'] . '.git' ?>/snapshot/master.tar.gz"><?= __('Download snapshot') ?></a></li> <li><a href="https://wiki.archlinux.org/index.php/Special:Search?search=<?= urlencode($row['Name']) ?>"><?= __('Search wiki') ?></a></li> <li><span class="flagged"><?php if ($row["OutOfDateTS"] !== NULL) { echo __('Flagged out-of-date')." (${out_of_date_time})"; } ?></span></li> <?php if ($uid): ?> diff --git a/web/template/pkgbase_details.php b/web/template/pkgbase_details.php index b8d36ed..994c7bd 100644 --- a/web/template/pkgbase_details.php +++ b/web/template/pkgbase_details.php @@ -1,4 +1,7 @@ <?php + +$cgit_uri = config_get('options', 'cgit_uri'); + $uid = uid_from_sid($SID); $base_id = intval($row['ID']); @@ -19,9 +22,6 @@ $updated_time = ($row["ModifiedTS"] == 0) ? $msg : gmdate("Y-m-d H:i", intval($r $submitted_time = ($row["SubmittedTS"] == 0) ? $msg : gmdate("Y-m-d H:i", intval($row["SubmittedTS"])); $out_of_date_time = ($row["OutOfDateTS"] == 0) ? $msg : gmdate("Y-m-d", intval($row["OutOfDateTS"])); -$package_url = config_get('options', 'package_url'); -$urlpath = $package_url . substr($row['Name'], 0, 2) . "/" . $row['Name']; - $pkgs = pkgbase_get_pkgnames($base_id); ?> <div id="pkgdetails" class="box"> @@ -30,8 +30,8 @@ $pkgs = pkgbase_get_pkgnames($base_id); <div id="actionlist"> <h4><?= __('Package Actions') ?></h4> <ul class="small"> - <li><a href="<?= $urlpath ?>/PKGBUILD"><?= __('View PKGBUILD') ?></a></li> - <li><a href="<?= $urlpath . '/' . $row['Name'] ?>.tar.gz"><?= __('Download tarball') ?></a></li> + <li><a href="<?= $cgit_uri . $row['Name'] . '.git' ?>/tree/PKGBUILD"><?= __('View PKGBUILD') ?></a></li> + <li><a href="<?= $cgit_uri . $row['Name'] . '.git' ?>/snapshot/master.tar.gz"><?= __('Download snapshot') ?></a></li> <li><a href="https://wiki.archlinux.org/index.php/Special:Search?search=<?= urlencode($row['Name']) ?>"><?= __('Search wiki') ?></a></li> <li><span class="flagged"><?php if ($row["OutOfDateTS"] !== NULL) { echo __('Flagged out-of-date')." (${out_of_date_time})"; } ?></span></li> <?php if ($uid): ?> -- 2.2.1
Signed-off-by: Lukas Fleischer <archlinux@cryptocrack.de> --- conf/config.proto | 1 + web/template/pkg_details.php | 5 +++++ web/template/pkgbase_details.php | 5 +++++ 3 files changed, 11 insertions(+) diff --git a/conf/config.proto b/conf/config.proto index d20b533..f726e97 100644 --- a/conf/config.proto +++ b/conf/config.proto @@ -19,6 +19,7 @@ max_filesize_uncompressed = 8388608 disable_http_login = 1 aur_location = http://localhost cgit_uri = https://git.aur.archlinux.org/ +git_clone_uri = git://git.aur.archlinux.org/pub/git/%s.git/ max_rpc_results = 5000 aur_request_ml = aur-requests@archlinux.org request_idle_time = 1209600 diff --git a/web/template/pkg_details.php b/web/template/pkg_details.php index a94042c..9036076 100644 --- a/web/template/pkg_details.php +++ b/web/template/pkg_details.php @@ -1,6 +1,7 @@ <?php $cgit_uri = config_get('options', 'cgit_uri'); +$git_clone_uri = config_get('options', 'git_clone_uri'); $uid = uid_from_sid($SID); @@ -158,6 +159,10 @@ $sources = pkg_sources($row["ID"]); <table id="pkginfo"> <tr> + <th><?= __('Git Clone URL') . ': ' ?></th> + <td><?= sprintf($git_clone_uri, htmlspecialchars($row['BaseName'])) ?></td> + </tr> + <tr> <th><?= __('Package Base') . ': ' ?></th> <td class="wrap"><a href="<?= htmlspecialchars(get_pkgbase_uri($row['BaseName']), ENT_QUOTES); ?>"><?= htmlspecialchars($row['BaseName']); ?></a></td> </tr> diff --git a/web/template/pkgbase_details.php b/web/template/pkgbase_details.php index 994c7bd..3f8f4a5 100644 --- a/web/template/pkgbase_details.php +++ b/web/template/pkgbase_details.php @@ -1,6 +1,7 @@ <?php $cgit_uri = config_get('options', 'cgit_uri'); +$git_clone_uri = config_get('options', 'git_clone_uri'); $uid = uid_from_sid($SID); @@ -109,6 +110,10 @@ $pkgs = pkgbase_get_pkgnames($base_id); <table id="pkginfo"> <tr> + <th><?= __('Git Clone URL') . ': ' ?></th> + <td><?= sprintf($git_clone_uri, htmlspecialchars($row['Name'])) ?></td> + </tr> + <tr> <th><?= __('Category') . ': ' ?></th> <?php if (has_credential(CRED_PKGBASE_CHANGE_CATEGORY, array($row["MaintainerUID"]))): -- 2.2.1
Signed-off-by: Lukas Fleischer <archlinux@cryptocrack.de> --- INSTALL | 108 ++++----------- .../0001-Patch-sshd-for-the-AUR.patch | 152 +++++++++++++++++++++ scripts/git-integration/sshd_config | 6 + 3 files changed, 185 insertions(+), 81 deletions(-) create mode 100644 scripts/git-integration/0001-Patch-sshd-for-the-AUR.patch create mode 100644 scripts/git-integration/sshd_config diff --git a/INSTALL b/INSTALL index cbb9f44..b198997 100644 --- a/INSTALL +++ b/INSTALL @@ -1,96 +1,42 @@ -Setup on Arch Linux: -==================== -1) Install Apache, MySQL, PHP, git and php-pear - # pacman -Syu apache mysql php git php-pear +Setup on Arch Linux +=================== -2) Set a local 'hostname' of 'aur' - - Edit /etc/hosts and append 'aur' to loopback address - 127.0.0.1 localhost aur +1) Clone the AUR project: -3) Configure Apache - - - Edit /etc/httpd/conf/httpd.conf and enable PHP support - by adding the following lines. - - LoadModule php5_module modules/libphp5.so - Include conf/extra/php5_module.conf - - - Also append the following snippet to enable the aur - Virtual Host in /etc/httpd/conf/extra/httpd-vhosts.conf. - Comment out the example vhosts and replace MYUSER with your username. - (You could put aur in /srv/http/aur and then create a symlink in ~ ) - - <VirtualHost aur:80> - Servername aur - DocumentRoot /home/MYUSER/aur/web/html - ErrorLog /var/log/httpd/aur-error.log - CustomLog /var/log/httpd/aur-access.log combined - <Directory /home/MYUSER/aur/web/html> - Options Indexes FollowSymLinks - AllowOverride All - Order allow,deny - Allow from all - </Directory> - </VirtualHost> - - - In httpd.conf, uncomment this line: - - Include conf/extra/httpd-vhosts.conf - -4) Clone the AUR project (using the MYUSER from above) - $ cd + $ cd /srv/http/ $ git clone git://projects.archlinux.org/aur.git -5) Configure PHP - Make sure you have mysql and json enabled in PHP. - - - Edit php.ini and uncomment/add this line: - extension=pdo_mysql.so - - If this PHP extension is a separate package on your system, install it. +2) Setup a web server with PHP and MySQL. -6) Configure MySQL - - Start the MySQL service. Example: - # systemctl start mysqld +3) Copy conf/config.proto to conf/config and adjust the configuration. - - Create database - # mysqladmin -p create AUR +4) Create a new MySQL database and a user and import the AUR SQL schema: - - Connect to the mysql client - $ mysql -uroot -p AUR + $ mysql -uaur -p AUR </srv/http/aur/schema/aur-schema.sql - - Issue the following commands to the mysql client - mysql> GRANT ALL PRIVILEGES ON AUR.* to aur@localhost - -> identified by 'aur'; - mysql> FLUSH PRIVILEGES; - mysql> quit +5) Clone the OpenSSH project, apply the AUR sshd patch and run `make`: - - Load the schema file - $ mysql -uaur -p AUR < ~/aur/schema/aur-schema.sql - (give password 'aur' at the prompt) + $ cd /srv/http/aur/ + $ git clone git://anongit.mindrot.org/openssh.git + $ cd openssh + $ git am ../scripts/git-integration/0001-Patch-sshd-for-the-AUR.patch + $ autoreconf + $ ./configure + $ make - - Optionally load some test data for development purposes. - # pacman -S words fortune-mod - $ cd ~/aur/schema/ - $ python gendummydata.py dummy-data.sql - $ bzip2 dummy-data.sql - $ bzcat dummy-data.sql.bz2 | mysql -uaur -p AUR - (give password 'aur' at the prompt) +6) Create and edit the sshd configuration: - If your test data consists of real people and real email addresses consider - inserting bogus addressess to avoid sending unwanted spam from testing. You - can insert garbage addresses with: - mysql> UPDATE Users SET Email = RAND() * RAND(); + $ cd /srv/http/aur/ + $ umask 077 + $ mkdir .ssh/ + $ ssh-keygen -f .ssh/ssh_host_rsa_key -N '' -t rsa + $ cp scripts/git-integration/sshd_config .ssh/ -7) Copy the config.inc.php.proto file to config.inc.php. Modify as needed. - $ cd ~/aur/web/lib/ - $ cp config.inc.php.proto config.inc.php +7) Create a new user and change ownership of the .ssh directory: - In case you set $USE_VIRTUAL_URLS to true (default nowadays) you should add - a rewrite rule. For Apache, add this ~/aur/web/html/.htaccess: + # useradd -U -d /srv/http/aur -c 'AUR user' aur + # chown aur:aur /srv/http/aur/.ssh/ - RewriteEngine on - RewriteCond %{REQUEST_URI} !^/index.php - RewriteRule ^(.*)$ /index.php/$1 +8) Run the sshd as the new user. -8) Point your browser to http://aur + $ /srv/http/aur/openssh/sshd -f /srv/http/aur/.ssh/sshd_config diff --git a/scripts/git-integration/0001-Patch-sshd-for-the-AUR.patch b/scripts/git-integration/0001-Patch-sshd-for-the-AUR.patch new file mode 100644 index 0000000..6b72712 --- /dev/null +++ b/scripts/git-integration/0001-Patch-sshd-for-the-AUR.patch @@ -0,0 +1,152 @@ +From e23745b61a46f034bca3cab9936c24c249afdc7f Mon Sep 17 00:00:00 2001 +From: Lukas Fleischer <archlinux@cryptocrack.de> +Date: Sun, 21 Dec 2014 22:17:48 +0100 +Subject: [PATCH] Patch sshd for the AUR + +* Add SSH_KEY_FINGERPRINT and SSH_KEY variables to the environment of + the AuthorizedKeysCommand which allows for efficiently looking up SSH + keys in the AUR database. + +* Remove the secure path check for the AuthorizedKeysCommand. We are + running the sshd under a non-privileged user who has as little + permissions as possible. In particular, he does not own the directory + that contains the scripts for the Git backend. + +* Prevent from running the sshd as root. + +Signed-off-by: Lukas Fleischer <archlinux@cryptocrack.de> +--- + auth2-pubkey.c | 48 +++++++++++++++++++++++++++++++++++++++++++----- + ssh.h | 12 ++++++++++++ + sshd.c | 5 +++++ + sshd_config.5 | 5 +++++ + 4 files changed, 65 insertions(+), 5 deletions(-) + +diff --git a/auth2-pubkey.c b/auth2-pubkey.c +index 0a3c1de..baf4922 100644 +--- a/auth2-pubkey.c ++++ b/auth2-pubkey.c +@@ -510,6 +510,8 @@ user_key_command_allowed2(struct passwd *user_pw, Key *key) + int status, devnull, p[2], i; + pid_t pid; + char *username, errmsg[512]; ++ struct sshbuf *b = NULL, *bb = NULL; ++ char *keytext, *uu = NULL; + + if (options.authorized_keys_command == NULL || + options.authorized_keys_command[0] != '/') +@@ -538,11 +540,6 @@ user_key_command_allowed2(struct passwd *user_pw, Key *key) + options.authorized_keys_command, strerror(errno)); + goto out; + } +- if (auth_secure_path(options.authorized_keys_command, &st, NULL, 0, +- errmsg, sizeof(errmsg)) != 0) { +- error("Unsafe AuthorizedKeysCommand: %s", errmsg); +- goto out; +- } + + if (pipe(p) != 0) { + error("%s: pipe: %s", __func__, strerror(errno)); +@@ -568,6 +565,47 @@ user_key_command_allowed2(struct passwd *user_pw, Key *key) + for (i = 0; i < NSIG; i++) + signal(i, SIG_DFL); + ++ keytext = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX); ++ if (setenv(SSH_KEY_FINGERPRINT_ENV_NAME, keytext, 1) == -1) { ++ error("%s: setenv: %s", __func__, strerror(errno)); ++ _exit(1); ++ } ++ ++ if (!(b = sshbuf_new()) || !(bb = sshbuf_new())) { ++ error("%s: sshbuf_new: %s", __func__, strerror(errno)); ++ _exit(1); ++ } ++ if (sshkey_to_blob_buf(key, bb) != 0) { ++ error("%s: sshkey_to_blob_buf: %s", __func__, ++ strerror(errno)); ++ _exit(1); ++ } ++ if (!(uu = sshbuf_dtob64(bb))) { ++ error("%s: sshbuf_dtob64: %s", __func__, ++ strerror(errno)); ++ _exit(1); ++ } ++ if (sshbuf_putf(b, "%s ", sshkey_ssh_name(key))) { ++ error("%s: sshbuf_putf: %s", __func__, ++ strerror(errno)); ++ _exit(1); ++ } ++ if (sshbuf_put(b, uu, strlen(uu) + 1)) { ++ error("%s: sshbuf_put: %s", __func__, ++ strerror(errno)); ++ _exit(1); ++ } ++ if (setenv(SSH_KEY_ENV_NAME, sshbuf_ptr(b), 1) == -1) { ++ error("%s: setenv: %s", __func__, strerror(errno)); ++ _exit(1); ++ } ++ if (uu) ++ free(uu); ++ if (b) ++ sshbuf_free(b); ++ if (bb) ++ sshbuf_free(bb); ++ + if ((devnull = open(_PATH_DEVNULL, O_RDWR)) == -1) { + error("%s: open %s: %s", __func__, _PATH_DEVNULL, + strerror(errno)); +diff --git a/ssh.h b/ssh.h +index c94633b..411ea86 100644 +--- a/ssh.h ++++ b/ssh.h +@@ -97,3 +97,15 @@ + + /* Listen backlog for sshd, ssh-agent and forwarding sockets */ + #define SSH_LISTEN_BACKLOG 128 ++ ++/* ++ * Name of the environment variable containing the incoming key passed ++ * to AuthorizedKeysCommand. ++ */ ++#define SSH_KEY_ENV_NAME "SSH_KEY" ++ ++/* ++ * Name of the environment variable containing the incoming key fingerprint ++ * passed to AuthorizedKeysCommand. ++ */ ++#define SSH_KEY_FINGERPRINT_ENV_NAME "SSH_KEY_FINGERPRINT" +diff --git a/sshd.c b/sshd.c +index 4e01855..60c676f 100644 +--- a/sshd.c ++++ b/sshd.c +@@ -1424,6 +1424,11 @@ main(int ac, char **av) + av = saved_argv; + #endif + ++ if (geteuid() == 0) { ++ fprintf(stderr, "this is a patched version of the sshd that must not be run as root.\n"); ++ exit(1); ++ } ++ + if (geteuid() == 0 && setgroups(0, NULL) == -1) + debug("setgroups(): %.200s", strerror(errno)); + +diff --git a/sshd_config.5 b/sshd_config.5 +index ef36d33..1d7bade 100644 +--- a/sshd_config.5 ++++ b/sshd_config.5 +@@ -223,6 +223,11 @@ It will be invoked with a single argument of the username + being authenticated, and should produce on standard output zero or + more lines of authorized_keys output (see AUTHORIZED_KEYS in + .Xr sshd 8 ) . ++The key being used for authentication (the key's type and the key text itself, ++separated by a space) will be available in the ++.Ev SSH_KEY ++environment variable, and the fingerprint of the key will be available in the ++.Ev SSH_KEY_FINGERPRINT environment variable. + If a key supplied by AuthorizedKeysCommand does not successfully authenticate + and authorize the user then public key authentication continues using the usual + .Cm AuthorizedKeysFile +-- +2.2.1 + diff --git a/scripts/git-integration/sshd_config b/scripts/git-integration/sshd_config new file mode 100644 index 0000000..fbe3578 --- /dev/null +++ b/scripts/git-integration/sshd_config @@ -0,0 +1,6 @@ +Port 2222 +HostKey ~/.ssh/ssh_host_rsa_key +PasswordAuthentication no +UsePrivilegeSeparation no +AuthorizedKeysCommand /srv/http/aur/scripts/git-integration/git-auth.py +AuthorizedKeysCommandUser aur -- 2.2.1
Signed-off-by: Lukas Fleischer <archlinux@cryptocrack.de> --- scripts/git-integration/git-auth.py | 3 ++- scripts/git-integration/git-serve.py | 6 ++++-- scripts/git-integration/git-update.py | 3 ++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/scripts/git-integration/git-auth.py b/scripts/git-integration/git-auth.py index 8701d5e..801a1d3 100755 --- a/scripts/git-integration/git-auth.py +++ b/scripts/git-integration/git-auth.py @@ -12,6 +12,7 @@ aur_db_host = config.get('database', 'host') aur_db_name = config.get('database', 'name') aur_db_user = config.get('database', 'user') aur_db_pass = config.get('database', 'password') +aur_db_socket = config.get('database', 'socket') key_prefixes = config.get('auth', 'key-prefixes').split() username_regex = config.get('auth', 'username-regex') @@ -25,7 +26,7 @@ if pubkey is None or not pubkey.startswith(valid_prefixes): db = mysql.connector.connect(host=aur_db_host, user=aur_db_user, passwd=aur_db_pass, db=aur_db_name, - buffered=True) + unix_socket=aur_db_socket, buffered=True) cur = db.cursor() cur.execute("SELECT Username FROM Users WHERE SSHPubKey = %s " + diff --git a/scripts/git-integration/git-serve.py b/scripts/git-integration/git-serve.py index e621677..0cad176 100755 --- a/scripts/git-integration/git-serve.py +++ b/scripts/git-integration/git-serve.py @@ -15,6 +15,7 @@ aur_db_host = config.get('database', 'host') aur_db_name = config.get('database', 'name') aur_db_user = config.get('database', 'user') aur_db_pass = config.get('database', 'password') +aur_db_socket = config.get('database', 'socket') repo_base_path = config.get('serve', 'repo-base') repo_regex = config.get('serve', 'repo-regex') @@ -40,7 +41,8 @@ def setup_repo(repo, user): die('invalid repository name: %s' % (repo)) db = mysql.connector.connect(host=aur_db_host, user=aur_db_user, - passwd=aur_db_pass, db=aur_db_name) + passwd=aur_db_pass, db=aur_db_name, + unix_socket=aur_db_socket) cur = db.cursor() cur.execute("SELECT COUNT(*) FROM PackageBases WHERE Name = %s ", [repo]) @@ -66,7 +68,7 @@ def setup_repo(repo, user): def check_permissions(pkgbase, user): db = mysql.connector.connect(host=aur_db_host, user=aur_db_user, passwd=aur_db_pass, db=aur_db_name, - buffered=True) + unix_socket=aur_db_socket, buffered=True) cur = db.cursor() cur.execute("SELECT COUNT(*) FROM PackageBases INNER JOIN Users " + diff --git a/scripts/git-integration/git-update.py b/scripts/git-integration/git-update.py index c20eede..74a55b5 100755 --- a/scripts/git-integration/git-update.py +++ b/scripts/git-integration/git-update.py @@ -17,6 +17,7 @@ aur_db_host = config.get('database', 'host') aur_db_name = config.get('database', 'name') aur_db_user = config.get('database', 'user') aur_db_pass = config.get('database', 'password') +aur_db_socket = config.get('database', 'socket') def save_srcinfo(srcinfo, db, cur, user): # Obtain package base ID and previous maintainer. @@ -206,7 +207,7 @@ srcinfo = aurinfo.ParseAurinfoFromIterable(srcinfo_raw) db = mysql.connector.connect(host=aur_db_host, user=aur_db_user, passwd=aur_db_pass, db=aur_db_name, - buffered=True) + unix_socket=aur_db_socket, buffered=True) cur = db.cursor() save_srcinfo(srcinfo, db, cur, user) db.close() -- 2.2.1
Use the package description of the first package as the Git repository description. Signed-off-by: Lukas Fleischer <archlinux@cryptocrack.de> --- scripts/git-integration/git-update.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/git-integration/git-update.py b/scripts/git-integration/git-update.py index 74a55b5..94da12e 100755 --- a/scripts/git-integration/git-update.py +++ b/scripts/git-integration/git-update.py @@ -211,3 +211,7 @@ db = mysql.connector.connect(host=aur_db_host, user=aur_db_user, cur = db.cursor() save_srcinfo(srcinfo, db, cur, user) db.close() + +with open(git_dir + '/description', 'w') as f: + pkginfo = srcinfo.GetMergedPackage(list(srcinfo.GetPackageNames())[0]) + f.write(pkginfo['pkgdesc']) -- 2.2.1
These are no longer needed. We use Git repositories now. Signed-off-by: Lukas Fleischer <archlinux@cryptocrack.de> --- scripts/cleanup | 46 -------------------------------------- scripts/uploadbuckets.sh | 58 ------------------------------------------------ 2 files changed, 104 deletions(-) delete mode 100755 scripts/cleanup delete mode 100755 scripts/uploadbuckets.sh diff --git a/scripts/cleanup b/scripts/cleanup deleted file mode 100755 index 0ccbe7d..0000000 --- a/scripts/cleanup +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/php -<?php -# Run this script by providing it with the top path of AUR. -# In that path you should see a file lib/aur.inc -# -# This will remove files which belong to deleted packages -# in unsupported. -# -# ex: php cleanup dev/aur/web -# -$dir = $argv[1]; - -if (empty($dir)) { - echo "Please specify AUR directory.\n"; - exit; -} - -set_include_path(get_include_path() . PATH_SEPARATOR . "$dir/lib"); -include("confparser.inc.php"); -include("aur.inc.php"); -include("pkgfuncs.inc.php"); - -$count = 0; - -$incoming_dir = config_get('paths', 'storage'); -$buckets = scandir($incoming_dir); -foreach ($buckets as $bucket) { - $bucketpath = $incoming_dir . $bucket; - if ($bucket == '.' || $bucket == '..' || !is_dir($bucketpath)) { - continue; - } - $files = scandir($incoming_dir . $bucket); - foreach ($files as $pkgname) { - if ($pkgname == '.' || $pkgname == '..') { - continue; - } - $fullpath = $incoming_dir . $bucket . "/" . $pkgname; - if (!pkg_from_name($pkgname) && is_dir($fullpath)) { - echo 'Removing ' . $fullpath . "\n"; - rm_tree($fullpath); - $count++; - } - } -} - -echo "\nRemoved $count directories.\n"; diff --git a/scripts/uploadbuckets.sh b/scripts/uploadbuckets.sh deleted file mode 100755 index 3252692..0000000 --- a/scripts/uploadbuckets.sh +++ /dev/null @@ -1,58 +0,0 @@ -#!/bin/bash - -DRYRUN=${DRYRUN:-1} - -source="$1" -dest="$2" - -if [[ -z $source || -z $dest ]]; then - echo 'usage: uploadbuckets.sh <source> <dest>' - echo 'Script runs in DRYRUN mode by default.' - echo 'To run for real, set DRYRUN=0 in your environment.' - exit 1 -fi - -if [[ ! -d $source ]]; then - echo 'error: source is not a directory' - exit 1 -fi - -if [[ -e $dest && ! -d $dest ]]; then - echo 'error: dest is not a directory' - exit 1 -fi - -if [[ $(readlink -e $dest) = $(readlink -e $source) ]]; then - echo 'error: source and dest cannot be the same. Rotate the result' - echo 'into place once the migration is complete.' - exit 1 -fi - -if [[ ! -d $dest ]]; then - mkdir $dest -fi - -shopt -s nullglob - -for package in "$source"/*; do - pkgname="${package##*/}" - newfolder="$dest/${pkgname:0:2}" - if [[ ! -d "$newfolder" ]]; then - if [[ $DRYRUN -gt 0 ]]; then - echo mkdir -p "$newfolder" - else - mkdir -p "$newfolder" - fi - fi - if [[ $DRYRUN -gt 0 ]]; then - echo mv "$source/$pkgname" "$newfolder/$pkgname" - else - mv "$source/$pkgname" "$newfolder/$pkgname" - fi -done - -if [[ $DRYRUN -gt 0 ]]; then - echo - echo 'DRYRUN mode was enabled.' - echo 'To run for real, set DRYRUN=0 in your environment.' -fi -- 2.2.1
participants (1)
-
Lukas Fleischer