clean up danbooru api code

This commit is contained in:
Shish 2015-09-26 22:50:05 +01:00
parent 937106c0d7
commit b9a0278f6e

View File

@ -55,237 +55,167 @@ class DanbooruApi extends Extension {
} }
// Danbooru API // Danbooru API
private function api_danbooru(PageRequestEvent $event) private function api_danbooru(PageRequestEvent $event) {
{
global $page; global $page;
global $config;
global $database;
global $user;
$page->set_mode("data"); $page->set_mode("data");
$page->set_type("application/xml");
//debug
//$page->set_type("text/plain");
$results = array(); if(($event->get_arg(1) == 'add_post') || (($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'create.xml'))) {
$danboorup_kludge=1; // danboorup for firefox makes broken links out of location: /path
/*
add_post()
Adds a post to the database.
Parameters
* login: login
* password: password
* file: file as a multipart form
* source: source url
* title: title **IGNORED**
* tags: list of tags as a string, delimited by whitespace
* md5: MD5 hash of upload in hexadecimal format
* rating: rating of the post. can be explicit, questionable, or safe. **IGNORED**
Notes
* The only necessary parameter is tags and either file or source.
* If you want to sign your post, you need a way to authenticate your account, either by supplying login and password, or by supplying a cookie.
* If an account is not supplied or if it doesn‘t authenticate, he post will be added anonymously.
* If the md5 parameter is supplied and does not match the hash of what‘s on the server, the post is rejected.
Response
The response depends on the method used:
Post
* X-Danbooru-Location set to the URL for newly uploaded post.
Get
* Redirected to the newly uploaded post.
*/
if(($event->get_arg(1) == 'add_post') || (($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'create.xml')))
{
// No XML data is returned from this function // No XML data is returned from this function
$page->set_type("text/plain"); $page->set_type("text/plain");
// Check first if a login was supplied, if it wasn't check if the user is logged in via cookie $this->api_add_post();
// If all that fails, it's an anonymous upload
$this->authenticate_user();
// Now we check if a file was uploaded or a url was provided to transload
// Much of this code is borrowed from /ext/upload
if($user->can("create_image"))
{
if(isset($_FILES['file']))
{ // A file was POST'd in
$file = $_FILES['file']['tmp_name'];
$filename = $_FILES['file']['name'];
// If both a file is posted and a source provided, I'm assuming source is the source of the file
if(isset($_REQUEST['source']) && !empty($_REQUEST['source']))
{
$source = $_REQUEST['source'];
} else
{
$source = null;
}
} elseif(isset($_FILES['post']))
{
$file = $_FILES['post']['tmp_name']['file'];
$filename = $_FILES['post']['name']['file'];
if(isset($_REQUEST['post']['source']) && !empty($_REQUEST['post']['source']))
{
$source = $_REQUEST['post']['source'];
} else
{
$source = null;
}
} elseif(isset($_REQUEST['source']) || isset($_REQUEST['post']['source']))
{ // A url was provided
$url = isset($_REQUEST['source']) ? $_REQUEST['source'] : $_REQUEST['post']['source'];
$source = $url;
$tmp_filename = tempnam("/tmp", "shimmie_transload");
// Are we using fopen wrappers or curl?
if($config->get_string("transload_engine") == "fopen")
{
$fp = fopen($url, "r");
if(!$fp) {
$page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: fopen read error");
} }
$data = ""; elseif(($event->get_arg(1) == 'find_posts') || (($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'index.xml'))) {
$length = 0; $page->set_type("application/xml");
while(!feof($fp) && $length <= $config->get_int('upload_size')) $page->set_data($this->api_find_posts());
{
$data .= fread($fp, 8192);
$length = strlen($data);
}
fclose($fp);
$fp = fopen($tmp_filename, "w");
fwrite($fp, $data);
fclose($fp);
} }
if($config->get_string("transload_engine") == "curl") elseif($event->get_arg(1) == 'find_tags') {
{ $page->set_type("application/xml");
$ch = curl_init($url); $page->set_data($this->api_find_tags());
$fp = fopen($tmp_filename, "w");
curl_setopt($ch, CURLOPT_FILE, $fp);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_exec($ch);
curl_close($ch);
fclose($fp);
}
$file = $tmp_filename;
$filename = basename($url);
} else
{ // Nothing was specified at all
$page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: no input files");
return;
} }
// Get tags out of url // Hackery for danbooruup 0.3.2 providing the wrong view url. This simply redirects to the proper
$posttags = Tag::explode(isset($_REQUEST['tags']) ? $_REQUEST['tags'] : $_REQUEST['post']['tags']); // Shimmie view page
$hash = md5_file($file); // Example: danbooruup says the url is http://shimmie/api/danbooru/post/show/123
// Was an md5 supplied? Does it match the file hash? // This redirects that to http://shimmie/post/view/123
if(isset($_REQUEST['md5'])) elseif(($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'show')) {
{ $fixedlocation = make_link("post/view/" . $event->get_arg(3));
if(strtolower($_REQUEST['md5']) != $hash) $page->set_mode("redirect");
{ $page->set_redirect($fixedlocation);
$page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: md5 mismatch");
return;
}
}
// Upload size checking is now performed in the upload extension
// It is also currently broken due to some confusion over file variable ($tmp_filename?)
// Does it exist already?
$existing = Image::by_hash($hash);
if(!is_null($existing)) {
$page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: duplicate");
$existinglink = make_link("post/view/" . $existing->id);
if($danboorup_kludge) $existinglink=make_http($existinglink);
$page->add_http_header("X-Danbooru-Location: $existinglink");
return; // wut!
}
// Fire off an event which should process the new file and add it to the db
$fileinfo = pathinfo($filename);
$metadata = array();
$metadata['filename'] = $fileinfo['basename'];
$metadata['extension'] = $fileinfo['extension'];
$metadata['tags'] = $posttags;
$metadata['source'] = $source;
//log_debug("danbooru_api","========== NEW($filename) =========");
//log_debug("danbooru_api", "upload($filename): fileinfo(".var_export($fileinfo,TRUE)."), metadata(".var_export($metadata,TRUE).")...");
try {
$nevent = new DataUploadEvent($file, $metadata);
//log_debug("danbooru_api", "send_event(".var_export($nevent,TRUE).")");
send_event($nevent);
// If it went ok, grab the id for the newly uploaded image and pass it in the header
$newimg = Image::by_hash($hash); // FIXME: Unsupported file doesn't throw an error?
$newid = make_link("post/view/" . $newimg->id);
if($danboorup_kludge) $newid=make_http($newid);
// Did we POST or GET this call?
if($_SERVER['REQUEST_METHOD'] == 'POST')
{
$page->add_http_header("X-Danbooru-Location: $newid");
}
else
$page->add_http_header("Location: $newid");
}
catch(UploadException $ex) {
// Did something screw up?
$page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: exception - " . $ex->getMessage());
return;
}
} else
{
$page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: authentication error");
return;
} }
} }
/* /**
find_posts() * Turns out I use this a couple times so let's make it a utility function
Find all posts that match the search criteria. Posts will be ordered by id descending. * Authenticates a user based on the contents of the login and password parameters
Parameters * or makes them anonymous. Does not set any cookies or anything permanent.
* md5: md5 hash to search for (comma delimited)
* id: id to search for (comma delimited)
* tags: what tags to search for
* limit: limit
* page: page number
* after_id: limit results to posts added after this id
*/ */
if(($event->get_arg(1) == 'find_posts') || (($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'index.xml'))) private function authenticate_user() {
{ global $config, $user;
if(isset($_REQUEST['login']) && isset($_REQUEST['password'])) {
// Get this user from the db, if it fails the user becomes anonymous
// Code borrowed from /ext/user
$name = $_REQUEST['login'];
$pass = $_REQUEST['password'];
$duser = User::by_name_and_pass($name, $pass);
if(!is_null($duser)) {
$user = $duser;
}
else {
$user = User::by_id($config->get_int("anon_id", 0));
}
}
}
/**
* find_tags()
* Find all tags that match the search criteria.
*
* Parameters
* - id: A comma delimited list of tag id numbers.
* - name: A comma delimited list of tag names.
* - tags: any typical tag query. See Tag#parse_query for details.
* - after_id: limit results to tags with an id number after after_id. Useful if you only want to refresh
*
* @return string
*/
private function api_find_tags() {
global $database;
$results = array();
if(isset($_GET['id'])) {
$idlist = explode(",", $_GET['id']);
foreach ($idlist as $id) {
$sqlresult = $database->get_all(
"SELECT id,tag,count FROM tags WHERE id = ?",
array($id));
foreach ($sqlresult as $row) {
$results[] = array($row['count'], $row['tag'], $row['id']);
}
}
}
elseif(isset($_GET['name'])) {
$namelist = explode(",", $_GET['name']);
foreach ($namelist as $name) {
$sqlresult = $database->get_all(
"SELECT id,tag,count FROM tags WHERE tag = ?",
array($name));
foreach ($sqlresult as $row) {
$results[] = array($row['count'], $row['tag'], $row['id']);
}
}
}
// Currently disabled to maintain identical functionality to danbooru 1.0's own "broken" find_tags
elseif(false && isset($_GET['tags'])) {
$start = isset($_GET['after_id']) ? int_escape($_GET['offset']) : 0;
$tags = Tag::explode($_GET['tags']);
}
else {
$start = isset($_GET['after_id']) ? int_escape($_GET['offset']) : 0;
$sqlresult = $database->get_all(
"SELECT id,tag,count FROM tags WHERE count > 0 AND id >= ? ORDER BY id DESC",
array($start));
foreach ($sqlresult as $row) {
$results[] = array($row['count'], $row['tag'], $row['id']);
}
}
// Tag results collected, build XML output
$xml = "<tags>\n";
foreach ($results as $tag) {
$xml .= xml_tag("tag", array(
"type" => "0",
"counts" => $tag[0],
"name" => $tag[1],
"id" => $tag[2],
));
}
$xml .= "</tags>";
return $xml;
}
/**
* find_posts()
* Find all posts that match the search criteria. Posts will be ordered by id descending.
*
* Parameters:
* - md5: md5 hash to search for (comma delimited)
* - id: id to search for (comma delimited)
* - tags: what tags to search for
* - limit: limit
* - page: page number
* - after_id: limit results to posts added after this id
*
* @return string
* @throws SCoreException
*/
private function api_find_posts() {
$results = array();
$this->authenticate_user(); $this->authenticate_user();
$start = 0; $start = 0;
if(isset($_GET['md5'])) if(isset($_GET['md5'])) {
{ $md5list = explode(",", $_GET['md5']);
$md5list = explode(",",$_GET['md5']); foreach ($md5list as $md5) {
foreach($md5list as $md5)
{
$results[] = Image::by_hash($md5); $results[] = Image::by_hash($md5);
} }
$count = count($results); $count = count($results);
} elseif(isset($_GET['id'])) }
{ elseif(isset($_GET['id'])) {
$idlist = explode(",",$_GET['id']); $idlist = explode(",", $_GET['id']);
foreach($idlist as $id) foreach ($idlist as $id) {
{
$results[] = Image::by_id($id); $results[] = Image::by_id($id);
} }
$count = count($results); $count = count($results);
} else }
{ else {
$limit = isset($_GET['limit']) ? int_escape($_GET['limit']) : 100; $limit = isset($_GET['limit']) ? int_escape($_GET['limit']) : 100;
// Calculate start offset. // Calculate start offset.
if (isset($_GET['page'])) // Danbooru API uses 'page' >= 1 if (isset($_GET['page'])) // Danbooru API uses 'page' >= 1
$start = (int_escape($_GET['page'])-1) * $limit; $start = (int_escape($_GET['page']) - 1) * $limit;
else if (isset($_GET['pid'])) // Gelbooru API uses 'pid' >= 0 else if (isset($_GET['pid'])) // Gelbooru API uses 'pid' >= 0
$start = int_escape($_GET['pid']) * $limit; $start = int_escape($_GET['pid']) * $limit;
else else
@ -299,11 +229,10 @@ class DanbooruApi extends Extension {
// Now we have the array $results filled with Image objects // Now we have the array $results filled with Image objects
// Let's display them // Let's display them
$xml = "<posts count=\"{$count}\" offset=\"{$start}\">\n"; $xml = "<posts count=\"{$count}\" offset=\"{$start}\">\n";
foreach($results as $img) foreach ($results as $img) {
{
// Sanity check to see if $img is really an image object // Sanity check to see if $img is really an image object
// If it isn't (e.g. someone requested an invalid md5 or id), break out of the this // If it isn't (e.g. someone requested an invalid md5 or id), break out of the this
if(!is_object($img)) if (!is_object($img))
continue; continue;
$taglist = $img->get_tag_list(); $taglist = $img->get_tag_list();
$owner = $img->get_owner(); $owner = $img->get_owner();
@ -328,106 +257,167 @@ class DanbooruApi extends Extension {
)); ));
} }
$xml .= "</posts>"; $xml .= "</posts>";
$page->set_data($xml); return $xml;
} }
/* /**
find_tags() Find all tags that match the search criteria. * add_post()
Parameters * Adds a post to the database.
* id: A comma delimited list of tag id numbers. *
* name: A comma delimited list of tag names. * Parameters:
* tags: any typical tag query. See Tag#parse_query for details. * - login: login
* after_id: limit results to tags with an id number after after_id. Useful if you only want to refresh * - password: password
* - file: file as a multipart form
* - source: source url
* - title: title **IGNORED**
* - tags: list of tags as a string, delimited by whitespace
* - md5: MD5 hash of upload in hexadecimal format
* - rating: rating of the post. can be explicit, questionable, or safe. **IGNORED**
*
* Notes:
* - The only necessary parameter is tags and either file or source.
* - If you want to sign your post, you need a way to authenticate your account, either by supplying login and password, or by supplying a cookie.
* - If an account is not supplied or if it doesn‘t authenticate, he post will be added anonymously.
* - If the md5 parameter is supplied and does not match the hash of what‘s on the server, the post is rejected.
*
* Response
* The response depends on the method used:
* Post:
* - X-Danbooru-Location set to the URL for newly uploaded post.
* Get:
* - Redirected to the newly uploaded post.
*/ */
if($event->get_arg(1) == 'find_tags') { private function api_add_post() {
if(isset($_GET['id'])) { global $user, $config, $page;
$idlist = explode(",",$_GET['id']); $danboorup_kludge = 1; // danboorup for firefox makes broken links out of location: /path
foreach($idlist as $id) {
$sqlresult = $database->get_all(
"SELECT id,tag,count FROM tags WHERE id = ?",
array($id));
foreach($sqlresult as $row) {
$results[] = array($row['count'], $row['tag'], $row['id']);
}
}
}
elseif(isset($_GET['name'])) {
$namelist = explode(",",$_GET['name']);
foreach($namelist as $name) {
$sqlresult = $database->get_all(
"SELECT id,tag,count FROM tags WHERE tag = ?",
array($name));
foreach($sqlresult as $row) {
$results[] = array($row['count'], $row['tag'], $row['id']);
}
}
}
/* Currently disabled to maintain identical functionality to danbooru 1.0's own "broken" find_tags
elseif(isset($_GET['tags'])) {
$start = isset($_GET['after_id']) ? int_escape($_GET['offset']) : 0;
$tags = Tag::explode($_GET['tags']);
} // Check first if a login was supplied, if it wasn't check if the user is logged in via cookie
*/ // If all that fails, it's an anonymous upload
else { $this->authenticate_user();
$start = isset($_GET['after_id']) ? int_escape($_GET['offset']) : 0; // Now we check if a file was uploaded or a url was provided to transload
$sqlresult = $database->get_all( // Much of this code is borrowed from /ext/upload
"SELECT id,tag,count FROM tags WHERE count > 0 AND id >= ? ORDER BY id DESC",
array($start)); if (!$user->can("create_image")) {
foreach($sqlresult as $row) { $page->set_code(409);
$results[] = array($row['count'], $row['tag'], $row['id']); $page->add_http_header("X-Danbooru-Errors: authentication error");
} return;
} }
// Tag results collected, build XML output if (isset($_FILES['file'])) { // A file was POST'd in
$xml = "<tags>\n"; $file = $_FILES['file']['tmp_name'];
foreach($results as $tag) { $filename = $_FILES['file']['name'];
$xml .= "<tag type=\"0\" count=\"$tag[0]\" name=\"" . $this->xmlspecialchars($tag[1]) . "\" id=\"$tag[2]\"/>\n"; // If both a file is posted and a source provided, I'm assuming source is the source of the file
if (isset($_REQUEST['source']) && !empty($_REQUEST['source'])) {
$source = $_REQUEST['source'];
} else {
$source = null;
} }
$xml .= "</tags>"; } elseif (isset($_FILES['post'])) {
$page->set_data($xml); $file = $_FILES['post']['tmp_name']['file'];
$filename = $_FILES['post']['name']['file'];
if (isset($_REQUEST['post']['source']) && !empty($_REQUEST['post']['source'])) {
$source = $_REQUEST['post']['source'];
} else {
$source = null;
}
} elseif (isset($_REQUEST['source']) || isset($_REQUEST['post']['source'])) { // A url was provided
$url = isset($_REQUEST['source']) ? $_REQUEST['source'] : $_REQUEST['post']['source'];
$source = $url;
$tmp_filename = tempnam("/tmp", "shimmie_transload");
// Are we using fopen wrappers or curl?
if ($config->get_string("transload_engine") == "fopen") {
$fp = fopen($url, "r");
if (!$fp) {
$page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: fopen read error");
} }
// Hackery for danbooruup 0.3.2 providing the wrong view url. This simply redirects to the proper $data = "";
// Shimmie view page $length = 0;
// Example: danbooruup says the url is http://shimmie/api/danbooru/post/show/123 while (!feof($fp) && $length <= $config->get_int('upload_size')) {
// This redirects that to http://shimmie/post/view/123 $data .= fread($fp, 8192);
if(($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'show')) { $length = strlen($data);
$fixedlocation = make_link("post/view/" . $event->get_arg(3));
$page->set_mode("redirect");
$page->set_redirect($fixedlocation);
} }
fclose($fp);
$fp = fopen($tmp_filename, "w");
fwrite($fp, $data);
fclose($fp);
} }
// Turns out I use this a couple times so let's make it a utility function if ($config->get_string("transload_engine") == "curl") {
// Authenticates a user based on the contents of the login and password parameters $ch = curl_init($url);
// or makes them anonymous. Does not set any cookies or anything permanent. $fp = fopen($tmp_filename, "w");
private function authenticate_user()
{
global $config;
global $user;
if(isset($_REQUEST['login']) && isset($_REQUEST['password'])) curl_setopt($ch, CURLOPT_FILE, $fp);
{ curl_setopt($ch, CURLOPT_HEADER, 0);
// Get this user from the db, if it fails the user becomes anonymous
// Code borrowed from /ext/user curl_exec($ch);
$name = $_REQUEST['login']; curl_close($ch);
$pass = $_REQUEST['password']; fclose($fp);
$duser = User::by_name_and_pass($name, $pass);
if(!is_null($duser)) {
$user = $duser;
} else
{
$user = User::by_id($config->get_int("anon_id", 0));
}
} }
$file = $tmp_filename;
$filename = basename($url);
} else { // Nothing was specified at all
$page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: no input files");
return;
} }
// From htmlspecialchars man page on php.net comments // Get tags out of url
// If tags contain quotes they need to be htmlified $posttags = Tag::explode(isset($_REQUEST['tags']) ? $_REQUEST['tags'] : $_REQUEST['post']['tags']);
private function xmlspecialchars($text) $hash = md5_file($file);
{ // Was an md5 supplied? Does it match the file hash?
return str_replace('&#039;', '&apos;', htmlspecialchars($text, ENT_QUOTES)); if (isset($_REQUEST['md5']) && strtolower($_REQUEST['md5']) != $hash) {
$page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: md5 mismatch");
return;
}
// Upload size checking is now performed in the upload extension
// It is also currently broken due to some confusion over file variable ($tmp_filename?)
// Does it exist already?
$existing = Image::by_hash($hash);
if (!is_null($existing)) {
$page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: duplicate");
$existinglink = make_link("post/view/" . $existing->id);
if ($danboorup_kludge) $existinglink = make_http($existinglink);
$page->add_http_header("X-Danbooru-Location: $existinglink");
return;
}
// Fire off an event which should process the new file and add it to the db
$fileinfo = pathinfo($filename);
$metadata = array();
$metadata['filename'] = $fileinfo['basename'];
$metadata['extension'] = $fileinfo['extension'];
$metadata['tags'] = $posttags;
$metadata['source'] = $source;
//log_debug("danbooru_api","========== NEW($filename) =========");
//log_debug("danbooru_api", "upload($filename): fileinfo(".var_export($fileinfo,TRUE)."), metadata(".var_export($metadata,TRUE).")...");
try {
$nevent = new DataUploadEvent($file, $metadata);
//log_debug("danbooru_api", "send_event(".var_export($nevent,TRUE).")");
send_event($nevent);
// If it went ok, grab the id for the newly uploaded image and pass it in the header
$newimg = Image::by_hash($hash); // FIXME: Unsupported file doesn't throw an error?
$newid = make_link("post/view/" . $newimg->id);
if ($danboorup_kludge) $newid = make_http($newid);
// Did we POST or GET this call?
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$page->add_http_header("X-Danbooru-Location: $newid");
} else {
$page->add_http_header("Location: $newid");
}
} catch (UploadException $ex) {
// Did something screw up?
$page->set_code(409);
$page->add_http_header("X-Danbooru-Errors: exception - " . $ex->getMessage());
}
} }
} }