From 5ea8138f2a86730524e184033b274399adcf0543 Mon Sep 17 00:00:00 2001 From: balu Date: Sun, 17 Jun 2012 17:05:53 +0200 Subject: [PATCH] Initial Commit FSyncMS v08 --- README | 11 + WBOJsonOutput.php | 154 ++++++++ index.php | 298 ++++++++++++++ settings.php | 14 + user.php | 152 ++++++++ weave_basic_object.php | 249 ++++++++++++ weave_storage.php | 864 +++++++++++++++++++++++++++++++++++++++++ weave_utils.php | 228 +++++++++++ 8 files changed, 1970 insertions(+) create mode 100644 README create mode 100644 WBOJsonOutput.php create mode 100644 index.php create mode 100644 settings.php create mode 100644 user.php create mode 100644 weave_basic_object.php create mode 100644 weave_storage.php create mode 100644 weave_utils.php diff --git a/README b/README new file mode 100644 index 0000000..8074db5 --- /dev/null +++ b/README @@ -0,0 +1,11 @@ +Visit http://www.ohnekontur.de/2011/07/24/how-to-install-fsyncms-firefox-sync-eigener-server/ for install instructions +Visit http://www.ohnekontur.de for the newest version + + +FSyncMS v 08 + +Should be working with firefox 11 and lower (tested with 11) + +Changelog, +Fixed user registration process, +fixed some delete problems diff --git a/WBOJsonOutput.php b/WBOJsonOutput.php new file mode 100644 index 0000000..01139d5 --- /dev/null +++ b/WBOJsonOutput.php @@ -0,0 +1,154 @@ +_full = $full; + if (array_key_exists('HTTP_ACCEPT', $_SERVER) + && !preg_match('/\*\/\*/', $_SERVER['HTTP_ACCEPT']) + && !preg_match('/application\/json/', $_SERVER['HTTP_ACCEPT'])) + { + if (preg_match('/application\/whoisi/', $_SERVER['HTTP_ACCEPT'])) + { + header("Content-type: application/whoisi"); + $this->_output_format = 'whoisi'; + } + elseif (preg_match('/application\/newlines/', $_SERVER['HTTP_ACCEPT'])) + { + header("Content-type: application/newlines"); + $this->_output_format = 'newlines'; + } + + } + } + + function set_format($format) + { + $this->_output_format = $format; + } + + + function output($sth) + { + if (($rowcount = $sth->rowCount()) > 0) + { + header('X-Weave-Records: ' . $rowcount); + } + if ($this->_output_format == 'newlines') + { + return $this->output_newlines($sth); + } + elseif ($this->_output_format == 'whoisi') + { + return $this->output_whoisi($sth); + } + else + { + return $this->output_json($sth); + } + } + + function output_json($sth) + { + echo '['; + + while ($result = $sth->fetch(PDO::FETCH_ASSOC)) + { + if ($this->_comma_flag) { echo ','; } else { $this->_comma_flag = 1; } + if ($this->_full) + { + $wbo = new wbo(); + $wbo->populate($result); + echo $wbo->json(); + } + else + echo json_encode($result{'id'}); + } + + echo ']'; + return 1; + } + + function output_whoisi($sth) + { + while ($result = $sth->fetch(PDO::FETCH_ASSOC)) + { + if ($this->_full) + { + $wbo = new wbo(); + $wbo->populate($result); + $output = $wbo->json(); + } + else + $output = json_encode($result{'id'}); + echo pack('N', mb_strlen($output, '8bit')) . $output; + } + return 1; + } + + function output_newlines($sth) + { + while ($result = $sth->fetch(PDO::FETCH_ASSOC)) + { + if ($this->_full) + { + $wbo = new wbo(); + $wbo->populate($result); + echo preg_replace('/\n/', '\u000a', $wbo->json()); + } + else + echo json_encode($result{'id'}); + echo "\n"; + } + return 1; + } +} +?> diff --git a/index.php b/index.php new file mode 100644 index 0000000..2e3c494 --- /dev/null +++ b/index.php @@ -0,0 +1,298 @@ + user.php + $include = true; + require 'user.php'; + exit(); // should not get here, but how knows + } + + header("Content-type: application/json"); + + if ($version != '1.0' && $version != '1.1') + report_problem('Function not found', 404); + + if ($function != "info" && $function != "storage") + report_problem(WEAVE_ERROR_FUNCTION_NOT_SUPPORTED, 400); + + if (!validate_username($username)) + report_problem(WEAVE_ERROR_INVALID_USERNAME, 400); + + #only a delete has meaning without a collection + if ($collection) + { + if (!validate_collection($collection)) + report_problem(WEAVE_ERROR_INVALID_COLLECTION, 400); + } + else if ($_SERVER['REQUEST_METHOD'] != 'DELETE') + report_problem(WEAVE_ERROR_INVALID_PROTOCOL, 400); + + + #quick check to make sure that any non-storage function calls are just using GET + if ($function != 'storage' && $_SERVER['REQUEST_METHOD'] != 'GET') + report_problem(WEAVE_ERROR_INVALID_PROTOCOL, 400); + + + + #user passes preliminaries, connections made, onto actually getting the data + try + { + $db = new WeaveStorage($username); + + #Auth the user + verify_user($username, $db); + + #user passes preliminaries, connections made, onto actually getting the data + if ($_SERVER['REQUEST_METHOD'] == 'GET') + { + if ($function == 'info') + { + switch ($collection) + { + case 'quota': + exit(json_encode(array($db->get_storage_total()))); + case 'collections': + exit(json_encode($db->get_collection_list_with_timestamps())); + case 'collection_counts': + exit(json_encode($db->get_collection_list_with_counts())); + case 'collection_usage': + $results = $db->get_collection_storage_totals(); + foreach (array_keys($results) as $collection) + { + $results[$collection] = ceil($results[$collection] / 1024); #converting to k from bytes + } + exit(json_encode($results)); + default: + report_problem(WEAVE_ERROR_INVALID_PROTOCOL, 400); + } + } + elseif ($function == 'storage') + { + log_error("function storage"); + if ($id) #retrieve a single record + { + $wbo = $db->retrieve_objects($collection, $id, 1); #get the full contents of one record + if (count($wbo) > 0) + { + $item = array_shift($wbo); + echo $item->json(); + } + else + report_problem("record not found", 404); + } + else #retrieve a batch of records. Sadly, due to potential record sizes, have the storage object stream the output... + { + log_error("retrieve a batch"); + $full = array_key_exists('full', $_GET) && $_GET['full']; + + $outputter = new WBOJsonOutput($full); + + $params = validate_search_params(); + + $ids = $db->retrieve_objects($collection, null, $full, $outputter, + $params['parentid'], $params['predecessorid'], + $params['newer'], $params['older'], + $params['sort'], + $params['limit'], $params['offset'], + $params['ids'], + $params['index_above'], $params['index_below'], $params['depth'] + ); + } + } + } + else if ($_SERVER['REQUEST_METHOD'] == 'PUT') #add a single record to the server + { + $wbo = new wbo(); + if (!$wbo->extract_json(get_json())) + report_problem(WEAVE_ERROR_JSON_PARSE, 400); + + check_quota($db); + check_timestamp($collection, $db); + + #use the url if the json object doesn't have an id + if (!$wbo->id() && $id) { $wbo->id($id); } + + $wbo->collection($collection); + $wbo->modified($server_time); #current microtime + + if ($wbo->validate()) + { + #if there's no payload (as opposed to blank), then update the metadata + if ($wbo->payload_exists()) + $db->store_object($wbo); + else + $db->update_object($wbo); + } + else + { + report_problem(WEAVE_ERROR_INVALID_WBO, 400); + } + echo json_encode($server_time); + } + else if ($_SERVER['REQUEST_METHOD'] == 'POST') + { + $json = get_json(); + + check_quota($db); + check_timestamp($collection, $db); + + $success_ids = array(); + $failed_ids = array(); + + $db->begin_transaction(); + + foreach ($json as $wbo_data) + { + $wbo = new wbo(); + + if (!$wbo->extract_json($wbo_data)) + { + $failed_ids[$wbo->id()] = $wbo->get_error(); + continue; + } + + $wbo->collection($collection); + $wbo->modified($server_time); + + + if ($wbo->validate()) + { + #if there's no payload (as opposed to blank), then update the metadata + if ($wbo->payload_exists()) + { + $db->store_object($wbo); + } + else + { + $db->update_object($wbo); + } + $success_ids[] = $wbo->id(); + } + else + { + $failed_ids[$wbo->id()] = $wbo->get_error(); + } + } + $db->commit_transaction(); + + echo json_encode(array('success' => $success_ids, 'failed' => $failed_ids)); + } + else if ($_SERVER['REQUEST_METHOD'] == 'DELETE') + { + check_timestamp($collection, $db); + + if ($id) + { + $db->delete_object($collection, $id); + } + else if ($collection) + { + $params = validate_search_params(); + + $db->delete_objects($collection, null, + $params['parentid'], $params['predecessorid'], + $params['newer'], $params['older'], + $params['sort'], + $params['limit'], $params['offset'], + $params['ids'], + $params['index_above'], $params['index_below'] + ); + } + else if($function == 'storage') // ich vermute mal storage reinigen + { + if (!array_key_exists('HTTP_X_CONFIRM_DELETE', $_SERVER)) + report_problem(WEAVE_ERROR_NO_OVERWRITE, 412); + $db->delete_storage($username); + } + else + { + if (!array_key_exists('HTTP_X_CONFIRM_DELETE', $_SERVER)) + report_problem(WEAVE_ERROR_NO_OVERWRITE, 412); + log_error("delete "."Server ".print_r( $_SERVER, true)); + $db->delete_user($username); + } + + echo json_encode($server_time); + + } + else + { + #bad protocol. There are protocols left? HEAD, I guess. + report_problem(1, 400); + } + } + catch(Exception $e) + { + report_problem($e->getMessage(), $e->getCode()); + } + + +#The datasets we might be dealing with here are too large for sticking it all into an array, so +#we need to define a direct-output method for the storage class to use. If we start producing multiples +#(unlikely), we can put them in their own class. + +#include_once "WBOJsonOutput.php"; +?> diff --git a/settings.php b/settings.php new file mode 100644 index 0000000..eb18b72 --- /dev/null +++ b/settings.php @@ -0,0 +1,14 @@ + diff --git a/user.php b/user.php new file mode 100644 index 0000000..b4038f2 --- /dev/null +++ b/user.php @@ -0,0 +1,152 @@ +create_user($name, $pwd)) + { + log_error("successfully created user"); + exit(json_encode(strtolower($name))); + } + else + { + log_error("create user failed"); + report_problem('Authentication failed', '401'); + } + } + catch(Exception $e) + { + log_error("db exception create user"); + header("X-Weave-Backoff: 1800"); + report_problem($e->getMessage(), $e->getCode()); + } + + } + else + { + log_error("register not enabled"); + report_problem(WEAVE_ERROR_FUNCTION_NOT_SUPPORTED,400); + } + } // ende put + } + catch(Exception $e) + { + report_problem($e->getMessage(), $e->getCode()); + } +#The datasets we might be dealing with here are too large for sticking it all into an array, so +#we need to define a direct-output method for the storage class to use. If we start producing multiples +#(unlikely), we can put them in their own class. + +#include_once "WBOJsonOutput.php"; + +?> diff --git a/weave_basic_object.php b/weave_basic_object.php new file mode 100644 index 0000000..305dfd5 --- /dev/null +++ b/weave_basic_object.php @@ -0,0 +1,249 @@ +_error[] = "unable to extract from json"; + return false; + } + + #must have an id, or all sorts of badness happens. However, it can be added later + if (array_key_exists('id', $extracted)) + { + $this->id($extracted['id']); + } + + if (array_key_exists('parentid', $extracted)) + { + $this->parentid($extracted['parentid']); + } + + if (array_key_exists('predecessorid', $extracted)) + { + $this->predecessorid($extracted['predecessorid']); + } + + if (array_key_exists('sortindex', $extracted)) + { + # Due to complicated logic in the getter, we need to validate + # the value space of sortindex here. + if (!is_numeric($extracted['sortindex'])) { + $this->_error[] = "invalid sortindex"; + return false; + } + $this->sortindex($extracted['sortindex']); + } + + if (array_key_exists('payload', $extracted)) + { + $this->payload($extracted['payload']); + } + return true; + } + + function populate(&$datahash) + { + if (array_key_exists('id', $datahash)) + $this->id($datahash['id']); + + if (array_key_exists('collection', $datahash)) + $this->collection($datahash['collection']); + + if (array_key_exists('parentid', $datahash)) + $this->parentid($datahash['parentid']); + + if (array_key_exists('modified', $datahash)) + $this->modified($datahash['modified']); + + if (array_key_exists('predecessorid', $datahash)) + $this->predecessorid($datahash['predecessorid']); + + if (array_key_exists('sortindex', $datahash)) + $this->sortindex($datahash['sortindex']); + + if (array_key_exists('payload', $datahash)) + $this->payload($datahash['payload']); + } + + function id($id = null) + { + if (!is_null($id)) { $this->wbo_hash['id'] = (string)$id; } + return array_key_exists('id', $this->wbo_hash) ? $this->wbo_hash['id'] : null; + } + + function collection($collection = null) + { + if (!is_null($collection)){ $this->_collection = $collection; } + return $this->_collection; + } + + function parentid($parentid = null) + { + if (!is_null($parentid)){ $this->wbo_hash['parentid'] = (string)$parentid; } + return array_key_exists('parentid', $this->wbo_hash) ? $this->wbo_hash['parentid'] : null; + } + + function parentid_exists() + { + return array_key_exists('parentid', $this->wbo_hash); + } + + function predecessorid($predecessorid = null) + { + if (!is_null($predecessorid)){ $this->wbo_hash['predecessorid'] = (string)$predecessorid; } + return array_key_exists('predecessorid', $this->wbo_hash) ? $this->wbo_hash['predecessorid'] : null; + } + + function predecessorid_exists() + { + return array_key_exists('predecessorid', $this->wbo_hash); + } + + function modified($modified = null) + { + if (!is_null($modified)){ $this->wbo_hash['modified'] = round((float)$modified, 2); } + return array_key_exists('modified', $this->wbo_hash) ? $this->wbo_hash['modified'] : null; + } + + function modified_exists() + { + return array_key_exists('modified', $this->wbo_hash); + } + + function payload($payload = null) + { + if (!is_null($payload)){ $this->wbo_hash['payload'] = $payload; } + return array_key_exists('payload', $this->wbo_hash) ? $this->wbo_hash['payload'] : null; + } + + function payload_exists() + { + return array_key_exists('payload', $this->wbo_hash); + } + + function payload_size() + { + return mb_strlen($this->wbo_hash['payload'], '8bit'); + } + + function sortindex($index = null) + { + if (!is_null($index)){ + $this->wbo_hash['sortindex'] = (int)($index); + } + return array_key_exists('sortindex', $this->wbo_hash) ? $this->wbo_hash['sortindex'] : null; + } + + function sortindex_exists() + { + return array_key_exists('sortindex', $this->wbo_hash); + } + + + function validate() + { + + if (!$this->id() || mb_strlen($this->id(), '8bit') > 64 || strpos($this->id(), '/') !== false) + { $this->_error[] = "invalid id"; } + + if ($this->parentid_exists() && mb_strlen($this->parentid(), '8bit') > 64) + { $this->_error[] = "invalid parentid"; } + + if ($this->predecessorid_exists() && mb_strlen($this->predecessorid(), '8bit') > 64) + { $this->_error[] = "invalid predecessorid"; } + + if (!is_numeric($this->modified())) + { $this->_error[] = "invalid modified date"; } + + if (!$this->modified()) + { $this->_error[] = "no modification date"; } + + if (!$this->_collection || mb_strlen($this->_collection, '8bit') > 64) + { $this->_error[] = "invalid collection"; } + + if ($this->sortindex_exists() && + (!is_numeric($this->wbo_hash['sortindex']) || + intval($this->sortindex()) > 999999999 || + intval($this->sortindex()) < -999999999 )) + { $this->_error[] = "invalid sortindex"; } + + if ($this->payload_exists()) + { + if (!is_string($this->wbo_hash['payload'])) + { $this->_error[] = "payload needs to be json-encoded"; } + } + + return !$this->get_error(); + } + + function get_error() + { + return $this->_error; + } + + function clear_error() + { + $this->_error = array(); + } + + function raw_hash() + { + return $this->wbo_hash; + } + + function json() + { + return json_encode($this->wbo_hash); + } +} + + +?> \ No newline at end of file diff --git a/weave_storage.php b/weave_storage.php new file mode 100644 index 0000000..9ab91f2 --- /dev/null +++ b/weave_storage.php @@ -0,0 +1,864 @@ +_username = $username; + $path = explode('/', $_SERVER['SCRIPT_FILENAME']); + $db_name = 'weave_db'; + array_pop($path); + array_push($path, $db_name); + $db_name = implode('/', $path); + + $create_tables = !file_exists($db_name); + log_error("Weaave Storage created : DBname".$db_name." | username:".$username); + try + { + $this->_dbh = new PDO('sqlite:' . $db_name); + $this->_dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + } + catch( PDOException $exception ) + { + log_error("database unavailable"); + throw new Exception("Database unavailable", 503); + } + if ($create_tables) + { + log_error("sqlite db create"); + $this->setup_db(); + } + + } + + function get_connection() + { + return $this->_dbh; + } + + function begin_transaction() + { + try + { + $this->_dbh->beginTransaction(); + } + catch( PDOException $exception ) + { + error_log("begin_transaction: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + return 1; + } + + function commit_transaction() + { + $this->_dbh->commit(); + return 1; + } + + function get_max_timestamp($collection) + { + if (!$collection) + { + return 0; + } + + try + { + $select_stmt = 'select max(modified) from wbo where username = :username and collection = :collection'; + $sth = $this->_dbh->prepare($select_stmt); + $sth->bindParam(':username', $this->_username); + $sth->bindParam(':collection', $collection); + $sth->execute(); + } + catch( PDOException $exception ) + { + error_log("get_max_timestamp: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + + $result = $sth->fetchColumn(); + return round((float)$result, 2); + } + + function get_collection_list() + { + try + { + $select_stmt = 'select distinct(collection) from wbo where username = :username'; + $sth = $this->_dbh->prepare($select_stmt); + $sth->bindParam(':username', $this->_username); + $sth->execute(); + } + catch( PDOException $exception ) + { + error_log("get_collection_list: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + + + $collections = array(); + while ($result = $sth->fetchColumn()) + { + $collections[] = $result; + } + + return $collections; + } + + + function get_collection_list_with_timestamps() + { + try + { + $select_stmt = 'select collection, max(modified) as timestamp from wbo where username = :username group by collection'; + $sth = $this->_dbh->prepare($select_stmt); + $sth->bindParam(':username', $this->_username); + $sth->execute(); + } + catch( PDOException $exception ) + { + error_log("get_collection_list: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + + $collections = array(); + while ($result = $sth->fetch(PDO::FETCH_NUM)) + { + $collections[$result[0]] = (float)$result[1]; + } + + return $collections; + } + + function get_collection_list_with_counts() + { + try + { + $select_stmt = 'select collection, count(*) as ct from wbo where username = :username group by collection'; + $sth = $this->_dbh->prepare($select_stmt); + $sth->bindParam(':username', $this->_username); + $sth->execute(); + } + catch( PDOException $exception ) + { + error_log("get_collection_list_with_counts: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + + + $collections = array(); + while ($result = $sth->fetch(PDO::FETCH_NUM)) + { + $collections[$result[0]] = (int)$result[1]; + } + + return $collections; + } + + function store_object(&$wbo) + { + + try + { + $insert_stmt = 'replace into wbo (username, id, collection, parentid, predecessorid, sortindex, modified, payload, payload_size) + values (:username, :id, :collection, :parentid, :predecessorid, :sortindex, :modified, :payload, :payload_size)'; + $sth = $this->_dbh->prepare($insert_stmt); + + $username = $this->_username; + $id = $wbo->id(); + $collection = $wbo->collection(); + $parentid = $wbo->parentid(); + $predecessorid = $wbo->predecessorid(); + $sortindex = $wbo->sortindex(); + $modified = $wbo->modified(); + $payload = $wbo->payload(); + $payload_size = $wbo->payload_size(); + + $sth->bindParam(':username', $username); + $sth->bindParam(':id', $id); + $sth->bindParam(':collection', $collection); + $sth->bindParam(':parentid', $parentid); + $sth->bindParam(':predecessorid', $predecessorid); + $sth->bindParam(':sortindex', $sortindex); + $sth->bindParam(':modified', $modified); + $sth->bindParam(':payload', $payload); + $sth->bindParam(':payload_size', $payload_size); + + $sth->execute(); + + } + catch( PDOException $exception ) + { + error_log("store_object: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + return 1; + } + + + function update_object(&$wbo) + { + $update = "update wbo set "; + $params = array(); + $update_list = array(); + + #make sure we have an id and collection. No point in continuing otherwise + if (!$wbo->id() || !$wbo->collection()) + { + error_log('Trying to update without a valid id or collection!'); + return 0; + } + + if ($wbo->parentid_exists()) + { + $update_list[] = "parentid = ?"; + $params[] = $wbo->parentid(); + } + + if ($wbo->predecessorid_exists()) + { + $update_list[] = "predecessorid = ?"; + $params[] = $wbo->predecessorid(); + } + + if ($wbo->sortindex_exists()) + { + $update_list[] = "sortindex = ?"; + $params[] = $wbo->sortindex(); + } + + if ($wbo->payload_exists()) + { + $update_list[] = "payload = ?"; + $update_list[] = "payload_size = ?"; + $params[] = $wbo->payload(); + $params[] = $wbo->payload_size(); + } + + # Don't modify the timestamp on a non-payload/non-parent change change + if ($wbo->parentid_exists() || $wbo->payload_exists()) + { + #better make sure we have a modified date. Should have been handled earlier + if (!$wbo->modified_exists()) + { + error_log("Called update_object with no defined timestamp. Please check"); + $wbo->modified(microtime(1)); + } + $update_list[] = "modified = ?"; + $params[] = $wbo->modified(); + + } + + + if (count($params) == 0) + { + return 0; + } + + $update .= join($update_list, ","); + + $update .= " where username = ? and collection = ? and id = ?"; + $params[] = $this->_username; + $params[] = $wbo->collection(); + $params[] = $wbo->id(); + + try + { + $sth = $this->_dbh->prepare($update); + $sth->execute($params); + } + catch( PDOException $exception ) + { + error_log("update_object: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + return 1; + } + + function delete_object($collection, $id) + { + try + { + $delete_stmt = 'delete from wbo where username = :username and collection = :collection and id = :id'; + $sth = $this->_dbh->prepare($delete_stmt); + $username = $this->_username; + $sth->bindParam(':username', $username); + $sth->bindParam(':collection', $collection); + $sth->bindParam(':id', $id); + $sth->execute(); + } + catch( PDOException $exception ) + { + error_log("delete_object: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + return 1; + } + + function delete_objects($collection, $id = null, $parentid = null, $predecessorid = null, $newer = null, + $older = null, $sort = null, $limit = null, $offset = null, $ids = null, + $index_above = null, $index_below = null) + { + $params = array(); + $select_stmt = ''; + + if ($limit || $offset || $sort) + { + #sqlite can't do sort or limit deletes without special compiled versions + #so, we need to grab the set, then delete it manually. + + $params = $this->retrieve_objects($collection, $id, 0, 0, $parentid, $predecessorid, $newer, $older, $sort, $limit, $offset, $ids, $index_above, $index_below); + if (!count($params)) + { + return 1; #nothing to delete + } + $paramqs = array(); + $select_stmt = "delete from wbo where username = ? and collection = ? and id in (" . join(", ", array_pad($paramqs, count($params), '?')) . ")"; + array_unshift($params, $collection); + array_unshift($params, $username); + } + else + { + + $select_stmt = "delete from wbo where username = ? and collection = ?"; + $params[] = $this->_username; + $params[] = $collection; + + + if ($id) + { + $select_stmt .= " and id = ?"; + $params[] = $id; + } + + if ($ids && count($ids) > 0) + { + $qmarks = array(); + $select_stmt .= " and id in ("; + foreach ($ids as $temp) + { + $params[] = $temp; + $qmarks[] = '?'; + } + $select_stmt .= implode(",", $qmarks); + $select_stmt .= ')'; + } + + if ($parentid) + { + $select_stmt .= " and parentid = ?"; + $params[] = $parentid; + } + + if ($predecessorid) + { + $select_stmt .= " and predecessorid = ?"; + $params[] = $parentid; + } + + if ($index_above) + { + $select_stmt .= " and sortindex > ?"; + $params[] = $parentid; + } + + if ($index_below) + { + $select_stmt .= " and sortindex < ?"; + $params[] = $parentid; + } + + if ($newer) + { + $select_stmt .= " and modified > ?"; + $params[] = $newer; + } + + if ($older) + { + $select_stmt .= " and modified < ?"; + $params[] = $older; + } + + if ($sort == 'index') + { + $select_stmt .= " order by sortindex desc"; + } + else if ($sort == 'newest') + { + $select_stmt .= " order by modified desc"; + } + else if ($sort == 'oldest') + { + $select_stmt .= " order by modified"; + } + + } + + try + { + $sth = $this->_dbh->prepare($select_stmt); + $sth->execute($params); + } + catch( PDOException $exception ) + { + error_log("delete_objects: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + return 1; + } + + function retrieve_object($collection, $id) + { + try + { + $select_stmt = 'select * from wbo where username = :username and collection = :collection and id = :id'; + $sth = $this->_dbh->prepare($select_stmt); + $username = $this->_username; + $sth->bindParam(':username', $username); + $sth->bindParam(':collection', $collection); + $sth->bindParam(':id', $id); + $sth->execute(); + } + catch( PDOException $exception ) + { + error_log("retrieve_object: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + + $result = $sth->fetch(PDO::FETCH_ASSOC); + + $wbo = new wbo(); + $wbo->populate($result); + return $wbo; + } + + function retrieve_objects($collection, $id = null, $full = null, $direct_output = null, $parentid = null, + $predecessorid = null, $newer = null, $older = null, $sort = null, + $limit = null, $offset = null, $ids = null, + $index_above = null, $index_below = null) + { + $full_list = $full ? '*' : 'id'; + + + $select_stmt = "select $full_list from wbo where username = ? and collection = ?"; + $params[] = $this->_username; + $params[] = $collection; + + + if ($id) + { + $select_stmt .= " and id = ?"; + $params[] = $id; + } + + if ($ids && count($ids) > 0) + { + $qmarks = array(); + $select_stmt .= " and id in ("; + foreach ($ids as $temp) + { + $params[] = $temp; + $qmarks[] = '?'; + } + $select_stmt .= implode(",", $qmarks); + $select_stmt .= ')'; + } + + if ($parentid) + { + $select_stmt .= " and parentid = ?"; + $params[] = $parentid; + } + + + if ($predecessorid) + { + $select_stmt .= " and predecessorid = ?"; + $params[] = $predecessorid; + } + + if ($index_above) + { + $select_stmt .= " and sortindex > ?"; + $params[] = $parentid; + } + + if ($index_below) + { + $select_stmt .= " and sortindex < ?"; + $params[] = $parentid; + } + + if ($newer) + { + $select_stmt .= " and modified > ?"; + $params[] = $newer; + } + + if ($older) + { + $select_stmt .= " and modified < ?"; + $params[] = $older; + } + + if ($sort == 'index') + { + $select_stmt .= " order by sortindex desc"; + } + else if ($sort == 'newest') + { + $select_stmt .= " order by modified desc"; + } + else if ($sort == 'oldest') + { + $select_stmt .= " order by modified"; + } + + if ($limit) + { + $select_stmt .= " limit " . intval($limit); + if ($offset) + { + $select_stmt .= " offset " . intval($offset); + } + } + + try + { + $sth = $this->_dbh->prepare($select_stmt); + $sth->execute($params); + } + catch( PDOException $exception ) + { + error_log("retrieve_collection: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + + if ($direct_output) + return $direct_output->output($sth); + + $ids = array(); + while ($result = $sth->fetch(PDO::FETCH_ASSOC)) + { + if ($full) + { + $wbo = new wbo(); + $wbo->populate($result); + $ids[] = $wbo; + } + else + $ids[] = $result{'id'}; + } + return $ids; + } + + function get_storage_total() + { + try + { + $select_stmt = 'select round(sum(length(payload))/1024) from wbo where username = :username'; + $sth = $this->_dbh->prepare($select_stmt); + $username = $this->_username; + $sth->bindParam(':username', $username); + $sth->execute(); + } + catch( PDOException $exception ) + { + error_log("get_storage_total: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + + return (int)$sth->fetchColumn(); + } + + function get_collection_storage_totals() + { + try + { + $select_stmt = 'select collection, sum(payload_size) from wbo where username = :username group by collection'; + $sth = $this->_dbh->prepare($select_stmt); + $username = $this->_username; + $sth->bindParam(':username', $username); + $sth->execute(); + } + catch( PDOException $exception ) + { + error_log("get_storage_total (" . $this->connection_details_string() . "): " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + $results = $sth->fetchAll(PDO::FETCH_NUM); + $sth->closeCursor(); + + $collections = array(); + foreach ($results as $result) + { + $collections[$result[0]] = (int)$result[1]; + } + return $collections; + } + + + function get_user_quota() + { + return null; + } + function delete_storage($username) + { + log_error("delete storage"); + if (!$username) + { + throw new Exception("3", 404); + } + try + { + $delete_stmt = 'delete from wbo where username = :username'; + $sth = $this->_dbh->prepare($delete_stmt); + $sth->bindParam(':username', $username); + $sth->execute(); + $sth->closeCursor(); + } + catch( PDOException $exception ) + { + error_log("delete_user: " . $exception->getMessage()); + return 0; + } + return 1; + + } + function delete_user($username) + { + log_error("delete User"); + if (!$username) + { + throw new Exception("3", 404); + } + + try + { + $delete_stmt = 'delete from users where username = :username'; + $sth = $this->_dbh->prepare($delete_stmt); + $sth->bindParam(':username', $username); + $sth->execute(); + $sth->closeCursor(); + + $delete_wbo_stmt = 'delete from wbo where username = :username'; + $sth = $this->_dbh->prepare($delete_wbo_stmt); + $sth->bindParam(':username', $username); + $sth->execute(); + + } + catch( PDOException $exception ) + { + error_log("delete_user: " . $exception->getMessage()); + return 0; + } + return 1; + } + + function create_user($username, $password) + { + log_error("Create User - Username: ".$username."|".$password); + /*try + { + $select_stmt = 'select username from users where username = :username'; + $sth = $this->_dbh->prepare($select_stmt); + $username = $this->_username; + $sth->bindParam(':username', $username); + $sth->execute(); + } + catch( PDOException $exception ) + { + error_log("exists_user: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + }*/ + + try + { + $create_statement = "insert into users values (:username, :md5)"; + + $sth = $this->_dbh->prepare($create_statement); + $password = md5($password); + $sth->bindParam(':username', $username); + $sth->bindParam(':md5', $password); + $sth->execute(); + } + catch( PDOException $exception ) + { + log_error("create_user:" . $exception->getMessage()); + error_log("create_user:" . $exception->getMessage()); + return 0; + } + return 1; + } + + function change_password($username, $password) + { + try + { + $update_statement = "update users set md5 = :md5 where username = :username"; + + $sth = $this->_dbh->prepare($update_statement); + $password = md5($password); + $sth->bindParam(':username', $username); + $sth->bindParam(':md5', $password); + $sth->execute(); + } + catch( PDOException $exception ) + { + log_error("change_password:" . $exception->getMessage()); + return 0; + } + return 1; + } + + #function checks if user exists + function exists_user() + { + try + { + $select_stmt = 'select username from users where username = :username'; + $sth = $this->_dbh->prepare($select_stmt); + $username = $this->_username; + $sth->bindParam(':username', $username); + $sth->execute(); + } + catch( PDOException $exception ) + { + error_log("exists_user: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + + if (!$result = $sth->fetch(PDO::FETCH_ASSOC)) + { + return null; + } + return 1; + } + + + function authenticate_user($password) + { + try + { + $select_stmt = 'select username from users where username = :username and md5 = :md5'; + $sth = $this->_dbh->prepare($select_stmt); + $username = $this->_username; + $password = md5($password); + $sth->bindParam(':username', $username); + $sth->bindParam(':md5', $password); + $sth->execute(); + } + catch( PDOException $exception ) + { + error_log("authenticate_user: " . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + + if (!$result = $sth->fetch(PDO::FETCH_ASSOC)) + { + return null; + } + + return 1; + } + + function setup_db() + { + + try + { + $create_statement =" +create table wbo +( + username text, + id text, + collection text, + parentid text, + predecessorid int, + modified real, + sortindex int, + payload text, + payload_size int, + ttl int, + primary key (username,collection,id) +)"; + + $create_statement2 = " +create table users +( + username text, + md5 text, + primary key (username) +) +"; + + $index1 = 'create index parentindex on wbo (username, parentid)'; + $index2 = 'create index predecessorindex on wbo (username, predecessorid)'; + $index3 = 'create index modifiedindex on wbo (username, collection, modified)'; + + + $sth = $this->_dbh->prepare($create_statement); + $sth->execute(); + $sth = $this->_dbh->prepare($create_statement2); + $sth->execute(); + $sth = $this->_dbh->prepare($index1); + $sth->execute(); + $sth = $this->_dbh->prepare($index2); + $sth->execute(); + $sth = $this->_dbh->prepare($index3); + $sth->execute(); + } + catch( PDOException $exception ) + { + log_error("initialize_user_db:" . $exception->getMessage()); + throw new Exception("Database unavailable", 503); + } + } +} + + + ?> diff --git a/weave_utils.php b/weave_utils.php new file mode 100644 index 0000000..bf8cfaa --- /dev/null +++ b/weave_utils.php @@ -0,0 +1,228 @@ + '400 Bad Request', + '401' => '401 Unauthorized', + '403' => '403 Forbidden', + '404' => '404 Not Found', + '412' => '412 Precondition Failed', + '503' => '503 Service Unavailable'); + header('HTTP/1.1 ' . $headers{$code},true,$code); + + if ($code == 401) + { + header('WWW-Authenticate: Basic realm="Weave"'); + } + log_error($message); + exit(json_encode($message)); + } + + + function fix_utf8_encoding($string) + { + if(mb_detect_encoding($string . " ", 'UTF-8,ISO-8859-1') == 'UTF-8') + return $string; + else + return utf8_encode($string); + } + + function get_json() + { + #stupid php being helpful with input data... + $putdata = fopen("php://input", "r"); + $jsonstring = ''; + while ($data = fread($putdata,2048)) {$jsonstring .= $data;} + $json = json_decode(fix_utf8_encoding($jsonstring), true); + + if ($json === null) + report_problem(WEAVE_ERROR_JSON_PARSE, 400); + + return $json; + } + + function validate_username($username) + { + if (!$username) + return false; + + if (strlen($username) > 32) + return false; + + return preg_match('/[^A-Z0-9._-]/i', $username) ? false : true; + } + + function validate_collection($collection) + { + if (!$collection) + return false; + + if (strlen($collection) > 32) + return false; + + return preg_match('/[^A-Z0-9._-]/i', $collection) ? false : true; + } + + #user exitsts + function exists_user( $db) + { + #$user = strtolower($user); + try{ + + if(!$db->exists_user()) + return 0; + return 1; + } + catch(Exception $e) + { + header("X-Weave-Backoff: 1800"); + report_problem($e->getMessage(), $e->getCode()); + } + } + # Gets the username and password out of the http headers, and checks them against the auth + function verify_user($url_user, $db) + { + if (!$url_user || !preg_match('/^[A-Z0-9._-]+$/i', $url_user)) + report_problem(WEAVE_ERROR_INVALID_USERNAME, 400); + + $auth_user = array_key_exists('PHP_AUTH_USER', $_SERVER) ? $_SERVER['PHP_AUTH_USER'] : null; + $auth_pw = array_key_exists('PHP_AUTH_PW', $_SERVER) ? $_SERVER['PHP_AUTH_PW'] : null; + + if (is_null($auth_user) || is_null($auth_pw)) + { + /* CGI/FCGI auth workarounds */ + $auth_str = null; + if (array_key_exists('Authorization', $_SERVER)) + /* Standard fastcgi configuration */ + $auth_str = $_SERVER['Authorization']; + else if (array_key_exists('AUTHORIZATION', $_SERVER)) + /* Alternate fastcgi configuration */ + $auth_str = $_SERVER['AUTHORIZATION']; + else if (array_key_exists('HTTP_AUTHORIZATION', $_SERVER)) + /* IIS/ISAPI and newer (yet to be released) fastcgi */ + $auth_str = $_SERVER['HTTP_AUTHORIZATION']; + else if (array_key_exists('REDIRECT_HTTP_AUTHORIZATION', $_SERVER)) + /* mod_rewrite - per-directory internal redirect */ + $auth_str = $_SERVER['REDIRECT_HTTP_AUTHORIZATION']; + if (!is_null($auth_str)) + { + /* Basic base64 auth string */ + if (preg_match('/Basic\s+(.*)$/', $auth_str)) + { + $auth_str = substr($auth_str, 6); + $auth_str = base64_decode($auth_str, true); + if ($auth_str != FALSE) { + $tmp = explode(':', $auth_str); + if (count($tmp) == 2) + { + $auth_user = $tmp[0]; + $auth_pw = $tmp[1]; + } + } + } + } + } + + if (!$auth_user || !$auth_pw) #do this first to avoid the cryptic error message if auth is missing + { + log_error("Auth failed{"); + log_error(" User pw:".$auth_user."|".$auth_pw); + log_error(" Url_user:".$url_user); + log_error("}"); + report_problem('Authentication failed', '401'); + } + $url_user = strtolower($url_user); + if (strtolower($auth_user) != $url_user) + { + log_error("(140) Missmatch:".strtolower($auth_user)."|".$url_user); + report_problem(WEAVE_ERROR_USERID_PATH_MISMATCH, 400); + } + + try + { + if (!$db->authenticate_user(fix_utf8_encoding($auth_pw))) + { + log_error("Auth failed{"); + log_error(" User pw:".$auth_user."|".$auth_pw ."|md5:".md5($auth_pw)."|fix:".fix_utf8_encoding($auth_pw)."|fix md5". md5(fix_utf8_encoding($auth_pw))); + log_error(" Url_user:".$url_user); + log_error("}"); + report_problem('Authentication failed', '401'); + } + } + catch(Exception $e) + { + header("X-Weave-Backoff: 1800"); + report_problem($e->getMessage(), $e->getCode()); + } + + return true; + } + + function check_quota(&$db) + { + return; + } + + function check_timestamp($collection, &$db) + { + if (array_key_exists('HTTP_X_IF_UNMODIFIED_SINCE', $_SERVER) + && $db->get_max_timestamp($collection) > $_SERVER['HTTP_X_IF_UNMODIFIED_SINCE']) + report_problem(WEAVE_ERROR_NO_OVERWRITE, 412); + } + + function validate_search_params() + { + $params = array(); + $params['parentid'] = (array_key_exists('parentid', $_GET) && mb_strlen($_GET['parentid'], '8bit') <= 64 && strpos($_GET['parentid'], '/') === false) ? $_GET['parentid'] : null; + $params['predecessorid'] = (array_key_exists('predecessorid', $_GET) && mb_strlen($_GET['predecessorid'], '8bit') <= 64 && strpos($_GET['predecessorid'], '/') === false) ? $_GET['predecessorid'] : null; + + $params['newer'] = (array_key_exists('newer', $_GET) && is_numeric($_GET['newer'])) ? round($_GET['newer'],2) : null; + $params['older'] = (array_key_exists('older', $_GET) && is_numeric($_GET['older'])) ? round($_GET['older'],2) : null; + + $params['sort'] = (array_key_exists('sort', $_GET) && ($_GET['sort'] == 'oldest' || $_GET['sort'] == 'newest' || $_GET['sort'] == 'index')) ? $_GET['sort'] : null; + $params['limit'] = (array_key_exists('limit', $_GET) && is_numeric($_GET['limit']) && $_GET['limit'] > 0) ? (int)$_GET['limit'] : null; + $params['offset'] = (array_key_exists('offset', $_GET) && is_numeric($_GET['offset']) && $_GET['offset'] > 0) ? (int)$_GET['offset'] : null; + + $params['ids'] = null; + if (array_key_exists('ids', $_GET)) + { + $params['ids'] = array(); + foreach(explode(',', $_GET['ids']) as $id) + { + if (mb_strlen($id, '8bit') <= 64 && strpos($id, '/') === false) + $params['ids'][] = $id; + } + } + + $params['index_above'] = (array_key_exists('index_above', $_GET) && is_numeric($_GET['index_above']) && $_GET['index_above'] > 0) ? (int)$_GET['index_above'] : null; + $params['index_below'] = (array_key_exists('index_below', $_GET) && is_numeric($_GET['index_below']) && $_GET['index_below'] > 0) ? (int)$_GET['index_below'] : null; + $params['depth'] = (array_key_exists('depth', $_GET) && is_numeric($_GET['depth']) && $_GET['depth'] > 0) ? (int)$_GET['depth'] : null; + + return $params; + } + +?>