git-interface: Add test suite and basic tests
[aur.git] / web / lib / aur.inc.php
blob9015ae8fabff9fd40e4ac14ce4e1ab0462b17c13
1 <?php
2 set_include_path(get_include_path() . PATH_SEPARATOR . '../lib' . PATH_SEPARATOR . '../template');
3 header('Content-Type: text/html; charset=utf-8');
4 header('Cache-Control: no-cache, must-revalidate');
5 header('Expires: Tue, 11 Oct 1988 22:00:00 GMT'); // quite a special day
6 header('Pragma: no-cache');
8 date_default_timezone_set('UTC');
10 include_once('translator.inc.php');
11 set_lang();
13 include_once("DB.class.php");
14 include_once("routing.inc.php");
15 include_once("version.inc.php");
16 include_once("acctfuncs.inc.php");
17 include_once("cachefuncs.inc.php");
18 include_once("confparser.inc.php");
19 include_once("credentials.inc.php");
21 /**
22 * Check if a visitor is logged in
24 * Query "Sessions" table with supplied cookie. Determine if the cookie is valid
25 * or not. Unset the cookie if invalid or session timeout reached. Update the
26 * session timeout if it is still valid.
28 * @global array $_COOKIE User cookie values
30 * @return void
32 function check_sid() {
33 global $_COOKIE;
35 if (isset($_COOKIE["AURSID"])) {
36 $failed = 0;
37 $timeout = config_get_int('options', 'login_timeout');
38 # the visitor is logged in, try and update the session
40 $dbh = DB::connect();
41 $q = "SELECT LastUpdateTS, UNIX_TIMESTAMP() FROM Sessions ";
42 $q.= "WHERE SessionID = " . $dbh->quote($_COOKIE["AURSID"]);
43 $result = $dbh->query($q);
44 $row = $result->fetch(PDO::FETCH_NUM);
46 if (!$row[0]) {
47 # Invalid SessionID - hacker alert!
49 $failed = 1;
50 } else {
51 $last_update = $row[0];
52 if ($last_update + $timeout <= $row[1]) {
53 $failed = 2;
57 if ($failed == 1) {
58 # clear out the hacker's cookie, and send them to a naughty page
59 # why do you have to be so harsh on these people!?
61 setcookie("AURSID", "", 1, "/", null, !empty($_SERVER['HTTPS']), true);
62 unset($_COOKIE['AURSID']);
63 } elseif ($failed == 2) {
64 # session id timeout was reached and they must login again.
66 delete_session_id($_COOKIE["AURSID"]);
68 setcookie("AURSID", "", 1, "/", null, !empty($_SERVER['HTTPS']), true);
69 unset($_COOKIE['AURSID']);
70 } else {
71 # still logged in and haven't reached the timeout, go ahead
72 # and update the idle timestamp
74 # Only update the timestamp if it is less than the
75 # current time plus $timeout.
77 # This keeps 'remembered' sessions from being
78 # overwritten.
79 if ($last_update < time() + $timeout) {
80 $q = "UPDATE Sessions SET LastUpdateTS = UNIX_TIMESTAMP() ";
81 $q.= "WHERE SessionID = " . $dbh->quote($_COOKIE["AURSID"]);
82 $dbh->exec($q);
86 return;
89 /**
90 * Verify the supplied CSRF token matches expected token
92 * @return bool True if the CSRF token is the same as the cookie SID, otherwise false
94 function check_token() {
95 if (isset($_POST['token']) && isset($_COOKIE['AURSID'])) {
96 return ($_POST['token'] == $_COOKIE['AURSID']);
97 } else {
98 return false;
103 * Verify a user supplied e-mail against RFC 3696 and DNS records
105 * @param string $addy E-mail address being validated in foo@example.com format
107 * @return bool True if e-mail passes validity checks, otherwise false
109 function valid_email($addy) {
110 // check against RFC 3696
111 if (filter_var($addy, FILTER_VALIDATE_EMAIL) === false) {
112 return false;
115 // check dns for mx, a, aaaa records
116 list($local, $domain) = explode('@', $addy);
117 if (!(checkdnsrr($domain, 'MX') || checkdnsrr($domain, 'A') || checkdnsrr($domain, 'AAAA'))) {
118 return false;
121 return true;
125 * Generate a unique session ID
127 * @return string MD5 hash of the concatenated user IP, random number, and current time
129 function new_sid() {
130 return md5($_SERVER['REMOTE_ADDR'] . uniqid(mt_rand(), true));
134 * Determine the user's username in the database using a user ID
136 * @param string $id User's ID
138 * @return string Username if it exists, otherwise null
140 function username_from_id($id) {
141 $id = intval($id);
143 $dbh = DB::connect();
144 $q = "SELECT Username FROM Users WHERE ID = " . $dbh->quote($id);
145 $result = $dbh->query($q);
146 if (!$result) {
147 return null;
150 $row = $result->fetch(PDO::FETCH_NUM);
151 return $row[0];
155 * Determine the user's username in the database using a session ID
157 * @param string $sid User's session ID
159 * @return string Username of the visitor
161 function username_from_sid($sid="") {
162 if (!$sid) {
163 return "";
165 $dbh = DB::connect();
166 $q = "SELECT Username ";
167 $q.= "FROM Users, Sessions ";
168 $q.= "WHERE Users.ID = Sessions.UsersID ";
169 $q.= "AND Sessions.SessionID = " . $dbh->quote($sid);
170 $result = $dbh->query($q);
171 if (!$result) {
172 return "";
174 $row = $result->fetch(PDO::FETCH_NUM);
176 return $row[0];
180 * Format a user name for inclusion in HTML data
182 * @param string $username The user name to format
184 * @return string The generated HTML code for the account link
186 function html_format_username($username) {
187 $username_fmt = $username ? htmlspecialchars($username, ENT_QUOTES) : __("None");
189 if ($username && isset($_COOKIE["AURSID"])) {
190 $link = '<a href="' . get_uri('/account/') . $username_fmt;
191 $link .= '" title="' . __('View account information for %s', $username_fmt);
192 $link .= '">' . $username_fmt . '</a>';
193 return $link;
194 } else {
195 return $username_fmt;
200 * Format the maintainer and co-maintainers for inclusion in HTML data
202 * @param string $maintainer The user name of the maintainer
203 * @param array $comaintainers The list of co-maintainer user names
205 * @return string The generated HTML code for the account links
207 function html_format_maintainers($maintainer, $comaintainers) {
208 $code = html_format_username($maintainer);
210 if (count($comaintainers) > 0) {
211 $code .= ' (';
212 foreach ($comaintainers as $comaintainer) {
213 $code .= html_format_username($comaintainer);
214 if ($comaintainer !== end($comaintainers)) {
215 $code .= ', ';
218 $code .= ')';
221 return $code;
225 * Format a link in the package actions box
227 * @param string $uri The link target
228 * @param string $inner The HTML code to use for the link label
230 * @return string The generated HTML code for the action link
232 function html_action_link($uri, $inner) {
233 if (isset($_COOKIE["AURSID"])) {
234 $code = '<a href="' . htmlspecialchars($uri, ENT_QUOTES) . '">';
235 } else {
236 $code = '<a href="' . get_uri('/login/', true) . '?referer=';
237 $code .= urlencode(rtrim(aur_location(), '/') . $uri) . '">';
239 $code .= $inner . '</a>';
241 return $code;
245 * Format a form in the package actions box
247 * @param string $uri The link target
248 * @param string $action The action name (passed as HTTP POST parameter)
249 * @param string $inner The HTML code to use for the link label
251 * @return string The generated HTML code for the action link
253 function html_action_form($uri, $action, $inner) {
254 if (isset($_COOKIE["AURSID"])) {
255 $code = '<form action="' . htmlspecialchars($uri, ENT_QUOTES) . '" ';
256 $code .= 'method="post">';
257 $code .= '<input type="hidden" name="token" value="';
258 $code .= htmlspecialchars($_COOKIE['AURSID'], ENT_QUOTES) . '" />';
259 $code .= '<input type="submit" class="button text-button" name="';
260 $code .= htmlspecialchars($action, ENT_QUOTES) . '" ';
261 $code .= 'value="' . $inner . '" />';
262 $code .= '</form>';
263 } else {
264 $code = '<a href="' . get_uri('/login/', true) . '">';
265 $code .= $inner . '</a>';
268 return $code;
272 * Determine the user's e-mail address in the database using a session ID
274 * @param string $sid User's session ID
276 * @return string User's e-mail address as given during registration
278 function email_from_sid($sid="") {
279 if (!$sid) {
280 return "";
282 $dbh = DB::connect();
283 $q = "SELECT Email ";
284 $q.= "FROM Users, Sessions ";
285 $q.= "WHERE Users.ID = Sessions.UsersID ";
286 $q.= "AND Sessions.SessionID = " . $dbh->quote($sid);
287 $result = $dbh->query($q);
288 if (!$result) {
289 return "";
291 $row = $result->fetch(PDO::FETCH_NUM);
293 return $row[0];
297 * Determine the user's account type in the database using a session ID
299 * @param string $sid User's session ID
301 * @return string Account type of user ("User", "Trusted User", or "Developer")
303 function account_from_sid($sid="") {
304 if (!$sid) {
305 return "";
307 $dbh = DB::connect();
308 $q = "SELECT AccountType ";
309 $q.= "FROM Users, AccountTypes, Sessions ";
310 $q.= "WHERE Users.ID = Sessions.UsersID ";
311 $q.= "AND AccountTypes.ID = Users.AccountTypeID ";
312 $q.= "AND Sessions.SessionID = " . $dbh->quote($sid);
313 $result = $dbh->query($q);
314 if (!$result) {
315 return "";
317 $row = $result->fetch(PDO::FETCH_NUM);
319 return $row[0];
323 * Determine the user's ID in the database using a session ID
325 * @param string $sid User's session ID
327 * @return string|int The user's name, 0 on query failure
329 function uid_from_sid($sid="") {
330 if (!$sid) {
331 return "";
333 $dbh = DB::connect();
334 $q = "SELECT Users.ID ";
335 $q.= "FROM Users, Sessions ";
336 $q.= "WHERE Users.ID = Sessions.UsersID ";
337 $q.= "AND Sessions.SessionID = " . $dbh->quote($sid);
338 $result = $dbh->query($q);
339 if (!$result) {
340 return 0;
342 $row = $result->fetch(PDO::FETCH_NUM);
344 return $row[0];
348 * Common AUR header displayed on all pages
350 * @global string $LANG Language selected by the visitor
351 * @global array $SUPPORTED_LANGS Languages that are supported by the AUR
352 * @param string $title Name of the AUR page to be displayed on browser
354 * @return void
356 function html_header($title="", $details=array()) {
357 global $LANG;
358 global $SUPPORTED_LANGS;
360 include('header.php');
361 return;
365 * Common AUR footer displayed on all pages
367 * @param string $ver The AUR version
369 * @return void
371 function html_footer($ver="") {
372 include('footer.php');
373 return;
377 * Determine if a user has permission to submit a package
379 * @param string $name Name of the package to be submitted
380 * @param string $sid User's session ID
382 * @return int 0 if the user can't submit, 1 if the user can submit
384 function can_submit_pkgbase($name="", $sid="") {
385 if (!$name || !$sid) {return 0;}
386 $dbh = DB::connect();
387 $q = "SELECT MaintainerUID ";
388 $q.= "FROM PackageBases WHERE Name = " . $dbh->quote($name);
389 $result = $dbh->query($q);
390 $row = $result->fetch(PDO::FETCH_NUM);
392 if (!$row[0]) {
393 return 1;
395 $my_uid = uid_from_sid($sid);
397 if ($row[0] === NULL || $row[0] == $my_uid) {
398 return 1;
401 return 0;
405 * Determine if a package can be overwritten by some package base
407 * @param string $name Name of the package to be submitted
408 * @param int $base_id The ID of the package base
410 * @return bool True if the package can be overwritten, false if not
412 function can_submit_pkg($name, $base_id) {
413 $dbh = DB::connect();
414 $q = "SELECT COUNT(*) FROM Packages WHERE ";
415 $q.= "Name = " . $dbh->quote($name) . " AND ";
416 $q.= "PackageBaseID <> " . intval($base_id);
417 $result = $dbh->query($q);
419 if (!$result) return false;
420 return ($result->fetchColumn() == 0);
424 * Recursively delete a directory
426 * @param string $dirname Name of the directory to be removed
428 * @return void
430 function rm_tree($dirname) {
431 if (empty($dirname) || !is_dir($dirname)) return;
433 foreach (scandir($dirname) as $item) {
434 if ($item != '.' && $item != '..') {
435 $path = $dirname . '/' . $item;
436 if (is_file($path) || is_link($path)) {
437 unlink($path);
439 else {
440 rm_tree($path);
445 rmdir($dirname);
447 return;
451 * Determine the user's ID in the database using a username
453 * @param string $username The username of an account
455 * @return string Return user ID if exists for username, otherwise null
457 function uid_from_username($username) {
458 $dbh = DB::connect();
459 $q = "SELECT ID FROM Users WHERE Username = " . $dbh->quote($username);
460 $result = $dbh->query($q);
461 if (!$result) {
462 return null;
465 $row = $result->fetch(PDO::FETCH_NUM);
466 return $row[0];
470 * Determine the user's ID in the database using a username or email address
472 * @param string $username The username or email address of an account
474 * @return string Return user ID if exists, otherwise null
476 function uid_from_loginname($loginname) {
477 $uid = uid_from_username($loginname);
478 if (!$uid) {
479 $uid = uid_from_email($loginname);
481 return $uid;
485 * Determine the user's ID in the database using an e-mail address
487 * @param string $email An e-mail address in foo@example.com format
489 * @return string The user's ID
491 function uid_from_email($email) {
492 $dbh = DB::connect();
493 $q = "SELECT ID FROM Users WHERE Email = " . $dbh->quote($email);
494 $result = $dbh->query($q);
495 if (!$result) {
496 return null;
499 $row = $result->fetch(PDO::FETCH_NUM);
500 return $row[0];
504 * Generate clean url with edited/added user values
506 * Makes a clean string of variables for use in URLs based on current $_GET and
507 * list of values to edit/add to that. Any empty variables are discarded.
509 * @example print "http://example.com/test.php?" . mkurl("foo=bar&bar=baz")
511 * @param string $append string of variables and values formatted as in URLs
513 * @return string clean string of variables to append to URL, urlencoded
515 function mkurl($append) {
516 $get = $_GET;
517 $append = explode('&', $append);
518 $uservars = array();
519 $out = '';
521 foreach ($append as $i) {
522 $ex = explode('=', $i);
523 $uservars[$ex[0]] = $ex[1];
526 foreach ($uservars as $k => $v) { $get[$k] = $v; }
528 foreach ($get as $k => $v) {
529 if ($v !== '') {
530 $out .= '&amp;' . urlencode($k) . '=' . urlencode($v);
534 return substr($out, 5);
538 * Determine a user's salt from the database
540 * @param string $user_id The user ID of the user trying to log in
542 * @return string|void Return the salt for the requested user, otherwise void
544 function get_salt($user_id) {
545 $dbh = DB::connect();
546 $q = "SELECT Salt FROM Users WHERE ID = " . $user_id;
547 $result = $dbh->query($q);
548 if ($result) {
549 $row = $result->fetch(PDO::FETCH_NUM);
550 return $row[0];
552 return;
556 * Save a user's salted password in the database
558 * @param string $user_id The user ID of the user who is salting their password
559 * @param string $passwd The password of the user logging in
561 function save_salt($user_id, $passwd) {
562 $dbh = DB::connect();
563 $salt = generate_salt();
564 $hash = salted_hash($passwd, $salt);
565 $q = "UPDATE Users SET Salt = " . $dbh->quote($salt) . ", ";
566 $q.= "Passwd = " . $dbh->quote($hash) . " WHERE ID = " . $user_id;
567 return $dbh->exec($q);
571 * Generate a string to be used for salting passwords
573 * @return string MD5 hash of concatenated random number and current time
575 function generate_salt() {
576 return md5(uniqid(mt_rand(), true));
580 * Combine salt and password to form a hash
582 * @param string $passwd User plaintext password
583 * @param string $salt MD5 hash to be used as user salt
585 * @return string The MD5 hash of the concatenated salt and user password
587 function salted_hash($passwd, $salt) {
588 if (strlen($salt) != 32) {
589 trigger_error('Salt does not look like an md5 hash', E_USER_WARNING);
591 return md5($salt . $passwd);
595 * Get a package comment
597 * @param int $comment_id The ID of the comment
599 * @return array The user ID and comment OR null, null in case of an error
601 function comment_by_id($comment_id) {
602 $dbh = DB::connect();
603 $q = "SELECT UsersID, Comments FROM PackageComments ";
604 $q.= "WHERE ID = " . intval($comment_id);
605 $result = $dbh->query($q);
606 if (!$result) {
607 return array(null, null);
610 return $result->fetch(PDO::FETCH_NUM);
614 * Process submitted comments so any links can be followed
616 * @param string $comment Raw user submitted package comment
618 * @return string User comment with links printed in HTML
620 function parse_comment($comment) {
621 $url_pattern = '/(\b(?:https?|ftp):\/\/[\w\/\#~:.?+=&%@!\-;,]+?' .
622 '(?=[.:?\-;,]*(?:[^\w\/\#~:.?+=&%@!\-;,]|$)))/iS';
624 $matches = preg_split($url_pattern, $comment, -1,
625 PREG_SPLIT_DELIM_CAPTURE);
627 $html = '';
628 for ($i = 0; $i < count($matches); $i++) {
629 if ($i % 2) {
630 # convert links
631 $html .= '<a href="' . htmlspecialchars($matches[$i]) .
632 '" rel="nofollow">' . htmlspecialchars($matches[$i]) . '</a>';
634 else {
635 # convert everything else
636 $html .= nl2br(htmlspecialchars($matches[$i]));
640 return $html;
644 * Wrapper for beginning a database transaction
646 function begin_atomic_commit() {
647 $dbh = DB::connect();
648 $dbh->beginTransaction();
652 * Wrapper for committing a database transaction
654 function end_atomic_commit() {
655 $dbh = DB::connect();
656 $dbh->commit();
660 * Merge pkgbase and package options
662 * Merges entries of the first and the second array. If any key appears in both
663 * arrays and the corresponding value in the second array is either a non-array
664 * type or a non-empty array, the value from the second array replaces the
665 * value from the first array. If the value from the second array is an array
666 * containing a single empty string, the value in the resulting array becomes
667 * an empty array instead. If the value in the second array is empty, the
668 * resulting array contains the value from the first array.
670 * @param array $pkgbase_info Options from the pkgbase section
671 * @param array $section_info Options from the package section
673 * @return array Merged information from both sections
675 function array_pkgbuild_merge($pkgbase_info, $section_info) {
676 $pi = $pkgbase_info;
677 foreach ($section_info as $opt_key => $opt_val) {
678 if (is_array($opt_val)) {
679 if ($opt_val == array('')) {
680 $pi[$opt_key] = array();
681 } elseif (count($opt_val) > 0) {
682 $pi[$opt_key] = $opt_val;
684 } else {
685 $pi[$opt_key] = $opt_val;
688 return $pi;
692 * Bound an integer value between two values
694 * @param int $n Integer value to bound
695 * @param int $min Lower bound
696 * @param int $max Upper bound
698 * @return int Bounded integer value
700 function bound($n, $min, $max) {
701 return min(max($n, $min), $max);
705 * Return the URL of the AUR root
707 * @return string The URL of the AUR root
709 function aur_location() {
710 $location = config_get('options', 'aur_location');
711 if (substr($location, -1) != '/') {
712 $location .= '/';
714 return $location;