summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSarven Capadisli <csarven@controlyourself.ca>2009-08-04 15:53:15 +0000
committerSarven Capadisli <csarven@controlyourself.ca>2009-08-04 15:53:15 +0000
commitd0a020dd4ea8d58bc8230ebcde22e54ad31894a8 (patch)
treec7d14f1e09bd46c72e577ca54645a7bab4a747e7
parent5f3af3e121a7ff06f5961bb4158e0b777ce5b5c1 (diff)
parentffa1d662a759a729151f2444bdf759749d59045e (diff)
Merge branch '0.8.x' of git@gitorious.org:laconica/mainline into 0.8.x
-rw-r--r--.gitignore2
-rw-r--r--actions/api.php1
-rw-r--r--actions/conversation.php2
-rw-r--r--actions/twitapifavorites.php26
-rw-r--r--actions/twitapigroups.php31
-rw-r--r--actions/twitapitags.php5
-rw-r--r--classes/File.php5
-rw-r--r--classes/Session.php17
-rw-r--r--db/074to080_pg.sql108
-rw-r--r--index.php19
-rw-r--r--install.php2
-rw-r--r--js/util.js13
-rw-r--r--lib/profilesection.php2
-rw-r--r--lib/twitterapi.php49
-rw-r--r--lib/util.php11
-rw-r--r--lighttpd.conf.example2
-rw-r--r--plugins/FBConnect/FBConnectPlugin.php6
-rw-r--r--plugins/recaptcha/LICENSE22
-rw-r--r--plugins/recaptcha/README23
-rw-r--r--plugins/recaptcha/recaptcha.php106
-rw-r--r--plugins/recaptcha/recaptchalib.php277
-rw-r--r--scripts/sessiongc.php36
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
diff --git a/index.php b/index.php
index a73983b59..5f13064da 100644
--- a/index.php
+++ b/index.php
@@ -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 = "&amp;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);