summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--web/lib/aurjson.class.php696
1 files changed, 349 insertions, 347 deletions
diff --git a/web/lib/aurjson.class.php b/web/lib/aurjson.class.php
index 2e5e06d..fe8044f 100644
--- a/web/lib/aurjson.class.php
+++ b/web/lib/aurjson.class.php
@@ -1,361 +1,363 @@
<?php
-/**
- * AurJSON
- *
- * This file contains the AurRPC remote handling class
- **/
+
include_once("aur.inc.php");
-/**
- * This class defines a remote interface for fetching data
- * from the AUR using JSON formatted elements.
+/*
+ * This class defines a remote interface for fetching data from the AUR using
+ * JSON formatted elements.
+ *
* @package rpc
* @subpackage classes
- **/
+ */
class AurJSON {
- private $dbh = false;
- private static $exposed_methods = array(
- 'search', 'info', 'multiinfo', 'msearch', 'suggest'
- );
- private static $fields = array(
- 'Packages.ID', 'Packages.Name', 'PackageBases.Name AS PackageBase',
- 'Version', 'CategoryID', 'Description', 'URL', 'NumVotes',
- 'OutOfDateTS AS OutOfDate', 'Users.UserName AS Maintainer',
- 'SubmittedTS AS FirstSubmitted', 'ModifiedTS AS LastModified'
- );
- private static $numeric_fields = array(
- 'ID', 'CategoryID', 'NumVotes', 'OutOfDate', 'FirstSubmitted',
- 'LastModified'
- );
-
- /**
- * Handles post data, and routes the request.
- * @param string $post_data The post data to parse and handle.
- * @return string The JSON formatted response data.
- **/
- public function handle($http_data) {
- // unset global aur headers from aur.inc
- // leave expires header to enforce validation
- // header_remove('Expires');
- // unset global aur.inc pragma header. We want to allow caching of data
- // in proxies, but require validation of data (if-none-match) if
- // possible
+ private $dbh = false;
+ private static $exposed_methods = array(
+ 'search', 'info', 'multiinfo', 'msearch', 'suggest'
+ );
+ private static $fields = array(
+ 'Packages.ID', 'Packages.Name',
+ 'PackageBases.Name AS PackageBase', 'Version', 'CategoryID',
+ 'Description', 'URL', 'NumVotes', 'OutOfDateTS AS OutOfDate',
+ 'Users.UserName AS Maintainer',
+ 'SubmittedTS AS FirstSubmitted', 'ModifiedTS AS LastModified'
+ );
+ private static $numeric_fields = array(
+ 'ID', 'CategoryID', 'NumVotes', 'OutOfDate', 'FirstSubmitted',
+ 'LastModified'
+ );
+
+ /*
+ * Handles post data, and routes the request.
+ *
+ * @param string $post_data The post data to parse and handle.
+ *
+ * @return string The JSON formatted response data.
+ */
+ public function handle($http_data) {
+ /*
+ * Unset global aur.inc.php Pragma header. We want to allow
+ * caching of data in proxies, but require validation of data
+ * (if-none-match) if possible.
+ */
header_remove('Pragma');
- // overwrite cache-control header set in aur.inc to allow caching, but
- // require validation
+ /*
+ * Overwrite cache-control header set in aur.inc.php to allow
+ * caching, but require validation.
+ */
header('Cache-Control: public, must-revalidate, max-age=0');
header('Content-Type: application/json, charset=utf-8');
- // handle error states
- if ( !isset($http_data['type']) || !isset($http_data['arg']) ) {
- return $this->json_error('No request type/data specified.');
- }
-
- // do the routing
- if ( in_array($http_data['type'], self::$exposed_methods) ) {
- // set up db connection.
- $this->dbh = DB::connect();
-
- // ugh. this works. I hate you php.
- $json = call_user_func(array(&$this, $http_data['type']),
- $http_data['arg']);
-
- // calculate etag as an md5 based on the json result
- // this could be optimized by calculating the etag on the
- // query result object before converting to json (step into
- // the above function call) and adding the 'type' to the response,
- // but having all this code here is cleaner and 'good enough'
- $etag = md5($json);
- header("Etag: \"$etag\"");
- // make sure to strip a few things off the if-none-match
- // header. stripping whitespace may not be required, but
- // removing the quote on the incoming header is required
- // to make the equality test
- $if_none_match = isset($_SERVER['HTTP_IF_NONE_MATCH']) ?
- trim($_SERVER['HTTP_IF_NONE_MATCH'], "\t\n\r\" ") : false;
- if ($if_none_match && $if_none_match == $etag) {
- header('HTTP/1.1 304 Not Modified');
- return;
+ if (!isset($http_data['type']) || !isset($http_data['arg'])) {
+ return $this->json_error('No request type/data specified.');
+ }
+ if (!in_array($http_data['type'], self::$exposed_methods)) {
+ return $this->json_error('Incorrect request type specified.');
+ }
+
+ $this->dbh = DB::connect();
+
+ $json = call_user_func(array(&$this, $http_data['type']), $http_data['arg']);
+
+ $etag = md5($json);
+ header("Etag: \"$etag\"");
+ /*
+ * Make sure to strip a few things off the
+ * if-none-match header. Stripping whitespace may not
+ * be required, but removing the quote on the incoming
+ * header is required to make the equality test.
+ */
+ $if_none_match = isset($_SERVER['HTTP_IF_NONE_MATCH']) ?
+ trim($_SERVER['HTTP_IF_NONE_MATCH'], "\t\n\r\" ") : false;
+ if ($if_none_match && $if_none_match == $etag) {
+ header('HTTP/1.1 304 Not Modified');
+ return;
+ }
+
+ if (isset($http_data['callback'])) {
+ header('content-type: text/javascript');
+ return $http_data['callback'] . "({$json})";
+ } else {
+ header('content-type: application/json');
+ return $json;
+ }
+ }
+
+ /*
+ * Returns a JSON formatted error string.
+ *
+ * @param $msg The error string to return
+ *
+ * @return mixed A json formatted error response.
+ */
+ private function json_error($msg) {
+ header('content-type: application/json');
+ return $this->json_results('error', 0, $msg);
+ }
+
+ /*
+ * Returns a JSON formatted result data.
+ *
+ * @param $type The response method type.
+ * @param $data The result data to return
+ *
+ * @return mixed A json formatted result response.
+ */
+ private function json_results($type, $count, $data) {
+ return json_encode(array(
+ 'version' => 2,
+ 'type' => $type,
+ 'resultcount' => $count,
+ 'results' => $data
+ ));
+ }
+
+ private function get_extended_fields($pkgid) {
+ $query = "SELECT DependencyTypes.Name AS Type, " .
+ "PackageDepends.DepName AS Name, " .
+ "PackageDepends.DepCondition AS Cond " .
+ "FROM PackageDepends " .
+ "LEFT JOIN DependencyTypes " .
+ "ON DependencyTypes.ID = PackageDepends.DepTypeID " .
+ "WHERE PackageDepends.PackageID = " . $pkgid . " " .
+ "UNION SELECT RelationTypes.Name AS Type, " .
+ "PackageRelations.RelName AS Name, " .
+ "PackageRelations.RelCondition AS Cond " .
+ "FROM PackageRelations " .
+ "LEFT JOIN RelationTypes " .
+ "ON RelationTypes.ID = PackageRelations.RelTypeID " .
+ "WHERE PackageRelations.PackageID = " . $pkgid . " " .
+ "UNION SELECT 'groups' AS Type, Groups.Name, '' AS Cond " .
+ "FROM Groups INNER JOIN PackageGroups " .
+ "ON PackageGroups.PackageID = " . $pkgid . " " .
+ "AND PackageGroups.GroupID = Groups.ID " .
+ "UNION SELECT 'license' AS Type, Licenses.Name, '' AS Cond " .
+ "FROM Licenses INNER JOIN PackageLicenses " .
+ "ON PackageLicenses.PackageID = " . $pkgid . " " .
+ "AND PackageLicenses.LicenseID = Licenses.ID";
+ $result = $this->dbh->query($query);
+
+ if (!$result) {
+ return null;
+ }
+
+ $type_map = array(
+ 'depends' => 'Depends',
+ 'makedepends' => 'MakeDepends',
+ 'checkdepends' => 'CheckDepends',
+ 'optdepends' => 'OptDepends',
+ 'conflicts' => 'Conflicts',
+ 'provides' => 'Provides',
+ 'replaces' => 'Replaces',
+ 'groups' => 'Groups',
+ 'license' => 'License',
+ );
+ $data = array();
+ while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
+ $type = $type_map[$row['Type']];
+ $data[$type][] = $row['Name'] . $row['Cond'];
+ }
+
+ return $data;
+ }
+
+ private function process_query($type, $where_condition) {
+ global $MAX_RPC_RESULTS;
+ $fields = implode(',', self::$fields);
+ $query = "SELECT {$fields} " .
+ "FROM Packages LEFT JOIN PackageBases " .
+ "ON PackageBases.ID = Packages.PackageBaseID " .
+ "LEFT JOIN Users ON PackageBases.MaintainerUID = Users.ID " .
+ "WHERE ${where_condition}";
+ $result = $this->dbh->query($query);
+
+ if ($result) {
+ $resultcount = 0;
+ $search_data = array();
+ while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
+ $resultcount++;
+ $pkgbase_name = $row['PackageBase'];
+ $row['URLPath'] = URL_DIR . substr($pkgbase_name, 0, 2) . "/" . $pkgbase_name . "/" . $pkgbase_name . ".tar.gz";
+
+ /*
+ * Unfortunately, mysql_fetch_assoc() returns
+ * all fields as strings. We need to coerce
+ * numeric values into integers to provide
+ * proper data types in the JSON response.
+ */
+ foreach (self::$numeric_fields as $field) {
+ $row[$field] = intval($row[$field]);
+ }
+
+ if ($type == 'info' || $type == 'multiinfo') {
+ $row = array_merge($row, $this->get_extended_fields($row['ID']));
+ }
+
+ if ($type == 'info') {
+ $search_data = $row;
+ break;
+ } else {
+ array_push($search_data, $row);
+ }
}
- // allow rpc callback for XDomainAjax
- if ( isset($http_data['callback']) ) {
- // it is more correct to send text/javascript
- // content-type for jsonp-callback
- header('content-type: text/javascript');
- return $http_data['callback'] . "({$json})";
- }
- else {
- // set content type header to app/json
- header('content-type: application/json');
- return $json;
- }
- }
- else {
- return $this->json_error('Incorrect request type specified.');
- }
- }
-
- /**
- * Returns a JSON formatted error string.
- *
- * @param $msg The error string to return
- * @return mixed A json formatted error response.
- **/
- private function json_error($msg) {
- // set content type header to app/json
- header('content-type: application/json');
- return $this->json_results('error', 0, $msg);
- }
-
- /**
- * Returns a JSON formatted result data.
- * @param $type The response method type.
- * @param $data The result data to return
- * @return mixed A json formatted result response.
- **/
- private function json_results($type, $count, $data) {
- return json_encode(array(
- 'version' => 2,
- 'type' => $type,
- 'resultcount' => $count,
- 'results' => $data
- ));
- }
-
- private function get_extended_fields($pkgid) {
- $query = "SELECT DependencyTypes.Name AS Type, " .
- "PackageDepends.DepName AS Name, " .
- "PackageDepends.DepCondition AS Cond " .
- "FROM PackageDepends " .
- "LEFT JOIN DependencyTypes " .
- "ON DependencyTypes.ID = PackageDepends.DepTypeID " .
- "WHERE PackageDepends.PackageID = " . $pkgid . " " .
- "UNION SELECT RelationTypes.Name AS Type, " .
- "PackageRelations.RelName AS Name, " .
- "PackageRelations.RelCondition AS Cond " .
- "FROM PackageRelations " .
- "LEFT JOIN RelationTypes " .
- "ON RelationTypes.ID = PackageRelations.RelTypeID " .
- "WHERE PackageRelations.PackageID = " . $pkgid . " " .
- "UNION SELECT 'groups' AS Type, Groups.Name, '' AS Cond " .
- "FROM Groups INNER JOIN PackageGroups " .
- "ON PackageGroups.PackageID = " . $pkgid . " " .
- "AND PackageGroups.GroupID = Groups.ID " .
- "UNION SELECT 'license' AS Type, Licenses.Name, '' AS Cond " .
- "FROM Licenses INNER JOIN PackageLicenses " .
- "ON PackageLicenses.PackageID = " . $pkgid . " " .
- "AND PackageLicenses.LicenseID = Licenses.ID";
- $result = $this->dbh->query($query);
-
- if (!$result) {
- return null;
- }
-
- $type_map = array(
- 'depends' => 'Depends',
- 'makedepends' => 'MakeDepends',
- 'checkdepends' => 'CheckDepends',
- 'optdepends' => 'OptDepends',
- 'conflicts' => 'Conflicts',
- 'provides' => 'Provides',
- 'replaces' => 'Replaces',
- 'groups' => 'Groups',
- 'license' => 'License',
- );
- $data = array();
- while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
- $type = $type_map[$row['Type']];
- $data[$type][] = $row['Name'] . $row['Cond'];
- }
-
- return $data;
- }
-
- private function process_query($type, $where_condition) {
- global $MAX_RPC_RESULTS;
- $fields = implode(',', self::$fields);
- $query = "SELECT {$fields} " .
- "FROM Packages LEFT JOIN PackageBases " .
- "ON PackageBases.ID = Packages.PackageBaseID " .
- "LEFT JOIN Users ON PackageBases.MaintainerUID = Users.ID " .
- "WHERE ${where_condition}";
- $result = $this->dbh->query($query);
-
- if ($result) {
- $resultcount = 0;
- $search_data = array();
- while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
- $resultcount++;
- $pkgbase_name = $row['PackageBase'];
- $row['URLPath'] = URL_DIR . substr($pkgbase_name, 0, 2) . "/" . $pkgbase_name . "/" . $pkgbase_name . ".tar.gz";
-
- /* Unfortunately, mysql_fetch_assoc() returns all fields as
- * strings. We need to coerce numeric values into integers to
- * provide proper data types in the JSON response.
- */
- foreach (self::$numeric_fields as $field) {
- $row[$field] = intval($row[$field]);
- }
-
- if ($type == 'info' || $type == 'multiinfo') {
- $row = array_merge($row, $this->get_extended_fields($row['ID']));
- }
-
- if ($type == 'info') {
- $search_data = $row;
- break;
- }
- else {
- array_push($search_data, $row);
- }
- }
-
- if ($resultcount === $MAX_RPC_RESULTS) {
- return $this->json_error('Too many package results.');
- }
-
- return $this->json_results($type, $resultcount, $search_data);
- }
- else {
- return $this->json_results($type, 0, array());
- }
- }
-
- /**
- * Parse the args to the multiinfo function. We may have a string or an
- * array, so do the appropriate thing. Within the elements, both * package
- * IDs and package names are valid; sort them into the relevant arrays and
- * escape/quote the names.
- * @param $args the arg string or array to parse.
- * @return mixed An array containing 'ids' and 'names'.
- **/
- private function parse_multiinfo_args($args) {
- if (!is_array($args)) {
- $args = array($args);
- }
-
- $id_args = array();
- $name_args = array();
- foreach ($args as $arg) {
- if (!$arg) {
- continue;
- }
- if (is_numeric($arg)) {
- $id_args[] = intval($arg);
- } else {
- $name_args[] = $this->dbh->quote($arg);
- }
- }
-
- return array('ids' => $id_args, 'names' => $name_args);
- }
-
- /**
- * Performs a fulltext mysql search of the package database.
- * @param $keyword_string A string of keywords to search with.
- * @return mixed Returns an array of package matches.
- **/
- private function search($keyword_string) {
- global $MAX_RPC_RESULTS;
- if (strlen($keyword_string) < 2) {
- return $this->json_error('Query arg too small');
- }
-
- $keyword_string = $this->dbh->quote("%" . addcslashes($keyword_string, '%_') . "%");
-
- $where_condition = "(Packages.Name LIKE {$keyword_string} OR ";
- $where_condition.= "Description LIKE {$keyword_string}) ";
- $where_condition.= "LIMIT {$MAX_RPC_RESULTS}";
-
- return $this->process_query('search', $where_condition);
- }
-
- /**
- * Returns the info on a specific package.
- * @param $pqdata The ID or name of the package. Package Query Data.
- * @return mixed Returns an array of value data containing the package data
- **/
- private function info($pqdata) {
- if ( is_numeric($pqdata) ) {
- // just using sprintf to coerce the pqd to an int
- // should handle sql injection issues, since sprintf will
- // bork if not an int, or convert the string to a number 0
- $where_condition = "Packages.ID={$pqdata}";
- }
- else {
- $where_condition = sprintf("Packages.Name=%s", $this->dbh->quote($pqdata));
- }
- return $this->process_query('info', $where_condition);
- }
-
- /**
- * Returns the info on multiple packages.
- * @param $pqdata A comma-separated list of IDs or names of the packages.
- * @return mixed Returns an array of results containing the package data
- **/
- private function multiinfo($pqdata) {
- global $MAX_RPC_RESULTS;
- $args = $this->parse_multiinfo_args($pqdata);
- $ids = $args['ids'];
- $names = $args['names'];
-
- if (!$ids && !$names) {
- return $this->json_error('Invalid query arguments');
- }
-
- $where_condition = "";
- if ($ids) {
- $ids_value = implode(',', $args['ids']);
- $where_condition .= "ID IN ({$ids_value}) ";
- }
- if ($ids && $names) {
- $where_condition .= "OR ";
- }
- if ($names) {
- // individual names were quoted in parse_multiinfo_args()
- $names_value = implode(',', $args['names']);
- $where_condition .= "Packages.Name IN ({$names_value}) ";
- }
-
- $where_condition .= "LIMIT {$MAX_RPC_RESULTS}";
-
- return $this->process_query('multiinfo', $where_condition);
- }
-
- /**
- * Returns all the packages for a specific maintainer.
- * @param $maintainer The name of the maintainer.
- * @return mixed Returns an array of value data containing the package data
- **/
- private function msearch($maintainer) {
- global $MAX_RPC_RESULTS;
- $maintainer = $this->dbh->quote($maintainer);
-
- $where_condition = "Users.Username = {$maintainer} ";
- $where_condition .= "LIMIT {$MAX_RPC_RESULTS}";
-
- return $this->process_query('msearch', $where_condition);
- }
-
- /**
- * Get all package names that start with $search.
- * @param string $search Search string.
- * @return string The JSON formatted response data.
- **/
- private function suggest($search) {
- $query = 'SELECT Name FROM Packages WHERE Name LIKE ' .
- $this->dbh->quote(addcslashes($search, '%_') . '%') .
- ' ORDER BY Name ASC LIMIT 20';
-
- $result = $this->dbh->query($query);
- $result_array = array();
-
- if ($result) {
- $result_array = $result->fetchAll(PDO::FETCH_COLUMN, 0);
- }
-
- return json_encode($result_array);
- }
+ if ($resultcount === $MAX_RPC_RESULTS) {
+ return $this->json_error('Too many package results.');
+ }
+
+ return $this->json_results($type, $resultcount, $search_data);
+ } else {
+ return $this->json_results($type, 0, array());
+ }
+ }
+
+ /*
+ * Parse the args to the multiinfo function. We may have a string or an
+ * array, so do the appropriate thing. Within the elements, both * package
+ * IDs and package names are valid; sort them into the relevant arrays and
+ * escape/quote the names.
+ *
+ * @param $args the arg string or array to parse.
+ *
+ * @return mixed An array containing 'ids' and 'names'.
+ */
+ private function parse_multiinfo_args($args) {
+ if (!is_array($args)) {
+ $args = array($args);
+ }
+
+ $id_args = array();
+ $name_args = array();
+ foreach ($args as $arg) {
+ if (!$arg) {
+ continue;
+ }
+ if (is_numeric($arg)) {
+ $id_args[] = intval($arg);
+ } else {
+ $name_args[] = $this->dbh->quote($arg);
+ }
+ }
+
+ return array('ids' => $id_args, 'names' => $name_args);
+ }
+
+ /*
+ * Performs a fulltext mysql search of the package database.
+ *
+ * @param $keyword_string A string of keywords to search with.
+ *
+ * @return mixed Returns an array of package matches.
+ */
+ private function search($keyword_string) {
+ global $MAX_RPC_RESULTS;
+
+ if (strlen($keyword_string) < 2) {
+ return $this->json_error('Query arg too small');
+ }
+
+ $keyword_string = $this->dbh->quote("%" . addcslashes($keyword_string, '%_') . "%");
+
+ $where_condition = "(Packages.Name LIKE $keyword_string OR ";
+ $where_condition .= "Description LIKE $keyword_string) ";
+ $where_condition .= "LIMIT $MAX_RPC_RESULTS";
+
+ return $this->process_query('search', $where_condition);
+ }
+
+ /*
+ * Returns the info on a specific package.
+ *
+ * @param $pqdata The ID or name of the package. Package Query Data.
+ *
+ * @return mixed Returns an array of value data containing the package data
+ */
+ private function info($pqdata) {
+ if (is_numeric($pqdata)) {
+ $where_condition = "Packages.ID = $pqdata";
+ } else {
+ $where_condition = "Packages.Name = " . $this->dbh->quote($pqdata);
+ }
+
+ return $this->process_query('info', $where_condition);
+ }
+
+ /*
+ * Returns the info on multiple packages.
+ *
+ * @param $pqdata A comma-separated list of IDs or names of the packages.
+ *
+ * @return mixed Returns an array of results containing the package data
+ */
+ private function multiinfo($pqdata) {
+ global $MAX_RPC_RESULTS;
+
+ $args = $this->parse_multiinfo_args($pqdata);
+ $ids = $args['ids'];
+ $names = $args['names'];
+
+ if (!$ids && !$names) {
+ return $this->json_error('Invalid query arguments');
+ }
+
+ $where_condition = "";
+ if ($ids) {
+ $ids_value = implode(',', $args['ids']);
+ $where_condition .= "ID IN ($ids_value) ";
+ }
+ if ($ids && $names) {
+ $where_condition .= "OR ";
+ }
+ if ($names) {
+ /*
+ * Individual names were quoted in
+ * parse_multiinfo_args().
+ */
+ $names_value = implode(',', $args['names']);
+ $where_condition .= "Packages.Name IN ($names_value) ";
+ }
+ $where_condition .= "LIMIT $MAX_RPC_RESULTS";
+
+ return $this->process_query('multiinfo', $where_condition);
+ }
+
+ /*
+ * Returns all the packages for a specific maintainer.
+ *
+ * @param $maintainer The name of the maintainer.
+ *
+ * @return mixed Returns an array of value data containing the package data
+ */
+ private function msearch($maintainer) {
+ global $MAX_RPC_RESULTS;
+
+ $maintainer = $this->dbh->quote($maintainer);
+
+ $where_condition = "Users.Username = $maintainer ";
+ $where_condition .= "LIMIT $MAX_RPC_RESULTS";
+
+ return $this->process_query('msearch', $where_condition);
+ }
+
+ /*
+ * Get all package names that start with $search.
+ *
+ * @param string $search Search string.
+ *
+ * @return string The JSON formatted response data.
+ */
+ private function suggest($search) {
+ $query = 'SELECT Name FROM Packages WHERE Name LIKE ' .
+ $this->dbh->quote(addcslashes($search, '%_') . '%') .
+ ' ORDER BY Name ASC LIMIT 20';
+
+ $result = $this->dbh->query($query);
+ $result_array = array();
+
+ if ($result) {
+ $result_array = $result->fetchAll(PDO::FETCH_COLUMN, 0);
+ }
+
+ return json_encode($result_array);
+ }
}