diff --git a/actions/postnotice.php b/actions/postnotice.php index dd13f60b2a..5b275e2d63 100644 --- a/actions/postnotice.php +++ b/actions/postnotice.php @@ -19,9 +19,77 @@ if (!defined('LACONICA')) { exit(1); } +require_once(INSTALLDIR.'/lib/omb.php'); +require_once('Auth/Yadis/Yadis.php'); + class PostnoticeAction extends Action { function handle($args) { parent::handle($args); - common_server_error(_t('Not yet implemented.')); + try { + $req = OAuthRequest::from_request(); + # Note: server-to-server function! + $server = omb_oauth_server(); + list($consumer, $token) = $server->verify_request($req); + if ($this->save_notice($req, $consumer, $token)) { + print "omb_version=".OMB_VERSION_01; + } + } catch (OAuthException $e) { + common_server_error($e->getMessage()); + return; + } + } + + function save_notice(&$req, &$consumer, &$token) { + $version = $req->get_parameter('omb_version'); + if ($version != OMB_VERSION_01) { + common_user_error(_t('Unsupported OMB version'), 400); + return false; + } + # First, check to see + $listenee = $req->get_parameter('omb_listenee'); + $remote_profile = Remote_profile::staticGet('uri', $listenee); + if (!$remote_profile) { + common_user_error(_t('Profile unknown'), 403); + return false; + } + $sub = Subscription::staticGet('token', $token->key); + if (!$sub) { + common_user_error(_t('No such subscription'), 403); + return false; + } + $content = $req->get_parameter('omb_notice_content'); + if (!$content || strlen($content) > 140) { + common_user_error(_t('Invalid notice content'), 400); + return false; + } + $notice_uri = $req->get_parameter('omb_notice'); + if (!Validate::uri($notice_uri) && + !common_valid_tag($notice_uri)) { + common_user_error(_t('Invalid notice uri'), 400); + return false; + } + $notice_url = $req->get_parameter('omb_notice_url'); + if ($notice_url && !common_valid_http_url($notice_url)) { + common_user_error(_t('Invalid notice url'), 400); + return false; + } + $notice = Notice::staticGet('uri', $notice_uri); + if (!$notice) { + $notice = new Notice(); + $notice->profile_id = $remote_profile->id; + $notice->uri = $notice->uri; + $notice->content = $content; + if ($notice_url) { + $notice->url = $notice_url; + } + $notice->created = DB_DataObject_Cast::dateTime(); # current time + $id = $notice->insert(); + if (!$id) { + common_server_error(_t('Error inserting notice'), 500); + return false; + } + common_broadcast_notice($notice, true); + } + return true; } } diff --git a/lib/util.php b/lib/util.php index 298c5c5de1..f431c64ecb 100644 --- a/lib/util.php +++ b/lib/util.php @@ -21,8 +21,21 @@ # Show a server error -function common_server_error($msg) { - header('HTTP/1.1 500 Server Error'); +function common_server_error($msg, $code=500) { + static $status = array(500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported'); + + if (!array_key_exists($code, $status)) { + $code = 500; + } + + $status_string = $status[$code]; + + header('HTTP/1.1 '.$code.' '.$status_string); header('Content-type: text/plain'); print $msg; @@ -31,7 +44,34 @@ function common_server_error($msg) { } # Show a user error -function common_user_error($msg, $code=200) { +function common_user_error($msg, $code=400) { + static $status = array(400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed'); + + if (!array_key_exists($code, $status)) { + $code = 400; + } + + $status_string = $status[$code]; + + header('HTTP/1.1 '.$code.' '.$status_string); + common_show_header('Error'); common_element('div', array('class' => 'error'), $msg); common_show_footer(); @@ -276,7 +316,7 @@ function common_textarea($id, $label, $content=NULL) { common_element('textarea', array('rows' => 3, 'cols' => 40, 'name' => $id, - 'id' => $id, + 'id' => $id, 'class' => 'width50'), ($content) ? $content : ' '); common_element_end('p'); @@ -427,7 +467,7 @@ function common_default_avatar($size) { AVATAR_STREAM_SIZE => 'stream', AVATAR_MINI_SIZE => 'mini'); global $config; - + return common_path($config['avatar']['default'][$sizenames[$size]]); } @@ -486,15 +526,78 @@ function common_redirect($url, $code=307) { common_element('a', array('href' => $url), $url); } -function common_broadcast_notice($notice) { - // XXX: broadcast notices to remote subscribers - // XXX: broadcast notices to SMS +function common_broadcast_notice($notice, $remote=false) { + // XXX: optionally use a queue system like http://code.google.com/p/microapps/wiki/NQDQ + if (!$remote) { + common_broadcast_remote_subscribers($notice); + } // XXX: broadcast notices to Jabber + // XXX: broadcast notices to SMS // XXX: broadcast notices to other IM - // XXX: use a queue system like http://code.google.com/p/microapps/wiki/NQDQ return true; } +function common_broadcast_remote_subscribers($notice) { + # First, get remote users subscribed to this profile + $sub = new Subscription(); + $sub->subscribed = $notice->profile_id; + $rp = new Remote_profile(); + $sub->addJoin($rp, 'INNER', NULL, 'subscriber'); + if ($sub->find()) { + $posted = array(); + while ($sub->fetch()) { + if (!$posted[$rp->postnoticeurl]) { + if (common_post_notice($notice, $rp, $sub)) { + $posted[$rp->postnoticeurl] = TRUE; + } + } + } + } +} + +function common_post_notice($notice, $remote_profile, $subscription) { + global $config; # for license URL + $user = User::staticGet('id', $notice->profile_id); + $con = omb_oauth_consumer(); + $token = new OAuthToken($subscription->token, $subscription->secret); + $url = $remote_profile->postnoticeurl; + $parsed = parse_url($url); + $params = array(); + parse_str($parsed['query'], $params); + $req = OAuthRequest::from_consumer_and_token($con, $token, + "POST", $url, $params); + $req->set_parameter('omb_version', OMB_VERSION_01); + $req->set_parameter('omb_listenee', $user->uri); + $req->set_parameter('omb_notice', $notice->uri); + $req->set_parameter('omb_notice_content', $notice->content); + $req->set_parameter('omb_notice_url', common_local_url('shownotice', + array('notice' => + $notice->id))); + $req->set_parameter('omb_notice_license', $config['license']['url']); + $req->sign_request(omb_hmac_sha1(), $con, $tok); + + # We re-use this tool's fetcher, since it's pretty good + + $fetcher = Auth_Yadis_Yadis::getHTTPFetcher(); + + $result = $fetcher->post($req->get_normalized_http_url(), + $req->to_postdata()); + + if ($result->status == 403) { # not authorized, don't send again + $subscription->delete(); + return false; + } else if ($result->status != 200) { + return false; + } else { # success! + parse_str($result->body, $return); + if ($return['omb_version'] == OMB_VERSION_01) { + return true; + } else { + return false; + } + } +} + function common_profile_url($nickname) { return common_local_url('showstream', array('nickname' => $nickname)); } @@ -509,7 +612,7 @@ function common_notice_form() { function common_mint_tag($extra) { global $config; - return + return 'tag:'.$config['tag']['authority'].','. $config['tag']['date'].':'.$config['tag']['prefix'].$extra; } @@ -565,7 +668,7 @@ function common_get_returnto() { function common_timestamp() { return date('YmdHis'); } - + // XXX: set up gettext function _t($str) { @@ -601,7 +704,7 @@ function common_valid_http_url($url) { function common_valid_tag($tag) { if (preg_match('/^tag:(.*?),(\d{4}(-\d{2}(-\d{2})?)?):(.*)$/', $tag, $matches)) { - return (Validate::email($matches[1]) || + return (Validate::email($matches[1]) || preg_match('/^([\w-\.]+)$/', $matches[1])); } return false;