diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | actions/api.php | 1 | ||||
-rw-r--r-- | actions/conversation.php | 2 | ||||
-rw-r--r-- | actions/twitapifavorites.php | 26 | ||||
-rw-r--r-- | actions/twitapigroups.php | 31 | ||||
-rw-r--r-- | actions/twitapitags.php | 5 | ||||
-rw-r--r-- | classes/File.php | 5 | ||||
-rw-r--r-- | classes/Session.php | 17 | ||||
-rw-r--r-- | db/074to080_pg.sql | 108 | ||||
-rw-r--r-- | index.php | 19 | ||||
-rw-r--r-- | install.php | 2 | ||||
-rw-r--r-- | js/util.js | 13 | ||||
-rw-r--r-- | lib/profilesection.php | 2 | ||||
-rw-r--r-- | lib/twitterapi.php | 49 | ||||
-rw-r--r-- | lib/util.php | 11 | ||||
-rw-r--r-- | lighttpd.conf.example | 2 | ||||
-rw-r--r-- | plugins/FBConnect/FBConnectPlugin.php | 6 | ||||
-rw-r--r-- | plugins/recaptcha/LICENSE | 22 | ||||
-rw-r--r-- | plugins/recaptcha/README | 23 | ||||
-rw-r--r-- | plugins/recaptcha/recaptcha.php | 106 | ||||
-rw-r--r-- | plugins/recaptcha/recaptchalib.php | 277 | ||||
-rw-r--r-- | scripts/sessiongc.php | 36 |
22 files changed, 713 insertions, 52 deletions
diff --git a/.gitignore b/.gitignore index f4c2bba5f..5394f5eac 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,4 @@ config-*.php good-config.php lac08.log php.log -config.php.* + diff --git a/actions/api.php b/actions/api.php index 8b92889f8..99ab262ad 100644 --- a/actions/api.php +++ b/actions/api.php @@ -130,6 +130,7 @@ class ApiAction extends Action 'laconica/wadl', 'tags/timeline', 'oembed/oembed', + 'groups/show', 'groups/timeline'); static $bareauth = array('statuses/user_timeline', diff --git a/actions/conversation.php b/actions/conversation.php index c8755ba6e..6b5d8d54d 100644 --- a/actions/conversation.php +++ b/actions/conversation.php @@ -167,6 +167,8 @@ class ConversationTree extends NoticeList function _buildTree() { + $cnt = 0; + $this->tree = array(); $this->table = array(); diff --git a/actions/twitapifavorites.php b/actions/twitapifavorites.php index 8256668f3..6f9361065 100644 --- a/actions/twitapifavorites.php +++ b/actions/twitapifavorites.php @@ -207,32 +207,10 @@ class TwitapifavoritesAction extends TwitterapiAction $other = User::staticGet('id', $notice->profile_id); if ($other && $other->id != $user->id) { if ($other->email && $other->emailnotifyfav) { - $this->notify_mail($other, $user, $notice); + mail_notify_fave($other, $user, $notice); } # XXX: notify by IM # XXX: notify by SMS } } - - function notify_mail($other, $user, $notice) - { - $profile = $user->getProfile(); - $bestname = $profile->getBestName(); - $subject = sprintf(_('%s added your notice as a favorite'), $bestname); - $body = sprintf(_("%1\$s just added your notice from %2\$s as one of their favorites.\n\n" . - "In case you forgot, you can see the text of your notice here:\n\n" . - "%3\$s\n\n" . - "You can see the list of %1\$s's favorites here:\n\n" . - "%4\$s\n\n" . - "Faithfully yours,\n" . - "%5\$s\n"), - $bestname, - common_exact_date($notice->created), - common_local_url('shownotice', array('notice' => $notice->id)), - common_local_url('showfavorites', array('nickname' => $user->nickname)), - common_config('site', 'name')); - - mail_to_user($other, $subject, $body); - } - -}
\ No newline at end of file +} diff --git a/actions/twitapigroups.php b/actions/twitapigroups.php index 71a0776f4..82604ebff 100644 --- a/actions/twitapigroups.php +++ b/actions/twitapigroups.php @@ -51,6 +51,32 @@ require_once INSTALLDIR.'/lib/twitterapi.php'; class TwitapigroupsAction extends TwitterapiAction { + function show($args, $apidata) + { + parent::handle($args); + + common_debug("in groups api action"); + + $this->auth_user = $apidata['user']; + $group = $this->get_group($apidata['api_arg'], $apidata); + + if (empty($group)) { + $this->clientError('Not Found', 404, $apidata['content-type']); + return; + } + + switch($apidata['content-type']) { + case 'xml': + $this->show_single_xml_group($group); + break; + case 'json': + $this->show_single_json_group($group); + break; + default: + $this->clientError(_('API method not found!'), $code = 404); + } + } + function timeline($args, $apidata) { parent::handle($args); @@ -88,8 +114,7 @@ require_once INSTALLDIR.'/lib/twitterapi.php'; $this->show_xml_timeline($notice); break; case 'rss': - $this->show_rss_timeline($notice, $title, $link, - $subtitle, $suplink); + $this->show_rss_timeline($notice, $title, $link, $subtitle); break; case 'atom': if (isset($apidata['api_arg'])) { @@ -101,7 +126,7 @@ require_once INSTALLDIR.'/lib/twitterapi.php'; 'api/laconica/groups/timeline.atom'; } $this->show_atom_timeline($notice, $title, $id, $link, - $subtitle, $suplink, $selfuri); + $subtitle, null, $selfuri); break; case 'json': $this->show_json_timeline($notice); diff --git a/actions/twitapitags.php b/actions/twitapitags.php index 5c8527530..e19e1b1ed 100644 --- a/actions/twitapitags.php +++ b/actions/twitapitags.php @@ -88,8 +88,7 @@ require_once INSTALLDIR.'/lib/twitterapi.php'; $this->show_xml_timeline($notice); break; case 'rss': - $this->show_rss_timeline($notice, $title, $link, - $subtitle, $suplink); + $this->show_rss_timeline($notice, $title, $link, $subtitle); break; case 'atom': if (isset($apidata['api_arg'])) { @@ -101,7 +100,7 @@ require_once INSTALLDIR.'/lib/twitterapi.php'; 'api/laconica/tags/timeline.atom'; } $this->show_atom_timeline($notice, $title, $id, $link, - $subtitle, $suplink, $selfuri); + $subtitle, null, $selfuri); break; case 'json': $this->show_json_timeline($notice); diff --git a/classes/File.php b/classes/File.php index 0c4fbf7e6..959301eda 100644 --- a/classes/File.php +++ b/classes/File.php @@ -93,7 +93,6 @@ class File extends Memcached_DataObject if (empty($file)) { $file_redir = File_redirection::staticGet('url', $given_url); if (empty($file_redir)) { - common_debug("processNew() '$given_url' not a known redirect.\n"); $redir_data = File_redirection::where($given_url); $redir_url = $redir_data['url']; if ($redir_url === $given_url) { @@ -114,7 +113,9 @@ class File extends Memcached_DataObject if (empty($x)) { $x = File::staticGet($file_id); - if (empty($x)) die('Impossible!'); + if (empty($x)) { + throw new ServerException("Robin thinks something is impossible."); + } } File_to_post::processNew($file_id, $notice_id); diff --git a/classes/Session.php b/classes/Session.php index ac80279c5..5ec509f5f 100644 --- a/classes/Session.php +++ b/classes/Session.php @@ -108,11 +108,24 @@ class Session extends Memcached_DataObject $epoch = common_sql_date(time() - $maxlifetime); + $ids = array(); + $session = new Session(); $session->whereAdd('modified < "'.$epoch.'"'); - $result = $session->delete(DB_DATAOBJECT_WHEREADD_ONLY); + $session->selectAdd(); + $session->selectAdd('id'); + + $session->find(); + + while ($session->fetch()) { + $ids[] = $session->id; + } + + $session->free(); - self::logdeb("garbage collection result = $result"); + foreach ($ids as $id) { + self::destroy($id); + } } static function setSaveHandler() diff --git a/db/074to080_pg.sql b/db/074to080_pg.sql new file mode 100644 index 000000000..0a7171ae5 --- /dev/null +++ b/db/074to080_pg.sql @@ -0,0 +1,108 @@ +BEGIN; +create sequence design_seq; +create table design ( + id bigint default nextval('design_seq') /* comment 'design ID'*/, + backgroundcolor integer /* comment 'main background color'*/ , + contentcolor integer /*comment 'content area background color'*/ , + sidebarcolor integer /*comment 'sidebar background color'*/ , + textcolor integer /*comment 'text color'*/ , + linkcolor integer /*comment 'link color'*/, + backgroundimage varchar(255) /*comment 'background image, if any'*/, + disposition int default 1 /*comment 'bit 1 = hide background image, bit 2 = display background image, bit 4 = tile background image'*/, + primary key (id) +); +alter table "user" + add column design_id integer references design(id); +alter table "user" + add column viewdesigns integer default 1; + +alter table notice add column + conversation integer references notice (id); + +create index notice_conversation_idx on notice(conversation); + +alter table foreign_user + alter column id TYPE bigint; + +alter table foreign_user alter column id set not null; + +alter table foreign_link + alter column foreign_id TYPE bigint; + +alter table user_group + add column design_id integer; + +/*attachments and URLs stuff */ +create sequence file_seq; +create table file ( + id bigint default nextval('file_seq') primary key /* comment 'unique identifier' */, + url varchar(255) unique, + mimetype varchar(50), + size integer, + title varchar(255), + date integer, + protected integer, + filename text /* comment 'if a local file, name of the file' */, + modified timestamp default CURRENT_TIMESTAMP /* comment 'date this record was modified'*/ +); + +create sequence file_oembed_seq; +create table file_oembed ( + file_id bigint default nextval('file_oembed_seq') primary key /* comment 'unique identifier' */, + version varchar(20), + type varchar(20), + provider varchar(50), + provider_url varchar(255), + width integer, + height integer, + html text, + title varchar(255), + author_name varchar(50), + author_url varchar(255), + url varchar(255) +); + +create sequence file_redirection_seq; +create table file_redirection ( + url varchar(255) primary key, + file_id bigint, + redirections integer, + httpcode integer +); + +create sequence file_thumbnail_seq; +create table file_thumbnail ( + file_id bigint primary key, + url varchar(255) unique, + width integer, + height integer +); +create sequence file_to_post_seq; +create table file_to_post ( + file_id bigint, + post_id bigint, + + primary key (file_id, post_id) +); + + +create table group_block ( + group_id integer not null /* comment 'group profile is blocked from' */ references user_group (id), + blocked integer not null /* comment 'profile that is blocked' */references profile (id), + blocker integer not null /* comment 'user making the block'*/ references "user" (id), + modified timestamp /* comment 'date of blocking'*/ , + + primary key (group_id, blocked) +); + +create table group_alias ( + + alias varchar(64) /* comment 'additional nickname for the group'*/ , + group_id integer not null /* comment 'group profile is blocked from'*/ references user_group (id), + modified timestamp /* comment 'date alias was created'*/, + primary key (alias) + +); +create index group_alias_group_id_idx on group_alias (group_id); + +COMMIT;
\ No newline at end of file @@ -107,6 +107,25 @@ function checkMirror($action_obj) function main() { + // fake HTTP redirects using lighttpd's 404 redirects + if (strpos($_SERVER['SERVER_SOFTWARE'], 'lighttpd') !== false) { + $_lighty_url = $base_url.$_SERVER['REQUEST_URI']; + $_lighty_url = @parse_url($_lighty_url); + + if ($_lighty_url['path'] != '/index.php' && $_lighty_url['path'] != '/') { + $_lighty_path = preg_replace('/^'.preg_quote(common_config('site','path')).'\//', '', substr($_lighty_url['path'], 1)); + $_SERVER['QUERY_STRING'] = 'p='.$_lighty_path; + if ($_lighty_url['query']) + $_SERVER['QUERY_STRING'] .= '&'.$_lighty_url['query']; + parse_str($_lighty_url['query'], $_lighty_query); + foreach ($_lighty_query as $key => $val) { + $_GET[$key] = $_REQUEST[$key] = $val; + } + $_GET['p'] = $_REQUEST['p'] = $_lighty_path; + } + } + $_SERVER['REDIRECT_URL'] = preg_replace("/\?.+$/", "", $_SERVER['REQUEST_URI']); + // quick check for fancy URL auto-detection support in installer. if (isset($_SERVER['REDIRECT_URL']) && (preg_replace("/^\/$/","",(dirname($_SERVER['REQUEST_URI']))) . '/check-fancy') === $_SERVER['REDIRECT_URL']) { die("Fancy URL support detection succeeded. We suggest you enable this to get fancy (pretty) URLs."); diff --git a/install.php b/install.php index c222afa7b..227f99789 100644 --- a/install.php +++ b/install.php @@ -163,7 +163,7 @@ E_O_T; function updateStatus($status, $error=false) { ?> - <li <?php echo ($error) ? 'class="error"': ''; ?>><?print $status;?></li> + <li <?php echo ($error) ? 'class="error"': ''; ?>><?php echo $status;?></li> <?php } diff --git a/js/util.js b/js/util.js index f3ed918cf..9d6e52b2d 100644 --- a/js/util.js +++ b/js/util.js @@ -25,7 +25,7 @@ $(document).ready(function(){ var counter = $("#notice_text-count"); counter.text(remaining); - if (remaining <= 0) { + if (remaining < 0) { $("#form_notice").addClass("warning"); } else { $("#form_notice").removeClass("warning"); @@ -256,10 +256,15 @@ function NoticeReplySet(nick,id) { rgx_username = /^[0-9a-zA-Z\-_.]*$/; if (nick.match(rgx_username)) { replyto = "@" + nick + " "; - if ($("#notice_data-text").length) { - $("#notice_data-text").val(replyto); + var text = $("#notice_data-text"); + if (text.length) { + text.val(replyto + text.val()); $("#form_notice input#notice_in-reply-to").val(id); - $("#notice_data-text").focus(); + if (text.get(0).setSelectionRange) { + var len = text.val().length; + text.get(0).setSelectionRange(len,len); + text.get(0).focus(); + } return false; } } diff --git a/lib/profilesection.php b/lib/profilesection.php index 9ff243fb5..d463a07b0 100644 --- a/lib/profilesection.php +++ b/lib/profilesection.php @@ -97,7 +97,7 @@ class ProfileSection extends Section $this->out->elementEnd('a'); $this->out->elementEnd('span'); $this->out->elementEnd('td'); - if ($profile->value) { + if (isset($profile->value)) { $this->out->element('td', 'value', $profile->value); } diff --git a/lib/twitterapi.php b/lib/twitterapi.php index b2602e77c..4115d9dcb 100644 --- a/lib/twitterapi.php +++ b/lib/twitterapi.php @@ -213,6 +213,26 @@ class TwitterapiAction extends Action return $twitter_status; } + function twitter_group_array($group) + { + $twitter_group=array(); + $twitter_group['id']=$group->id; + $twitter_group['url']=$group->permalink(); + $twitter_group['nickname']=$group->nickname; + $twitter_group['fullname']=$group->fullname; + $twitter_group['homepage_url']=$group->homepage_url; + $twitter_group['original_logo']=$group->original_logo; + $twitter_group['homepage_logo']=$group->homepage_logo; + $twitter_group['stream_logo']=$group->stream_logo; + $twitter_group['mini_logo']=$group->mini_logo; + $twitter_group['homepage']=$group->homepage; + $twitter_group['description']=$group->description; + $twitter_group['location']=$group->location; + $twitter_group['created']=$this->date_twitter($group->created); + $twitter_group['modified']=$this->date_twitter($group->modified); + return $twitter_group; + } + function twitter_rss_entry_array($notice) { $profile = $notice->getProfile(); @@ -413,6 +433,15 @@ class TwitterapiAction extends Action $this->elementEnd('status'); } + function show_twitter_xml_group($twitter_group) + { + $this->elementStart('group'); + foreach($twitter_group as $element => $value) { + $this->element($element, null, $value); + } + $this->elementEnd('group'); + } + function show_twitter_xml_user($twitter_user, $role='user') { $this->elementStart($role); @@ -450,12 +479,12 @@ class TwitterapiAction extends Action $this->element('link', null, $entry['link']); # RSS only supports 1 enclosure per item - if($entry['enclosures']){ + if(array_key_exists('enclosures', $entry) and !empty($entry['enclosures'])){ $enclosure = $entry['enclosures'][0]; $this->element('enclosure', array('url'=>$enclosure['url'],'type'=>$enclosure['mimetype'],'length'=>$enclosure['size']), null); } - if($entry['tags']){ + if(array_key_exists('tags', $entry)){ foreach($entry['tags'] as $tag){ $this->element('category', null,$tag); } @@ -639,6 +668,22 @@ class TwitterapiAction extends Action $this->end_document('json'); } + function show_single_json_group($group) + { + $this->init_document('json'); + $twitter_group = $this->twitter_group_array($group); + $this->show_json_objects($twitter_group); + $this->end_document('json'); + } + + function show_single_xml_group($group) + { + $this->init_document('xml'); + $twitter_group = $this->twitter_group_array($group); + $this->show_twitter_xml_group($twitter_group); + $this->end_document('xml'); + } + // Anyone know what date format this is? // Twitter's dates look like this: "Mon Jul 14 23:52:38 +0000 2008" -- Zach function date_twitter($dt) diff --git a/lib/util.php b/lib/util.php index d784bb793..c8e318efe 100644 --- a/lib/util.php +++ b/lib/util.php @@ -140,7 +140,7 @@ function common_have_session() function common_ensure_session() { $c = null; - if (array_key_exists(session_name, $_COOKIE)) { + if (array_key_exists(session_name(), $_COOKIE)) { $c = $_COOKIE[session_name()]; } if (!common_have_session()) { @@ -1410,20 +1410,21 @@ function common_client_ip() return null; } - if ($_SERVER['HTTP_X_FORWARDED_FOR']) { - if ($_SERVER['HTTP_CLIENT_IP']) { + if (array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER)) { + if (array_key_exists('HTTP_CLIENT_IP', $_SERVER)) { $proxy = $_SERVER['HTTP_CLIENT_IP']; } else { $proxy = $_SERVER['REMOTE_ADDR']; } $ip = $_SERVER['HTTP_X_FORWARDED_FOR']; } else { - if ($_SERVER['HTTP_CLIENT_IP']) { + $proxy = null; + if (array_key_exists('HTTP_CLIENT_IP', $_SERVER)) { $ip = $_SERVER['HTTP_CLIENT_IP']; } else { $ip = $_SERVER['REMOTE_ADDR']; } } - return array($ip, $proxy); + return array($proxy, $ip); } diff --git a/lighttpd.conf.example b/lighttpd.conf.example new file mode 100644 index 000000000..b8baafc9e --- /dev/null +++ b/lighttpd.conf.example @@ -0,0 +1,2 @@ +# Add this line to lighttpd.conf to enable pseudo-rewrites using 404s +server.error-handler-404 = "/index.php" diff --git a/plugins/FBConnect/FBConnectPlugin.php b/plugins/FBConnect/FBConnectPlugin.php index 2e32ad198..6788793b2 100644 --- a/plugins/FBConnect/FBConnectPlugin.php +++ b/plugins/FBConnect/FBConnectPlugin.php @@ -122,9 +122,7 @@ class FBConnectPlugin extends Plugin FB_RequireFeatures( ["XFBML"], function() { - FB.init("%s", "../xd_receiver.html", - {"doNotUseCachedConnectState":true }); - + FB.init("%s", "../xd_receiver.html"); } ); } @@ -222,7 +220,7 @@ class FBConnectPlugin extends Plugin try { $facebook = getFacebook(); - $fbuid = $facebook->api_client->users_getLoggedInUser(); + $fbuid = $facebook->get_loggedin_user(); } catch (Exception $e) { common_log(LOG_WARNING, diff --git a/plugins/recaptcha/LICENSE b/plugins/recaptcha/LICENSE new file mode 100644 index 000000000..b612f71f0 --- /dev/null +++ b/plugins/recaptcha/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2007 reCAPTCHA -- http://recaptcha.net +AUTHORS: + Mike Crawford + Ben Maurer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/plugins/recaptcha/README b/plugins/recaptcha/README new file mode 100644 index 000000000..3100f697e --- /dev/null +++ b/plugins/recaptcha/README @@ -0,0 +1,23 @@ +Laconica reCAPTCHA plugin 0.2 8/3/09 +==================================== +Adds a captcha to your registration page to reduce automated spam bots registering. + +Use: +1. Get an API key from http://recaptcha.net + +2. In config.php add: +include_once('plugins/recaptcha.php'); +$captcha = new recaptcha(publickey, privatekey, showErrors); + +Changelog +========= +0.1 initial release +0.2 Work around for webkit browsers + +reCAPTCHA README +================ + +The reCAPTCHA PHP Lirary helps you use the reCAPTCHA API. Documentation +for this library can be found at + + http://recaptcha.net/plugins/php diff --git a/plugins/recaptcha/recaptcha.php b/plugins/recaptcha/recaptcha.php new file mode 100644 index 000000000..5ef8352d1 --- /dev/null +++ b/plugins/recaptcha/recaptcha.php @@ -0,0 +1,106 @@ +<?php +/** + * Laconica, the distributed open-source microblogging tool + * + * Plugin to show reCaptcha when a user registers + * + * PHP version 5 + * + * LICENCE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category Plugin + * @package Laconica + * @author Eric Helgeson <erichelgeson@gmail.com> + * @copyright 2009 + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + */ + +if (!defined('LACONICA')) { + exit(1); +} + +define('RECAPTCHA', '0.2'); + +class recaptcha extends Plugin +{ + var $private_key; + var $public_key; + var $display_errors; + var $failed; + var $ssl; + + function __construct($public_key, $private_key, $display_errors=false) + { + parent::__construct(); + require_once(INSTALLDIR.'/plugins/recaptcha/recaptchalib.php'); + $this->public_key = $public_key; + $this->private_key = $private_key; + $this->display_errors = $display_errors; + } + + function checkssl(){ + if(common_config('site', 'ssl') === 'sometimes' || common_config('site', 'ssl') === 'always') { + return true; + } + return false; + } + + function onStartShowHTML($action) + { + //XXX: Horrible hack to make Safari, FF2, and Chrome work with + //reChapcha. reChapcha beaks xhtml strict + header('Content-Type: text/html'); + + $action->extraHeaders(); + + $action->startXML('html', + '-//W3C//DTD XHTML 1.0 Strict//EN', + 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'); + + $action->raw('<style type="text/css">#recaptcha_area{float:left;}</style>'); + return false; + } + + function onEndRegistrationFormData($action) + { + $action->elementStart('li'); + $action->raw('<label for="recaptcha_area">Captcha</label>'); + if($this->checkssl() === true){ + $action->raw(recaptcha_get_html($this->public_key), null, true); + } else { + $action->raw(recaptcha_get_html($this->public_key)); + } + $action->elementEnd('li'); + return true; + } + + function onStartRegistrationTry($action) + { + $resp = recaptcha_check_answer ($this->private_key, + $_SERVER["REMOTE_ADDR"], + $action->trimmed('recaptcha_challenge_field'), + $action->trimmed('recaptcha_response_field')); + + if (!$resp->is_valid) + { + if($this->display_errors) + { + $action->showForm ("(reCAPTCHA said: " . $resp->error . ")"); + } + $action->showForm("Captcha does not match!"); + return false; + } + } +} diff --git a/plugins/recaptcha/recaptchalib.php b/plugins/recaptcha/recaptchalib.php new file mode 100644 index 000000000..897c50981 --- /dev/null +++ b/plugins/recaptcha/recaptchalib.php @@ -0,0 +1,277 @@ +<?php +/* + * This is a PHP library that handles calling reCAPTCHA. + * - Documentation and latest version + * http://recaptcha.net/plugins/php/ + * - Get a reCAPTCHA API Key + * http://recaptcha.net/api/getkey + * - Discussion group + * http://groups.google.com/group/recaptcha + * + * Copyright (c) 2007 reCAPTCHA -- http://recaptcha.net + * AUTHORS: + * Mike Crawford + * Ben Maurer + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * The reCAPTCHA server URL's + */ +define("RECAPTCHA_API_SERVER", "http://api.recaptcha.net"); +define("RECAPTCHA_API_SECURE_SERVER", "https://api-secure.recaptcha.net"); +define("RECAPTCHA_VERIFY_SERVER", "api-verify.recaptcha.net"); + +/** + * Encodes the given data into a query string format + * @param $data - array of string elements to be encoded + * @return string - encoded request + */ +function _recaptcha_qsencode ($data) { + $req = ""; + foreach ( $data as $key => $value ) + $req .= $key . '=' . urlencode( stripslashes($value) ) . '&'; + + // Cut the last '&' + $req=substr($req,0,strlen($req)-1); + return $req; +} + + + +/** + * Submits an HTTP POST to a reCAPTCHA server + * @param string $host + * @param string $path + * @param array $data + * @param int port + * @return array response + */ +function _recaptcha_http_post($host, $path, $data, $port = 80) { + + $req = _recaptcha_qsencode ($data); + + $http_request = "POST $path HTTP/1.0\r\n"; + $http_request .= "Host: $host\r\n"; + $http_request .= "Content-Type: application/x-www-form-urlencoded;\r\n"; + $http_request .= "Content-Length: " . strlen($req) . "\r\n"; + $http_request .= "User-Agent: reCAPTCHA/PHP\r\n"; + $http_request .= "\r\n"; + $http_request .= $req; + + $response = ''; + if( false == ( $fs = @fsockopen($host, $port, $errno, $errstr, 10) ) ) { + die ('Could not open socket'); + } + + fwrite($fs, $http_request); + + while ( !feof($fs) ) + $response .= fgets($fs, 1160); // One TCP-IP packet + fclose($fs); + $response = explode("\r\n\r\n", $response, 2); + + return $response; +} + + + +/** + * Gets the challenge HTML (javascript and non-javascript version). + * This is called from the browser, and the resulting reCAPTCHA HTML widget + * is embedded within the HTML form it was called from. + * @param string $pubkey A public key for reCAPTCHA + * @param string $error The error given by reCAPTCHA (optional, default is null) + * @param boolean $use_ssl Should the request be made over ssl? (optional, default is false) + + * @return string - The HTML to be embedded in the user's form. + */ +function recaptcha_get_html ($pubkey, $error = null, $use_ssl = false) +{ + if ($pubkey == null || $pubkey == '') { + die ("To use reCAPTCHA you must get an API key from <a href='http://recaptcha.net/api/getkey'>http://recaptcha.net/api/getkey</a>"); + } + + if ($use_ssl) { + $server = RECAPTCHA_API_SECURE_SERVER; + } else { + $server = RECAPTCHA_API_SERVER; + } + + $errorpart = ""; + if ($error) { + $errorpart = "&error=" . $error; + } + return '<script type="text/javascript" src="'. $server . '/challenge?k=' . $pubkey . $errorpart . '"></script> + + <noscript> + <iframe src="'. $server . '/noscript?k=' . $pubkey . $errorpart . '" height="300" width="500" frameborder="0"></iframe><br/> + <textarea name="recaptcha_challenge_field" rows="3" cols="40"></textarea> + <input type="hidden" name="recaptcha_response_field" value="manual_challenge"/> + </noscript>'; +} + + + + +/** + * A ReCaptchaResponse is returned from recaptcha_check_answer() + */ +class ReCaptchaResponse { + var $is_valid; + var $error; +} + + +/** + * Calls an HTTP POST function to verify if the user's guess was correct + * @param string $privkey + * @param string $remoteip + * @param string $challenge + * @param string $response + * @param array $extra_params an array of extra variables to post to the server + * @return ReCaptchaResponse + */ +function recaptcha_check_answer ($privkey, $remoteip, $challenge, $response, $extra_params = array()) +{ + if ($privkey == null || $privkey == '') { + die ("To use reCAPTCHA you must get an API key from <a href='http://recaptcha.net/api/getkey'>http://recaptcha.net/api/getkey</a>"); + } + + if ($remoteip == null || $remoteip == '') { + die ("For security reasons, you must pass the remote ip to reCAPTCHA"); + } + + + + //discard spam submissions + if ($challenge == null || strlen($challenge) == 0 || $response == null || strlen($response) == 0) { + $recaptcha_response = new ReCaptchaResponse(); + $recaptcha_response->is_valid = false; + $recaptcha_response->error = 'incorrect-captcha-sol'; + return $recaptcha_response; + } + + $response = _recaptcha_http_post (RECAPTCHA_VERIFY_SERVER, "/verify", + array ( + 'privatekey' => $privkey, + 'remoteip' => $remoteip, + 'challenge' => $challenge, + 'response' => $response + ) + $extra_params + ); + + $answers = explode ("\n", $response [1]); + $recaptcha_response = new ReCaptchaResponse(); + + if (trim ($answers [0]) == 'true') { + $recaptcha_response->is_valid = true; + } + else { + $recaptcha_response->is_valid = false; + $recaptcha_response->error = $answers [1]; + } + return $recaptcha_response; + +} + +/** + * gets a URL where the user can sign up for reCAPTCHA. If your application + * has a configuration page where you enter a key, you should provide a link + * using this function. + * @param string $domain The domain where the page is hosted + * @param string $appname The name of your application + */ +function recaptcha_get_signup_url ($domain = null, $appname = null) { + return "http://recaptcha.net/api/getkey?" . _recaptcha_qsencode (array ('domain' => $domain, 'app' => $appname)); +} + +function _recaptcha_aes_pad($val) { + $block_size = 16; + $numpad = $block_size - (strlen ($val) % $block_size); + return str_pad($val, strlen ($val) + $numpad, chr($numpad)); +} + +/* Mailhide related code */ + +function _recaptcha_aes_encrypt($val,$ky) { + if (! function_exists ("mcrypt_encrypt")) { + die ("To use reCAPTCHA Mailhide, you need to have the mcrypt php module installed."); + } + $mode=MCRYPT_MODE_CBC; + $enc=MCRYPT_RIJNDAEL_128; + $val=_recaptcha_aes_pad($val); + return mcrypt_encrypt($enc, $ky, $val, $mode, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"); +} + + +function _recaptcha_mailhide_urlbase64 ($x) { + return strtr(base64_encode ($x), '+/', '-_'); +} + +/* gets the reCAPTCHA Mailhide url for a given email, public key and private key */ +function recaptcha_mailhide_url($pubkey, $privkey, $email) { + if ($pubkey == '' || $pubkey == null || $privkey == "" || $privkey == null) { + die ("To use reCAPTCHA Mailhide, you have to sign up for a public and private key, " . + "you can do so at <a href='http://mailhide.recaptcha.net/apikey'>http://mailhide.recaptcha.net/apikey</a>"); + } + + + $ky = pack('H*', $privkey); + $cryptmail = _recaptcha_aes_encrypt ($email, $ky); + + return "http://mailhide.recaptcha.net/d?k=" . $pubkey . "&c=" . _recaptcha_mailhide_urlbase64 ($cryptmail); +} + +/** + * gets the parts of the email to expose to the user. + * eg, given johndoe@example,com return ["john", "example.com"]. + * the email is then displayed as john...@example.com + */ +function _recaptcha_mailhide_email_parts ($email) { + $arr = preg_split("/@/", $email ); + + if (strlen ($arr[0]) <= 4) { + $arr[0] = substr ($arr[0], 0, 1); + } else if (strlen ($arr[0]) <= 6) { + $arr[0] = substr ($arr[0], 0, 3); + } else { + $arr[0] = substr ($arr[0], 0, 4); + } + return $arr; +} + +/** + * Gets html to display an email address given a public an private key. + * to get a key, go to: + * + * http://mailhide.recaptcha.net/apikey + */ +function recaptcha_mailhide_html($pubkey, $privkey, $email) { + $emailparts = _recaptcha_mailhide_email_parts ($email); + $url = recaptcha_mailhide_url ($pubkey, $privkey, $email); + + return htmlentities($emailparts[0]) . "<a href='" . htmlentities ($url) . + "' onclick=\"window.open('" . htmlentities ($url) . "', '', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=500,height=300'); return false;\" title=\"Reveal this e-mail address\">...</a>@" . htmlentities ($emailparts [1]); + +} + + +?> diff --git a/scripts/sessiongc.php b/scripts/sessiongc.php new file mode 100644 index 000000000..314b641eb --- /dev/null +++ b/scripts/sessiongc.php @@ -0,0 +1,36 @@ +#!/usr/bin/env php +<?php +/* + * Laconica - a distributed open-source microblogging tool + * Copyright (C) 2008, 2009, Control Yourself, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); + +$helptext = <<<END_OF_GC_HELP +sessiongc.php + +Delete old sessions from the server + +END_OF_GC_HELP; + +require_once INSTALLDIR.'/scripts/commandline.inc'; + +$maxlifetime = ini_get('session.gc_maxlifetime'); + +print "Deleting sessions older than $maxlifetime seconds.\n"; + +Session::gc($maxlifetime); |