summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--INSTALL115
-rw-r--r--conf/aur-sshd.socket6
-rw-r--r--conf/aur-sshd@.service8
-rw-r--r--conf/cgitrc.proto29
-rw-r--r--conf/config.proto21
-rw-r--r--schema/aur-schema.sql12
-rwxr-xr-xscripts/cleanup46
-rw-r--r--scripts/git-integration/0001-Patch-sshd-for-the-AUR.patch152
-rw-r--r--scripts/git-integration/aurinfo.py204
-rwxr-xr-xscripts/git-integration/git-auth.py42
-rwxr-xr-xscripts/git-integration/git-serve.py157
-rwxr-xr-xscripts/git-integration/git-update.py246
-rwxr-xr-xscripts/git-integration/init-repos.py51
-rw-r--r--scripts/git-integration/sshd_config6
-rwxr-xr-xscripts/uploadbuckets.sh58
-rw-r--r--upgrading/4.0.0.txt32
-rw-r--r--web/html/account.php5
-rw-r--r--web/html/comaintainers.php21
-rw-r--r--web/html/css/archnavbar/aurlogo.pngbin0 -> 5997 bytes
-rw-r--r--web/html/css/aur.css4
-rw-r--r--web/html/css/cgit.css866
-rw-r--r--web/html/index.php5
-rw-r--r--web/html/pkgbase.php4
-rw-r--r--web/html/pkgsubmit.php489
-rw-r--r--web/lib/Archive/PEAR.php1063
-rw-r--r--web/lib/Archive/PEAR5.php33
-rw-r--r--web/lib/Archive/Tar.php1993
-rw-r--r--web/lib/acctfuncs.inc.php78
-rw-r--r--web/lib/aurjson.class.php1
-rw-r--r--web/lib/confparser.inc.php2
-rw-r--r--web/lib/credentials.inc.php2
-rw-r--r--web/lib/pkgbasefuncs.inc.php87
-rw-r--r--web/lib/pkgfuncs.inc.php33
-rw-r--r--web/lib/routing.inc.php1
-rw-r--r--web/lib/stats.inc.php1
-rw-r--r--web/template/account_edit_form.php7
-rw-r--r--web/template/cgit/footer.html6
-rw-r--r--web/template/cgit/header.html14
-rw-r--r--web/template/comaintainers_form.php20
-rw-r--r--web/template/header.php3
-rw-r--r--web/template/pkg_details.php24
-rw-r--r--web/template/pkgbase_details.php24
42 files changed, 2117 insertions, 3854 deletions
diff --git a/INSTALL b/INSTALL
index cbb9f44..2368db1 100644
--- a/INSTALL
+++ b/INSTALL
@@ -1,96 +1,51 @@
-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
+2) Setup a web server with PHP and MySQL. Configure the web server to redirect
+ all URLs to /index.php/foo/bar/. The following block can be used with nginx:
- If this PHP extension is a separate package on your system, install it.
+ location ~ .* {
+ rewrite ^/(.*)$ /index.php/$1 last;
+ }
-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 checkout V_6_7_P1
+ $ 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) Add, enable and start systemd unit files for the new sshd:
-8) Point your browser to http://aur
+ # cp /srv/http/aur/conf/aur-sshd.socket /etc/systemd/system/
+ # cp /srv/http/aur/conf/aur-sshd@.service /etc/systemd/system/
+ # systemctl enable aur-sshd.socket
+ # systemctl start aur-sshd.socket
diff --git a/conf/aur-sshd.socket b/conf/aur-sshd.socket
new file mode 100644
index 0000000..5b0c3df
--- /dev/null
+++ b/conf/aur-sshd.socket
@@ -0,0 +1,6 @@
+[Socket]
+ListenStream=2222
+Accept=yes
+
+[Install]
+WantedBy=sockets.target
diff --git a/conf/aur-sshd@.service b/conf/aur-sshd@.service
new file mode 100644
index 0000000..e29c292
--- /dev/null
+++ b/conf/aur-sshd@.service
@@ -0,0 +1,8 @@
+[Unit]
+Description=AUR OpenSSH Per-Connection Daemon
+
+[Service]
+ExecStart=-/srv/http/aur/openssh/sshd -i -f /srv/http/aur/.ssh/sshd_config
+User=aur
+StandardInput=socket
+StandardError=syslog
diff --git a/conf/cgitrc.proto b/conf/cgitrc.proto
new file mode 100644
index 0000000..e2b6892
--- /dev/null
+++ b/conf/cgitrc.proto
@@ -0,0 +1,29 @@
+virtual-root=/cgit/
+clone-prefix=git+ssh://aur@aur.archlinux.org:2222
+noheader=0
+logo=
+css=/css/cgit.css
+snapshots=tar.gz
+readme=:README.md
+readme=:README
+enable-index-owner=0
+enable-index-links=1
+
+cache-root=/var/cache/cgit
+cache-size=500000
+cache-dynamic-ttl=15
+cache-repo-ttl=15
+cache-root-ttl=60
+cache-scanrc-ttl=120
+cache-static-ttl=60
+
+root-title=AUR Package Repositories
+root-desc=Web interface to the AUR Package Repositories
+header=/srv/http/aur/web/template/cgit/header.html
+footer=/srv/http/aur/web/template/cgit/footer.html
+max-repodesc-length=50
+max-blob-size=2048
+max-stats=year
+enable-http-clone=1
+
+scan-path=/srv/http/aur/repos/
diff --git a/conf/config.proto b/conf/config.proto
index f00b352..ea6c063 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
@@ -20,9 +17,23 @@ login_timeout = 7200
persistent_cookie_timeout = 2592000
max_filesize_uncompressed = 8388608
disable_http_login = 1
-aur_location = http://localhost
-package_url = /packages/
+aur_location = https://aur.archlinux.org
+cgit_uri = https://aur.archlinux.org/cgit/
+git_clone_uri_anon = https://aur.archlinux.org/cgit/%s.git/
+git_clone_uri_priv = ssh+git://aur@aur.archlinux.org:2222/%s.git/
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 = /srv/http/aur/repos/
+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/schema/aur-schema.sql b/schema/aur-schema.sql
index 9c57683..9c647d8 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,
@@ -275,6 +276,17 @@ CREATE TABLE PackageComments (
FOREIGN KEY (PackageBaseID) REFERENCES PackageBases(ID) ON DELETE CASCADE
) ENGINE = InnoDB;
+-- Package base co-maintainers
+--
+CREATE TABLE PackageComaintainers (
+ UsersID INTEGER UNSIGNED NOT NULL,
+ PackageBaseID INTEGER UNSIGNED NOT NULL,
+ INDEX (UsersID),
+ INDEX (PackageBaseID),
+ FOREIGN KEY (UsersID) REFERENCES Users(ID) ON DELETE CASCADE,
+ FOREIGN KEY (PackageBaseID) REFERENCES PackageBases(ID) ON DELETE CASCADE
+) ENGINE = InnoDB;
+
-- Comment addition notifications
--
CREATE TABLE CommentNotify (
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/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/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-auth.py b/scripts/git-integration/git-auth.py
new file mode 100755
index 0000000..801a1d3
--- /dev/null
+++ b/scripts/git-integration/git-auth.py
@@ -0,0 +1,42 @@
+#!/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')
+aur_db_socket = config.get('database', 'socket')
+
+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,
+ unix_socket=aur_db_socket, 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..227e37b
--- /dev/null
+++ b/scripts/git-integration/git-serve.py
@@ -0,0 +1,157 @@
+#!/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')
+aur_db_socket = config.get('database', 'socket')
+
+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')
+ssh_cmdline = config.get('serve', 'ssh-cmdline')
+
+def repo_path_validate(path):
+ if not path.startswith(repo_base_path):
+ return False
+ if path.endswith('.git'):
+ repo = path[len(repo_base_path):-4]
+ elif path.endswith('.git/'):
+ repo = path[len(repo_base_path):-5]
+ else:
+ return False
+ 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 list_repos(user):
+ db = mysql.connector.connect(host=aur_db_host, user=aur_db_user,
+ passwd=aur_db_pass, db=aur_db_name,
+ unix_socket=aur_db_socket)
+ cur = db.cursor()
+
+ cur.execute("SELECT ID FROM Users WHERE Username = %s ", [user])
+ userid = cur.fetchone()[0]
+ if userid == 0:
+ die('%s: unknown user: %s' % (action, user))
+
+ cur.execute("SELECT Name, PackagerUID FROM PackageBases " +
+ "WHERE MaintainerUID = %s ", [userid])
+ for row in cur:
+ print((' ' if row[1] else '*') + row[0])
+ db.close()
+
+def setup_repo(repo, user):
+ if not re.match(repo_regex, repo):
+ die('%s: invalid repository name: %s' % (action, repo))
+
+ db = mysql.connector.connect(host=aur_db_host, user=aur_db_user,
+ 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])
+ if cur.fetchone()[0] > 0:
+ die('%s: package base already exists: %s' % (action, repo))
+
+ cur.execute("SELECT ID FROM Users WHERE Username = %s ", [user])
+ userid = cur.fetchone()[0]
+ if userid == 0:
+ die('%s: unknown user: %s' % (action, user))
+
+ cur.execute("INSERT INTO PackageBases (Name, SubmittedTS, ModifiedTS, " +
+ "SubmitterUID, MaintainerUID) VALUES (%s, UNIX_TIMESTAMP(), " +
+ "UNIX_TIMESTAMP(), %s, %s)", [repo, userid, userid])
+
+ db.commit()
+ db.close()
+
+ 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,
+ passwd=aur_db_pass, db=aur_db_name,
+ unix_socket=aur_db_socket, buffered=True)
+ cur = db.cursor()
+
+ cur.execute("SELECT AccountTypeID FROM Users WHERE UserName = %s ", [user])
+ if cur.fetchone()[0] > 1:
+ return True
+
+ cur.execute("SELECT COUNT(*) FROM PackageBases " +
+ "LEFT JOIN PackageComaintainers " +
+ "ON PackageComaintainers.PackageBaseID = PackageBases.ID " +
+ "INNER JOIN Users ON Users.ID = PackageBases.MaintainerUID " +
+ "OR PackageBases.MaintainerUID IS NULL " +
+ "OR Users.ID = PackageComaintainers.UsersID " +
+ "WHERE Name = %s AND Username = %s", [pkgbase, user])
+ return cur.fetchone()[0] > 0
+
+def die(msg):
+ sys.stderr.write("%s\n" % (msg))
+ exit(1)
+
+def die_with_help(msg):
+ die(msg + "\nTry `%s help` for a list of commands." % (ssh_cmdline))
+
+user = sys.argv[1]
+cmd = os.environ.get("SSH_ORIGINAL_COMMAND")
+if not cmd:
+ die_with_help("Interactive shell is disabled.")
+cmdargv = shlex.split(cmd)
+action = cmdargv[0]
+
+if action == 'git-upload-pack' or action == 'git-receive-pack':
+ if len(cmdargv) < 2:
+ die_with_help("%s: missing path" % (action))
+ path = repo_base_path.rstrip('/') + cmdargv[1]
+ if not repo_path_validate(path):
+ die('%s: invalid path: %s' % (action, path))
+ pkgbase = repo_path_get_pkgbase(path)
+ if not os.path.exists(path):
+ setup_repo(pkgbase, user)
+ if action == 'git-receive-pack':
+ if not check_permissions(pkgbase, user):
+ die('%s: permission denied: %s' % (action, user))
+ os.environ["AUR_USER"] = user
+ os.environ["AUR_GIT_DIR"] = path
+ os.environ["AUR_PKGBASE"] = pkgbase
+ cmd = action + " '" + path + "'"
+ os.execl(git_shell_cmd, git_shell_cmd, '-c', cmd)
+elif action == 'list-repos':
+ if len(cmdargv) > 1:
+ die_with_help("%s: too many arguments" % (action))
+ list_repos(user)
+elif action == 'setup-repo':
+ if len(cmdargv) < 2:
+ die_with_help("%s: missing repository name" % (action))
+ if len(cmdargv) > 2:
+ die_with_help("%s: too many arguments" % (action))
+ setup_repo(cmdargv[1], user)
+elif action == 'help':
+ die("Commands:\n" +
+ " help Show this help message and exit.\n" +
+ " list-repos List all your repositories.\n" +
+ " setup-repo <name> Create an empty repository.\n" +
+ " git-receive-pack Internal command used with Git.\n" +
+ " git-upload-pack Internal command used with Git.")
+else:
+ die_with_help("invalid command: %s" % (action))
diff --git a/scripts/git-integration/git-update.py b/scripts/git-integration/git-update.py
new file mode 100755
index 0000000..3d2742a
--- /dev/null
+++ b/scripts/git-integration/git-update.py
@@ -0,0 +1,246 @@
+#!/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')
+aur_db_socket = config.get('database', 'socket')
+
+def extract_arch_fields(pkginfo, field):
+ values = []
+
+ if field in pkginfo:
+ for val in pkginfo[field]:
+ values.append({"value": val, "arch": None})
+
+ for arch in ['i686', 'x86_64']:
+ if field + '_' + arch in pkginfo:
+ for val in pkginfo[field + '_' + arch]:
+ values.append({"value": val, "arch": arch})
+
+ return values
+
+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(), " +
+ "PackagerUID = %s, OutOfDateTS = NULL WHERE ID = %s",
+ [user_id, pkgbase_id])
+ cur.execute("UPDATE PackageBases SET MaintainerUID = %s " +
+ "WHERE ID = %s AND MaintainerUID IS NULL",
+ [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 int(pkginfo['epoch']) > 0:
+ ver = '%d:%s-%s' % (int(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_info in extract_arch_fields(pkginfo, 'source'):
+ cur.execute("INSERT INTO PackageSources (PackageID, Source, " +
+ "SourceArch) VALUES (%s, %s, %s)",
+ [pkgid, source_info['value'], source_info['arch']])
+
+ # Add package dependencies.
+ for deptype in ('depends', 'makedepends',
+ 'checkdepends', 'optdepends'):
+ cur.execute("SELECT ID FROM DependencyTypes WHERE Name = %s",
+ [deptype])
+ deptypeid = cur.fetchone()[0]
+ for dep_info in extract_arch_fields(pkginfo, deptype):
+ depname = re.sub(r'(<|=|>).*', '', dep_info['value'])
+ depcond = dep_info['value'][len(depname):]
+ deparch = dep_info['arch']
+ cur.execute("INSERT INTO PackageDepends (PackageID, " +
+ "DepTypeID, DepName, DepCondition, DepArch) " +
+ "VALUES (%s, %s, %s, %s, %s)",
+ [pkgid, deptypeid, depname, depcond, deparch])
+
+ # Add package relations (conflicts, provides, replaces).
+ for reltype in ('conflicts', 'provides', 'replaces'):
+ cur.execute("SELECT ID FROM RelationTypes WHERE Name = %s",
+ [reltype])
+ reltypeid = cur.fetchone()[0]
+ for rel_info in extract_arch_fields(pkginfo, reltype):
+ relname = re.sub(r'(<|=|>).*', '', rel_info['value'])
+ relcond = rel_info['value'][len(relname):]
+ relarch = rel_info['arch']
+ cur.execute("INSERT INTO PackageRelations (PackageID, " +
+ "RelTypeID, RelName, RelCondition, RelArch) " +
+ "VALUES (%s, %s, %s, %s, %s)",
+ [pkgid, reltypeid, relname, relcond, relarch])
+
+ # 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)
+
+db = mysql.connector.connect(host=aur_db_host, user=aur_db_user,
+ passwd=aur_db_pass, db=aur_db_name,
+ unix_socket=aur_db_socket, buffered=True)
+cur = db.cursor()
+
+cur.execute("SELECT Name FROM PackageBlacklist")
+blacklist = [row[0] for row in cur.fetchall()]
+
+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 'epoch' in pkginfo and not pkginfo['epoch'].isdigit():
+ die_commit('invalid epoch: %s' % (pkginfo['epoch']), commit.id)
+
+ if not re.match(r'[a-z0-9][a-z0-9\.+_-]*$', pkginfo['pkgname']):
+ die_commit('invalid package name: %s' % (pkginfo['pkgname']),
+ commit.id)
+
+ if pkginfo['pkgname'] in blacklist:
+ die_commit('package is blacklisted: %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)
+
+save_srcinfo(srcinfo, db, cur, user)
+
+db.close()
+
+pkglist = list(srcinfo.GetPackageNames())
+if len(pkglist) > 0:
+ with open(git_dir + '/description', 'w') as f:
+ pkginfo = srcinfo.GetMergedPackage(pkglist[0])
+ f.write(pkginfo['pkgdesc'])
diff --git a/scripts/git-integration/init-repos.py b/scripts/git-integration/init-repos.py
new file mode 100755
index 0000000..62c51b1
--- /dev/null
+++ b/scripts/git-integration/init-repos.py
@@ -0,0 +1,51 @@
+#!/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')
+aur_db_socket = config.get('database', 'socket')
+
+repo_base_path = config.get('serve', 'repo-base')
+repo_regex = config.get('serve', 'repo-regex')
+git_update_hook = config.get('serve', 'git-update-hook')
+
+def die(msg):
+ sys.stderr.write("%s\n" % (msg))
+ exit(1)
+
+db = mysql.connector.connect(host=aur_db_host, user=aur_db_user,
+ passwd=aur_db_pass, db=aur_db_name,
+ unix_socket=aur_db_socket)
+cur = db.cursor()
+
+cur.execute("SELECT Name FROM PackageBases")
+repos = [row[0] for row in cur]
+db.close()
+
+for repo in repos:
+ if not re.match(repo_regex, repo):
+ die('invalid repository name: %s' % (repo))
+
+i = 1
+n = len(repos)
+
+for repo in repos:
+ print("[%s/%d] %s" % (str(i).rjust(len(str(n))), n, repo))
+
+ repo_path = repo_base_path + '/' + repo + '.git/'
+ pygit2.init_repository(repo_path, True)
+ os.symlink(git_update_hook, repo_path + 'hooks/update')
+
+ i += 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
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
diff --git a/upgrading/4.0.0.txt b/upgrading/4.0.0.txt
new file mode 100644
index 0000000..b1582f2
--- /dev/null
+++ b/upgrading/4.0.0.txt
@@ -0,0 +1,32 @@
+1. Add a field for the SSH public key to the Users table:
+
+----
+ALTER TABLE Users ADD COLUMN SSHPubKey VARCHAR(4096) NULL DEFAULT NULL;
+----
+
+2. Create a new user and set up a patched version of the sshd as
+described in INSTALL.
+
+3. Create a directory for the package base Git repositories and run
+init-repos.py to initialize them.
+
+4. Reset the packager field of all package bases:
+
+----
+UPDATE PackageBases SET PackagerUID = NULL;
+----
+
+5. Create a new table for package base co-maintainers:
+
+----
+CREATE TABLE PackageComaintainers (
+ UsersID INTEGER UNSIGNED NOT NULL,
+ PackageBaseID INTEGER UNSIGNED NOT NULL,
+ INDEX (UsersID),
+ INDEX (PackageBaseID),
+ FOREIGN KEY (UsersID) REFERENCES Users(ID) ON DELETE CASCADE,
+ FOREIGN KEY (PackageBaseID) REFERENCES PackageBases(ID) ON DELETE CASCADE
+) ENGINE = InnoDB;
+----
+
+6. (optional) Setup cgit to browse the Git repositories via HTTP.
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/html/comaintainers.php b/web/html/comaintainers.php
new file mode 100644
index 0000000..591fcad
--- /dev/null
+++ b/web/html/comaintainers.php
@@ -0,0 +1,21 @@
+<?php
+
+set_include_path(get_include_path() . PATH_SEPARATOR . '../lib');
+
+include_once("aur.inc.php");
+include_once("pkgbasefuncs.inc.php");
+
+set_lang();
+check_sid();
+
+if (!isset($base_id) || !has_credential(CRED_PKGBASE_EDIT_COMAINTAINERS, array(pkgbase_maintainer_uid($base_id)))) {
+ header('Location: /');
+ exit();
+}
+
+html_header(__("Manage Co-maintainers"));
+$users = pkgbase_get_comaintainers($base_id);
+include('comaintainers_form.php');
+html_footer(AUR_VERSION);
+
+
diff --git a/web/html/css/archnavbar/aurlogo.png b/web/html/css/archnavbar/aurlogo.png
new file mode 100644
index 0000000..69110d8
--- /dev/null
+++ b/web/html/css/archnavbar/aurlogo.png
Binary files differ
diff --git a/web/html/css/aur.css b/web/html/css/aur.css
index 654116a..dfa6717 100644
--- a/web/html/css/aur.css
+++ b/web/html/css/aur.css
@@ -3,6 +3,10 @@
color: white !important;
}
+#archnavbarlogo {
+ background: url('archnavbar/aurlogo.png') !important;
+}
+
#lang_sub {
float: right;
}
diff --git a/web/html/css/cgit.css b/web/html/css/cgit.css
new file mode 100644
index 0000000..429b5f5
--- /dev/null
+++ b/web/html/css/cgit.css
@@ -0,0 +1,866 @@
+/*
+ * ARCH GLOBAL NAVBAR
+ * We're forcing all generic selectors with !important
+ * to help prevent other stylesheets from interfering.
+ */
+
+/* container for the entire bar */
+#archnavbar { height: 40px !important; padding: 10px 15px !important; background: #333 !important; border-bottom: 5px #08c solid !important; }
+#archnavbarlogo { float: left !important; margin: 0 !important; padding: 0 !important; height: 40px !important; width: 190px !important; background: url('archnavbar/archlogo.png') no-repeat !important; }
+
+/* move the heading text offscreen */
+#archnavbarlogo h1 { margin: 0 !important; padding: 0 !important; text-indent: -9999px !important; }
+
+/* make the link the same size as the logo */
+#archnavbarlogo a { display: block !important; height: 40px !important; width: 190px !important; }
+
+/* display the list inline, float it to the right and style it */
+#archnavbarlist { display: inline !important; float: right !important; list-style: none !important; margin: 0 !important; padding: 0 !important; }
+#archnavbarlist li { float: left !important; font-size: 14px !important; font-family: sans-serif !important; line-height: 45px !important; padding-right: 15px !important; padding-left: 15px !important; }
+
+/* style the links */
+#archnavbarlist li a { color: #999; font-weight: bold !important; text-decoration: none !important; }
+#archnavbarlist li a:hover { color: white !important; text-decoration: underline !important; }
+
+/* END ARCH GLOBAL NAVBAR */
+
+#footer {
+ clear: both;
+ margin: 0;
+}
+
+#footer p {
+ margin: 1em;
+}
+
+#archnavbar.anb-aur ul li#anb-aur a {
+ color: white !important;
+}
+
+#archnavbarlogo {
+ background: url('archnavbar/aurlogo.png') !important;
+}
+
+body {
+ padding: 0;
+ margin: 0;
+ font-family: sans-serif;
+ font-size: 10pt;
+ color: #333;
+ background: white;
+}
+
+div#cgit a {
+ color: blue;
+ text-decoration: none;
+}
+
+div#cgit a:hover {
+ text-decoration: underline;
+}
+
+div#cgit table {
+ border-collapse: collapse;
+}
+
+div#cgit table#header {
+ width: 100%;
+ margin-bottom: 1em;
+}
+
+div#cgit table#header td.logo {
+ width: 96px;
+ vertical-align: top;
+}
+
+div#cgit table#header td.main {
+ font-size: 250%;
+ padding-left: 10px;
+ white-space: nowrap;
+}
+
+div#cgit table#header td.main a {
+ color: #000;
+}
+
+div#cgit table#header td.form {
+ text-align: right;
+ vertical-align: bottom;
+ padding-right: 1em;
+ padding-bottom: 2px;
+ white-space: nowrap;
+}
+
+div#cgit table#header td.form form,
+div#cgit table#header td.form input,
+div#cgit table#header td.form select {
+ font-size: 90%;
+}
+
+div#cgit table#header td.sub {
+ color: #777;
+ border-top: solid 1px #ccc;
+ padding-left: 10px;
+}
+
+div#cgit table.tabs {
+ border-bottom: solid 3px #ccc;
+ border-collapse: collapse;
+ margin-top: 2em;
+ margin-bottom: 0px;
+ width: 100%;
+}
+
+div#cgit table.tabs td {
+ padding: 0px 1em;
+ vertical-align: bottom;
+}
+
+div#cgit table.tabs td a {
+ padding: 2px 0.75em;
+ color: #777;
+ font-size: 110%;
+}
+
+div#cgit table.tabs td a.active {
+ color: #000;
+ background-color: #ccc;
+}
+
+div#cgit table.tabs td.form {
+ text-align: right;
+}
+
+div#cgit table.tabs td.form form {
+ padding-bottom: 2px;
+ font-size: 90%;
+ white-space: nowrap;
+}
+
+div#cgit table.tabs td.form input,
+div#cgit table.tabs td.form select {
+ font-size: 90%;
+}
+
+div#cgit div.path {
+ margin: 0px;
+ padding: 5px 2em 2px 2em;
+ color: #000;
+ background-color: #eee;
+}
+
+div#cgit div.content {
+ margin: 0px;
+ padding: 2em;
+ border-bottom: solid 3px #ccc;
+}
+
+
+div#cgit table.list {
+ width: 100%;
+ border: none;
+ border-collapse: collapse;
+}
+
+div#cgit table.list tr {
+ background: white;
+}
+
+div#cgit table.list tr.logheader {
+ background: #eee;
+}
+
+div#cgit table.list tr:hover {
+ background: #eee;
+}
+
+div#cgit table.list tr.nohover:hover {
+ background: white;
+}
+
+div#cgit table.list th {
+ font-weight: bold;
+ /* color: #888;
+ border-top: dashed 1px #888;
+ border-bottom: dashed 1px #888;
+ */
+ padding: 0.1em 0.5em 0.05em 0.5em;
+ vertical-align: baseline;
+}
+
+div#cgit table.list td {
+ border: none;
+ padding: 0.1em 0.5em 0.1em 0.5em;
+}
+
+div#cgit table.list td.commitgraph {
+ font-family: monospace;
+ white-space: pre;
+}
+
+div#cgit table.list td.commitgraph .column1 {
+ color: #a00;
+}
+
+div#cgit table.list td.commitgraph .column2 {
+ color: #0a0;
+}
+
+div#cgit table.list td.commitgraph .column3 {
+ color: #aa0;
+}
+
+div#cgit table.list td.commitgraph .column4 {
+ color: #00a;
+}
+
+div#cgit table.list td.commitgraph .column5 {
+ color: #a0a;
+}
+
+div#cgit table.list td.commitgraph .column6 {
+ color: #0aa;
+}
+
+div#cgit table.list td.logsubject {
+ font-family: monospace;
+ font-weight: bold;
+}
+
+div#cgit table.list td.logmsg {
+ font-family: monospace;
+ white-space: pre;
+ padding: 0 0.5em;
+}
+
+div#cgit table.list td a {
+ color: black;
+}
+
+div#cgit table.list td a.ls-dir {
+ font-weight: bold;
+ color: #00f;
+}
+
+div#cgit table.list td a:hover {
+ color: #00f;
+}
+
+div#cgit img {
+ border: none;
+}
+
+div#cgit input#switch-btn {
+ margin: 2px 0px 0px 0px;
+}
+
+div#cgit td#sidebar input.txt {
+ width: 100%;
+ margin: 2px 0px 0px 0px;
+}
+
+div#cgit table#grid {
+ margin: 0px;
+}
+
+div#cgit td#content {
+ vertical-align: top;
+ padding: 1em 2em 1em 1em;
+ border: none;
+}
+
+div#cgit div#summary {
+ vertical-align: top;
+ margin-bottom: 1em;
+}
+
+div#cgit table#downloads {
+ float: right;
+ border-collapse: collapse;
+ border: solid 1px #777;
+ margin-left: 0.5em;
+ margin-bottom: 0.5em;
+}
+
+div#cgit table#downloads th {
+ background-color: #ccc;
+}
+
+div#cgit div#blob {
+ border: solid 1px black;
+}
+
+div#cgit div.error {
+ color: red;
+ font-weight: bold;
+ margin: 1em 2em;
+}
+
+div#cgit a.ls-blob, div#cgit a.ls-dir, div#cgit a.ls-mod {
+ font-family: monospace;
+}
+
+div#cgit td.ls-size {
+ text-align: right;
+ font-family: monospace;
+ width: 10em;
+}
+
+div#cgit td.ls-mode {
+ font-family: monospace;
+ width: 10em;
+}
+
+div#cgit table.blob {
+ margin-top: 0.5em;
+ border-top: solid 1px black;
+}
+
+div#cgit table.blob td.lines {
+ margin: 0; padding: 0 0 0 0.5em;
+ vertical-align: top;
+ color: black;
+}
+
+div#cgit table.blob td.linenumbers {
+ margin: 0; padding: 0 0.5em 0 0.5em;
+ vertical-align: top;
+ text-align: right;
+ border-right: 1px solid gray;
+}
+
+div#cgit table.blob pre {
+ padding: 0; margin: 0;
+}
+
+div#cgit table.blob a.no, div#cgit table.ssdiff a.no {
+ color: gray;
+ text-align: right;
+ text-decoration: none;
+}
+
+div#cgit table.blob a.no a:hover {
+ color: black;
+}
+
+div#cgit table.bin-blob {
+ margin-top: 0.5em;
+ border: solid 1px black;
+}
+
+div#cgit table.bin-blob th {
+ font-family: monospace;
+ white-space: pre;
+ border: solid 1px #777;
+ padding: 0.5em 1em;
+}
+
+div#cgit table.bin-blob td {
+ font-family: monospace;
+ white-space: pre;
+ border-left: solid 1px #777;
+ padding: 0em 1em;
+}
+
+div#cgit table.nowrap td {
+ white-space: nowrap;
+}
+
+div#cgit table.commit-info {
+ border-collapse: collapse;
+ margin-top: 1.5em;
+}
+
+div#cgit div.cgit-panel {
+ float: right;
+ margin-top: 1.5em;
+}
+
+div#cgit div.cgit-panel table {
+ border-collapse: collapse;
+ border: solid 1px #aaa;
+ background-color: #eee;
+}
+
+div#cgit div.cgit-panel th {
+ text-align: center;
+}
+
+div#cgit div.cgit-panel td {
+ padding: 0.25em 0.5em;
+}
+
+div#cgit div.cgit-panel td.label {
+ padding-right: 0.5em;
+}
+
+div#cgit div.cgit-panel td.ctrl {
+ padding-left: 0.5em;
+}
+
+div#cgit table.commit-info th {
+ text-align: left;
+ font-weight: normal;
+ padding: 0.1em 1em 0.1em 0.1em;
+ vertical-align: top;
+}
+
+div#cgit table.commit-info td {
+ font-weight: normal;
+ padding: 0.1em 1em 0.1em 0.1em;
+}
+
+div#cgit div.commit-subject {
+ font-weight: bold;
+ font-size: 125%;
+ margin: 1.5em 0em 0.5em 0em;
+ padding: 0em;
+}
+
+div#cgit div.commit-msg {
+ white-space: pre;
+ font-family: monospace;
+}
+
+div#cgit div.notes-header {
+ font-weight: bold;
+ padding-top: 1.5em;
+}
+
+div#cgit div.notes {
+ white-space: pre;
+ font-family: monospace;
+ border: solid 1px #ee9;
+ background-color: #ffd;
+ padding: 0.3em 2em 0.3em 1em;
+ float: left;
+}
+
+div#cgit div.notes-footer {
+ clear: left;
+}
+
+div#cgit div.diffstat-header {
+ font-weight: bold;
+ padding-top: 1.5em;
+}
+
+div#cgit table.diffstat {
+ border-collapse: collapse;
+ border: solid 1px #aaa;
+ background-color: #eee;
+}
+
+div#cgit table.diffstat th {
+ font-weight: normal;
+ text-align: left;
+ text-decoration: underline;
+ padding: 0.1em 1em 0.1em 0.1em;
+ font-size: 100%;
+}
+
+div#cgit table.diffstat td {
+ padding: 0.2em 0.2em 0.1em 0.1em;
+ font-size: 100%;
+ border: none;
+}
+
+div#cgit table.diffstat td.mode {
+ white-space: nowrap;
+}
+
+div#cgit table.diffstat td span.modechange {
+ padding-left: 1em;
+ color: red;
+}
+
+div#cgit table.diffstat td.add a {
+ color: green;
+}
+
+div#cgit table.diffstat td.del a {
+ color: red;
+}
+
+div#cgit table.diffstat td.upd a {
+ color: blue;
+}
+
+div#cgit table.diffstat td.graph {
+ width: 500px;
+ vertical-align: middle;
+}
+
+div#cgit table.diffstat td.graph table {
+ border: none;
+}
+
+div#cgit table.diffstat td.graph td {
+ padding: 0px;
+ border: 0px;
+ height: 7pt;
+}
+
+div#cgit table.diffstat td.graph td.add {
+ background-color: #5c5;
+}
+
+div#cgit table.diffstat td.graph td.rem {
+ background-color: #c55;
+}
+
+div#cgit div.diffstat-summary {
+ color: #888;
+ padding-top: 0.5em;
+}
+
+div#cgit table.diff {
+ width: 100%;
+}
+
+div#cgit table.diff td {
+ font-family: monospace;
+ white-space: pre;
+}
+
+div#cgit table.diff td div.head {
+ font-weight: bold;
+ margin-top: 1em;
+ color: black;
+}
+
+div#cgit table.diff td div.hunk {
+ color: #009;
+}
+
+div#cgit table.diff td div.add {
+ color: green;
+}
+
+div#cgit table.diff td div.del {
+ color: red;
+}
+
+div#cgit .sha1 {
+ font-family: monospace;
+ font-size: 90%;
+}
+
+div#cgit .left {
+ text-align: left;
+}
+
+div#cgit .right {
+ text-align: right;
+ float: none !important;
+ width: auto !important;
+ padding: 0 !important;
+}
+
+div#cgit table.list td.reposection {
+ font-style: italic;
+ color: #888;
+}
+
+div#cgit a.button {
+ font-size: 80%;
+ padding: 0em 0.5em;
+}
+
+div#cgit a.primary {
+ font-size: 100%;
+}
+
+div#cgit a.secondary {
+ font-size: 90%;
+}
+
+div#cgit td.toplevel-repo {
+
+}
+
+div#cgit table.list td.sublevel-repo {
+ padding-left: 1.5em;
+}
+
+div#cgit ul.pager {
+ list-style-type: none;
+ text-align: center;
+ margin: 1em 0em 0em 0em;
+ padding: 0;
+}
+
+div#cgit ul.pager li {
+ display: inline-block;
+ margin: 0.25em 0.5em;
+}
+
+div#cgit ul.pager a {
+ color: #777;
+}
+
+div#cgit ul.pager .current {
+ font-weight: bold;
+}
+
+div#cgit span.age-mins {
+ font-weight: bold;
+ color: #080;
+}
+
+div#cgit span.age-hours {
+ color: #080;
+}
+
+div#cgit span.age-days {
+ color: #040;
+}
+
+div#cgit span.age-weeks {
+ color: #444;
+}
+
+div#cgit span.age-months {
+ color: #888;
+}
+
+div#cgit span.age-years {
+ color: #bbb;
+}
+div#cgit div.footer {
+ margin-top: 0.5em;
+ text-align: center;
+ font-size: 80%;
+ color: #ccc;
+}
+div#cgit a.branch-deco {
+ color: #000;
+ margin: 0px 0.5em;
+ padding: 0px 0.25em;
+ background-color: #88ff88;
+ border: solid 1px #007700;
+}
+div#cgit a.tag-deco {
+ color: #000;
+ margin: 0px 0.5em;
+ padding: 0px 0.25em;
+ background-color: #ffff88;
+ border: solid 1px #777700;
+}
+div#cgit a.remote-deco {
+ color: #000;
+ margin: 0px 0.5em;
+ padding: 0px 0.25em;
+ background-color: #ccccff;
+ border: solid 1px #000077;
+}
+div#cgit a.deco {
+ color: #000;
+ margin: 0px 0.5em;
+ padding: 0px 0.25em;
+ background-color: #ff8888;
+ border: solid 1px #770000;
+}
+
+div#cgit div.commit-subject a.branch-deco,
+div#cgit div.commit-subject a.tag-deco,
+div#cgit div.commit-subject a.remote-deco,
+div#cgit div.commit-subject a.deco {
+ margin-left: 1em;
+ font-size: 75%;
+}
+
+div#cgit table.stats {
+ border: solid 1px black;
+ border-collapse: collapse;
+}
+
+div#cgit table.stats th {
+ text-align: left;
+ padding: 1px 0.5em;
+ background-color: #eee;
+ border: solid 1px black;
+}
+
+div#cgit table.stats td {
+ text-align: right;
+ padding: 1px 0.5em;
+ border: solid 1px black;
+}
+
+div#cgit table.stats td.total {
+ font-weight: bold;
+ text-align: left;
+}
+
+div#cgit table.stats td.sum {
+ color: #c00;
+ font-weight: bold;
+/* background-color: #eee; */
+}
+
+div#cgit table.stats td.left {
+ text-align: left;
+}
+
+div#cgit table.vgraph {
+ border-collapse: separate;
+ border: solid 1px black;
+ height: 200px;
+}
+
+div#cgit table.vgraph th {
+ background-color: #eee;
+ font-weight: bold;
+ border: solid 1px white;
+ padding: 1px 0.5em;
+}
+
+div#cgit table.vgraph td {
+ vertical-align: bottom;
+ padding: 0px 10px;
+}
+
+div#cgit table.vgraph div.bar {
+ background-color: #eee;
+}
+
+div#cgit table.hgraph {
+ border: solid 1px black;
+ width: 800px;
+}
+
+div#cgit table.hgraph th {
+ background-color: #eee;
+ font-weight: bold;
+ border: solid 1px black;
+ padding: 1px 0.5em;
+}
+
+div#cgit table.hgraph td {
+ vertical-align: middle;
+ padding: 2px 2px;
+}
+
+div#cgit table.hgraph div.bar {
+ background-color: #eee;
+ height: 1em;
+}
+
+div#cgit table.ssdiff {
+ width: 100%;
+}
+
+div#cgit table.ssdiff td {
+ font-size: 75%;
+ font-family: monospace;
+ white-space: pre;
+ padding: 1px 4px 1px 4px;
+ border-left: solid 1px #aaa;
+ border-right: solid 1px #aaa;
+}
+
+div#cgit table.ssdiff td.add {
+ color: black;
+ background: #cfc;
+ min-width: 50%;
+}
+
+div#cgit table.ssdiff td.add_dark {
+ color: black;
+ background: #aca;
+ min-width: 50%;
+}
+
+div#cgit table.ssdiff span.add {
+ background: #cfc;
+ font-weight: bold;
+}
+
+div#cgit table.ssdiff td.del {
+ color: black;
+ background: #fcc;
+ min-width: 50%;
+}
+
+div#cgit table.ssdiff td.del_dark {
+ color: black;
+ background: #caa;
+ min-width: 50%;
+}
+
+div#cgit table.ssdiff span.del {
+ background: #fcc;
+ font-weight: bold;
+}
+
+div#cgit table.ssdiff td.changed {
+ color: black;
+ background: #ffc;
+ min-width: 50%;
+}
+
+div#cgit table.ssdiff td.changed_dark {
+ color: black;
+ background: #cca;
+ min-width: 50%;
+}
+
+div#cgit table.ssdiff td.lineno {
+ color: black;
+ background: #eee;
+ text-align: right;
+ width: 3em;
+ min-width: 3em;
+}
+
+div#cgit table.ssdiff td.hunk {
+ color: black;
+ background: #ccf;
+ border-top: solid 1px #aaa;
+ border-bottom: solid 1px #aaa;
+}
+
+div#cgit table.ssdiff td.head {
+ border-top: solid 1px #aaa;
+ border-bottom: solid 1px #aaa;
+}
+
+div#cgit table.ssdiff td.head div.head {
+ font-weight: bold;
+ color: black;
+}
+
+div#cgit table.ssdiff td.foot {
+ border-top: solid 1px #aaa;
+ border-left: none;
+ border-right: none;
+ border-bottom: none;
+}
+
+div#cgit table.ssdiff td.space {
+ border: none;
+}
+
+div#cgit table.ssdiff td.space div {
+ min-height: 3em;
+}
+
+/*
+ * Style definitions generated by highlight 3.14, http://www.andre-simon.de/
+ * Highlighting theme: Kwrite Editor
+ */
+div#cgit table.blob .num { color:#b07e00; }
+div#cgit table.blob .esc { color:#ff00ff; }
+div#cgit table.blob .str { color:#bf0303; }
+div#cgit table.blob .pps { color:#818100; }
+div#cgit table.blob .slc { color:#838183; font-style:italic; }
+div#cgit table.blob .com { color:#838183; font-style:italic; }
+div#cgit table.blob .ppc { color:#008200; }
+div#cgit table.blob .opt { color:#000000; }
+div#cgit table.blob .ipl { color:#0057ae; }
+div#cgit table.blob .lin { color:#555555; }
+div#cgit table.blob .kwa { color:#000000; font-weight:bold; }
+div#cgit table.blob .kwb { color:#0057ae; }
+div#cgit table.blob .kwc { color:#000000; font-weight:bold; }
+div#cgit table.blob .kwd { color:#010181; }
diff --git a/web/html/index.php b/web/html/index.php
index 95989f5..cfd6598 100644
--- a/web/html/index.php
+++ b/web/html/index.php
@@ -78,6 +78,9 @@ if (!empty($tokens[1]) && '/' . $tokens[1] == get_pkg_route()) {
case "request":
include('pkgreq.php');
return;
+ case "comaintainers":
+ include('comaintainers.php');
+ return;
default:
header("HTTP/1.0 404 Not Found");
include "./404.php";
@@ -141,6 +144,7 @@ if (!empty($tokens[1]) && '/' . $tokens[1] == get_pkg_route()) {
switch ($path) {
case "/css/archweb.css":
case "/css/aur.css":
+ case "/css/cgit.css":
case "/css/archnavbar/archnavbar.css":
header("Content-Type: text/css");
readfile("./$path");
@@ -151,6 +155,7 @@ if (!empty($tokens[1]) && '/' . $tokens[1] == get_pkg_route()) {
readfile("./$path");
break;
case "/css/archnavbar/archlogo.png":
+ case "/css/archnavbar/aurlogo.png":
case "/images/AUR-logo-80.png":
case "/images/AUR-logo.png":
case "/images/favicon.ico":
diff --git a/web/html/pkgbase.php b/web/html/pkgbase.php
index bdce516..201749e 100644
--- a/web/html/pkgbase.php
+++ b/web/html/pkgbase.php
@@ -97,6 +97,8 @@ if (check_token()) {
list($ret, $output) = pkgreq_file($ids, $_POST['type'], $_POST['merge_into'], $_POST['comments']);
} elseif (current_action("do_CloseRequest")) {
list($ret, $output) = pkgreq_close($_POST['reqid'], $_POST['reason'], $_POST['comments']);
+ } elseif (current_action("do_EditComaintainers")) {
+ list($ret, $output) = pkgbase_set_comaintainers($base_id, explode("\n", $_POST['users']));
}
if (isset($_REQUEST['comment'])) {
@@ -124,7 +126,7 @@ if (check_token()) {
}
$pkgs = pkgbase_get_pkgnames($base_id);
-if (count($pkgs) == 1) {
+if (!$output && count($pkgs) == 1) {
/* Not a split package. Redirect to the package page. */
if (empty($_SERVER['QUERY_STRING'])) {
header('Location: ' . get_pkg_uri($pkgs[0]));
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/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/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/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];
diff --git a/web/lib/credentials.inc.php b/web/lib/credentials.inc.php
index 6c70ede..b813b90 100644
--- a/web/lib/credentials.inc.php
+++ b/web/lib/credentials.inc.php
@@ -11,6 +11,7 @@ define("CRED_PKGBASE_ADOPT", 7);
define("CRED_PKGBASE_CHANGE_CATEGORY", 8);
define("CRED_PKGBASE_DELETE", 9);
define("CRED_PKGBASE_DISOWN", 10);
+define("CRED_PKGBASE_EDIT_COMAINTAINERS", 24);
define("CRED_PKGBASE_FLAG", 11);
define("CRED_PKGBASE_LIST_VOTERS", 12);
define("CRED_PKGBASE_NOTIFY", 13);
@@ -61,6 +62,7 @@ function has_credential($credential, $approved_users=array()) {
case CRED_PKGBASE_ADOPT:
case CRED_PKGBASE_CHANGE_CATEGORY:
case CRED_PKGBASE_DELETE:
+ case CRED_PKGBASE_EDIT_COMAINTAINERS:
case CRED_PKGBASE_DISOWN:
case CRED_PKGBASE_LIST_VOTERS:
case CRED_PKGBASE_SUBMIT_BLACKLISTED:
diff --git a/web/lib/pkgbasefuncs.inc.php b/web/lib/pkgbasefuncs.inc.php
index 708f861..5741b01 100644
--- a/web/lib/pkgbasefuncs.inc.php
+++ b/web/lib/pkgbasefuncs.inc.php
@@ -915,55 +915,80 @@ function pkgbase_change_category($base_id) {
}
/**
- * Add package base information to the database
+ * Change the category a package base belongs to
*
- * @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
+ * @param int $base_id The package base ID to change the category for
+ * @param int $category_id The new category ID for the package
*
- * @return int ID of the new package base
+ * @return void
*/
-function pkgbase_create($name, $category_id, $uid) {
+function pkgbase_update_category($base_id, $category_id) {
$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);
+ $q = sprintf("UPDATE PackageBases SET CategoryID = %d WHERE ID = %d",
+ $category_id, $base_id);
$dbh->exec($q);
- return $dbh->lastInsertId();
}
/**
- * Update package base information for a specific package base
+ * Get a list of package base co-maintainers
*
- * @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
+ * @param int $base_id The package base ID to retrieve the co-maintainers for
*
- * @return void
+ * @return array An array of co-maintainer user names
*/
-function pkgbase_update($base_id, $name, $uid) {
+function pkgbase_get_comaintainers($base_id) {
$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);
+ $q = "SELECT UserName FROM PackageComaintainers ";
+ $q .= "INNER JOIN Users ON Users.ID = PackageComaintainers.UsersID ";
+ $q .= "WHERE PackageComaintainers.PackageBaseID = " . intval($base_id);
+ $result = $dbh->query($q);
+
+ if ($result) {
+ return $result->fetchAll(PDO::FETCH_COLUMN, 0);
+ } else {
+ return array();
+ }
}
/**
- * Change the category a package base belongs to
+ * Update the list of co-maintainers of a package base
*
- * @param int $base_id The package base ID to change the category for
- * @param int $category_id The new category ID for the package
+ * @param int $base_id The package base ID to update the co-maintainers of
+ * @param array $users Array of co-maintainer user names
*
- * @return void
+ * @return array Tuple of success/failure indicator and error message
*/
-function pkgbase_update_category($base_id, $category_id) {
+function pkgbase_set_comaintainers($base_id, $users) {
+ if (!has_credential(CRED_PKGBASE_EDIT_COMAINTAINERS, array(pkgbase_maintainer_uid($base_id)))) {
+ return array(false, __("You are not allowed to manage co-maintainers of this package base."));
+ }
+
+ /* Remove empty and duplicate user names. */
+ $users = array_unique(array_filter(array_map('trim', $users)));
+
$dbh = DB::connect();
- $q = sprintf("UPDATE PackageBases SET CategoryID = %d WHERE ID = %d",
- $category_id, $base_id);
+
+ $uids = array();
+ foreach($users as $user) {
+ $q = "SELECT ID FROM Users ";
+ $q .= "WHERE UserName = " . $dbh->quote($user);
+ $result = $dbh->query($q);
+ $uid = $result->fetchColumn(0);
+
+ if (!$uid) {
+ return array(false, __("Invalid user name: %s", $user));
+ }
+
+ $uids[] = $uid;
+ }
+
+ $q = sprintf("DELETE FROM PackageComaintainers WHERE PackageBaseID = %d", $base_id);
$dbh->exec($q);
+
+ foreach ($uids as $uid) {
+ $q = sprintf("INSERT INTO PackageComaintainers (PackageBaseID, UsersID) VALUES (%d, %d)", $base_id, $uid);
+ $dbh->exec($q);
+ }
+
+ return array(true, __("The package base co-maintainers have been updated."));
}
diff --git a/web/lib/pkgfuncs.inc.php b/web/lib/pkgfuncs.inc.php
index 7e7a98f..c71358a 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
@@ -593,7 +563,7 @@ function pkg_search_page($SID="") {
$q_from_extra = "";
}
- $q_where = "WHERE 1 = 1 ";
+ $q_where = 'WHERE PackageBases.PackagerUID IS NOT NULL ';
/*
* TODO: Possibly do string matching on category to make request
* variable values more sensible.
@@ -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/lib/stats.inc.php b/web/lib/stats.inc.php
index d63767c..1dcb9b9 100644
--- a/web/lib/stats.inc.php
+++ b/web/lib/stats.inc.php
@@ -14,6 +14,7 @@ function updates_table() {
$q = 'SELECT Packages.Name, Version, ModifiedTS, SubmittedTS ';
$q.= 'FROM Packages INNER JOIN PackageBases ON ';
$q.= 'Packages.PackageBaseID = PackageBases.ID ';
+ $q.= 'WHERE PackageBases.PackagerUID IS NOT NULL ';
$q.= 'ORDER BY ModifiedTS DESC LIMIT 10';
$result = $dbh->query($q);
diff --git a/web/template/account_edit_form.php b/web/template/account_edit_form.php
index 17dd937..110fbb5 100644
--- a/web/template/account_edit_form.php
+++ b/web/template/account_edit_form.php
@@ -97,6 +97,13 @@
<input type="text" size="30" maxlength="50" name="K" id="id_pgp" value="<?= html_format_pgp_fingerprint($K) ?>" />
</p>
+ <?php if ($A == "UpdateAccount"): ?>
+ <p>
+ <label for="id_ssh"><?= __("SSH Public Key") ?>:</label>
+ <textarea name="PK" id="id_ssh" rows="5" cols="30"><?= htmlspecialchars($PK) ?></textarea>
+ </p>
+ <?php endif; ?>
+
<p>
<label for="id_language"><?= __("Language") ?>:</label>
<select name="L" id="id_language">
diff --git a/web/template/cgit/footer.html b/web/template/cgit/footer.html
new file mode 100644
index 0000000..f90aeb7
--- /dev/null
+++ b/web/template/cgit/footer.html
@@ -0,0 +1,6 @@
+<div id="footer">
+ <p>
+ Copyright &copy; 2004-2014 AUR Development Team &ndash;
+ <strong>Unsupported packages are user produced content. Any use of the provided files is at your own risk.</strong>
+ </p>
+</div>
diff --git a/web/template/cgit/header.html b/web/template/cgit/header.html
new file mode 100644
index 0000000..0217f2f
--- /dev/null
+++ b/web/template/cgit/header.html
@@ -0,0 +1,14 @@
+ <div id="archnavbar" class="anb-aur">
+ <div id="archnavbarlogo"><h1><a href="/" title="Return to the main page">Arch Linux User Repository</a></h1></div>
+ <div id="archnavbarmenu">
+ <ul id="archnavbarlist">
+ <li id="anb-home"><a href="https://www.archlinux.org/" title="Arch news, packages, projects and more">Home</a></li>
+ <li id="anb-packages"><a href="https://www.archlinux.org/packages/" title="Arch Package Database">Packages</a></li>
+ <li id="anb-forums"><a href="https://bbs.archlinux.org/" title="Community forums">Forums</a></li>
+ <li id="anb-wiki"><a href="https://wiki.archlinux.org/" title="Community documentation">Wiki</a></li>
+ <li id="anb-bugs"><a href="https://bugs.archlinux.org/" title="Report and track bugs">Bugs</a></li>
+ <li id="anb-aur"><a href="/" title="Arch Linux User Repository">AUR</a></li>
+ <li id="anb-download"><a href="https://www.archlinux.org/download/" title="Get Arch Linux">Download</a></li>
+ </ul>
+ </div>
+ </div><!-- #archnavbar -->
diff --git a/web/template/comaintainers_form.php b/web/template/comaintainers_form.php
new file mode 100644
index 0000000..050f255
--- /dev/null
+++ b/web/template/comaintainers_form.php
@@ -0,0 +1,20 @@
+<div class="box">
+ <h2><?= __('Manage Co-maintainers: %s', htmlspecialchars($pkgbase_name)) ?></h2>
+ <p>
+ <?= __('Use this form to add co-maintainers for %s%s%s (one user name per line):',
+ '<strong>', htmlspecialchars($pkgbase_name), '</strong>'); ?>
+ </p>
+ <form action="<?= get_pkgbase_uri($pkgbase_name); ?>" method="post">
+ <fieldset>
+ <input type="hidden" name="token" value="<?= htmlspecialchars($_COOKIE['AURSID']) ?>" />
+ <p>
+ <label for="id_users"><?= __("Users") ?>:</label>
+ <textarea name="users" id="id_users" rows="5" cols="50"><?= htmlspecialchars(implode("\n", $users)) ?></textarea>
+ </p>
+ <p>
+ <input type="submit" class="button" name="do_EditComaintainers" value="<?= __("Save") ?>" />
+ </p>
+ </fieldset>
+ </form>
+</div>
+
diff --git a/web/template/header.php b/web/template/header.php
index 6167fb7..8a1494c 100644
--- a/web/template/header.php
+++ b/web/template/header.php
@@ -16,7 +16,7 @@
</head>
<body>
<div id="archnavbar" class="anb-aur">
- <div id="archnavbarlogo"><h1><a href="https://www.archlinux.org/" title="Return to the main page">Arch Linux</a></h1></div>
+ <div id="archnavbarlogo"><h1><a href="/" title="Return to the main page">Arch Linux User Repository</a></h1></div>
<div id="archnavbarmenu">
<ul id="archnavbarlist">
<li id="anb-home"><a href="https://www.archlinux.org/" title="Arch news, packages, projects and more">Home</a></li>
@@ -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 b0ceb8c..52afba2 100644
--- a/web/template/pkg_details.php
+++ b/web/template/pkg_details.php
@@ -1,4 +1,9 @@
<?php
+
+$cgit_uri = config_get('options', 'cgit_uri');
+$git_clone_uri_anon = sprintf(config_get('options', 'git_clone_uri_anon'), htmlspecialchars($row['Name']));
+$git_clone_uri_priv = sprintf(config_get('options', 'git_clone_uri_priv'), htmlspecialchars($row['Name']));
+
$uid = uid_from_sid($SID);
$pkgid = intval($row['ID']);
@@ -21,9 +26,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 +81,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): ?>
@@ -129,6 +131,9 @@ $sources = pkg_sources($row["ID"]);
</form>
</li>
<?php endif; ?>
+ <?php if (has_credential(CRED_PKGBASE_EDIT_COMAINTAINERS, array($row["MaintainerUID"]))): ?>
+ <li><a href="<?= get_pkgbase_uri($row['BaseName']) . 'comaintainers/'; ?>"><?= __('Manage Co-Maintainers'); ?></a></li>
+ <?php endif; ?>
<li><span class="flagged"><?php if ($row["RequestCount"] > 0) { echo _n('%d pending request', '%d pending requests', $row["RequestCount"]); } ?></span></li>
<li><a href="<?= get_pkgbase_uri($row['BaseName']) . 'request/'; ?>"><?= __('File Request'); ?></a></li>
<?php if (has_credential(CRED_PKGBASE_DELETE)): ?>
@@ -158,6 +163,15 @@ $sources = pkg_sources($row["ID"]);
<table id="pkginfo">
<tr>
+ <th><?= __('Git Clone URL') . ': ' ?></th>
+ <td>
+ <a href="<?= $git_clone_uri_anon ?>"><?= $git_clone_uri_anon ?></a>
+ <?php if ($uid == $row["MaintainerUID"]): ?>
+ <br /> <a href="<?= $git_clone_uri_priv ?>"><?= $git_clone_uri_priv ?></a>
+ <?php endif; ?>
+ </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 b8d36ed..ae363fd 100644
--- a/web/template/pkgbase_details.php
+++ b/web/template/pkgbase_details.php
@@ -1,4 +1,9 @@
<?php
+
+$cgit_uri = config_get('options', 'cgit_uri');
+$git_clone_uri_anon = sprintf(config_get('options', 'git_clone_uri_anon'), htmlspecialchars($row['Name']));
+$git_clone_uri_priv = sprintf(config_get('options', 'git_clone_uri_priv'), htmlspecialchars($row['Name']));
+
$uid = uid_from_sid($SID);
$base_id = intval($row['ID']);
@@ -19,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['Name'], 0, 2) . "/" . $row['Name'];
-
$pkgs = pkgbase_get_pkgnames($base_id);
?>
<div id="pkgdetails" class="box">
@@ -30,8 +32,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): ?>
@@ -80,6 +82,9 @@ $pkgs = pkgbase_get_pkgnames($base_id);
</form>
</li>
<?php endif; ?>
+ <?php if (has_credential(CRED_PKGBASE_EDIT_COMAINTAINERS, array($row["MaintainerUID"]))): ?>
+ <li><a href="<?= get_pkgbase_uri($row['Name']) . 'comaintainers/'; ?>"><?= __('Manage Co-Maintainers'); ?></a></li>
+ <?php endif; ?>
<li><span class="flagged"><?php if ($row["RequestCount"] > 0) { echo _n('%d pending request', '%d pending requests', $row["RequestCount"]); } ?></span></li>
<li><a href="<?= get_pkgbase_uri($row['Name']) . 'request/'; ?>"><?= __('File Request'); ?></a></li>
<?php if (has_credential(CRED_PKGBASE_DELETE)): ?>
@@ -109,6 +114,15 @@ $pkgs = pkgbase_get_pkgnames($base_id);
<table id="pkginfo">
<tr>
+ <th><?= __('Git Clone URL') . ': ' ?></th>
+ <td>
+ <a href="<?= $git_clone_uri_anon ?>"><?= $git_clone_uri_anon ?></a>
+ <?php if ($uid == $row["MaintainerUID"]): ?>
+ <br /> <a href="<?= $git_clone_uri_priv ?>"><?= $git_clone_uri_priv ?></a>
+ <?php endif; ?>
+ </td>
+ </tr>
+ <tr>
<th><?= __('Category') . ': ' ?></th>
<?php
if (has_credential(CRED_PKGBASE_CHANGE_CATEGORY, array($row["MaintainerUID"]))):