├── 2010 ├── 01 │ ├── sample.txt │ ├── another_sample.txt │ ├── sample.comments │ ├── sample.html │ └── another_sample.html └── 02 │ ├── footnote.txt │ └── footnote.html ├── robots.txt ├── images ├── openid.png └── credits.md ├── cgi ├── Lingua │ ├── Stem.pod │ ├── test.pl │ └── Stem │ │ ├── AutoLoader.pm │ │ ├── De.pm │ │ ├── Pt.pm │ │ ├── Gl.pm │ │ ├── Sv.pm │ │ ├── No.pm │ │ ├── Da.pm │ │ └── EnBroken.pm ├── vector_index ├── .htaccess ├── mmd2web ├── archives_title.cgi ├── tagmap.xslt ├── pages_in_category.cgi ├── MultiMarkdownCMS.pm ├── map_my_site.pl ├── Net │ └── OpenID │ │ ├── Yadis │ │ └── Service.pm │ │ ├── URIFetch.pm │ │ ├── Consumer │ │ └── Lite.pm │ │ ├── Association.pm │ │ ├── IndirectMessage.pm │ │ ├── VerifiedIdentity.pm │ │ └── ClaimedIdentity.pm ├── query_site.cgi ├── openid_send.cgi ├── tagmap.cgi ├── similar_pages.cgi ├── comments.cgi ├── atom.cgi ├── latest_stories.cgi ├── submit_comment.cgi ├── google_sitemap.cgi ├── tags.cgi ├── atom-comments.cgi ├── openid_response.cgi ├── XSLT │ └── xhtml-static-site.xslt ├── archives.cgi ├── commenter.js ├── accept_comment.cgi ├── TagCategorizer.pl └── Crypt │ └── DH.pm ├── category ├── sub-category │ ├── index.txt │ └── index.html ├── index.txt └── index.html ├── templates ├── header.html ├── tagmap.html ├── archives.html ├── tags.html ├── search.html ├── sidebar.html ├── footer.html ├── head.html └── .htaccess ├── index.txt ├── index.html ├── Makefile ├── css ├── main.css ├── layout.css └── less │ └── layout.less ├── notfound.html └── .htaccess /robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / 3 | 4 | -------------------------------------------------------------------------------- /images/openid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fletcher/MultiMarkdown-CMS/HEAD/images/openid.png -------------------------------------------------------------------------------- /cgi/Lingua/Stem.pod: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fletcher/MultiMarkdown-CMS/HEAD/cgi/Lingua/Stem.pod -------------------------------------------------------------------------------- /2010/01/sample.txt: -------------------------------------------------------------------------------- 1 | Title: Sample posting 2 | Date: 01/31/2010 15:21:06 3 | Tags: MultiMarkdown 4 | 5 | This is a sample post. 6 | 7 | -------------------------------------------------------------------------------- /2010/01/another_sample.txt: -------------------------------------------------------------------------------- 1 | Title: Another Sample posting 2 | Date: 01/31/2010 15:59:56 3 | Tags: MultiMarkdown 4 | 5 | Another sample post. -------------------------------------------------------------------------------- /images/credits.md: -------------------------------------------------------------------------------- 1 | Title: Image credits 2 | 3 | 4 | The [OpenID](http://openid.net/) logo was designed by [Randy Reddig](http://ydnar.com/). 5 | 6 | 7 | -------------------------------------------------------------------------------- /2010/01/sample.comments: -------------------------------------------------------------------------------- 1 | AUTHOR: Fletcher 2 | URL: http://fletcherpenney.net/ 3 | DATE: 01/31/2010 15:52:27 4 | COMMENT: 5 |

Sample comment.

6 | 7 | 8 | -------------------------------------------------------------------------------- /category/sub-category/index.txt: -------------------------------------------------------------------------------- 1 | Title: Sub Category 2 | Date: 02/03/2010 14:51:09 3 | Tags: 4 | 5 | 6 | This page is a "child" of the top-level Category [page](category). -------------------------------------------------------------------------------- /category/index.txt: -------------------------------------------------------------------------------- 1 | Title: Test Category 2 | Date: 02/03/2010 14:51:09 3 | Tags: 4 | 5 | 6 | This pages represents a "category". There should be an entry below for a page 7 | in a subfolder. -------------------------------------------------------------------------------- /templates/header.html: -------------------------------------------------------------------------------- 1 |
2 | Home 3 |
4 | 5 |
6 | 7 |
8 | 9 | -------------------------------------------------------------------------------- /cgi/Lingua/test.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use Lingua::Stem qw (stem); 4 | my @words = qw(a list of words to be stemmed for testing purposes); 5 | my $stemmed_words = stem(@words); 6 | print join("\n",@$stemmed_words); 7 | 8 | -------------------------------------------------------------------------------- /index.txt: -------------------------------------------------------------------------------- 1 | Title: MultiMarkdown-based CMS 2 | Tags: 3 | 4 | This is the default page for the MultiMarkdown-based Content Management 5 | System... 6 | 7 | Learn more at . 8 | 9 | -------------------------------------------------------------------------------- /templates/tagmap.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Tag Map 5 | 6 | 7 | 8 | 9 | 10 |

Tag Map

11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /templates/archives.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <!--#include virtual="../cgi/archives_title.cgi" --> 5 | 6 | 7 | 8 | 9 | 10 |

11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /2010/02/footnote.txt: -------------------------------------------------------------------------------- 1 | Title: Footnote test 2 | Date: 02/15/2010 20:16:15 3 | Tags: 4 | 5 | 6 | 7 | This is a test footnote [^footnote]. The link to the footnote will work with 8 | the "advanced" branch of MMD-CMS, but not the regular ("master") branch. 9 | 10 | If this is a problem, I suggest getting the regular version working, and then 11 | upgrading to the advanced version (which will possibly require a change to 12 | your web server setup). 13 | 14 | 15 | [^footnote]: This is the footnote. -------------------------------------------------------------------------------- /templates/tags.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Tags 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 | 15 | 16 | 17 |
18 | 19 | 21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /templates/search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Search Results 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /templates/sidebar.html: -------------------------------------------------------------------------------- 1 | 2 | 30 | -------------------------------------------------------------------------------- /templates/footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 18 | 19 | -------------------------------------------------------------------------------- /2010/01/sample.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Sample posting 7 | 8 | 9 | 10 |

Sample posting

11 |
01/31/2010 15:21:06
12 |

This is a sample post.

13 | 14 | 15 | -------------------------------------------------------------------------------- /category/sub-category/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Sub Category 7 | 8 | 9 | 10 |

Sub Category

11 |
02/03/2010 14:51:09
12 |

This page is a “child” of the top-level Category page.

13 | 14 | 15 | -------------------------------------------------------------------------------- /2010/01/another_sample.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Another Sample posting 7 | 8 | 9 | 10 |

Another Sample posting

11 |
01/31/2010 15:59:56
12 |

Another sample post.

13 | 14 | 15 | -------------------------------------------------------------------------------- /category/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Test Category 7 | 8 | 9 | 10 |

Test Category

11 |
02/03/2010 14:51:09
12 |

This pages represents a “category”. There should be an entry below for a page 13 | in a subfolder.

14 | 15 | 16 | -------------------------------------------------------------------------------- /templates/head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | :///"/> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | MultiMarkdown-based CMS 7 | 8 | 9 |

MultiMarkdown-based CMS

10 |

This is the default page for the MultiMarkdown-based Content Management 11 | System…

12 | 13 |

Learn more at http://fletcherpenney.net/multimarkdown/cms/.

14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for MultiMarkdown-CMS based wikis 2 | # Thanks to Dr. Drang for inspiring me to use make: 3 | # http://www.leancrew.com/all-this/2008/06/my-no-server-personal-wiki—part-3/ 4 | 5 | 6 | # 7 | # NOTE: MultiMarkdown 3.0 must be installed for this to work. 8 | #s 9 | 10 | srcfiles := $(filter-out cgi/* templates/* css/* images/* robots.txt, $(wildcard *.txt */*.txt */*/*.txt */*/*/*.txt)) 11 | 12 | htmlfiles := $(patsubst %.txt, %.html, $(srcfiles)) 13 | 14 | templates := $(wildcard templates/*.html) 15 | 16 | 17 | all: $(htmlfiles) cgi/vector_index 18 | 19 | 20 | cgi/vector_index: $(htmlfiles) 21 | cd cgi; ./map_my_site.pl > vector_index 22 | 23 | 24 | %.html: %.txt # $(templates) 25 | cgi/mmd2web $*.txt 26 | chmod 755 $*.html 27 | 28 | 29 | clean: 30 | rm $(htmlfiles) 31 | 32 | 33 | fast: $(htmlfiles) 34 | -------------------------------------------------------------------------------- /templates/.htaccess: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2010 Fletcher T. Penney 2 | # 3 | # This program is free software; you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation; either version 2 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the 15 | # Free Software Foundation, Inc. 16 | # 59 Temple Place, Suite 330 17 | # Boston, MA 02111-1307 USA 18 | 19 | Options +Includes -------------------------------------------------------------------------------- /cgi/vector_index: -------------------------------------------------------------------------------- 1 | /2010/01/another_sample.html /2010/01/sample.html 1 2 | /2010/01/another_sample.html /2010/02/footnote.html 0.101360606759923 3 | /2010/01/another_sample.html /category/index.html 0.21650635094611 4 | /2010/01/another_sample.html /category/sub-category/index.html 0.231455024943138 5 | /2010/01/another_sample.html /index.html 0.132453235706504 6 | /2010/01/sample.html /2010/02/footnote.html 0.101360606759923 7 | /2010/01/sample.html /category/index.html 0.21650635094611 8 | /2010/01/sample.html /category/sub-category/index.html 0.231455024943138 9 | /2010/01/sample.html /index.html 0.132453235706504 10 | /2010/02/footnote.html /category/index.html 0.146301433995163 11 | /2010/02/footnote.html /category/sub-category/index.html 0.0938416870634782 12 | /2010/02/footnote.html /index.html 0.0537021613541055 13 | /category/index.html /category/sub-category/index.html 0.734968415259167 14 | /category/index.html /index.html 0.229415733870562 15 | /category/sub-category/index.html /index.html 0.245255735793986 16 | -------------------------------------------------------------------------------- /cgi/.htaccess: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2010 Fletcher T. Penney 2 | # 3 | # This program is free software; you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation; either version 2 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the 15 | # Free Software Foundation, Inc. 16 | # 59 Temple Place, Suite 330 17 | # Boston, MA 02111-1307 USA 18 | 19 | 20 | Options +ExecCGI 21 | 22 | # Block access to .pl files (local use only) 23 | 24 | Order allow,deny 25 | Deny from all 26 | 27 | -------------------------------------------------------------------------------- /css/main.css: -------------------------------------------------------------------------------- 1 | /* CSS System 2 | 3 | Copyright (c) 2010 Fletcher T. Penney 4 | 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | 20 | @import "layout.css"; /* Import layout code */ 21 | 22 | /* Default Settings */ 23 | 24 | textarea[name="text"] { 25 | width:95%; 26 | } 27 | textarea[name="summary"] { 28 | width:100%; 29 | } 30 | input[type="text"] { 31 | display: block; 32 | } 33 | 34 | 35 | /* Insert your CSS here... */ 36 | 37 | -------------------------------------------------------------------------------- /notfound.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Page Not Found 6 | 7 | 8 | 9 |

Notice

10 | 11 |

If you're using Internet Explorer to visit this site and are having 12 | difficulty navigating, I suggest you try using a real browser instead. I've 13 | given up putting so much effort into making this site compatible with a crappy 14 | browser....

15 | 16 | 24 | 28 | 29 | 30 |

Alternative Links

31 | 32 |

Back to the home page

33 | 34 | 35 | -------------------------------------------------------------------------------- /2010/02/footnote.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Footnote test 7 | 8 | 9 | 10 |

Footnote test

11 |
02/15/2010 20:16:15
12 |

This is a test footnote [1]. The link to the footnote will work with 13 | the “advanced” branch of MMD-CMS, but not the regular (“master”) branch.

14 | 15 |

If this is a problem, I suggest getting the regular version working, and then 16 | upgrading to the advanced version (which will possibly require a change to 17 | your web server setup).

18 | 19 |
20 |
21 |
    22 | 23 |
  1. 24 |

    This is the footnote.  ↩

    25 |
  2. 26 | 27 |
28 |
29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /cgi/mmd2web: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # mmd2web --- MultiMarkdown convenience script 4 | # 5 | # Copyright (c) 2010-2011 Fletcher T. Penney 6 | # 7 | # Pass arguments on to the binary to convert text to XHTML 8 | # and post-process using XSLT for MultiMarkdown-CMS 9 | # 10 | 11 | # Requires that MMD 3.0 be installed 12 | # Modify the next command if MMD is not installed in: 13 | # /usr/local/bin/multimarkdown 14 | 15 | # Be sure to include multimarkdown in our PATH 16 | export PATH="/usr/local/bin:$PATH" 17 | 18 | 19 | # Locate XSLT directory 20 | xslt_path=`dirname "$0"` 21 | 22 | 23 | if [ $# = 0 ] 24 | then 25 | # No arguments, so use stdin/stdout 26 | 27 | # Need a temporary file 28 | file_name=`mktemp mmdtempXXXX.txt` 29 | 30 | cat > $file_name 31 | 32 | # Determine stylesheet to use 33 | mode="xhtml-static-site.xslt" 34 | 35 | multimarkdown "$file_name" | xsltproc -nonet -novalid $xslt_path/XSLT/$mode - 36 | shift 37 | 38 | rm $file_name 39 | else 40 | until [ "$*" = "" ] 41 | do 42 | # process each argument separately 43 | file_name=`echo $1| sed 's/\.[^.]*$//'` 44 | 45 | # Determine stylesheet to use 46 | mode="xhtml-static-site.xslt" 47 | 48 | multimarkdown "$1" | xsltproc -nonet -novalid $xslt_path/XSLT/$mode - > "$file_name.html" 49 | shift 50 | done 51 | fi 52 | 53 | 54 | -------------------------------------------------------------------------------- /cgi/archives_title.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # Copyright (C) 2010 Fletcher T. Penney 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the 17 | # Free Software Foundation, Inc. 18 | # 59 Temple Place, Suite 330 19 | # Boston, MA 02111-1307 USA 20 | 21 | print "Content-type: text/html\n\n"; 22 | 23 | # Where am I called from 24 | my $search_path = $ENV{REQUEST_URI}; 25 | 26 | $search_path =~ /(\d\d\d\d)(?:.*?(\d\d))?/; 27 | my $year = $1; 28 | my $month = $2; 29 | 30 | my @months = qw(January February March April May June July August 31 | September October November December); 32 | 33 | 34 | my $title = ""; 35 | 36 | if ($month ne "") { 37 | $title = "$months[$month-1] "; 38 | } 39 | 40 | $title .= "$year Archives"; 41 | 42 | print $title; -------------------------------------------------------------------------------- /css/layout.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | border: 0; 5 | min-width: 60em; 6 | text-align: center; 7 | } 8 | div { text-align: left; } 9 | .header { 10 | background-position: -10px 0px; 11 | position: relative; 12 | padding: 10px 10px 0px 10px; 13 | max-width: 60em; 14 | margin: 1.75em auto 0em auto; 15 | } 16 | .wrapper { 17 | position: relative; 18 | width: 100%; 19 | min-width: 59em; 20 | clear: both; 21 | overflow: hidden; 22 | max-width: 59em; 23 | margin: 0 auto; 24 | margin-bottom: 1.75em; 25 | } 26 | .innerwrapper { 27 | float: left; 28 | width: 200%; 29 | position: relative; 30 | } 31 | .contentwrapper { 32 | position: relative; 33 | padding-bottom: 1em; 34 | } 35 | .content { 36 | max-width: 35em; 37 | overflow: hidden; 38 | margin: 0 2.5em 0 16.5em; 39 | } 40 | .sidebar { 41 | width: 14em; 42 | position: relative; 43 | } 44 | .footer { 45 | clear: both; 46 | float: left; 47 | width: 100%; 48 | min-width: 59em; 49 | font-size: 0.75em; 50 | } 51 | .rightmenu .innerwrapper { 52 | margin-left: -14em; 53 | right: 100%; 54 | } 55 | .rightmenu .contentwrapper { 56 | float: left; 57 | left: 50%; 58 | } 59 | .rightmenu .sidebar { 60 | float: right; 61 | left: 11.5em; 62 | } 63 | .leftmenu .innerwrapper { left: 19em; } 64 | .leftmenu .contentwrapper { 65 | float: right; 66 | right: 24em; 67 | } 68 | .leftmenu .content { 69 | position: relative; 70 | right: 100%; 71 | } 72 | .leftmenu .sidebar { 73 | float: left; 74 | right: 16.5em; 75 | } 76 | -------------------------------------------------------------------------------- /cgi/tagmap.xslt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 22 | 23 | 24 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
    36 | 37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 |
  • 46 | 47 | 48 |
      49 | 50 |
    51 |
    52 |
  • 53 |
    54 | 55 | 56 |
    -------------------------------------------------------------------------------- /cgi/pages_in_category.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # Copyright (C) 2010 Fletcher T. Penney 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the 17 | # Free Software Foundation, Inc. 18 | # 59 Temple Place, Suite 330 19 | # Boston, MA 02111-1307 USA 20 | 21 | use MultiMarkdownCMS; 22 | my $debug = 0; # Enables extra output for debugging 23 | 24 | 25 | print "Content-type: text/html\n\n"; 26 | 27 | 28 | # Get commonly needed paths 29 | my ($site_root, $requested_url, $document_url) 30 | = MultiMarkdownCMS::getHostingPaths($0); 31 | 32 | # Debugging aid 33 | print qq{ 34 | Site root directory: $site_root
    35 | Request: $requested_url
    36 | Document: $document_url
    37 | } if $debug; 38 | 39 | 40 | # Don't do this on the home page 41 | if ($requested_url =~ /^\/?$/) { 42 | exit; 43 | } 44 | 45 | 46 | # We want to search only in the current directory 47 | 48 | $search_path = $site_root . $requested_url; 49 | 50 | 51 | local $/; 52 | 53 | my $content = ""; 54 | 55 | foreach my $filepath (glob("$search_path*/index.html")) { 56 | open (FILE, "<$filepath"); 57 | my $data = ; 58 | if ($data =~ /

    (.*)<\/h1>/) { 59 | my $title = $1; 60 | $filepath =~ /$site_root\/(.*\/)index.html/; 61 | $content .= "
  • $title
  • \n"; 62 | } 63 | } 64 | 65 | if ($content ne "") { 66 | print qq{
      67 | $content 68 |
    69 | }; 70 | } 71 | -------------------------------------------------------------------------------- /cgi/MultiMarkdownCMS.pm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # Copyright (C) 2010 Fletcher T. Penney 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the 17 | # Free Software Foundation, Inc. 18 | # 59 Temple Place, Suite 330 19 | # Boston, MA 02111-1307 USA 20 | 21 | # 22 | # This package provides certain utility functions to minimize redundant 23 | # code, and to ensure consistency when updating. 24 | # 25 | 26 | package MultiMarkdownCMS; 27 | 28 | use strict; 29 | #use warnings; 30 | use File::Basename; 31 | use Cwd 'abs_path'; 32 | 33 | sub getHostingPaths { 34 | # Figure out where the URL's and directories for this request actually are 35 | 36 | # Given path to calling script, find the parent of the "cgi" directory 37 | # Assumes calling script is located in "root"/cgi/ 38 | my $site_root = shift; 39 | $site_root = dirname($site_root); 40 | $site_root = abs_path($site_root); 41 | $site_root =~ s/\/cgi$//; 42 | 43 | # Figure out the requested URI as a relative link 44 | (my $request = "/" . $ENV{REQUEST_URI}); 45 | $request =~ s/$ENV{Base_URL}// if ($ENV{Base_URL} ne ""); 46 | $request =~ s/\/\//\//g; 47 | 48 | # Figure out the implicit filepath with extensions, etc 49 | (my $document = "/" . $ENV{DOCUMENT_URI}); 50 | $document =~ s/$ENV{Base_URL}// if ($ENV{Base_URL} ne ""); 51 | $document =~ s/\/\//\//g; 52 | 53 | 54 | return ($site_root, $request, $document); 55 | } 56 | 57 | 58 | 1; -------------------------------------------------------------------------------- /cgi/map_my_site.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # Copyright (C) 2010 Fletcher T. Penney 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the 17 | # Free Software Foundation, Inc. 18 | # 59 Temple Place, Suite 330 19 | # Boston, MA 02111-1307 USA 20 | 21 | use strict; 22 | use warnings; 23 | use VectorMap; 24 | use File::Find; 25 | use MultiMarkdownCMS; 26 | 27 | my $debug = 0; # Enables extra output for debugging 28 | 29 | my $map = VectorMap->new(); 30 | 31 | # Get commonly needed paths 32 | my ($site_root, $requested_url, $document_url) 33 | = MultiMarkdownCMS::getHostingPaths($0); 34 | 35 | # Debugging aid 36 | print qq{ 37 | Site root directory: $site_root
    38 | Request: $requested_url
    39 | Document: $document_url
    40 | } if $debug; 41 | 42 | # Index all documents 43 | find(\&find_pages, $site_root); 44 | 45 | # Iterate through objects and calculate similarites 46 | my %matrix = $map->map_relationships(); 47 | 48 | # Display results 49 | foreach my $a (sort keys %matrix) { 50 | foreach my $b (sort keys %{$matrix{$a}}) { 51 | my ($a1,$b1); 52 | for (($a1,$b1) = ($a,$b)) {s/(\A$site_root|\.txt$)//g; $_.=".html"}; 53 | print "$a1\t$b1\t" . $matrix{$a}{$b} . "\n"; 54 | } 55 | } 56 | 57 | sub find_pages { 58 | # We're looking for .txt files 59 | my $filepath = $File::Find::name; 60 | if ($filepath !~ /^\/cgi\/|robots\.txt/) { 61 | $map->add_file($filepath) if ($filepath =~ /\.txt$/); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /cgi/Net/OpenID/Yadis/Service.pm: -------------------------------------------------------------------------------- 1 | 2 | package Net::OpenID::Yadis::Service; 3 | 4 | use strict; 5 | use warnings; 6 | 7 | sub URI { Net::OpenID::Yadis::_pack_array(shift->{'URI'}) } 8 | sub Type { Net::OpenID::Yadis::_pack_array(shift->{'Type'}) } 9 | sub priority { shift->{'priority'} } 10 | 11 | sub extra_field { 12 | my $self = shift; 13 | my ($field,$xmlns) = @_; 14 | $xmlns and $field = "\{$xmlns\}$field"; 15 | $self->{$field}; 16 | } 17 | 18 | 1; 19 | __END__ 20 | 21 | =head1 NAME 22 | 23 | Net::OpenID::Yadis::Service - Class representing an XRDS Service element 24 | 25 | =head1 SYNOPSIS 26 | 27 | use Net::OpenID::Yadis; 28 | my $disc = Net::OpenID::Yadis->new(); 29 | my @xrd = $disc->discover("http://id.example.com/") or Carp::croak($disc->err); 30 | 31 | foreach my $srv (@xrd) { # Loop for Each Service in Yadis Resourse Descriptor 32 | print $srv->priority; # Service priority (sorted) 33 | print $srv->Type; # Identifier of some version of some service (scalar, array or array ref) 34 | print $srv->URI; # URI that resolves to a resource providing the service (scalar, array or array ref) 35 | print $srv->extra_field("Delegate","http://openid.net/xmlns/1.0"); 36 | # Extra field of some service 37 | } 38 | 39 | =head1 DESCRIPTION 40 | 41 | After L performs discovery, the result is a list 42 | of instances of this class. 43 | 44 | =head1 METHODS 45 | 46 | =over 4 47 | 48 | =item $srv->B 49 | 50 | The priority value for the service. 51 | 52 | =item $srv->B 53 | 54 | The URI representing the kind of service provided at the endpoint for this record. 55 | 56 | =item $srv->B 57 | 58 | The URI of the service endpoint. 59 | 60 | =item $srv->B( $fieldname , $namespace ) 61 | 62 | Fetch the value of extension fields not provided directly by this class. 63 | 64 | If C<$namespace> is not specified, the default is the namespace whose name is the empty string. 65 | 66 | =head1 COPYRIGHT, WARRANTY, AUTHOR 67 | 68 | See L for author, copyrignt and licensing information. 69 | 70 | =head1 SEE ALSO 71 | 72 | L 73 | 74 | Yadis website: L 75 | -------------------------------------------------------------------------------- /cgi/query_site.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # Copyright (C) 2010 Fletcher T. Penney 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the 17 | # Free Software Foundation, Inc. 18 | # 59 Temple Place, Suite 330 19 | # Boston, MA 02111-1307 USA 20 | 21 | use strict; 22 | use warnings; 23 | use VectorMap; 24 | use File::Find; 25 | use MultiMarkdownCMS; 26 | use CGI; 27 | 28 | my $debug = 0; # Enables extra output for debugging 29 | 30 | my $map = VectorMap->new(); 31 | my $cgi = CGI::new(); 32 | my $query = $cgi->param("query") || "test query multimarkdown"; 33 | #$query = $cgi->path_info; 34 | 35 | print $cgi->header(); 36 | my $content = ""; 37 | 38 | 39 | # Get commonly needed paths 40 | my ($site_root, $requested_url, $document_url) 41 | = MultiMarkdownCMS::getHostingPaths($0); 42 | 43 | # Debugging aid 44 | print qq{ 45 | Site root directory: $site_root
    46 | Request: $requested_url
    47 | Document: $document_url
    48 | } if $debug; 49 | 50 | 51 | # Index all documents 52 | find(\&find_pages, $site_root); 53 | 54 | # Iterate through objects and calculate similarites 55 | $map->add_object('query',$query); 56 | my %matrix = $map->query_map('query'); 57 | 58 | # Display results 59 | foreach my $a (sort { $matrix{$b} <=> $matrix{$a}} keys %matrix) { 60 | next if ($matrix{$a} == 0); 61 | my $title =""; 62 | local $/; 63 | if (open (FILE, "<$a")) { 64 | my $data = ; 65 | close FILE; 66 | $data =~ /Title:\s*(.*?)\n/; 67 | $title = $1; 68 | } 69 | (my $a1 = $a) =~ s/(\A$site_root|(\/index)?\.txt$)//g; 70 | $content .= "
  • $title: $matrix{$a}
  • \n"; 71 | } 72 | 73 | print "

    Matches for \"$query\"

    "; 74 | 75 | if ($content) { 76 | print qq{ 77 |
      78 | $content 79 |
    }; 80 | } else { 81 | print "

    No matches...

    "; 82 | } 83 | 84 | 85 | sub find_pages { 86 | # We're looking for .txt files 87 | my $filepath = $File::Find::name; 88 | $map->add_file($filepath) if ($filepath =~ /\.txt$/); 89 | } 90 | -------------------------------------------------------------------------------- /cgi/openid_send.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # Based on code in Net::OpenID::Consumer by Brad Fitzpatrick 4 | # 5 | # My changes Copyright (C) 2010 Fletcher T. Penney 6 | # 7 | # 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program; if not, write to the 20 | # Free Software Foundation, Inc. 21 | # 59 Temple Place, Suite 330 22 | # Boston, MA 02111-1307 USA 23 | 24 | use warnings; 25 | use strict; 26 | use Net::OpenID::Consumer; 27 | use LWP::UserAgent; 28 | use CGI; 29 | use CGI::Carp 'fatalsToBrowser'; 30 | my $cgi = CGI::new(); 31 | my $openid = $cgi->param("openid"); 32 | 33 | 34 | # Do a quickie check of the input 35 | fail ('No OpenID') if ! $openid; 36 | fail ('Bad OpenID') if $openid =~ /[^a-z0-9\._:\/-]/i; 37 | 38 | # Workaround for a known problem with myopenid. Change "http" to "https". 39 | $openid =~ s@(^http://|^(?!https))@https://@ if $openid =~ /myopenid/; 40 | 41 | my $csr = Net::OpenID::Consumer->new ( 42 | # The user agent which sends the openid off to the server. 43 | ua => LWP::UserAgent->new, 44 | # Who we are. 45 | required_root => "http://$ENV{HTTP_HOST}/", 46 | # Our password. 47 | consumer_secret => 'enter your password here', 48 | ); 49 | 50 | my $claimed_id = $csr->claimed_identity($openid); 51 | 52 | if ($claimed_id) { 53 | my $prior_page = "$ENV{HTTP_REFERER}"; 54 | $prior_page =~ s/^https?:\/\/.*?\///; 55 | 56 | $claimed_id->set_extension_args( 57 | 'http://openid.net/extensions/sreg/1.1', 58 | { 59 | optional => 'email,fullname,nickname', 60 | policy_url => 'http://example.com/privacypolicy.html', 61 | }, 62 | ); 63 | 64 | my $check_url = $claimed_id->check_url ( 65 | # The place we go back to. 66 | return_to => "http://$ENV{HTTP_HOST}$ENV{Base_URL}/cgi/openid_response.cgi?referer=$prior_page;", 67 | # Having this simplifies the login process. 68 | trust_root => "http://$ENV{HTTP_HOST}/", 69 | delayed_return => 1, 70 | ); 71 | # Automatically redirect the user to the endpoint. 72 | print $cgi->redirect ($check_url); 73 | } else { 74 | fail ("claimed_identity for '$openid' failed: ".$csr->errcode()); 75 | } 76 | 77 | exit 0; 78 | 79 | # Simple error handler 80 | 81 | sub fail 82 | { 83 | my ($message) = @_; 84 | print $cgi->header, $cgi->start_html, $message, $cgi->end_html; 85 | exit 0; 86 | } 87 | -------------------------------------------------------------------------------- /cgi/tagmap.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # Copyright (C) 2010 Fletcher T. Penney 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the 17 | # Free Software Foundation, Inc. 18 | # 59 Temple Place, Suite 330 19 | # Boston, MA 02111-1307 USA 20 | 21 | 22 | use CGI; 23 | use File::Find; 24 | use MultiMarkdownCMS; 25 | 26 | 27 | my $debug = 0; # Enables extra output for debugging 28 | 29 | 30 | my $cgi = CGI::new(); 31 | 32 | my $content = ""; 33 | 34 | 35 | #(my $request = $ENV{REQUEST_URI}) =~ s/$ENV{Base_URL}//; 36 | 37 | #(my $site_root = $ENV{SCRIPT_FILENAME} . $request ) =~ s/\/cgi\/.*?\//\//; 38 | #$site_root =~ s/\/?tags//; 39 | #$site_root =~ s/\/*$//; 40 | 41 | #my $search_path; 42 | ##if ($ENV{Base_URL}) { 43 | # # We're called from Apache# 44 | # $search_path = $site_root; 45 | #} else {# 46 | # # We're called from the command line 47 | # my $me = $0; 48 | # $me = dirname($me); 49 | ## ($search_path = $me) =~ s/\/cgi$//; 50 | #} 51 | 52 | 53 | print "Content-type: text/html\n\n"; 54 | 55 | # Get commonly needed paths 56 | my ($site_root, $requested_url, $document_url) 57 | = MultiMarkdownCMS::getHostingPaths($0); 58 | 59 | # Debugging aid 60 | print qq{ 61 | Site root directory: $site_root
    62 | Request: $requested_url
    63 | Document: $document_url
    64 | } if $debug; 65 | 66 | 67 | my $content = "\n"; 68 | 69 | # Index all documents 70 | find(\&find_pages, $site_root); 71 | $content .= "\n"; 72 | 73 | 74 | 75 | open (TagCat, "| ./TagCategorizer.pl | xsltproc -nonet -novalid tagmap.xslt -"); 76 | print TagCat $content; 77 | close TagCat; 78 | 79 | 80 | 81 | sub find_pages { 82 | # We're looking for .html files 83 | my $filepath = $File::Find::name; 84 | 85 | if ($filepath =~ /\.html$/) { 86 | local $/; 87 | if (open (FILE, "<$filepath")) { 88 | my $data = ; 89 | close FILE; 90 | $content .= "$filepath\n"; 91 | if ($data =~ / 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the 17 | # Free Software Foundation, Inc. 18 | # 59 Temple Place, Suite 330 19 | # Boston, MA 02111-1307 USA 20 | 21 | use MultiMarkdownCMS; 22 | 23 | 24 | 25 | # Configuration 26 | 27 | $threshold = 0.25; # Minimum relatedness score (0...1) 28 | $max_matches = 5; # Maximum related pages to show 29 | my $debug = 0; # Enables extra output for debugging 30 | 31 | 32 | print "Content-type: text/html\n\n"; 33 | 34 | # Don't match these pages 35 | if ($ENV{DOCUMENT_URI} =~ /(\/index.html|\/?\d+\/\d+\/index|\/?archives|\/.*tagmap\.html)/) { 36 | exit; 37 | } 38 | 39 | 40 | # Get commonly needed paths 41 | my ($site_root, $requested_url, $document_url) 42 | = MultiMarkdownCMS::getHostingPaths($0); 43 | 44 | # Debugging aid 45 | print qq{ 46 | Site root directory: $site_root
    47 | Request: $requested_url
    48 | Document: $document_url
    49 | } if $debug; 50 | 51 | 52 | local $/; 53 | 54 | 55 | open(INDEX, "< $site_root/cgi/vector_index"); 56 | my $index = ; 57 | close(INDEX); 58 | 59 | my %matches = (); 60 | my $query = "$document_url"; 61 | 62 | my $content = ""; 63 | 64 | 65 | while ($index =~ /^(.*$query.*)$/mig) { 66 | $1 =~ /^(\S+)\t(\S+)\t([\.\d]+)$/; 67 | $a = $1; 68 | $b = $2; 69 | $score = $3; 70 | next if ($score < $threshold); 71 | if ($a eq $query) { 72 | $matches{$b} = $score; 73 | } else { 74 | $matches{$a} = $score; 75 | } 76 | } 77 | 78 | 79 | my $count = 0; 80 | foreach my $match (sort {$matches{$b} <=> $matches{$a}} keys %matches) { 81 | if ($count < $max_matches) { 82 | open (FILE, "<$site_root$match"); 83 | my $data = ; 84 | close(FILE); 85 | my $title = $match; 86 | if ($data =~ /(.*)<\/h1>/) { 87 | $title = $1; 88 | } 89 | my $score = ""; 90 | $score = $matches{$match} if ($debug); 91 | $match =~ s/^\///; 92 | $match =~ s/(index)?\.html//; 93 | $content .= "
  • $title
  • $score\n"; 94 | } 95 | $count++; 96 | } 97 | if ($content ne "") { 98 | print qq{

    Similar Pages

    99 |
      100 | $content 101 |
    102 | }; 103 | } 104 | 105 | -------------------------------------------------------------------------------- /cgi/comments.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # Copyright (C) 2010 Fletcher T. Penney 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the 17 | # Free Software Foundation, Inc. 18 | # 59 Temple Place, Suite 330 19 | # Boston, MA 02111-1307 USA 20 | 21 | use strict; 22 | use warnings; 23 | 24 | use IO::String; 25 | use CGI; 26 | use MultiMarkdownCMS; 27 | 28 | 29 | my $cgi = CGI::new(); 30 | my $debug = 0; # Enables extra output for debugging 31 | 32 | 33 | print "Content-type: text/html\n\n"; 34 | 35 | print "
    36 |

    Comments

    37 | "; 38 | 39 | 40 | # Get commonly needed paths 41 | my ($site_root, $requested_url, $document_url) 42 | = MultiMarkdownCMS::getHostingPaths($0); 43 | 44 | # Debugging aid 45 | print qq{ 46 | Site root directory: $site_root
    47 | Request: $requested_url
    48 | Document: $document_url
    49 | } if $debug; 50 | 51 | 52 | (my $filepath = $site_root . $document_url) =~ s/(\.html)?$/.comments/; 53 | 54 | 55 | my @months = qw(January February March April May June July August 56 | September October November December); 57 | 58 | if (-f $filepath) { 59 | local $/; 60 | open(FILE, "<$filepath"); 61 | my $data = ; 62 | close FILE; 63 | my $count = 0; 64 | 65 | $data =~ s{ 66 | AUTHOR: 67 | (.*?) 68 | (\n\n\n|\Z) 69 | }{ 70 | my $comment = $1; 71 | $count++; 72 | 73 | $comment =~ /^\s*(.*?)\n/m; # First line is author 74 | my $author = $1; 75 | 76 | $comment =~ /URL:\s*(.*?)$/m; 77 | my $url = $1; 78 | 79 | $comment =~ /DATE:\s*(.*?)$/m; 80 | my $date = $1; 81 | 82 | $comment =~ /COMMENT:\s*(.*)$/s; 83 | my $body = $1; 84 | 85 | $date =~ s/(\d\d)\/0?(\d+)\//$months[$1-1] $2,/; 86 | 87 | qq{
    88 | 91 |
    92 | $body 93 |
    94 | 95 | }; 96 | }egsx; 97 | 98 | print $data; 99 | } 100 | 101 | print qq{ 102 |

    Leave a comment

    103 |
    104 | 107 |
    108 | 109 |
    110 | }; -------------------------------------------------------------------------------- /cgi/Lingua/Stem/AutoLoader.pm: -------------------------------------------------------------------------------- 1 | package Lingua::Stem::AutoLoader; 2 | 3 | # $RCSfile: AutoLoader.pm,v $ $Revision: 1.2 $ $Date: 1999/06/17 21:59:24 $ $Author: snowhare $ 4 | 5 | =head1 NAME 6 | 7 | Lingua::Stem::AutoLoader - A manager for autoloading Lingua::Stem modules 8 | 9 | =head1 SYNOPSIS 10 | 11 | use Lingua::Stem::AutoLoader; 12 | 13 | =head1 DESCRIPTION 14 | 15 | Sets up the autoloader to load the modules in the Lingua::Stem system on demand. 16 | 17 | Lingua::Stem::Da - Danish 18 | Lingua::Stem::De - German 19 | Lingua::Stem::En - English 20 | Lingua::Stem::Fr - French 21 | Lingua::Stem::Gl - Galician 22 | Lingua::Stem::It - Italian 23 | Lingua::Stem::No - Norwegian 24 | Lingua::Stem::Ru - Rusian 25 | Lingua::Stem::Pt - Portuguese 26 | Lingua::Stem::Sv - Swedish 27 | 28 | =head1 CHANGES 29 | 30 | 1.03 2004.07.25 - Added 'Lingua::Stem::Ru' 31 | 32 | 1.02 2004.04.26 - Added 'Lingua::Stem::Fr' 33 | 34 | 1.01 2003.04.05 - Added 'Lingua::Stem::De', 'Lingua::Stem::Da', 35 | 'Lingua::Stem::Gl', 'Lingua::Stem::It', 36 | 'Lingua::Stem::No', 'Lingua::Stem::Pt', 37 | 'Lingua::Stem::Sv', 38 | 39 | to the list of autoloaded modules. 40 | 41 | =cut 42 | 43 | use strict; 44 | use vars qw($VERSION $AUTOLOAD); 45 | 46 | $VERSION = "1.02"; 47 | 48 | my $_autoloaded_functions = {}; 49 | 50 | my @packageslist = ( 51 | 'Lingua::Stem::De', 52 | 'Lingua::Stem::En', 53 | 'Lingua::Stem::Fr', 54 | 'Lingua::Stem::Da', 55 | 'Lingua::Stem::Gl', 56 | 'Lingua::Stem::It', 57 | 'Lingua::Stem::No', 58 | 'Lingua::Stem::Pt', 59 | 'Lingua::Stem::Sv', 60 | 'Lingua::Stem::EnBroken', 61 | ); 62 | 63 | my $autoloader =<<'EOF'; 64 | package ----packagename----; 65 | use vars qw($AUTOLOAD); 66 | sub AUTOLOAD { 67 | return if ($AUTOLOAD =~ m/::(END|DESTROY)$/o); 68 | if (exists $_autoloaded_functions->{$AUTOLOAD}) { 69 | die("Attempted to autoload function '$AUTOLOAD' more than once - does it exist?\n"); 70 | } 71 | $_autoloaded_functions->{$AUTOLOAD} = 1; 72 | my ($packagename) = $AUTOLOAD =~ m/^(.*)::[A-Z_][A-Z0-9_]*$/ois; 73 | eval ("use $packagename;"); 74 | if ($@ ne '') { 75 | die ("Unable to use packagename: $@\n"); 76 | } 77 | goto &$AUTOLOAD; 78 | } 79 | 80 | EOF 81 | 82 | my $fullload = ''; 83 | foreach my $packagename (@packageslist) { 84 | my ($loader) = $autoloader; 85 | $loader =~ s/(----packagename----)/$packagename/; 86 | $fullload .= $loader; 87 | } 88 | eval $fullload; 89 | if ($@ ne '') { 90 | die ("Failed to initialize AUTOLOAD: $@\n"); 91 | } 92 | 93 | =head1 COPYRIGHT 94 | 95 | Copyright 1999, Benjamin Franz () and 96 | FreeRun Technologies, Inc. (). All Rights Reserved. 97 | This software may be copied or redistributed under the same terms as Perl itelf. 98 | 99 | =head1 AUTHOR 100 | 101 | Benjamin Franz 102 | 103 | =head1 TODO 104 | 105 | Nothing. 106 | 107 | =cut 108 | 109 | 1; 110 | -------------------------------------------------------------------------------- /cgi/atom.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # Copyright (C) 2010 Fletcher T. Penney 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the 17 | # Free Software Foundation, Inc. 18 | # 59 Temple Place, Suite 330 19 | # Boston, MA 02111-1307 USA 20 | 21 | use warnings; 22 | 23 | use XML::Atom::SimpleFeed; 24 | use File::Find; 25 | use CGI; 26 | use MultiMarkdownCMS; 27 | 28 | my $host; 29 | if ($ENV{HTTP_HOST}) { 30 | $host = $ENV{HTTP_HOST}; 31 | } else { 32 | $host = "127.0.0.1"; 33 | } 34 | 35 | my $feed = XML::Atom::SimpleFeed->new( 36 | title => "$host", 37 | link => "http://$host$ENV{Base_URL}/", 38 | link => { rel => 'self', href => "http://$host$ENV{Base_URL}/atom.xml", }, 39 | author => 'MultiMarkdown CMS', 40 | ); 41 | 42 | local $/; 43 | 44 | my $max_count = 25; 45 | 46 | print "Content-type: application/atom+xml\n\n"; 47 | 48 | # Get commonly needed paths 49 | my ($site_root, $requested_url, $document_url) 50 | = MultiMarkdownCMS::getHostingPaths($0); 51 | 52 | my %pages = (); 53 | 54 | find(\&index_file, $site_root); 55 | 56 | my $count = 0; 57 | 58 | 59 | foreach my $date (sort {$b cmp $a} keys %pages) { 60 | foreach my $filepath (sort {$b cmp $a}keys %{$pages{$date}}) { 61 | if ($count < $max_count) { 62 | my $title = $pages{$date}{$filepath}; 63 | my $content = $pages{$date}{$filepath}{body}; 64 | $filepath =~ s/^$site_root//; 65 | $feed->add_entry( 66 | title => $title, 67 | link => "http://$host$ENV{Base_URL}$filepath", 68 | updated => $date, 69 | content => $content, 70 | ); 71 | 72 | $count++; 73 | } 74 | } 75 | } 76 | 77 | $feed->print; 78 | 79 | sub index_file { 80 | my $filepath = $File::Find::name; 81 | 82 | return if ($filepath =~ /index.html$/); 83 | 84 | if ($filepath =~ /$site_root\/(\d\d\d\d)\/(\d\d)\/.*\.html$/) { 85 | my $date = ""; 86 | 87 | open (FILE, "<$filepath"); 88 | my $data = ; 89 | close FILE; 90 | 91 | if ($data =~ //i) { 92 | $date = $1; 93 | $date =~ s/(\d?\d)\/(\d\d)\/(\d\d\d\d).*?(\d\d:\d\d:\d\d).*/$3-$1-$2T$4-04:00/; 94 | } 95 | if ($data =~ /

    (.*)<\/h1>/) { 96 | my $title = $1; 97 | $pages{$date}{$filepath} = $title; 98 | } 99 | if ($data =~ /(.*)<\/body>/s) { 100 | my $body = $1; 101 | $pages{$date}{$filepath}{'body'} = $body; 102 | } 103 | 104 | } 105 | 106 | 107 | } -------------------------------------------------------------------------------- /cgi/latest_stories.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # Copyright (C) 2010 Fletcher T. Penney 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the 17 | # Free Software Foundation, Inc. 18 | # 59 Temple Place, Suite 330 19 | # Boston, MA 02111-1307 USA 20 | 21 | use warnings; 22 | 23 | use File::Find; 24 | use MultiMarkdownCMS; 25 | 26 | 27 | my $debug = 0; # Enables extra output for debugging 28 | 29 | local $/; 30 | 31 | my $max_count = 15; 32 | 33 | print "Content-type: text/html\n\n"; 34 | 35 | 36 | # Get commonly needed paths 37 | my ($site_root, $requested_url, $document_url) 38 | = MultiMarkdownCMS::getHostingPaths($0); 39 | 40 | # Debugging aid 41 | print qq{ 42 | Site root directory: $site_root
    43 | Request: $requested_url
    44 | Document: $document_url
    45 | } if $debug; 46 | 47 | 48 | my %pages = (); 49 | 50 | find(\&index_file, $site_root); 51 | 52 | my $count = 0; 53 | my $output = ""; 54 | 55 | foreach my $year (sort {$b cmp $a} keys %pages) { 56 | foreach my $month (sort {$b cmp $a}keys %{$pages{$year}}) { 57 | foreach my $day (sort {$b cmp $a}keys %{$pages{$year}{$month}}) { 58 | foreach my $filepath (sort {$b cmp $a}keys %{$pages{$year}{$month}{$day}}) { 59 | if ($count < $max_count) { 60 | my $title = $pages{$year}{$month}{$day}{$filepath}; 61 | $filepath =~ s/^$site_root//; 62 | $filepath =~ s/\.html$//; 63 | $output .= qq{
  • $year.$month.$day: $title
  • \n}; 64 | $count++; 65 | } 66 | } 67 | } 68 | } 69 | } 70 | 71 | if ($output) { 72 | print qq{

    Latest Entries

    73 |
      74 | $output 75 |
    76 | }; 77 | } 78 | 79 | sub index_file { 80 | my $filepath = $File::Find::name; 81 | 82 | return if ($filepath =~ /index.html$/); 83 | 84 | if ($filepath =~ /$site_root\/(\d\d\d\d)\/(\d\d)\/.*\.html$/) { 85 | my $year = $1; 86 | my $month = $2; 87 | my $day = ""; 88 | 89 | open (FILE, "<$filepath"); 90 | my $data = ; 91 | close FILE; 92 | 93 | if ($data =~ //i) { 94 | $date = $1; 95 | $date =~ s/(\d?\d)\/(\d\d)\/(\d\d\d\d).*/$3.$1.$2/; 96 | $month = $1; 97 | $day = $2; 98 | $year = $3; 99 | } 100 | 101 | if ($data =~ /

    (.*)<\/h1>/) { 102 | my $title = $1; 103 | $pages{$year}{$month}{$day}{$filepath} = $title; 104 | } 105 | } 106 | 107 | 108 | } -------------------------------------------------------------------------------- /cgi/submit_comment.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # Based on code in Net::OpenID::Consumer by Brad Fitzpatrick 4 | # 5 | # My changes Copyright (C) 2010 Fletcher T. Penney 6 | # 7 | # 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program; if not, write to the 20 | # Free Software Foundation, Inc. 21 | # 59 Temple Place, Suite 330 22 | # Boston, MA 02111-1307 USA 23 | 24 | use warnings; 25 | use strict; 26 | use Net::OpenID::Consumer; 27 | use LWP::UserAgent; 28 | use CGI; 29 | use CGI::Carp 'fatalsToBrowser'; 30 | my $cgi = CGI::new(); 31 | my $openid = $cgi->cookie("OpenID"); 32 | my $comment = $cgi->param("text"); 33 | my $user = $cgi->param("user"); 34 | 35 | # Do a quickie check of the input 36 | fail ('No OpenID') if ! $openid; 37 | fail ('Bad OpenID') if $openid =~ /[^a-z0-9\._:\/-]/i; 38 | 39 | # Workaround for a known problem with myopenid. Change "http" to "https". 40 | $openid =~ s@(^http://|^(?!https))@https://@ if $openid =~ /myopenid/; 41 | 42 | my $csr = Net::OpenID::Consumer->new ( 43 | # The user agent which sends the openid off to the server. 44 | ua => LWP::UserAgent->new, 45 | # Who we are. 46 | required_root => "http://$ENV{HTTP_HOST}/", 47 | # Our password. 48 | consumer_secret => 'enter your password here', 49 | ); 50 | 51 | my $claimed_id = $csr->claimed_identity($openid); 52 | 53 | if ($claimed_id) { 54 | my $prior_page = "$ENV{HTTP_REFERER}"; 55 | $prior_page =~ s/^https?:\/\/.*?\///; 56 | 57 | $claimed_id->set_extension_args( 58 | 'http://openid.net/extensions/sreg/1.1', 59 | { 60 | optional => 'email,fullname,nickname', 61 | policy_url => 'http://example.com/privacypolicy.html', 62 | }, 63 | ); 64 | 65 | my $check_url = $claimed_id->check_url ( 66 | # The place we go back to. 67 | return_to => "http://$ENV{HTTP_HOST}$ENV{Base_URL}/cgi/accept_comment.cgi?referer=$prior_page;", 68 | # Having this simplifies the login process. 69 | trust_root => "http://$ENV{HTTP_HOST}/", 70 | delayed_return => 1, 71 | ); 72 | 73 | # Automatically redirect the user to the endpoint. 74 | my $cookie1 = $cgi->cookie( 75 | -name=>'Comment', 76 | -value=>$comment, 77 | -expires=>'+1h', 78 | -path=>'/'); 79 | 80 | my $cookie2 = $cgi->cookie( 81 | -name=>'User', 82 | -value=>$user, 83 | -path=>'/'); 84 | 85 | print $cgi->redirect (-cookie=>[$cookie1,$cookie2], -location => $check_url); 86 | } else { 87 | fail ("claimed_identity for '$openid' failed: ".$csr->errcode()); 88 | } 89 | 90 | exit 0; 91 | 92 | # Simple error handler 93 | 94 | sub fail 95 | { 96 | my ($message) = @_; 97 | print $cgi->header, $cgi->start_html, $message, $cgi->end_html; 98 | exit 0; 99 | } 100 | -------------------------------------------------------------------------------- /cgi/google_sitemap.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # Copyright (C) 2010 Fletcher T. Penney 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the 17 | # Free Software Foundation, Inc. 18 | # 59 Temple Place, Suite 330 19 | # Boston, MA 02111-1307 USA 20 | 21 | use strict; 22 | use warnings; 23 | 24 | use File::Find; 25 | use CGI; 26 | use MultiMarkdownCMS; 27 | 28 | my $debug = 0; # Enables extra output for debugging 29 | 30 | 31 | print "Content-type: text/html\n\n"; 32 | 33 | 34 | # Get commonly needed paths 35 | my ($site_root, $requested_url, $document_url) 36 | = MultiMarkdownCMS::getHostingPaths($0); 37 | 38 | # Debugging aid 39 | print qq{ 40 | Site root directory: $site_root
    41 | Request: $requested_url
    42 | Document: $document_url
    43 | } if $debug; 44 | 45 | # Determine local directory of "root" of web site and generate a 46 | # "relative" URL request to that root 47 | # 48 | # $ENV{Base_URL} is set by apache's .htaccess configuration file on a 49 | # per host basis 50 | 51 | #(my $request = $ENV{REQUEST_URI}) =~ s/$ENV{Base_URL}//; 52 | # 53 | #(my $site_root = $ENV{SCRIPT_FILENAME} . $request ) =~ s/\/cgi\/.*?\//\//; 54 | #$site_root =~ s/\/?sitemap.xml//; 55 | #$site_root =~ s/\/*$//; 56 | 57 | 58 | 59 | my $root_folder = $site_root; 60 | my $root_url = "http://$ENV{HTTP_HOST}$ENV{Base_URL}"; 61 | 62 | 63 | # Print sitemap header 64 | print qq{ 65 | 69 | 70 | }; 71 | 72 | find(\&index_file, $root_folder); 73 | 74 | 75 | print "\n"; 76 | 77 | 78 | sub index_file { 79 | my $filepath = $File::Find::name; 80 | 81 | if ($filepath =~ s/^$root_folder(.*?)(index)?\.html$/$1/i) { 82 | # Ignore certain files 83 | return if ($filepath =~ /^\/(cgi\/|templates\/|google......|notfound|mt\/|mt-static)/); 84 | 85 | my $priority = "0.8"; 86 | # my @d = gmtime ((stat("$File::Find::name"))[9]); # get file's modification time 87 | my @d = gmtime(); 88 | 89 | my $lastmod = sprintf "%4d-%02d-%02dT%02d:%02d:%02d-04:00", $d[5]+1900,$d[4]+1,$d[3],$d[2],$d[1],$d[0]; 90 | 91 | my $change = "daily\n"; 92 | 93 | if ($filepath =~ /^$/) { 94 | # Site root 95 | $priority = "1.0"; 96 | } 97 | print qq{ 98 | $root_url$filepath 99 | $change$lastmod 100 | $priority 101 | 102 | 103 | } 104 | 105 | } 106 | 107 | } -------------------------------------------------------------------------------- /cgi/tags.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # Copyright (C) 2010 Fletcher T. Penney 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the 17 | # Free Software Foundation, Inc. 18 | # 59 Temple Place, Suite 330 19 | # Boston, MA 02111-1307 USA 20 | 21 | use CGI; 22 | use File::Find; 23 | use MultiMarkdownCMS; 24 | 25 | 26 | my $cgi = CGI::new(); 27 | my $debug = 0; # Enables extra output for debugging 28 | 29 | my @tag_query = (); 30 | my $content = ""; 31 | 32 | print "Content-type: text/html\n\n"; 33 | 34 | # Get commonly needed paths 35 | my ($site_root, $requested_url, $document_url) 36 | = MultiMarkdownCMS::getHostingPaths($0); 37 | 38 | # Debugging aid 39 | print qq{ 40 | Site root directory: $site_root
    41 | Request: $requested_url
    42 | Document: $document_url
    43 | } if $debug; 44 | 45 | 46 | if ($document_url eq "/templates/tags.html") { 47 | # We are looking for pages that match given tag(s) 48 | 49 | # Convert path into list of tags to match 50 | my $path = $cgi->param('query'); 51 | 52 | # Clean up tag names 53 | $path =~ s/_/ /g; 54 | 55 | # Allow multiple tags separated by '/' 56 | @tag_query = split('\s*/\s*',$path); 57 | 58 | # Index all documents 59 | find(\&find_pages, $site_root); 60 | 61 | $query = join(', ',@tag_query); 62 | print qq{

    Pages tagged $query

    63 | }; 64 | 65 | if ($content) {print qq{ 66 |
      67 | $content 68 |
    69 | }; 70 | }; 71 | 72 | } else { 73 | # We are processing tags on a given page 74 | 75 | # Where am I called from 76 | my $file_path = $site_root . $document_url; 77 | 78 | local $/; 79 | my $output = ""; 80 | 81 | if (open (FILE, "<$file_path")) { 82 | my $data = ; 83 | close FILE; 84 | if ($data =~ /$1<\/a>/; s/>(.*)_(.*)$1 $2$output\n"; 93 | } 94 | 95 | 96 | sub find_pages { 97 | # We're looking for .html files 98 | my $filepath = $File::Find::name; 99 | 100 | if ($filepath =~ /\.html$/) { 101 | local $/; 102 | if (open (FILE, "<$filepath")) { 103 | my $data = ; 104 | close FILE; 105 | if ($data =~ /(.*?)<\/title>/; 114 | $content .= "
  • $1
  • \n" if $match; 115 | } 116 | } 117 | 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /cgi/atom-comments.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # Copyright (C) 2010 Fletcher T. Penney 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the 17 | # Free Software Foundation, Inc. 18 | # 59 Temple Place, Suite 330 19 | # Boston, MA 02111-1307 USA 20 | 21 | use warnings; 22 | 23 | use XML::Atom::SimpleFeed; 24 | use CGI; 25 | use File::Find; 26 | use MultiMarkdownCMS; 27 | 28 | my $host; 29 | if ($ENV{HTTP_HOST}) { 30 | $host = $ENV{HTTP_HOST}; 31 | } else { 32 | $host = "127.0.0.1"; 33 | } 34 | 35 | my $feed = XML::Atom::SimpleFeed->new( 36 | title => "$host comments", 37 | link => "http://$host$ENV{Base_URL}/", 38 | link => { rel => 'self', href => "http://$host$ENV{Base_URL}/atom-comments.xml", }, 39 | author => 'MultiMarkdown CMS', 40 | ); 41 | 42 | local $/; 43 | 44 | my $max_count = 25; 45 | 46 | print "Content-type: application/atom+xml\n\n"; 47 | 48 | # Get commonly needed paths 49 | my ($site_root, $requested_url, $document_url) 50 | = MultiMarkdownCMS::getHostingPaths($0); 51 | 52 | my %pages = (); 53 | 54 | find(\&index_file, $site_root); 55 | 56 | my $count = 0; 57 | 58 | 59 | foreach my $date (sort {$b cmp $a} keys %pages) { 60 | foreach my $filepath (sort {$b cmp $a}keys %{$pages{$date}}) { 61 | if ($count < $max_count) { 62 | my $title = $pages{$date}{$filepath}; 63 | my $content = $pages{$date}{$filepath}{body}; 64 | $filepath =~ s/^$site_root//; 65 | $feed->add_entry( 66 | title => $title, 67 | link => "http://$host$ENV{Base_URL}$filepath", 68 | updated => $date, 69 | content => $content, 70 | ); 71 | 72 | $count++; 73 | } 74 | } 75 | } 76 | 77 | $feed->print; 78 | 79 | sub index_file { 80 | my $filepath = $File::Find::name; 81 | 82 | return if ($filepath =~ /index.html$/); 83 | 84 | if ($filepath =~ /$site_root\/(\d\d\d\d)\/(\d\d)\/.*\.comments$/) { 85 | my $date = ""; 86 | 87 | open (FILE, "<$filepath"); 88 | my $data = ; 89 | close FILE; 90 | 91 | $filepath =~ s/\.comments$//; 92 | 93 | my $counter = 0; 94 | $data =~ s{ 95 | AUTHOR: 96 | (.*?) 97 | (\n\n\n|\Z) 98 | }{ 99 | my $comment = $1; 100 | $counter++; 101 | 102 | $comment =~ /^\s*(.*?)\n/m; # First line is author 103 | my $author = $1; 104 | 105 | $comment =~ /URL:\s*(.*?)$/m; 106 | my $url = $1; 107 | 108 | $comment =~ /DATE:\s*(.*?)$/m; 109 | my $date = $1; 110 | $date =~ s/(\d?\d)\/(\d\d)\/(\d\d\d\d).*?(\d\d:\d\d:\d\d).*/$3-$1-$2T$4-04:00/; 111 | 112 | $comment =~ /COMMENT:\s*(.*)$/s; 113 | my $body = $1; 114 | 115 | my $clean_path = $filepath; 116 | $clean_path =~ s/$site_root//; 117 | 118 | $pages{$date}{$filepath . "#comment-" . $counter} = "comment $counter on $clean_path"; 119 | $pages{$date}{$filepath . "#comment-" . $counter}{'body'} = $body; 120 | 121 | ""; 122 | }egsx; 123 | 124 | } 125 | 126 | 127 | } -------------------------------------------------------------------------------- /cgi/openid_response.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # Based on code in Net::OpenID::Consumer by Brad Fitzpatrick 4 | # 5 | # My changes Copyright (C) 2010 Fletcher T. Penney 6 | # 7 | # 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program; if not, write to the 20 | # Free Software Foundation, Inc. 21 | # 59 Temple Place, Suite 330 22 | # Boston, MA 02111-1307 USA 23 | 24 | use warnings; 25 | use strict; 26 | use Net::OpenID::Consumer; 27 | use LWP::UserAgent; 28 | use CGI; 29 | 30 | my $cgi = CGI::new(); 31 | my $csr = Net::OpenID::Consumer->new ( 32 | # The root of our URL. 33 | required_root => "http://$ENV{HTTP_HOST}/", 34 | # Our password. 35 | consumer_secret => 'enter your password here', 36 | # Where to get the information from. 37 | args => $cgi, 38 | ); 39 | 40 | # Start of HTML output 41 | my $refer = $cgi->param('referer'); 42 | 43 | my $message = ""; 44 | 45 | 46 | $csr->handle_server_response ( 47 | not_openid => sub { 48 | $message = "That's not an OpenID message. Did you just type in the URL?"; 49 | }, 50 | setup_required => sub { 51 | my $setup_url = shift; 52 | $message = "You need to do something here."; 53 | }, 54 | cancelled => sub { 55 | $message = 'You cancelled your login.'; 56 | }, 57 | verified => sub { 58 | my $vident = shift; 59 | my $url = $vident->url; 60 | 61 | # Successful authentication 62 | 63 | # Create an authentication cookie 64 | my $cookie = $cgi->cookie( 65 | -name=>'OpenID', 66 | -value=>$url, 67 | -expires=>'+4h', 68 | -path=>'/'); 69 | 70 | # Fetch nickname or first name 71 | my $sreg = $vident->signed_extension_fields( 72 | 'http://openid.net/extensions/sreg/1.1', 73 | ); 74 | 75 | my $user = ""; 76 | if ($sreg->{nickname}) { 77 | $user = $sreg->{nickname}; 78 | } elsif ($sreg->{fullname}) { 79 | $user = $sreg->{fullname}; 80 | } 81 | 82 | my $cookie2 = $cgi->cookie( 83 | -name=>'User', 84 | -value=>$user, 85 | -expires=>'+4h', 86 | -path=>'/'); 87 | 88 | print $cgi->redirect (-cookie=>[$cookie,$cookie2], -location => "http://" . $ENV{SERVER_NAME} . "/" . $refer . "#leave-comment"); 89 | }, 90 | error => \&handle_errors, 91 | ); 92 | 93 | if ($message ne "") { 94 | print $cgi->header(), $cgi->start_html(); 95 | print "

    OpenID Login

    \n"; 96 | print $message; 97 | print $cgi->end_html(); 98 | 99 | } 100 | 101 | exit 0; 102 | 103 | # Handle errors, suggest possible causes of the error. 104 | 105 | sub handle_errors 106 | { 107 | my ($err) = @_; 108 | print $cgi->header(), $cgi->start_html(); 109 | print "

    OpenID Login

    \n"; 110 | print "Error: $err. \n"; 111 | if ($err eq 'server_not_allowed') { 112 | print <end_html(); 120 | } 121 | -------------------------------------------------------------------------------- /cgi/XSLT/xhtml-static-site.xslt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 | 36 | 37 | 38 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | ]]> 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | ]]> 73 | 74 |

    75 |
    76 | 77 |
    78 |
    79 |
    80 | 81 | ]]> 82 | 83 | 84 | ]]> 85 |
    86 |
    87 | 88 | 89 |
    -------------------------------------------------------------------------------- /cgi/archives.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # Copyright (C) 2010 Fletcher T. Penney 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the 17 | # Free Software Foundation, Inc. 18 | # 59 Temple Place, Suite 330 19 | # Boston, MA 02111-1307 USA 20 | 21 | use MultiMarkdownCMS; 22 | 23 | my $debug = 0; # Enables extra output for debugging 24 | 25 | print "Content-type: text/html\n\n"; 26 | 27 | # Get commonly needed paths 28 | my ($site_root, $requested_url, $document_url) 29 | = MultiMarkdownCMS::getHostingPaths($0); 30 | 31 | # Debugging aid 32 | print qq{ 33 | Site root directory: $site_root
    34 | Request: $requested_url
    35 | Document: $document_url
    36 | } if $debug; 37 | 38 | 39 | my @months = qw(January February March April May June July August 40 | September October November December); 41 | 42 | 43 | local $/; 44 | 45 | if ($requested_url =~ /^\/?(\d\d\d\d).*?(\d\d)/) { 46 | my $year = $1; 47 | my $month = $2; 48 | # Print entries in the current month 49 | my %pages = (); 50 | 51 | foreach my $filepath (glob("$site_root/$year/$month/*.html")) { 52 | if ($filepath !~ /index.html$/) { 53 | open (FILE, "<$filepath"); 54 | my $data = ; 55 | if ($data =~ /

    (.*)<\/h1>/) { 56 | my ($title, $date) = ($1,""); 57 | if ($data =~ //i) { 58 | $date = $1; 59 | $date =~ s/(\d?\d)\/(\d\d)\/(\d\d\d\d).*/$3.$1.$2/; 60 | } 61 | $filepath =~ /$site_root\/(.*).html/; 62 | $pages{$date}{$title} = "$1"; 63 | } 64 | } 65 | } 66 | 67 | if ( scalar keys %pages > 0 ) { 68 | print "
      \n"; 69 | foreach my $date (sort { $b cmp $a} keys(%pages)) { 70 | foreach my $title (sort keys %{$pages{$date}}) { 71 | print "
    • $date: $title
    • \n";"" 72 | } 73 | } 74 | print "
    \n"; 75 | } 76 | } elsif ($requested_url =~ /^\/?(\d\d\d\d)/) { 77 | # Print months in current year that have entries 78 | my $year = $1; 79 | local $/; 80 | 81 | my $content = ""; 82 | 83 | my %months_with_entries = (); 84 | 85 | foreach my $filepath (glob("$site_root/$year/*/*.html")) { 86 | $filepath =~ /\d\d\d\d\/(\d\d)/; 87 | 88 | $months_with_entries{$1} = 1; 89 | } 90 | 91 | foreach (sort keys %months_with_entries) { 92 | my $month = $months[$_-1]; 93 | $content .= "
  • $month $year Archives
  • \n"; 94 | } 95 | 96 | 97 | if ($content ne "") { 98 | print qq{
      99 | $content 100 |
    101 | }; 102 | } 103 | } elsif ($requested_url =~ /^\/?archives/) { 104 | my %pages = (); 105 | my $content = ""; 106 | 107 | foreach my $filepath (glob("$site_root/*/*/*.html")) { 108 | if ($filepath =~ /(\d\d\d\d)\/(\d\d)/){ 109 | $pages{$1}{$2} = 1; 110 | } 111 | 112 | } 113 | 114 | foreach my $year (sort { $b cmp $a} keys(%pages)) { 115 | foreach my $month (sort { $b cmp $a}keys %{$pages{$year}}) { 116 | $content .= "
  • $months[$month-1] $year
  • \n"; 117 | } 118 | } 119 | if ($content ne "") { 120 | print qq{
      121 | $content 122 |
    123 | }; 124 | 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /cgi/commenter.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2010 Fletcher T. Penney 2 | // 3 | // This program is free software; you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation; either version 2 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program; if not, write to the 15 | // Free Software Foundation, Inc. 16 | // 59 Temple Place, Suite 330 17 | // Boston, MA 02111-1307 USA 18 | 19 | function writeCommenterGreeting(){ 20 | container = document.getElementById("commenter-greeting"); 21 | var content; 22 | var openId; 23 | openId = getCookie('OpenID'); 24 | user = getCookie('User'); 25 | 26 | if (openId != '') { 27 | content = '

    You are signed in as ' + openId + '. You may sign out.

    '; 28 | } else { 29 | content = '

    Please sign in using your OpenID. If you don\'t have an OpenID, you may also email me.

    '; 30 | } 31 | 32 | container.innerHTML = content; 33 | } 34 | 35 | 36 | // Remainder of this document is Copyright (c) 1996-1997 Athenia Associates. 37 | // http://www.webreference.com/js/ 38 | // License is granted if and only if this entire 39 | // copyright notice is included. By Tomer Shiran. 40 | 41 | function setCookie (name, value, expires, path, domain, secure) { 42 | var curCookie = name + "=" + escape(value) + (expires ? "; expires=" + expires.toGMTString() : "") + 43 | (path ? "; path=" + path : "") + (domain ? "; domain=" + domain : "") + (secure ? "secure" : ""); 44 | document.cookie = curCookie; 45 | } 46 | 47 | function getCookie (name) { 48 | var prefix = name + '='; 49 | var c = document.cookie; 50 | var nullstring = ''; 51 | var cookieStartIndex = c.indexOf(prefix); 52 | if (cookieStartIndex == -1) 53 | return nullstring; 54 | var cookieEndIndex = c.indexOf(";", cookieStartIndex + prefix.length); 55 | if (cookieEndIndex == -1) 56 | cookieEndIndex = c.length; 57 | return unescape(c.substring(cookieStartIndex + prefix.length, cookieEndIndex)); 58 | } 59 | 60 | function deleteCookie (name, path, domain) { 61 | if (getCookie(name)) 62 | document.cookie = name + "=" + ((path) ? "; path=" + path : "") + 63 | ((domain) ? "; domain=" + domain : "") + "; expires=Thu, 01-Jan-70 00:00:01 GMT"; 64 | } 65 | 66 | function fixDate (date) { 67 | var base = new Date(0); 68 | var skew = base.getTime(); 69 | if (skew > 0) 70 | date.setTime(date.getTime() - skew); 71 | } 72 | 73 | function rememberMe (f) { 74 | var now = new Date(); 75 | fixDate(now); 76 | now.setTime(now.getTime() + 365 * 24 * 60 * 60 * 1000); 77 | now = now.toGMTString(); 78 | if (f.author != undefined) 79 | setCookie('mtcmtauth', f.author.value, now, '/', '', ''); 80 | if (f.email != undefined) 81 | setCookie('mtcmtmail', f.email.value, now, '/', '', ''); 82 | if (f.url != undefined) 83 | setCookie('mtcmthome', f.url.value, now, '/', '', ''); 84 | } 85 | 86 | function forgetMe (f) { 87 | deleteCookie('OpenID', '/', ''); 88 | deleteCookie('Comment', '/', ''); 89 | deleteCookie('User', '/', ''); 90 | writeCommenterGreeting(); 91 | } 92 | 93 | 94 | -------------------------------------------------------------------------------- /cgi/accept_comment.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # Copyright (C) 2010 Fletcher T. Penney 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the 17 | # Free Software Foundation, Inc. 18 | # 59 Temple Place, Suite 330 19 | # Boston, MA 02111-1307 USA 20 | 21 | 22 | use warnings; 23 | use strict; 24 | use Net::OpenID::Consumer; 25 | use LWP::UserAgent; 26 | use CGI; 27 | use POSIX; 28 | use MultiMarkdownCMS; 29 | 30 | my $cgi = CGI::new(); 31 | my $comment = $cgi->cookie("Comment"); 32 | my $user = $cgi->cookie("User"); 33 | my $timezone = -4; # Configure as needed 34 | my $debug = 0; # Enables extra output for debugging 35 | 36 | 37 | # Get commonly needed paths 38 | my ($site_root, $requested_url, $document_url) 39 | = MultiMarkdownCMS::getHostingPaths($0); 40 | 41 | # Debugging aid 42 | print qq{ 43 | Site root directory: $site_root
    44 | Request: $requested_url
    45 | Document: $document_url
    46 | } if $debug; 47 | 48 | 49 | my $csr = Net::OpenID::Consumer->new ( 50 | # The root of our URL. 51 | required_root => "http://$ENV{HTTP_HOST}/", 52 | # Our password. 53 | consumer_secret => 'enter your password here', 54 | # Where to get the information from. 55 | args => $cgi, 56 | ); 57 | 58 | # Start of HTML output 59 | my $refer = $cgi->param('referer'); 60 | 61 | my $message = ""; 62 | 63 | 64 | $csr->handle_server_response ( 65 | not_openid => sub { 66 | $message = "That's not an OpenID message. Did you just type in the URL?"; 67 | }, 68 | setup_required => sub { 69 | my $setup_url = shift; 70 | $message = "You need to do something here."; 71 | }, 72 | cancelled => sub { 73 | $message = 'You cancelled your login.'; 74 | }, 75 | verified => sub { 76 | my $vident = shift; 77 | my $url = $vident->url; 78 | 79 | # Successful authentication - accept the comment and addend 80 | 81 | my $local_root = $site_root; 82 | my $URI = "/" . $refer; 83 | $URI =~ s/(\.html)?$/.html/; 84 | $URI =~ s/$ENV{Base_URL}// if ($ENV{Base_URL} ne ""); 85 | $URI =~ s/^\/?//; 86 | 87 | # This doesn't work, but worth a shot to trigger a refresh? 88 | system("touch $local_root/$URI"); 89 | 90 | $URI =~ s/(\.html)?$/.comments/; 91 | 92 | my $filepath = "$local_root/$URI"; 93 | 94 | my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(); 95 | $year = $year + 1900; 96 | $mon += 1; 97 | 98 | my $date = ""; 99 | 100 | if (strftime ("%Z", localtime(time)) eq "UTC") { 101 | $date = strftime "%m/%d/%Y %H:%M:%S", localtime(time + ($timezone * 60 * 60)); 102 | } else { 103 | $date = strftime "%m/%d/%Y %H:%M:%S", localtime(time); 104 | } 105 | open (FILE, ">> $filepath"); 106 | print FILE "AUTHOR: $user\nURL: $url\nDATE: $date\nCOMMENT:\n"; 107 | close FILE; 108 | 109 | # Protect against HTML by encoding "<" 110 | $comment =~ s/\> $filepath; echo \"\" >> $filepath; echo \"\" >> $filepath"); 113 | print MMD $comment; 114 | close MMD; 115 | 116 | # Delete comment cookie 117 | 118 | # $message = "You are verified as '$url'.
    "; 119 | # $message .= "Comment: $comment
    "; 120 | # $message .= "Addend to $filepath
    "; 121 | 122 | 123 | print $cgi->redirect (-location => "http://" . $ENV{SERVER_NAME} . "/" . $refer . "#leave-comment"); 124 | 125 | }, 126 | error => \&handle_errors, 127 | ); 128 | 129 | if ($message ne "") { 130 | print $cgi->header(), $cgi->start_html(); 131 | print "

    Comment Submission

    \n"; 132 | print $message; 133 | print $cgi->end_html(); 134 | 135 | } 136 | 137 | exit 0; 138 | 139 | # Handle errors, suggest possible causes of the error. 140 | 141 | sub handle_errors 142 | { 143 | my ($err) = @_; 144 | print $cgi->header(), $cgi->start_html(); 145 | print "

    OpenID Login

    \n"; 146 | print "Error: $err. \n"; 147 | if ($err eq 'server_not_allowed') { 148 | print <end_html(); 156 | } 157 | -------------------------------------------------------------------------------- /css/less/layout.less: -------------------------------------------------------------------------------- 1 | /* CSS System 2 | 3 | Portions modified from from: 4 | http://matthewjamestaylor.com/blog/ultimate-multi-column-liquid-layouts-em-and-pixel-widths 5 | 6 | by Matthew James Taylor 7 | 8 | 9 | Remainder Copyright (c) 2010 Fletcher T. Penney 10 | 11 | 12 | This program is free software: you can redistribute it and/or modify 13 | it under the terms of the GNU General Public License as published by 14 | the Free Software Foundation, either version 3 of the License, or 15 | (at your option) any later version. 16 | 17 | This program is distributed in the hope that it will be useful, 18 | but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | GNU General Public License for more details. 21 | 22 | You should have received a copy of the GNU General Public License 23 | along with this program. If not, see . 24 | */ 25 | 26 | // layout.less - define the overall page layout, sidebar positioning, etc. 27 | 28 | 29 | /* NOTE: This file is created with [LESS](http://lesscss.org/). To convert it 30 | to CSS, you must process it with the lessc tool. 31 | 32 | Alternatively, you can look at the measurements section and calculate the 33 | changes you wish to make, and modify `layout.css` by hand. 34 | 35 | Option 1 may be easier if you plan on making changes frequently. 36 | 37 | */ 38 | 39 | 40 | // Measurements 41 | 42 | @content_width: 35em; // Width for the main content section 43 | @sidebar_width: 14em; // Width for sidebar content 44 | @content_margin: 2.5em; // Left-Right margin around content 45 | @sidebar_margin: 2.5em; // Left-Right margin around sidebar 46 | @header_width: 60em; // Width for header 47 | @page_margin: 1.75em; // Margin at top and bottom of page 48 | @bottom_padding: 1em; // Padding at bottom of content 49 | @minimum_width: 60em; // Minimum width for the body 50 | 51 | @footer_font_size: 0.75em; // Relative font size for footer 52 | 53 | @total_width: @content_width + @sidebar_width + @sidebar_margin + @sidebar_margin + @content_margin + @content_margin; 54 | 55 | @sidebar_and_margins: @sidebar_width + @content_margin; 56 | 57 | 58 | // CSS 59 | body { 60 | margin: 0; 61 | padding: 0; 62 | border: 0; 63 | /* This removes the border around the viewport in old versions of IE */ 64 | 65 | /* Minimum width of layout - remove line if not required */ 66 | /* The min-width property does not work in old versions of Internet Explorer */ 67 | min-width: @minimum_width; 68 | text-align: center; // To keep content in middle of page 69 | } 70 | 71 | 72 | div { 73 | // border:thin solid; // Border can be useful for debugging 74 | text-align: left; 75 | } 76 | 77 | 78 | // Put some spacing around the header - feel free to change 79 | 80 | .header { 81 | background-position: -10px 0px; 82 | position: relative; 83 | padding: 10px 10px 0px 10px; 84 | max-width: @header_width; 85 | margin: @page_margin auto 0em auto; 86 | } 87 | 88 | 89 | 90 | 91 | // Settings for a two-column layout 92 | // main content + a sidebar to the left *or* right 93 | // In general, you should not need to adjust the sections below 94 | 95 | // Settings for both "handedness" types 96 | 97 | .wrapper { 98 | position: relative; 99 | /* This fixes the IE7 overflow hidden bug and stops the layout jumping out of place */ 100 | width: 100%; /* width of whole page */ 101 | min-width: @total_width; 102 | clear: both; 103 | overflow: hidden; /* This chops off any overhanging divs */ 104 | max-width: @content_width + @sidebar_width + @sidebar_margin + @content_margin + @sidebar_margin + @content_margin; 105 | margin: 0 auto; 106 | margin-bottom: @page_margin; 107 | } 108 | .innerwrapper { 109 | float: left; 110 | width: 200%; 111 | position: relative; 112 | } 113 | .contentwrapper { 114 | position: relative; 115 | padding-bottom: @bottom_padding; 116 | } 117 | .content { 118 | max-width: @content_width; 119 | overflow: hidden; 120 | margin: 0 @content_margin 0 @sidebar_and_margins; 121 | } 122 | .sidebar { 123 | width: @sidebar_width; 124 | position: relative; 125 | } 126 | .footer { 127 | clear: both; 128 | float: left; 129 | width: 100%; 130 | min-width: @total_width; 131 | font-size: @footer_font_size; 132 | } 133 | 134 | 135 | // Settings for a right-sided menu 136 | 137 | .rightmenu { 138 | .innerwrapper { 139 | margin-left: 0 - @sidebar_width; 140 | right: 100%; 141 | } 142 | .contentwrapper { 143 | float: left; 144 | left: 50%; 145 | } 146 | .sidebar { 147 | float: right; 148 | left: @sidebar_width - @sidebar_margin; 149 | } 150 | } 151 | 152 | 153 | // Settings for a left-sided menu 154 | 155 | .leftmenu { 156 | .innerwrapper { 157 | left: @sidebar_width + @sidebar_margin + @sidebar_margin; 158 | } 159 | .contentwrapper { 160 | float: right; 161 | right: @sidebar_width + @sidebar_margin + @sidebar_margin + @sidebar_margin + @sidebar_margin; 162 | } 163 | .content { 164 | position: relative; 165 | right: 100%; 166 | } 167 | .sidebar { 168 | float: left; 169 | right: @sidebar_width + @sidebar_margin; 170 | } 171 | 172 | } -------------------------------------------------------------------------------- /cgi/Net/OpenID/URIFetch.pm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | =head1 NAME 4 | 5 | Net::OpenID::URIFetch - fetch and cache content from HTTP URLs 6 | 7 | =head1 DESCRIPTION 8 | 9 | This is roughly based on Ben Trott's URI::Fetch module, but 10 | URI::Fetch doesn't cache enough headers that Yadis can be implemented 11 | with it, so this is a lame copy altered to allow Yadis support. 12 | 13 | Hopefully one day URI::Fetch can be modified to do what we need and 14 | this can go away. 15 | 16 | This module is tailored to the needs of Net::OpenID::Consumer and probably 17 | isn't much use outside of it. See URI::Fetch for a more general module. 18 | 19 | =cut 20 | 21 | package Net::OpenID::URIFetch; 22 | 23 | use HTTP::Request; 24 | use HTTP::Status; 25 | use strict; 26 | use warnings; 27 | use Carp; 28 | 29 | our $HAS_ZLIB; 30 | BEGIN { 31 | $HAS_ZLIB = eval "use Compress::Zlib (); 1;"; 32 | } 33 | 34 | use constant URI_OK => 200; 35 | use constant URI_MOVED_PERMANENTLY => 301; 36 | use constant URI_NOT_MODIFIED => 304; 37 | use constant URI_GONE => 410; 38 | 39 | sub fetch { 40 | my ($class, $uri, $consumer, $content_hook) = @_; 41 | 42 | if ($uri eq 'x-xrds-location') { 43 | Carp::confess("Buh?"); 44 | } 45 | 46 | my $ua = $consumer->ua; 47 | my $cache = $consumer->cache; 48 | my $ref; 49 | 50 | # By prefixing the cache key, we can ensure we won't 51 | # get left-over cache items from older versions of Consumer 52 | # that used URI::Fetch. 53 | my $cache_key = 'URIFetch:'.$uri; 54 | 55 | if ($cache) { 56 | if (my $blob = $cache->get($cache_key)) { 57 | $ref = Storable::thaw($blob); 58 | } 59 | } 60 | 61 | # We just serve anything from the last 60 seconds right out of the cache, 62 | # thus avoiding doing several requests to the same URL when we do 63 | # Yadis, then HTML discovery. 64 | # TODO: Make this tunable? 65 | if ($ref && $ref->{CacheTime} > (time() - 60)) { 66 | $consumer->_debug("Cache HIT for $uri"); 67 | return Net::OpenID::URIFetch::Response->new( 68 | status => 200, 69 | content => $ref->{Content}, 70 | headers => $ref->{Headers}, 71 | final_uri => $ref->{FinalURI}, 72 | ); 73 | } 74 | else { 75 | $consumer->_debug("Cache MISS for $uri"); 76 | } 77 | 78 | my $req = HTTP::Request->new(GET => $uri); 79 | if ($HAS_ZLIB) { 80 | $req->header('Accept-Encoding', 'gzip'); 81 | } 82 | if ($ref) { 83 | if (my $etag = ($ref->{Headers}->{etag})) { 84 | $req->header('If-None-Match', $etag); 85 | } 86 | if (my $ts = ($ref->{Headers}->{'last-modified'})) { 87 | $req->if_modified_since($ts); 88 | } 89 | } 90 | 91 | my $res = $ua->request($req); 92 | 93 | # There are only a few headers that OpenID/Yadis care about 94 | my @useful_headers = qw(last-modified etag content-type x-yadis-location x-xrds-location); 95 | 96 | my %response_fields; 97 | 98 | if ($res->code == HTTP::Status::RC_NOT_MODIFIED()) { 99 | $consumer->_debug("Server says it's not modified. Serving from cache."); 100 | return Net::OpenID::URIFetch::Response->new( 101 | status => 200, 102 | content => $ref->{Content}, 103 | headers => $ref->{Headers}, 104 | final_uri => $ref->{FinalURI}, 105 | ); 106 | } 107 | else { 108 | my $content = $res->content; 109 | my $final_uri = $res->request->uri->as_string(); 110 | my $final_cache_key = "URIFetch:".$final_uri; 111 | 112 | if ($res->content_encoding && $res->content_encoding eq 'gzip') { 113 | $content = Compress::Zlib::memGunzip($content); 114 | } 115 | 116 | if ($content_hook) { 117 | $content_hook->(\$content); 118 | } 119 | 120 | my $headers = {}; 121 | foreach my $k (@useful_headers) { 122 | $headers->{$k} = $res->header($k); 123 | } 124 | 125 | my $ret = Net::OpenID::URIFetch::Response->new( 126 | status => $res->code, 127 | content => $content, 128 | headers => $headers, 129 | final_uri => $final_uri, 130 | ); 131 | 132 | if ($cache && $res->code == 200) { 133 | my $cache_data = { 134 | Headers => $ret->headers, 135 | Content => $ret->content, 136 | CacheTime => time(), 137 | FinalURI => $final_uri, 138 | }; 139 | my $cache_blob = Storable::freeze($cache_data); 140 | $cache->set($final_cache_key, $cache_blob); 141 | $cache->set($cache_key, $cache_blob); 142 | } 143 | 144 | return $ret; 145 | } 146 | 147 | } 148 | 149 | package Net::OpenID::URIFetch::Response; 150 | 151 | sub new { 152 | my ($class, %opts) = @_; 153 | 154 | my $self = {}; 155 | $self->{final_uri} = delete($opts{final_uri}); 156 | $self->{status} = delete($opts{status}); 157 | $self->{content} = delete($opts{content}); 158 | $self->{headers} = delete($opts{headers}); 159 | 160 | return bless $self, $class; 161 | } 162 | 163 | sub final_uri { 164 | return $_[0]->{final_uri}; 165 | } 166 | 167 | sub status { 168 | return $_[0]->{status}; 169 | } 170 | 171 | sub content { 172 | return $_[0]->{content}; 173 | } 174 | 175 | sub headers { 176 | return $_[0]->{headers}; 177 | } 178 | 179 | sub header { 180 | return $_[0]->{headers}{lc($_[1])}; 181 | } 182 | 183 | 1; 184 | -------------------------------------------------------------------------------- /cgi/Lingua/Stem/De.pm: -------------------------------------------------------------------------------- 1 | package Lingua::Stem::De; 2 | 3 | # $RCSfile: De.pm,v $ $Revision: 1.4 $ $Date: 1999/06/24 23:33:37 $ $Author: snowhare $ 4 | 5 | =head1 NAME 6 | 7 | Lingua::Stem::De - Stemming algorithm for German 8 | 9 | =head1 SYNOPSIS 10 | 11 | use Lingua::Stem::De; 12 | my $stems = Lingua::Stem::De::stem({ -words => $word_list_reference, 13 | -locale => 'de', 14 | -exceptions => $exceptions_hash, 15 | }); 16 | 17 | =head1 DESCRIPTION 18 | 19 | This routine applies a stemming slgorithm to a passed anon array of German words, 20 | returning the stemmed words as an anon array. 21 | 22 | It is a 'convienence' wrapper for 'Text::German' that provides 23 | a standardized interface and caching. 24 | 25 | =head1 CHANGES 26 | 27 | 1.01 2003.09.28 - Documentation fix 28 | 29 | 1.00 2003.04.05 - Initial release 30 | 31 | =cut 32 | 33 | ####################################################################### 34 | # Initialization 35 | ####################################################################### 36 | 37 | use strict; 38 | 39 | use Text::German; 40 | 41 | use Exporter; 42 | use Carp; 43 | use vars qw (@ISA @EXPORT_OK @EXPORT %EXPORT_TAGS $VERSION); 44 | BEGIN { 45 | @ISA = qw (Exporter); 46 | @EXPORT = (); 47 | @EXPORT_OK = qw (stem clear_stem_cache stem_caching); 48 | %EXPORT_TAGS = (); 49 | } 50 | $VERSION = "1.01"; 51 | 52 | my $Stem_Caching = 0; 53 | my $Stem_Cache = {}; 54 | 55 | =head1 METHODS 56 | 57 | =cut 58 | 59 | ####################################################################### 60 | 61 | =over 4 62 | 63 | =item stem({ -words => \@words, -locale => 'de', -exceptions => \%exceptions }); 64 | 65 | Stems a list of passed words using the rules of German Returns 66 | an anonymous array reference to the stemmed words. 67 | 68 | Example: 69 | 70 | my $stemmed_words = Lingua::Stem::De::stem({ -words => \@words, 71 | -locale => 'de', 72 | -exceptions => \%exceptions, 73 | }); 74 | 75 | =back 76 | 77 | =cut 78 | 79 | sub stem { 80 | return [] if ($#_ == -1); 81 | my $parm_ref; 82 | if (ref $_[0]) { 83 | $parm_ref = shift; 84 | } else { 85 | $parm_ref = { @_ }; 86 | } 87 | 88 | my $words = []; 89 | my $locale = 'de'; 90 | my $exceptions = {}; 91 | foreach (keys %$parm_ref) { 92 | my $key = lc ($_); 93 | if ($key eq '-words') { 94 | @$words = @{$parm_ref->{$key}}; 95 | } elsif ($key eq '-exceptions') { 96 | $exceptions = $parm_ref->{$key}; 97 | } elsif ($key eq '-locale') { 98 | $locale = $parm_ref->{$key}; 99 | } else { 100 | croak (__PACKAGE__ . "::stem() - Unknown parameter '$key' with value '$parm_ref->{$key}'\n"); 101 | } 102 | } 103 | 104 | local $_; 105 | foreach (@$words) { 106 | 107 | # Check against exceptions list 108 | if (exists $exceptions->{$_}) { 109 | $_ = $exceptions->{$_}; 110 | next; 111 | } 112 | 113 | # Check against cache of stemmed words 114 | my $original_word = $_; 115 | if ($Stem_Caching && exists $Stem_Cache->{$original_word}) { 116 | $_ = $Stem_Cache->{$original_word}; 117 | next; 118 | } 119 | 120 | $_= Text::German::reduce("$_"); 121 | $Stem_Cache->{$original_word} = $_ if $Stem_Caching; 122 | } 123 | $Stem_Cache = {} if ($Stem_Caching < 2); 124 | 125 | return $words; 126 | } 127 | 128 | ############################################################## 129 | 130 | =over 4 131 | 132 | =item stem_caching({ -level => 0|1|2 }); 133 | 134 | Sets the level of stem caching. 135 | 136 | '0' means 'no caching'. This is the default level. 137 | 138 | '1' means 'cache per run'. This caches stemming results during a single 139 | call to 'stem'. 140 | 141 | '2' means 'cache indefinitely'. This caches stemming results until 142 | either the process exits or the 'clear_stem_cache' method is called. 143 | 144 | =back 145 | 146 | =cut 147 | 148 | sub stem_caching { 149 | my $parm_ref; 150 | if (ref $_[0]) { 151 | $parm_ref = shift; 152 | } else { 153 | $parm_ref = { @_ }; 154 | } 155 | my $caching_level = $parm_ref->{-level}; 156 | if (defined $caching_level) { 157 | if ($caching_level !~ m/^[012]$/) { 158 | croak(__PACKAGE__ . "::stem_caching() - Legal values are '0','1' or '2'. '$caching_level' is not a legal value"); 159 | } 160 | $Stem_Caching = $caching_level; 161 | } 162 | return $Stem_Caching; 163 | } 164 | 165 | ############################################################## 166 | 167 | =over 4 168 | 169 | =item clear_stem_cache; 170 | 171 | Clears the cache of stemmed words 172 | 173 | =back 174 | 175 | =cut 176 | 177 | sub clear_stem_cache { 178 | $Stem_Cache = {}; 179 | } 180 | 181 | ############################################################## 182 | 183 | =head1 NOTES 184 | 185 | This code is almost entirely derived from Text::German 186 | written by Ulrich Pfeifer 187 | 188 | =head1 SEE ALSO 189 | 190 | Lingua::Stem Text::German 191 | 192 | =head1 AUTHOR 193 | 194 | Ulrich Pfeifer 195 | 196 | Integration in Lingua::Stem by 197 | Benjamin Franz, FreeRun Technologies, 198 | snowhare@nihongo.org or http://www.nihongo.org/snowhare/ 199 | 200 | =head1 COPYRIGHT 201 | 202 | Ulrich Pfeifer 203 | Benjamin Franz, FreeRun Technologies 204 | 205 | This code is freely available under the same terms as Perl. 206 | 207 | =head1 BUGS 208 | 209 | =head1 TODO 210 | 211 | =cut 212 | 213 | 1; 214 | -------------------------------------------------------------------------------- /cgi/Lingua/Stem/Pt.pm: -------------------------------------------------------------------------------- 1 | package Lingua::Stem::Pt; 2 | 3 | # $RCSfile: De.pm,v $ $Revision: 1.4 $ $Date: 1999/06/24 23:33:37 $ $Author: snowhare $ 4 | 5 | =head1 NAME 6 | 7 | Lingua::Stem::Pt - Stemming algorithm for Portuguese 8 | 9 | =head1 SYNOPSIS 10 | 11 | use Lingua::Stem::Pt; 12 | my $stems = Lingua::Stem::Pt::stem({ -words => $word_list_reference, 13 | -locale => 'pt', 14 | -exceptions => $exceptions_hash, 15 | }); 16 | 17 | =head1 DESCRIPTION 18 | 19 | This routine applies a stemming slgorithm to a passed anon array of Portuguese words, 20 | returning the stemmed words as an anon array. 21 | 22 | It is a 'convienence' wrapper for 'Lingua::Stemmer::PT' that provides 23 | a standardized interface and caching. 24 | 25 | =head1 CHANGES 26 | 27 | 1.01 2003.09.28 - Documenation fix 28 | 29 | 1.00 2003.04.05 - Initial release 30 | 31 | =cut 32 | 33 | ####################################################################### 34 | # Initialization 35 | ####################################################################### 36 | 37 | use strict; 38 | 39 | use Lingua::PT::Stemmer; 40 | 41 | use Exporter; 42 | use Carp; 43 | use vars qw (@ISA @EXPORT_OK @EXPORT %EXPORT_TAGS $VERSION); 44 | BEGIN { 45 | @ISA = qw (Exporter); 46 | @EXPORT = (); 47 | @EXPORT_OK = qw (stem clear_stem_cache stem_caching); 48 | %EXPORT_TAGS = (); 49 | } 50 | $VERSION = "1.01"; 51 | 52 | my $Stem_Caching = 0; 53 | my $Stem_Cache = {}; 54 | 55 | =head1 METHODS 56 | 57 | =cut 58 | 59 | ####################################################################### 60 | 61 | =over 4 62 | 63 | =item stem({ -words => \@words, -locale => 'pt', -exceptions => \%exceptions }); 64 | 65 | Stems a list of passed words using the rules of Portuguese. Returns 66 | an anonymous array reference to the stemmed words. 67 | 68 | Example: 69 | 70 | my $stemmed_words = Lingua::Stem::Pt::stem({ -words => \@words, 71 | -locale => 'pt', 72 | -exceptions => \%exceptions, 73 | }); 74 | 75 | =back 76 | 77 | =cut 78 | 79 | sub stem { 80 | return [] if ($#_ == -1); 81 | my $parm_ref; 82 | if (ref $_[0]) { 83 | $parm_ref = shift; 84 | } else { 85 | $parm_ref = { @_ }; 86 | } 87 | 88 | my $words = []; 89 | my $locale = 'pt'; 90 | my $exceptions = {}; 91 | foreach (keys %$parm_ref) { 92 | my $key = lc ($_); 93 | if ($key eq '-words') { 94 | @$words = @{$parm_ref->{$key}}; 95 | } elsif ($key eq '-exceptions') { 96 | $exceptions = $parm_ref->{$key}; 97 | } elsif ($key eq '-locale') { 98 | $locale = $parm_ref->{$key}; 99 | } else { 100 | croak (__PACKAGE__ . "::stem() - Unknown parameter '$key' with value '$parm_ref->{$key}'\n"); 101 | } 102 | } 103 | 104 | local $_; 105 | foreach (@$words) { 106 | 107 | # Check against exceptions list 108 | if (exists $exceptions->{$_}) { 109 | $_ = $exceptions->{$_}; 110 | next; 111 | } 112 | 113 | # Check against cache of stemmed words 114 | my $original_word = $_; 115 | if ($Stem_Caching && exists $Stem_Cache->{$original_word}) { 116 | $_ = $Stem_Cache->{$original_word}; 117 | next; 118 | } 119 | ($_) = Lingua::PT::Stemmer::stem("$_"); 120 | $Stem_Cache->{$original_word} = $_ if $Stem_Caching; 121 | } 122 | $Stem_Cache = {} if ($Stem_Caching < 2); 123 | 124 | return $words; 125 | } 126 | 127 | ############################################################## 128 | 129 | =over 4 130 | 131 | =item stem_caching({ -level => 0|1|2 }); 132 | 133 | Sets the level of stem caching. 134 | 135 | '0' means 'no caching'. This is the default level. 136 | 137 | '1' means 'cache per run'. This caches stemming results during a single 138 | call to 'stem'. 139 | 140 | '2' means 'cache indefinitely'. This caches stemming results until 141 | either the process exits or the 'clear_stem_cache' method is called. 142 | 143 | =back 144 | 145 | =cut 146 | 147 | sub stem_caching { 148 | my $parm_ref; 149 | if (ref $_[0]) { 150 | $parm_ref = shift; 151 | } else { 152 | $parm_ref = { @_ }; 153 | } 154 | my $caching_level = $parm_ref->{-level}; 155 | if (defined $caching_level) { 156 | if ($caching_level !~ m/^[012]$/) { 157 | croak(__PACKAGE__ . "::stem_caching() - Legal values are '0','1' or '2'. '$caching_level' is not a legal value"); 158 | } 159 | $Stem_Caching = $caching_level; 160 | } 161 | return $Stem_Caching; 162 | } 163 | 164 | ############################################################## 165 | 166 | =over 4 167 | 168 | =item clear_stem_cache; 169 | 170 | Clears the cache of stemmed words 171 | 172 | =back 173 | 174 | =cut 175 | 176 | sub clear_stem_cache { 177 | $Stem_Cache = {}; 178 | } 179 | 180 | ############################################################## 181 | 182 | =head1 NOTES 183 | 184 | This code is a wrapper around Lingua::Stem::Snowball::Pt written by 185 | Ask Solem Hoel, 186 | 187 | =head1 SEE ALSO 188 | 189 | Lingua::Stem Lingua::PT::Stemmer; 190 | 191 | =head1 AUTHOR 192 | 193 | Integration in Lingua::Stem by 194 | Benjamin Franz, FreeRun Technologies, 195 | snowhare@nihongo.org or http://www.nihongo.org/snowhare/ 196 | 197 | =head1 COPYRIGHT 198 | 199 | Benjamin Franz, FreeRun Technologies 200 | 201 | This code is freely available under the same terms as Perl. 202 | 203 | =head1 BUGS 204 | 205 | =head1 TODO 206 | 207 | =cut 208 | 209 | 1; 210 | -------------------------------------------------------------------------------- /cgi/Lingua/Stem/Gl.pm: -------------------------------------------------------------------------------- 1 | package Lingua::Stem::Gl; 2 | 3 | # $RCSfile: De.pm,v $ $Revision: 1.4 $ $Date: 1999/06/24 23:33:37 $ $Author: snowhare $ 4 | 5 | =head1 NAME 6 | 7 | Lingua::Stem::Gl - Stemming algorithm for Galacian 8 | 9 | =head1 SYNOPSIS 10 | 11 | use Lingua::Stem::Gl; 12 | my $stems = Lingua::Stem::Gl::stem({ -words => $word_list_reference, 13 | -locale => 'gl', 14 | -exceptions => $exceptions_hash, 15 | }); 16 | 17 | =head1 DESCRIPTION 18 | 19 | This routine applies a stemming algorithm to a passed anon array of Galician words, 20 | returning the stemmed words as an anon array. 21 | 22 | It is a 'convienence' wrapper for 'Lingua::Stemmer::GL' that provides 23 | a standardized interface and caching. 24 | 25 | =head1 CHANGES 26 | 27 | 1.02 2004.04.26 - Documenation fix 28 | 29 | 1.01 2003.09.28 - Documentation fix 30 | 31 | 1.00 2003.04.05 - Initial release 32 | 33 | =cut 34 | 35 | ####################################################################### 36 | # Initialization 37 | ####################################################################### 38 | 39 | use strict; 40 | 41 | use Lingua::GL::Stemmer; 42 | 43 | use Exporter; 44 | use Carp; 45 | use vars qw (@ISA @EXPORT_OK @EXPORT %EXPORT_TAGS $VERSION); 46 | BEGIN { 47 | @ISA = qw (Exporter); 48 | @EXPORT = (); 49 | @EXPORT_OK = qw (stem clear_stem_cache stem_caching); 50 | %EXPORT_TAGS = (); 51 | } 52 | $VERSION = "1.02"; 53 | 54 | my $Stem_Caching = 0; 55 | my $Stem_Cache = {}; 56 | 57 | =head1 METHODS 58 | 59 | =cut 60 | 61 | ####################################################################### 62 | 63 | =over 4 64 | 65 | =item stem({ -words => \@words, -locale => 'gl', -exceptions => \%exceptions }); 66 | 67 | Stems a list of passed words using the rules of Galican. Returns 68 | an anonymous list reference to the stemmed words. 69 | 70 | Example: 71 | 72 | my $stemmed_words = Lingua::Stem::Gl::stem({ -words => \@words, 73 | -locale => 'gl', 74 | -exceptions => \%exceptions, 75 | }); 76 | 77 | =back 78 | 79 | =cut 80 | 81 | sub stem { 82 | return [] if ($#_ == -1); 83 | my $parm_ref; 84 | if (ref $_[0]) { 85 | $parm_ref = shift; 86 | } else { 87 | $parm_ref = { @_ }; 88 | } 89 | 90 | my $words = []; 91 | my $locale = 'gl'; 92 | my $exceptions = {}; 93 | foreach (keys %$parm_ref) { 94 | my $key = lc ($_); 95 | if ($key eq '-words') { 96 | @$words = @{$parm_ref->{$key}}; 97 | } elsif ($key eq '-exceptions') { 98 | $exceptions = $parm_ref->{$key}; 99 | } elsif ($key eq '-locale') { 100 | $locale = $parm_ref->{$key}; 101 | } else { 102 | croak (__PACKAGE__ . "::stem() - Unknown parameter '$key' with value '$parm_ref->{$key}'\n"); 103 | } 104 | } 105 | 106 | local $_; 107 | foreach (@$words) { 108 | 109 | # Check against exceptions list 110 | if (exists $exceptions->{$_}) { 111 | $_ = $exceptions->{$_}; 112 | next; 113 | } 114 | 115 | # Check against cache of stemmed words 116 | my $original_word = $_; 117 | if ($Stem_Caching && exists $Stem_Cache->{$original_word}) { 118 | $_ = $Stem_Cache->{$original_word}; 119 | next; 120 | } 121 | 122 | ($_) = Lingua::GL::Stemmer::stem("$_"); 123 | $Stem_Cache->{$original_word} = $_ if $Stem_Caching; 124 | } 125 | $Stem_Cache = {} if ($Stem_Caching < 2); 126 | 127 | return $words; 128 | } 129 | 130 | ############################################################## 131 | 132 | =over 4 133 | 134 | =item stem_caching({ -level => 0|1|2 }); 135 | 136 | Sets the level of stem caching. 137 | 138 | '0' means 'no caching'. This is the default level. 139 | 140 | '1' means 'cache per run'. This caches stemming results during a single 141 | call to 'stem'. 142 | 143 | '2' means 'cache indefinitely'. This caches stemming results until 144 | either the process exits or the 'clear_stem_cache' method is called. 145 | 146 | =back 147 | 148 | =cut 149 | 150 | sub stem_caching { 151 | my $parm_ref; 152 | if (ref $_[0]) { 153 | $parm_ref = shift; 154 | } else { 155 | $parm_ref = { @_ }; 156 | } 157 | my $caching_level = $parm_ref->{-level}; 158 | if (defined $caching_level) { 159 | if ($caching_level !~ m/^[012]$/) { 160 | croak(__PACKAGE__ . "::stem_caching() - Legal values are '0','1' or '2'. '$caching_level' is not a legal value"); 161 | } 162 | $Stem_Caching = $caching_level; 163 | } 164 | return $Stem_Caching; 165 | } 166 | 167 | ############################################################## 168 | 169 | =over 4 170 | 171 | =item clear_stem_cache; 172 | 173 | Clears the cache of stemmed words 174 | 175 | =back 176 | 177 | =cut 178 | 179 | sub clear_stem_cache { 180 | $Stem_Cache = {}; 181 | } 182 | 183 | ############################################################## 184 | 185 | =head1 NOTES 186 | 187 | This code is a wrapper around Lingua::Stemmer::GL written by 188 | xern 189 | 190 | =head1 SEE ALSO 191 | 192 | Lingua::Stem Lingua::Stemmer::GL; 193 | 194 | =head1 AUTHOR 195 | 196 | Integration in Lingua::Stem by 197 | Benjamin Franz, FreeRun Technologies, 198 | snowhare@nihongo.org or http://www.nihongo.org/snowhare/ 199 | 200 | =head1 COPYRIGHT 201 | 202 | Benjamin Franz, FreeRun Technologies 203 | 204 | This code is freely available under the same terms as Perl. 205 | 206 | =head1 BUGS 207 | 208 | =head1 TODO 209 | 210 | =cut 211 | 212 | 1; 213 | -------------------------------------------------------------------------------- /cgi/Lingua/Stem/Sv.pm: -------------------------------------------------------------------------------- 1 | package Lingua::Stem::Sv; 2 | 3 | # $RCSfile: De.pm,v $ $Revision: 1.4 $ $Date: 1999/06/24 23:33:37 $ $Author: snowhare $ 4 | 5 | =head1 NAME 6 | 7 | Lingua::Stem::Sv - Stemming algorithm for Swedish 8 | 9 | =head1 SYNOPSIS 10 | 11 | use Lingua::Stem::Sv; 12 | my $stems = Lingua::Stem::Sv::stem({ -words => $word_list_reference, 13 | -locale => 'sv', 14 | -exceptions => $exceptions_hash, 15 | }); 16 | 17 | =head1 DESCRIPTION 18 | 19 | This routine applies a stemming slgorithm to a passed anon array of Swedish words, 20 | returning the stemmed words as an anon array. 21 | 22 | It is a 'convienence' wrapper for 'Lingua::Stem::Snowball::Se' that provides 23 | a standardized interface and caching. 24 | 25 | =head1 CHANGES 26 | 27 | 1.01 2003.09.28 - Documentation fix 28 | 29 | 1.00 2003.04.05 - Initial release 30 | 31 | =cut 32 | 33 | ####################################################################### 34 | # Initialization 35 | ####################################################################### 36 | 37 | use strict; 38 | 39 | use Lingua::Stem::Snowball::Se; 40 | 41 | use Exporter; 42 | use Carp; 43 | use vars qw (@ISA @EXPORT_OK @EXPORT %EXPORT_TAGS $VERSION); 44 | BEGIN { 45 | @ISA = qw (Exporter); 46 | @EXPORT = (); 47 | @EXPORT_OK = qw (stem clear_stem_cache stem_caching); 48 | %EXPORT_TAGS = (); 49 | } 50 | $VERSION = "1.01"; 51 | 52 | my $Stem_Caching = 0; 53 | my $Stem_Cache = {}; 54 | 55 | =head1 METHODS 56 | 57 | =cut 58 | 59 | ####################################################################### 60 | 61 | =over 4 62 | 63 | =item stem({ -words => \@words, -locale => 'sv', -exceptions => \%exceptions }); 64 | 65 | Stems a list of passed words using the rules of Swedish. Returns 66 | an anonymous array reference to the stemmed words. 67 | 68 | Example: 69 | 70 | my $stemmed_words = Lingua::Stem::Sv::stem({ -words => \@words, 71 | -locale => 'sv', 72 | -exceptions => \%exceptions, 73 | }); 74 | 75 | =back 76 | 77 | =cut 78 | 79 | sub stem { 80 | return [] if ($#_ == -1); 81 | my $parm_ref; 82 | if (ref $_[0]) { 83 | $parm_ref = shift; 84 | } else { 85 | $parm_ref = { @_ }; 86 | } 87 | 88 | my $words = []; 89 | my $locale = 'sv'; 90 | my $exceptions = {}; 91 | foreach (keys %$parm_ref) { 92 | my $key = lc ($_); 93 | if ($key eq '-words') { 94 | @$words = @{$parm_ref->{$key}}; 95 | } elsif ($key eq '-exceptions') { 96 | $exceptions = $parm_ref->{$key}; 97 | } elsif ($key eq '-locale') { 98 | $locale = $parm_ref->{$key}; 99 | } else { 100 | croak (__PACKAGE__ . "::stem() - Unknown parameter '$key' with value '$parm_ref->{$key}'\n"); 101 | } 102 | } 103 | 104 | local $_; 105 | my $use_cache = ($Stem_Caching > 1) ? 1 : 0; 106 | my $stemmer = Lingua::Stem::Snowball::Se->new( use_cache => $use_cache ); 107 | foreach (@$words) { 108 | 109 | # Check against exceptions list 110 | if (exists $exceptions->{$_}) { 111 | $_ = $exceptions->{$_}; 112 | next; 113 | } 114 | 115 | # Check against cache of stemmed words 116 | my $original_word = $_; 117 | if ($Stem_Caching && defined ($Stem_Cache->{$original_word})) { 118 | $_ = $Stem_Cache->{$original_word}; 119 | next; 120 | } 121 | 122 | $_ = $stemmer->stem("$_"); 123 | $Stem_Cache->{$original_word} = $_ if $Stem_Caching; 124 | } 125 | $Stem_Cache = {} if ($Stem_Caching < 2); 126 | 127 | return $words; 128 | } 129 | 130 | ############################################################## 131 | 132 | =over 4 133 | 134 | =item stem_caching({ -level => 0|1|2 }); 135 | 136 | Sets the level of stem caching. 137 | 138 | '0' means 'no caching'. This is the default level. 139 | 140 | '1' means 'cache per run'. This caches stemming results during a single 141 | call to 'stem'. 142 | 143 | '2' means 'cache indefinitely'. This caches stemming results until 144 | either the process exits or the 'clear_stem_cache' method is called. 145 | 146 | =back 147 | 148 | =cut 149 | 150 | sub stem_caching { 151 | my $parm_ref; 152 | if (ref $_[0]) { 153 | $parm_ref = shift; 154 | } else { 155 | $parm_ref = { @_ }; 156 | } 157 | my $caching_level = $parm_ref->{-level}; 158 | if (defined $caching_level) { 159 | if ($caching_level !~ m/^[012]$/) { 160 | croak(__PACKAGE__ . "::stem_caching() - Legal values are '0','1' or '2'. '$caching_level' is not a legal value"); 161 | } 162 | $Stem_Caching = $caching_level; 163 | } 164 | return $Stem_Caching; 165 | } 166 | 167 | ############################################################## 168 | 169 | =over 4 170 | 171 | =item clear_stem_cache; 172 | 173 | Clears the cache of stemmed words 174 | 175 | =back 176 | 177 | =cut 178 | 179 | sub clear_stem_cache { 180 | $Stem_Cache = {}; 181 | } 182 | 183 | ############################################################## 184 | 185 | =head1 NOTES 186 | 187 | This code is a wrapper around Lingua::Stem::Snowball::Sv written by 188 | Ask Solem Hoel, 189 | 190 | =head1 SEE ALSO 191 | 192 | Lingua::Stem Lingua::Stem::Snowball::Sv 193 | 194 | =head1 AUTHOR 195 | 196 | Integration in Lingua::Stem by 197 | Benjamin Franz, FreeRun Technologies, 198 | snowhare@nihongo.org or http://www.nihongo.org/snowhare/ 199 | 200 | =head1 COPYRIGHT 201 | 202 | Benjamin Franz, FreeRun Technologies 203 | 204 | This code is freely available under the same terms as Perl. 205 | 206 | =head1 BUGS 207 | 208 | =head1 TODO 209 | 210 | =cut 211 | 212 | 1; 213 | -------------------------------------------------------------------------------- /cgi/Lingua/Stem/No.pm: -------------------------------------------------------------------------------- 1 | package Lingua::Stem::No; 2 | 3 | # $RCSfile: De.pm,v $ $Revision: 1.4 $ $Date: 1999/06/24 23:33:37 $ $Author: snowhare $ 4 | 5 | =head1 NAME 6 | 7 | Lingua::Stem::No - Stemming algorithm for Danish 8 | 9 | =head1 SYNOPSIS 10 | 11 | use Lingua::Stem::No; 12 | my $stems = Lingua::Stem::No::stem({ -words => $word_list_reference, 13 | -locale => 'no', 14 | -exceptions => $exceptions_hash, 15 | }); 16 | 17 | =head1 DESCRIPTION 18 | 19 | This routine applies a stemming slgorithm to a passed anon array of Norwegian words, 20 | returning the stemmed words as an anon array. 21 | 22 | It is a 'convienence' wrapper for 'Lingua::Stem::Snowball::No' that provides 23 | a standardized interface and caching. 24 | 25 | =head1 CHANGES 26 | 27 | 1.01 2003.09.28 - Documenation fix 28 | 29 | 1.00 2003.04.05 - Initial release 30 | 31 | =cut 32 | 33 | ####################################################################### 34 | # Initialization 35 | ####################################################################### 36 | 37 | use strict; 38 | 39 | use Lingua::Stem::Snowball::No; 40 | 41 | use Exporter; 42 | use Carp; 43 | use vars qw (@ISA @EXPORT_OK @EXPORT %EXPORT_TAGS $VERSION); 44 | BEGIN { 45 | @ISA = qw (Exporter); 46 | @EXPORT = (); 47 | @EXPORT_OK = qw (stem clear_stem_cache stem_caching); 48 | %EXPORT_TAGS = (); 49 | } 50 | $VERSION = "1.01"; 51 | 52 | my $Stem_Caching = 0; 53 | my $Stem_Cache = {}; 54 | 55 | =head1 METHODS 56 | 57 | =cut 58 | 59 | ####################################################################### 60 | 61 | =over 4 62 | 63 | =item stem({ -words => \@words, -locale => 'no', -exceptions => \%exceptions }); 64 | 65 | Stems a list of passed words using the rules of Danish. Returns 66 | an anonymous array reference to the stemmed words. 67 | 68 | Example: 69 | 70 | my $stemmed_words = Lingua::Stem::No::stem({ -words => \@words, 71 | -locale => 'no', 72 | -exceptions => \%exceptions, 73 | }); 74 | 75 | =back 76 | 77 | =cut 78 | 79 | sub stem { 80 | return [] if ($#_ == -1); 81 | my $parm_ref; 82 | if (ref $_[0]) { 83 | $parm_ref = shift; 84 | } else { 85 | $parm_ref = { @_ }; 86 | } 87 | 88 | my $words = []; 89 | my $locale = 'no'; 90 | my $exceptions = {}; 91 | foreach (keys %$parm_ref) { 92 | my $key = lc ($_); 93 | if ($key eq '-words') { 94 | @$words = @{$parm_ref->{$key}}; 95 | } elsif ($key eq '-exceptions') { 96 | $exceptions = $parm_ref->{$key}; 97 | } elsif ($key eq '-locale') { 98 | $locale = $parm_ref->{$key}; 99 | } else { 100 | croak (__PACKAGE__ . "::stem() - Unknown parameter '$key' with value '$parm_ref->{$key}'\n"); 101 | } 102 | } 103 | 104 | local $_; 105 | my $use_cache = ($Stem_Caching > 1) ? 1 : 0; 106 | my $stemmer = Lingua::Stem::Snowball::No->new( use_cache => $use_cache ); 107 | foreach (@$words) { 108 | 109 | # Check against exceptions list 110 | if (exists $exceptions->{$_}) { 111 | $_ = $exceptions->{$_}; 112 | next; 113 | } 114 | 115 | # Check against cache of stemmed words 116 | my $original_word = $_; 117 | if ($Stem_Caching && exists $Stem_Cache->{$original_word}) { 118 | $_ = $Stem_Cache->{$original_word}; 119 | next; 120 | } 121 | 122 | $_ = $stemmer->stem("$_"); 123 | $Stem_Cache->{$original_word} = $_ if $Stem_Caching; 124 | } 125 | 126 | $Stem_Cache = {} if ($Stem_Caching < 2); 127 | 128 | return $words; 129 | } 130 | 131 | ############################################################## 132 | 133 | =over 4 134 | 135 | =item stem_caching({ -level => 0|1|2 }); 136 | 137 | Sets the level of stem caching. 138 | 139 | '0' means 'no caching'. This is the default level. 140 | 141 | '1' means 'cache per run'. This caches stemming results during a single 142 | call to 'stem'. 143 | 144 | '2' means 'cache indefinitely'. This caches stemming results until 145 | either the process exits or the 'clear_stem_cache' method is called. 146 | 147 | =back 148 | 149 | =cut 150 | 151 | sub stem_caching { 152 | my $parm_ref; 153 | if (ref $_[0]) { 154 | $parm_ref = shift; 155 | } else { 156 | $parm_ref = { @_ }; 157 | } 158 | my $caching_level = $parm_ref->{-level}; 159 | if (defined $caching_level) { 160 | if ($caching_level !~ m/^[012]$/) { 161 | croak(__PACKAGE__ . "::stem_caching() - Legal values are '0','1' or '2'. '$caching_level' is not a legal value"); 162 | } 163 | $Stem_Caching = $caching_level; 164 | } 165 | return $Stem_Caching; 166 | } 167 | 168 | ############################################################## 169 | 170 | =over 4 171 | 172 | =item clear_stem_cache; 173 | 174 | Clears the cache of stemmed words 175 | 176 | =back 177 | 178 | =cut 179 | 180 | sub clear_stem_cache { 181 | $Stem_Cache = {}; 182 | } 183 | 184 | ############################################################## 185 | 186 | =head1 NOTES 187 | 188 | This code is a wrapper around Lingua::Stem::Snowball::No written by 189 | Ask Solem Hoel, 190 | 191 | =head1 SEE ALSO 192 | 193 | Lingua::Stem Lingua::Stem::Snowball::No 194 | 195 | =head1 AUTHOR 196 | 197 | Integration in Lingua::Stem by 198 | Benjamin Franz, FreeRun Technologies, 199 | snowhare@nihongo.org or http://www.nihongo.org/snowhare/ 200 | 201 | =head1 COPYRIGHT 202 | 203 | Benjamin Franz, FreeRun Technologies 204 | 205 | This code is freely available under the same terms as Perl. 206 | 207 | =head1 BUGS 208 | 209 | =head1 TODO 210 | 211 | =cut 212 | 213 | 1; 214 | -------------------------------------------------------------------------------- /.htaccess: -------------------------------------------------------------------------------- 1 | # .htaccess file for MultiMarkdown based CMS 2 | # 3 | # Copyright (c) 2010 by Fletcher T. Penney 4 | # 5 | # 6 | # 7 | # 8 | # Copyright (C) 2010 Fletcher T. Penney 9 | # 10 | # This program is free software; you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation; either version 2 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # This program is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with this program; if not, write to the 22 | # Free Software Foundation, Inc. 23 | # 59 Temple Place, Suite 330 24 | # Boston, MA 02111-1307 USA 25 | # 26 | # 27 | 28 | 29 | # Customizable Settings 30 | 31 | SetEnv TZ US/Eastern 32 | 33 | 34 | # Keep this to establish default base of / in all other hosts 35 | RewriteCond %{HTTP_HOST} .* 36 | RewriteRule .* - [E=Base_URL:] 37 | 38 | 39 | # Force trailing slash for directories 40 | RewriteCond %{REQUEST_FILENAME} -d 41 | RewriteRule ^(.*[^/]+)$ /$1/ [R] 42 | 43 | 44 | # If your MMD-CMS site is not a "root-level" site, you need to use the 45 | # following pattern to establish a top-level URL 46 | # 47 | # for example, if you reach your site at http://mymachine.local/~user/mmd-cms 48 | # then you would enter the following lines: 49 | # 50 | # RewriteCond %{HTTP_HOST} mymachine.local 51 | # RewriteRule .* - [E=Base_URL:/~user/mmd-cms] 52 | # 53 | # If you use the same folders at multiple URL's, you can repeat this pattern. 54 | # 55 | # If your site is at http://mymachine.local/, then you do not need to use this 56 | # pattern 57 | 58 | # RewriteCond %{HTTP_HOST} mymachine.local 59 | # RewriteRule .* - [E=Base_URL:/~user/mmd-cms] 60 | 61 | 62 | # Now, you can customize the URL to the notfound.html file, e.g. 63 | # ErrorDocument 404 /~user/mmd-cms/notfound.html 64 | # 65 | # Unfortunately, I haven't found a way to do this on a host by host basis 66 | 67 | ErrorDocument 404 /notfound.html 68 | 69 | 70 | # General Settings 71 | 72 | Options +Includes +FollowSymLinks +ExecCGI +Indexes 73 | XBitHack Full 74 | 75 | # AddCharset UTF-8 .html 76 | 77 | AddHandler server-parsed .html 78 | AddHandler cgi-script .cgi 79 | 80 | # What files should we use to index directories? 81 | 82 | DirectoryIndex index index.xhtml index.html 83 | 84 | 85 | 86 | # Pass requests for atom.xml to the script 87 | 88 | RewriteCond %{REQUEST_FILENAME} /atom.xml 89 | RewriteRule ^(.*)$ %{ENV:Base_URL}/cgi/atom.cgi [L,QSA,T=application/xml;charset=UTF-8] 90 | 91 | RewriteCond %{REQUEST_URI} %{ENV:Base_URL}/cgi/atom.cgi 92 | RewriteRule .* - [T=application/xml;charset=UTF-8] 93 | 94 | 95 | # Ditto for atom-comments.xml 96 | 97 | RewriteCond %{REQUEST_FILENAME} /atom-comments.xml 98 | RewriteRule ^(.*)$ %{ENV:Base_URL}/cgi/atom-comments.cgi [L,QSA,T=application/xml;charset=UTF-8] 99 | 100 | RewriteCond %{REQUEST_URI} /cgi/atom-comments.cgi 101 | RewriteRule .* - [T=application/xml;charset=UTF-8] 102 | 103 | 104 | # Google Sitemap to allow better indexing 105 | # Add /sitemap.xml to your google webmaster tools 106 | # https://www.google.com/webmasters/tools/sitemap-list 107 | 108 | RewriteCond %{REQUEST_FILENAME} /sitemap.xml 109 | RewriteRule ^(.*)$ %{ENV:Base_URL}/cgi/google_sitemap.cgi [L,QSA,T=application/xml;charset=UTF-8] 110 | 111 | RewriteCond %{REQUEST_URI} /cgi/google_sitemap.cgi 112 | RewriteRule .* - [T=application/xml;charset=UTF-8] 113 | 114 | 115 | # Archives template 116 | # URLS that look like /YYYY/ or /YYYY/MM/ are passed to archives.html 117 | 118 | RewriteCond %{REQUEST_URI} /[0-9][0-9][0-9][0-9](/[0-9][0-9])?//?index.html$ 119 | RewriteRule ^(.*)$ %{ENV:Base_URL}/templates/archives.html 120 | 121 | RewriteCond %{REQUEST_URI} /archives\/?$ 122 | RewriteRule ^(.*)$ %{ENV:Base_URL}/templates/archives.html 123 | 124 | 125 | # TagMap 126 | RewriteCond %{REQUEST_URI} /tags\/?$ 127 | RewriteRule ^(.*)$ %{ENV:Base_URL}/templates/tagmap.html [L,QSA] 128 | 129 | # Tag Search 130 | 131 | RewriteEngine on 132 | RewriteCond %{REQUEST_URI} /tags\/.+$ 133 | RewriteRule ^\/?tags\/?(.*)$ %{ENV:Base_URL}/templates/tags.html?query=$1 [L,QSA] 134 | 135 | 136 | # Local Site Search 137 | RewriteCond %{REQUEST_URI} /search 138 | RewriteRule ^\/?search\/?(.*)$ %{ENV:Base_URL}/templates/search.html$1 [L,QSA] 139 | 140 | 141 | 142 | # Pass requests to proper files 143 | RewriteEngine on 144 | 145 | # If the .html file exists, use it 146 | RewriteCond %{REQUEST_FILENAME}.html -f 147 | RewriteRule ^(.*)$ $1.html [L,QSA] 148 | 149 | # If the directory exists, use the index.html for that directory 150 | RewriteCond %{REQUEST_FILENAME} -d 151 | RewriteRule ^(.*?)/?$ %{ENV:Base_URL}/$1/index.html [L,QSA] 152 | 153 | 154 | 155 | # FINALLY!!!! 156 | # Send proper mime type for XHTML!!! 157 | # 158 | # Thanks to: 159 | # http://bitworking.org/news/134/Content-Negotiation-Considered-Harmful-Again 160 | # 161 | # To serve your website in HTML instead of XHTML, comment out or delete the 162 | # following lines. FYI - MathML will not work properly in HTML, but 163 | # everything else should. 164 | 165 | # RewriteCond %{HTTP_ACCEPT} application/xhtml\+xml 166 | # RewriteCond %{HTTP_ACCEPT} !application/xhtml\+xml\s*;\s*q=0\.?0*(\s|,|$) 167 | # RewriteCond %{REQUEST_URI} !(notfound.html|test.html)$ 168 | # RewriteCond %{REQUEST_URI} \.html$ 169 | # RewriteRule .* - [T=application/xhtml+xml;charset=UTF-8] 170 | # RewriteCond %{HTTP_ACCEPT} application/xhtml\+xml 171 | # RewriteCond %{HTTP_ACCEPT} !application/xhtml\+xml\s*;\s*q=0\.?0*(\s|,|$) 172 | # RewriteCond %{REQUEST_URI} !(notfound.html|test.html)$ 173 | # RewriteCond %{REQUEST_URI} !\. 174 | # RewriteRule .* - [T=application/xhtml+xml;charset=UTF-8] 175 | -------------------------------------------------------------------------------- /cgi/Lingua/Stem/Da.pm: -------------------------------------------------------------------------------- 1 | package Lingua::Stem::Da; 2 | 3 | # $RCSfile: De.pm,v $ $Revision: 1.4 $ $Date: 1999/06/24 23:33:37 $ $Author: snowhare $ 4 | 5 | =head1 NAME 6 | 7 | Lingua::Stem::Da - Stemming algorithm for Danish 8 | 9 | =head1 SYNOPSIS 10 | 11 | use Lingua::Stem::Da; 12 | my $stems = Lingua::Stem::Da::stem({ -words => $word_list_reference, 13 | -locale => 'da', 14 | -exceptions => $exceptions_hash, 15 | }); 16 | 17 | =head1 DESCRIPTION 18 | 19 | This routine applies a stemming slgorithm to a passed anon array of Danish words, 20 | returning the stemmed words as an anon array. 21 | 22 | It is a 'convienence' wrapper for 'Lingua::Stem::Snowball::Da' that provides 23 | a standardized interface and caching. 24 | 25 | =head1 CHANGES 26 | 27 | 1.01 2003.09.28 - Documentation fix. 28 | 29 | 1.00 2003.04.05 - Initial release 30 | 31 | =cut 32 | 33 | ####################################################################### 34 | # Initialization 35 | ####################################################################### 36 | 37 | use strict; 38 | 39 | use Lingua::Stem::Snowball::Da; 40 | 41 | use Exporter; 42 | use Carp; 43 | use vars qw (@ISA @EXPORT_OK @EXPORT %EXPORT_TAGS $VERSION); 44 | BEGIN { 45 | @ISA = qw (Exporter); 46 | @EXPORT = (); 47 | @EXPORT_OK = qw (stem clear_stem_cache stem_caching); 48 | %EXPORT_TAGS = (); 49 | } 50 | $VERSION = "1.01"; 51 | 52 | my $Stem_Caching = 0; 53 | my $Stem_Cache = {}; 54 | 55 | =head1 METHODS 56 | 57 | =cut 58 | 59 | ####################################################################### 60 | 61 | =over 4 62 | 63 | =item stem({ -words => \@words, -locale => 'da', -exceptions => \%exceptions }); 64 | 65 | Stems a list of passed words using the rules of Danish. Returns 66 | an anonymous array reference to the stemmed words. 67 | 68 | Example: 69 | 70 | my $stemmed_words = Lingua::Stem::Da::stem({ -words => \@words, 71 | -locale => 'da', 72 | -exceptions => \%exceptions, 73 | }); 74 | 75 | =back 76 | 77 | =cut 78 | 79 | sub stem { 80 | return [] if ($#_ == -1); 81 | my $parm_ref; 82 | if (ref $_[0]) { 83 | $parm_ref = shift; 84 | } else { 85 | $parm_ref = { @_ }; 86 | } 87 | 88 | my $words = []; 89 | my $locale = 'en'; 90 | my $exceptions = {}; 91 | foreach (keys %$parm_ref) { 92 | my $key = lc ($_); 93 | if ($key eq '-words') { 94 | @$words = @{$parm_ref->{$key}}; 95 | } elsif ($key eq '-exceptions') { 96 | $exceptions = $parm_ref->{$key}; 97 | } elsif ($key eq '-locale') { 98 | $locale = $parm_ref->{$key}; 99 | } else { 100 | croak (__PACKAGE__ . "::stem() - Unknown parameter '$key' with value '$parm_ref->{$key}'\n"); 101 | } 102 | } 103 | 104 | local $_; 105 | my $use_cache = ($Stem_Caching > 1) ? 1 :0; 106 | my $stemmer = Lingua::Stem::Snowball::Da->new( use_cache => $use_cache ); 107 | foreach (@$words) { 108 | 109 | # Check against exceptions list 110 | if (exists $exceptions->{$_}) { 111 | $_ = $exceptions->{$_}; 112 | next; 113 | } 114 | 115 | # Check against cache of stemmed words 116 | my $original_word = $_; 117 | if ($Stem_Caching && exists $Stem_Cache->{$original_word}) { 118 | $_ = $Stem_Cache->{$original_word}; 119 | next; 120 | } 121 | 122 | $_ = $stemmer->stem("$_"); 123 | $Stem_Cache->{$original_word} = $_ if $Stem_Caching; 124 | } 125 | $Stem_Cache = {} if ($Stem_Caching < 2); 126 | 127 | return $words; 128 | } 129 | 130 | ############################################################## 131 | 132 | =over 4 133 | 134 | =item stem_caching({ -level => 0|1|2 }); 135 | 136 | Sets the level of stem caching. 137 | 138 | '0' means 'no caching'. This is the default level. 139 | 140 | '1' means 'cache per run'. This caches stemming results during a single 141 | call to 'stem'. 142 | 143 | '2' means 'cache indefinitely'. This caches stemming results until 144 | either the process exits or the 'clear_stem_cache' method is called. 145 | 146 | =back 147 | 148 | =cut 149 | 150 | sub stem_caching { 151 | my $parm_ref; 152 | if (ref $_[0]) { 153 | $parm_ref = shift; 154 | } else { 155 | $parm_ref = { @_ }; 156 | } 157 | my $caching_level = $parm_ref->{-level}; 158 | if (defined $caching_level) { 159 | if ($caching_level !~ m/^[012]$/) { 160 | croak(__PACKAGE__ . "::stem_caching() - Legal values are '0','1' or '2'. '$caching_level' is not a legal value"); 161 | } 162 | $Stem_Caching = $caching_level; 163 | } 164 | return $Stem_Caching; 165 | } 166 | 167 | ############################################################## 168 | 169 | =over 4 170 | 171 | =item clear_stem_cache; 172 | 173 | Clears the cache of stemmed words 174 | 175 | =back 176 | 177 | =cut 178 | 179 | sub clear_stem_cache { 180 | $Stem_Cache = {}; 181 | } 182 | 183 | ############################################################## 184 | 185 | =head1 NOTES 186 | 187 | This code is a wrapper around Lingua::Stem::Snowball::Da written by 188 | 189 | Dennis Haney 190 | 191 | and 192 | 193 | Ask Solem Hoel, (Swedish version) 194 | 195 | =head1 SEE ALSO 196 | 197 | Lingua::Stem Lingua::Stem::Snowball::Da 198 | 199 | =head1 AUTHOR 200 | 201 | Integration in Lingua::Stem by 202 | Benjamin Franz, FreeRun Technologies, 203 | snowhare@nihongo.org or http://www.nihongo.org/snowhare/ 204 | 205 | =head1 COPYRIGHT 206 | 207 | Benjamin Franz, FreeRun Technologies 208 | 209 | This code is freely available under the same terms as Perl. 210 | 211 | =head1 BUGS 212 | 213 | =head1 TODO 214 | 215 | =cut 216 | 217 | 1; 218 | -------------------------------------------------------------------------------- /cgi/Net/OpenID/Consumer/Lite.pm: -------------------------------------------------------------------------------- 1 | package Net::OpenID::Consumer::Lite; 2 | use strict; 3 | use warnings; 4 | use 5.00800; 5 | our $VERSION = '0.02'; 6 | use LWP::UserAgent; 7 | use Carp (); 8 | 9 | my $TIMEOUT = 4; 10 | our $IGNORE_SSL_ERROR = 0; 11 | 12 | sub _ua { 13 | my $agent = "Net::OpenID::Consumer::Lite/$Net::OpenID::Consumer::Lite::VERSION"; 14 | LWP::UserAgent->new( 15 | agent => $agent, 16 | timeout => $TIMEOUT, 17 | max_redirect => 0, 18 | ); 19 | } 20 | 21 | sub _get { 22 | my $url = shift; 23 | my $ua = _ua(); 24 | my $res = $ua->get($url); 25 | unless ($IGNORE_SSL_ERROR) { 26 | if ( my $warnings = $res->header('Client-SSL-Warning') ) { 27 | Carp::croak("invalid ssl? ${url}, ${warnings}"); 28 | } 29 | } 30 | unless ($res->is_success) { 31 | Carp::croak("cannot get $url : @{[ $res->status_line ]}"); 32 | } 33 | $res; 34 | } 35 | 36 | sub check_url { 37 | my ($class, $server_url, $return_to, $extensions) = (shift, shift, shift, shift); 38 | Carp::croak("missing params") unless $return_to; 39 | Carp::croak("this module supports only https: $server_url") unless $server_url =~ /^https/; 40 | 41 | my $url = URI->new($server_url); 42 | my %args = ( 43 | 'openid.mode' => 'checkid_immediate', 44 | 'openid.return_to' => $return_to, 45 | ); 46 | if ($extensions) { 47 | my $i = 1; 48 | while (my ($ns, $args) = each %$extensions) { 49 | my $ext_alias = "e$i"; 50 | $args{"openid.ns.$ext_alias"} = $ns; 51 | while (my ($key, $val) = each %$args) { 52 | $args{"openid.${ext_alias}.${key}"} = $val; 53 | } 54 | $i++; 55 | } 56 | } 57 | $url->query_form(%args); 58 | return $url->as_string; 59 | } 60 | 61 | sub _check_authentication { 62 | my ($class, $request) = @_; 63 | my $url = do { 64 | $request->{'openid.mode'} = 'check_authentication'; 65 | my $request_url = URI->new($request->{'openid.op_endpoint'}); 66 | $request_url->query_form(%$request); 67 | $request_url; 68 | }; 69 | my $res = _get($url); 70 | $res->is_success() or die "cannot load $url"; 71 | my $content = $res->content; 72 | return _parse_keyvalue($content)->{is_valid} ? 1 : 0; 73 | } 74 | 75 | sub handle_server_response { 76 | my $class = shift; 77 | my $request = shift; 78 | my %callbacks_in = @_; 79 | my %callbacks = (); 80 | 81 | for my $cb (qw(not_openid setup_required cancelled verified error)) { 82 | $callbacks{$cb} = delete( $callbacks_in{$cb} ) 83 | || sub { Carp::croak( "No " . $cb . " callback" ) }; 84 | } 85 | 86 | my $mode = $request->{'openid.mode'}; 87 | unless ($mode) { 88 | return $callbacks{not_openid}->(); 89 | } 90 | 91 | if ($mode eq 'cancel') { 92 | return $callbacks{cancelled}->(); 93 | } 94 | 95 | if (my $url = $request->{'openid.user_setup_url'}) { 96 | return $callbacks{'setup_required'}->($url); 97 | } 98 | 99 | if ($class->_check_authentication($request)) { 100 | my $vident; 101 | for my $key (split /,/, $request->{'openid.signed'}) { 102 | $vident->{$key} = $request->{"openid.$key"}; 103 | } 104 | return $callbacks{'verified'}->($vident); 105 | } else { 106 | return $callbacks{'error'}->(); 107 | } 108 | } 109 | 110 | sub _parse_keyvalue { 111 | my $reply = shift; 112 | my %ret; 113 | $reply =~ s/\r//g; 114 | foreach ( split /\n/, $reply ) { 115 | next unless /^(\S+?):(.*)/; 116 | $ret{$1} = $2; 117 | } 118 | return \%ret; 119 | } 120 | 121 | 122 | 1; 123 | __END__ 124 | 125 | =encoding utf8 126 | 127 | =head1 NAME 128 | 129 | Net::OpenID::Consumer::Lite - OpenID consumer library for minimalist 130 | 131 | =head1 SYNOPSIS 132 | 133 | use Net::OpenID::Consumer::Lite; 134 | my $csr = Net::OpenID::Consumer::Lite->new(); 135 | 136 | # get check url 137 | my $check_url = Net::OpenID::Consumer::Lite->check_url( 138 | 'https://mixi.jp/openid_server.pl', # OpenID server url 139 | 'http://example.com/back_to_here', # return url 140 | { 141 | "http://openid.net/extensions/sreg/1.1" => { required => join( ",", qw/email nickname/ ) } 142 | }, # extensions(optional) 143 | ); 144 | 145 | # handle response of OP 146 | Net::OpenID::Consumer::Lite->handle_server_response( 147 | $request => ( 148 | not_openid => sub { 149 | die "Not an OpenID message"; 150 | }, 151 | setup_required => sub { 152 | my $setup_url = shift; 153 | # Redirect the user to $setup_url 154 | }, 155 | cancelled => sub { 156 | # Do something appropriate when the user hits "cancel" at the OP 157 | }, 158 | verified => sub { 159 | my $vident = shift; 160 | # Do something with the VerifiedIdentity object $vident 161 | }, 162 | error => sub { 163 | my $err = shift; 164 | die($err); 165 | }, 166 | ) 167 | ); 168 | 169 | =head1 DESCRIPTION 170 | 171 | Net::OpenID::Consumer::Lite is limited version of OpenID consumer library. 172 | This module works fast.This module works well on rental server/CGI. 173 | 174 | This module depend to L, (L|L) and L. 175 | This module doesn't depend to L!! 176 | 177 | =head1 LIMITATION 178 | 179 | This module supports OpenID 2.0 only. 180 | This module supports SSL OPs only. 181 | This module doesn't care the XRDS Location. Please pass me the real OpenID server path. 182 | 183 | =head1 How to solve SSL Certifications Error 184 | 185 | If L or L says "Peer certificate not verified" or other error messages, 186 | please see the manual of your SSL libraries =) This is SSL library's problem. 187 | 188 | =head1 AUTHOR 189 | 190 | Tokuhiro Matsuno Etokuhirom@gmail.comE 191 | 192 | =head1 SEE ALSO 193 | 194 | L 195 | 196 | =head1 LICENSE 197 | 198 | This library is free software; you can redistribute it and/or modify 199 | it under the same terms as Perl itself. 200 | 201 | =cut 202 | -------------------------------------------------------------------------------- /cgi/TagCategorizer.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | # 3 | # TagCategorizer Version 1.01 4 | # 5 | # Copyright (C) 2005-2010 Fletcher T. Penney 6 | # 7 | # 8 | # Given a list of objects and their associated tags, use this information 9 | # to develop a graph of the relationships between tags 10 | # 11 | # Inspired by an idea by Kaspar Schiess 12 | # http://eule.isa-geek.com/ 13 | # 14 | # 15 | # This program is free software; you can redistribute it and/or modify 16 | # it under the terms of the GNU General Public License as published by 17 | # the Free Software Foundation; either version 2 of the License, or 18 | # (at your option) any later version. 19 | # 20 | # This program is distributed in the hope that it will be useful, 21 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | # GNU General Public License for more details. 24 | # 25 | # You should have received a copy of the GNU General Public License 26 | # along with this program; if not, write to the 27 | # Free Software Foundation, Inc. 28 | # 59 Temple Place, Suite 330 29 | # Boston, MA 02111-1307 USA 30 | # 31 | # 32 | # Input format: 33 | # 34 | # 35 | # 36 | # unique id 37 | # tag1 38 | # long tag 39 | # 40 | # 41 | # 42 | # Output format: 43 | # 44 | # 45 | # 46 | # 47 | # object identifier 48 | # 49 | # another object 50 | # 51 | # 52 | # 53 | 54 | package TagCategorizer; 55 | 56 | # Globals 57 | 58 | my %g_processedSets = (); 59 | my %g_synonyms = (); 60 | my %g_hierarchy = (); 61 | my %g_supersets = (); 62 | my %g_subsets = (); 63 | my %g_tagSets = (); 64 | 65 | # Command-line version - import xml data as above 66 | # Import stdin (xml format) 67 | undef $/; 68 | $data = <>; 69 | 70 | if ($data ne "") { 71 | ProcessXML($data); 72 | } 73 | 74 | # Need a module version that allows direct access to 75 | # the hash 76 | 77 | 78 | sub ProcessXML{ 79 | ($xml) = @_; 80 | while ($xml =~ /\(.*?)\<\/object\>/gsi ) { 81 | $object = $1; 82 | 83 | if ($object =~ /\(.*?)\<\/id\>/s) { 84 | $id = $1; 85 | $id =~ s/^[ \t\n\r]*(.*?)[ \n\r]*$/$1/; 86 | $id =~ s/[ \t\n\r]/ /gs; 87 | } { 88 | while ($object =~ /\(.*?)\<\/tag\>/gsi ) { 89 | $tag = $1; 90 | $tag =~ s/^[ \t\n\r]*(.*?)[ \n\r]*$/$1/; 91 | $tag =~ s/[ \t\n\r]/ /gs; 92 | $tag =~ s/"//g; #" 93 | 94 | # Populate hash 95 | $g_tagSets{$tag}{$id} = 1; 96 | } 97 | } 98 | } 99 | 100 | # Now, process the parsed data 101 | ProcessSets(%g_tagSets); 102 | } 103 | 104 | sub PrintHash{ 105 | (%theSets) = @_; 106 | foreach $tag ( sort keys %theSets ) { 107 | print "$tag\n"; 108 | foreach $id (sort keys %{$theSets{$tag}} ) { 109 | print "\t$id\n"; 110 | } 111 | print "\n"; 112 | } 113 | } 114 | 115 | sub ProcessSets{ 116 | (%rawSets) = @_; 117 | 118 | # Iterate through each set 119 | foreach $raw ( sort keys %rawSets ) { 120 | $isSynonym = 0; 121 | # Compare to processed sets 122 | foreach $processed (sort keys %g_processedSets) { 123 | next if $isSynonym; 124 | $subset = 125 | IsSubset(\%{$rawSets{$raw}}, %{$g_processedSets{$processed}}); 126 | $superset = 127 | IsSubset(\%{$g_processedSets{$processed}}, %{$rawSets{$raw}}); 128 | 129 | if ($superset && $subset) { 130 | # This is a synonym to another set 131 | $g_synonyms{$processed}{$raw} = 1; 132 | $isSynonym = 1; 133 | } else { 134 | # Only p 135 | if ( $subset ) { 136 | $g_supersets{$raw}{$processed} = 1; 137 | $g_subsets{$processed}{$raw} = 1; 138 | } 139 | 140 | if ($superset) { 141 | $g_subsets{$raw}{$processed} = 1; 142 | $g_supersets{$processed}{$raw} = 1; 143 | } 144 | } 145 | } 146 | 147 | if (! $isSynonym) { 148 | # Add set to the processed hash 149 | %{$g_processedSets{$raw}} = %{$rawSets{$raw}}; 150 | } 151 | } 152 | 153 | # Now, take the processed sets and compose a hierarchy 154 | 155 | # First, strip any synonyms that made it through 156 | PruneSynonyms(); 157 | 158 | foreach $tag (sort keys %g_processedSets) { 159 | 160 | # Find all top-level items (e.g. no supersets) 161 | my $count = (keys %{$g_supersets{$tag}}); 162 | if ($count == 0) { 163 | # This is a top-level item 164 | 165 | # Clean it up 166 | PruneNode($tag); 167 | } 168 | 169 | } 170 | 171 | print "\n"; 172 | print PrintHierarchy(); 173 | } 174 | 175 | sub IsSubset { 176 | # Is %$a a subset of %b? 177 | ($a, %b) = @_; 178 | 179 | foreach $member (sort keys %$a) { 180 | return 0 if $b{$member} != 1; 181 | } 182 | return 1; 183 | } 184 | 185 | sub SynonymName { 186 | ($tag) = @_; 187 | my $result = $tag; 188 | 189 | foreach (sort keys %{$g_synonyms{$tag}}) { 190 | $result .= "/" . $_; 191 | } 192 | 193 | return $result; 194 | } 195 | 196 | 197 | sub PruneNode { 198 | # children/grandchildren should not be on same level 199 | # similarly, parents/grandparents should not be on same level 200 | ($self) = @_; 201 | 202 | @descendants = (sort keys %{$g_subsets{$self}}); 203 | 204 | foreach $a (@descendants) { 205 | foreach $b (@descendants) { 206 | next if ($a eq $b); 207 | if ($g_subsets{$a}{$b} == 1) { 208 | # $b is a subset of $a 209 | delete $g_subsets{$self}{$b} 210 | } 211 | } 212 | } 213 | 214 | # Now, tags of children should not be in parent 215 | 216 | foreach $child (sort keys %{$g_subsets{$self}}) { 217 | foreach (sort keys %{$g_tagSets{$child}}) { 218 | delete $g_tagSets{$self}{$_}; 219 | } 220 | } 221 | 222 | # Now, do the same things for descendants 223 | 224 | foreach (sort keys %{$g_subsets{$self}}) { 225 | PruneNode($_); 226 | } 227 | } 228 | 229 | sub PrintHierarchy{ 230 | my $result; 231 | my $tabs = "\t"; 232 | 233 | $result = "\n"; 234 | 235 | foreach $tag (sort {lc $a cmp lc $b} keys %g_processedSets) { 236 | 237 | # Find all top-level items (e.g. no supersets) 238 | my $count = (keys %{$g_supersets{$tag}}); 239 | if ($count == 0) { 240 | $result.= PrintNode($tag,$tabs); 241 | } 242 | } 243 | 244 | $result .= "\n"; 245 | 246 | return $result; 247 | } 248 | 249 | sub PrintNode{ 250 | my ($self, $tabs) = @_; 251 | my $result; 252 | 253 | $result .= $tabs . "\n"; 254 | $tabs .= "\t"; 255 | 256 | foreach (sort keys %{$g_subsets{$self}}) { 257 | $result .= PrintNode($_,$tabs); 258 | } 259 | 260 | foreach (sort keys %{$g_tagSets{$self}}) { 261 | $result .= $tabs . "" .$_ . "\n"; 262 | } 263 | 264 | $tabs =~ s/\t$//; 265 | 266 | $result .= $tabs . "\n"; 267 | 268 | return $result; 269 | } 270 | 271 | sub PruneSynonyms{ 272 | 273 | foreach (sort keys %g_synonyms) { 274 | foreach $synonym (sort keys %{$g_synonyms{$_}}) { 275 | foreach (sort keys %g_subsets) { 276 | delete $g_subsets{$_}{$synonym}; 277 | } 278 | } 279 | } 280 | } -------------------------------------------------------------------------------- /cgi/Crypt/DH.pm: -------------------------------------------------------------------------------- 1 | # $Id: DH.pm 1860 2005-06-11 06:15:44Z btrott $ 2 | 3 | package Crypt::DH; 4 | use strict; 5 | 6 | use Math::BigInt lib => "GMP,Pari"; 7 | our $VERSION = '0.06'; 8 | 9 | sub new { 10 | my $class = shift; 11 | my $dh = bless {}, $class; 12 | 13 | my %param = @_; 14 | for my $w (qw( p g priv_key )) { 15 | next unless exists $param{$w}; 16 | $dh->$w(delete $param{$w}); 17 | } 18 | die "Unknown parameters to constructor: " . join(", ", keys %param) if %param; 19 | 20 | $dh; 21 | } 22 | 23 | BEGIN { 24 | no strict 'refs'; 25 | for my $meth (qw( p g pub_key priv_key )) { 26 | *$meth = sub { 27 | my $key = shift; 28 | if (@_) { 29 | $key->{$meth} = _any2bigint(shift); 30 | } 31 | my $ret = $key->{$meth} || ""; 32 | $ret; 33 | }; 34 | } 35 | } 36 | 37 | sub _any2bigint { 38 | my($value) = @_; 39 | if (ref $value eq 'Math::BigInt') { 40 | return $value; 41 | } 42 | elsif (ref $value eq 'Math::Pari') { 43 | return Math::BigInt->new(Math::Pari::pari2pv($value)); 44 | } 45 | elsif (defined $value && !(ref $value)) { 46 | return Math::BigInt->new($value); 47 | } 48 | elsif (defined $value) { 49 | die "Unknown parameter type: $value\n"; 50 | } 51 | } 52 | 53 | sub generate_keys { 54 | my $dh = shift; 55 | 56 | unless (defined $dh->{priv_key}) { 57 | my $i = _bitsize($dh->{p}) - 1; 58 | $dh->{priv_key} = 59 | $Crypt::Random::VERSION ? 60 | Crypt::Random::makerandom_itv(Strength => 0, Uniform => 1, 61 | Lower => 1, Upper => $dh->{p} - 1) : 62 | _makerandom_itv($i, 1, $dh->{p} - 1); 63 | } 64 | 65 | $dh->{pub_key} = $dh->{g}->copy->bmodpow($dh->{priv_key}, $dh->{p}); 66 | } 67 | 68 | sub compute_key { 69 | my $dh = shift; 70 | my $pub_key = _any2bigint(shift); 71 | $pub_key->copy->bmodpow($dh->{priv_key}, $dh->{p}); 72 | } 73 | *compute_secret = \&compute_key; 74 | 75 | sub _bitsize { 76 | return length($_[0]->as_bin) - 2; 77 | } 78 | 79 | sub _makerandom_itv { 80 | my ($size, $min_inc, $max_exc) = @_; 81 | 82 | while (1) { 83 | my $r = _makerandom($size); 84 | return $r if $r >= $min_inc && $r < $max_exc; 85 | } 86 | } 87 | 88 | sub _makerandom { 89 | my $size = shift; 90 | 91 | my $bytes = int($size / 8) + ($size % 8 ? 1 : 0); 92 | 93 | my $rand; 94 | if (-e "/dev/urandom") { 95 | my $fh; 96 | open($fh, '/dev/urandom') 97 | or die "Couldn't open /dev/urandom"; 98 | my $got = sysread $fh, $rand, $bytes; 99 | die "Didn't read all bytes from urandom" unless $got == $bytes; 100 | close $fh; 101 | } else { 102 | for (1..$bytes) { 103 | $rand .= chr(int(rand(256))); 104 | } 105 | } 106 | 107 | my $bits = unpack("b*", $rand); 108 | die unless length($bits) >= $size; 109 | 110 | Math::BigInt->new('0b' . substr($bits, 0, $size)); 111 | } 112 | 113 | 1; 114 | __END__ 115 | 116 | =head1 NAME 117 | 118 | Crypt::DH - Diffie-Hellman key exchange system 119 | 120 | =head1 SYNOPSIS 121 | 122 | use Crypt::DH; 123 | my $dh = Crypt::DH->new; 124 | $dh->g($g); 125 | $dh->p($p); 126 | 127 | ## Generate public and private keys. 128 | $dh->generate_keys; 129 | 130 | $my_pub_key = $dh->pub_key; 131 | 132 | ## Send $my_pub_key to "other" party, and receive "other" 133 | ## public key in return. 134 | 135 | ## Now compute shared secret from "other" public key. 136 | my $shared_secret = $dh->compute_secret( $other_pub_key ); 137 | 138 | =head1 DESCRIPTION 139 | 140 | I is a Perl implementation of the Diffie-Hellman key 141 | exchange system. Diffie-Hellman is an algorithm by which two 142 | parties can agree on a shared secret key, known only to them. 143 | The secret is negotiated over an insecure network without the 144 | two parties ever passing the actual shared secret, or their 145 | private keys, between them. 146 | 147 | =head1 THE ALGORITHM 148 | 149 | The algorithm generally works as follows: Party A and Party B 150 | choose a property I

    and a property I; these properties are 151 | shared by both parties. Each party then computes a random private 152 | key integer I, where the length of I is at 153 | most (number of bits in I

    ) - 1. Each party then computes a 154 | public key based on I, I, and I

    ; the exact value 155 | is 156 | 157 | g ^ priv_key mod p 158 | 159 | The parties exchange these public keys. 160 | 161 | The shared secret key is generated based on the exchanged public 162 | key, the private key, and I

    . If the public key of Party B is 163 | denoted I, then the shared secret is equal to 164 | 165 | pub_key_B ^ priv_key mod p 166 | 167 | The mathematical principles involved insure that both parties will 168 | generate the same shared secret key. 169 | 170 | More information can be found in PKCS #3 (Diffie-Hellman Key 171 | Agreement Standard): 172 | 173 | http://www.rsasecurity.com/rsalabs/pkcs/pkcs-3/ 174 | 175 | =head1 USAGE 176 | 177 | I implements the core routines needed to use 178 | Diffie-Hellman key exchange. To actually use the algorithm, 179 | you'll need to start with values for I

    and I; I

    is a 180 | large prime, and I is a base which must be larger than 0 181 | and less than I

    . 182 | 183 | I uses I internally for big-integer 184 | calculations. All accessor methods (I

    , I, I, and 185 | I) thus return I objects, as does the 186 | I method. The accessors, however, allow setting with a 187 | scalar decimal string, hex string (^0x), Math::BigInt object, or 188 | Math::Pari object (for backwards compatibility). 189 | 190 | =head2 $dh = Crypt::DH->new([ %param ]). 191 | 192 | Constructs a new I object and returns the object. 193 | I<%param> may include none, some, or all of the keys I

    , I, and 194 | I. 195 | 196 | =head2 $dh->p([ $p ]) 197 | 198 | Given an argument I<$p>, sets the I

    parameter (large prime) for 199 | this I object. 200 | 201 | Returns the current value of I

    . (as a Math::BigInt object) 202 | 203 | =head2 $dh->g([ $g ]) 204 | 205 | Given an argument I<$g>, sets the I parameter (base) for 206 | this I object. 207 | 208 | Returns the current value of I. 209 | 210 | =head2 $dh->generate_keys 211 | 212 | Generates the public and private key portions of the I 213 | object, assuming that you've already filled I

    and I with 214 | appropriate values. 215 | 216 | If you've provided a priv_key, it's used, otherwise a random priv_key 217 | is created using either Crypt::Random (if already loaded), or 218 | /dev/urandom, or Perl's rand, in that order. 219 | 220 | =head2 $dh->compute_secret( $public_key ) 221 | 222 | Given the public key I<$public_key> of Party B (the party with which 223 | you're performing key negotiation and exchange), computes the shared 224 | secret key, based on that public key, your own private key, and your 225 | own large prime value (I

    ). 226 | 227 | The historical method name "compute_key" is aliased to this for 228 | compatibility. 229 | 230 | =head2 $dh->priv_key([ $priv_key ]) 231 | 232 | Returns the private key. Given an argument I<$priv_key>, sets the 233 | I parameter for this I object. 234 | 235 | =head2 $dh->pub_key 236 | 237 | Returns the public key. 238 | 239 | =head1 AUTHOR & COPYRIGHT 240 | 241 | Benjamin Trott, ben@rhumba.pair.com 242 | 243 | Brad Fitzpatrick, brad@danga.com 244 | 245 | Except where otherwise noted, Crypt::DH is Copyright 2001 246 | Benjamin Trott. All rights reserved. Crypt::DH is free 247 | software; you may redistribute it and/or modify it under 248 | the same terms as Perl itself. 249 | 250 | =cut 251 | -------------------------------------------------------------------------------- /cgi/Net/OpenID/Association.pm: -------------------------------------------------------------------------------- 1 | use strict; 2 | use Carp (); 3 | 4 | ############################################################################ 5 | package Net::OpenID::Association; 6 | use fields ( 7 | 'server', # author-identity identity server endpoint 8 | 'secret', # the secret for this association 9 | 'handle', # the 255-character-max ASCII printable handle (33-126) 10 | 'expiry', # unixtime, adjusted, of when this association expires 11 | 'type', # association type 12 | ); 13 | 14 | use Storable (); 15 | use Digest::SHA1 qw(sha1); 16 | 17 | sub new { 18 | my Net::OpenID::Association $self = shift; 19 | $self = fields::new( $self ) unless ref $self; 20 | my %opts = @_; 21 | for my $f (qw( server secret handle expiry type )) { 22 | $self->{$f} = delete $opts{$f}; 23 | } 24 | Carp::croak("unknown options: " . join(", ", keys %opts)) if %opts; 25 | return $self; 26 | } 27 | 28 | sub handle { 29 | my $self = shift; 30 | die if @_; 31 | $self->{'handle'}; 32 | } 33 | 34 | sub secret { 35 | my $self = shift; 36 | die if @_; 37 | $self->{'secret'}; 38 | } 39 | 40 | sub type { 41 | my $self = shift; 42 | die if @_; 43 | $self->{'type'}; 44 | } 45 | 46 | sub server { 47 | my Net::OpenID::Association $self = shift; 48 | Carp::croak("Too many parameters") if @_; 49 | return $self->{server}; 50 | } 51 | 52 | sub expired { 53 | my Net::OpenID::Association $self = shift; 54 | return time() > $self->{'expiry'}; 55 | } 56 | 57 | sub usable { 58 | my Net::OpenID::Association $self = shift; 59 | return 0 unless $self->{'handle'} =~ /^[\x21-\x7e]{1,255}$/; 60 | return 0 unless $self->{'expiry'} =~ /^\d+$/; 61 | return 0 unless $self->{'secret'}; 62 | return 0 if $self->expired; 63 | return 1; 64 | } 65 | 66 | 67 | # return a handle for an identity server, or undef if 68 | # no local storage/cache is available, in which case the caller 69 | # goes into dumb consumer mode. will do a POST and allocate 70 | # a new assoc_handle if none is found, or has expired 71 | sub server_assoc { 72 | my ($csr, $server, $force_reassociate, %opts) = @_; 73 | 74 | my $protocol_version = delete $opts{protocol_version} || 1; 75 | Carp::croak("unknown options: " . join(", ", keys %opts)) if %opts; 76 | 77 | # closure to return undef (dumb consumer mode) and log why 78 | my $dumb = sub { 79 | $csr->_debug("server_assoc: dumb mode: $_[0]"); 80 | return undef; 81 | }; 82 | 83 | my $cache = $csr->cache; 84 | return $dumb->("no_cache") unless $cache; 85 | 86 | unless ($force_reassociate) { 87 | # try first from cached association handle 88 | if (my $handle = $cache->get("shandle:$server")) { 89 | my $assoc = handle_assoc($csr, $server, $handle); 90 | 91 | if ($assoc && $assoc->usable) { 92 | $csr->_debug("Found association from cache (handle=$handle)"); 93 | return $assoc; 94 | } 95 | } 96 | } 97 | 98 | # make a new association 99 | my $dh = _default_dh(); 100 | 101 | my %post = ( 102 | "openid.mode" => "associate", 103 | "openid.assoc_type" => "HMAC-SHA1", 104 | "openid.session_type" => "DH-SHA1", 105 | "openid.dh_consumer_public" => OpenID::util::bi2arg($dh->pub_key), 106 | ); 107 | 108 | if ($protocol_version == 2) { 109 | $post{"openid.ns"} = OpenID::util::version_2_namespace(); 110 | } 111 | 112 | my $req = HTTP::Request->new(POST => $server); 113 | $req->header("Content-Type" => "application/x-www-form-urlencoded"); 114 | $req->content(join("&", map { "$_=" . OpenID::util::eurl($post{$_}) } keys %post)); 115 | 116 | $csr->_debug("Associate mode request: " . $req->content); 117 | 118 | my $ua = $csr->ua; 119 | my $res = $ua->request($req); 120 | 121 | # uh, some failure, let's go into dumb mode? 122 | return $dumb->("http_failure_no_associate") unless $res && $res->is_success; 123 | 124 | my $recv_time = time(); 125 | my $content = $res->content; 126 | my %args = OpenID::util::parse_keyvalue($content); 127 | $csr->_debug("Response to associate mode: [$content] parsed = " . join(",", %args)); 128 | 129 | return $dumb->("unknown_assoc_type") unless $args{'assoc_type'} eq "HMAC-SHA1"; 130 | 131 | my $stype = $args{'session_type'}; 132 | return $dumb->("unknown_session_type") if $stype && $stype ne "DH-SHA1"; 133 | 134 | # protocol version 1.1 135 | my $expires_in = $args{'expires_in'}; 136 | 137 | # protocol version 1.0 (DEPRECATED) 138 | if (! $expires_in) { 139 | if (my $issued = OpenID::util::w3c_to_time($args{'issued'})) { 140 | my $expiry = OpenID::util::w3c_to_time($args{'expiry'}); 141 | my $replace_after = OpenID::util::w3c_to_time($args{'replace_after'}); 142 | 143 | # seconds ahead (positive) or behind (negative) the server is 144 | $expires_in = ($replace_after || $expiry) - $issued; 145 | } 146 | } 147 | 148 | # between 1 second and 2 years 149 | return $dumb->("bogus_expires_in") unless $expires_in > 0 && $expires_in < 63072000; 150 | 151 | my $ahandle = $args{'assoc_handle'}; 152 | 153 | my $secret; 154 | if ($stype ne "DH-SHA1") { 155 | $secret = OpenID::util::d64($args{'mac_key'}); 156 | } else { 157 | my $server_pub = OpenID::util::arg2bi($args{'dh_server_public'}); 158 | my $dh_sec = $dh->compute_secret($server_pub); 159 | $secret = OpenID::util::d64($args{'enc_mac_key'}) ^ sha1(OpenID::util::bi2bytes($dh_sec)); 160 | } 161 | return $dumb->("secret_not_20_bytes") unless length($secret) == 20; 162 | 163 | my %assoc = ( 164 | handle => $ahandle, 165 | server => $server, 166 | secret => $secret, 167 | type => $args{'assoc_type'}, 168 | expiry => $recv_time + $expires_in, 169 | ); 170 | 171 | my $assoc = Net::OpenID::Association->new( %assoc ); 172 | return $dumb->("assoc_undef") unless $assoc; 173 | 174 | $cache->set("hassoc:$server:$ahandle", Storable::freeze(\%assoc)); 175 | $cache->set("shandle:$server", $ahandle); 176 | 177 | # now we test that the cache object given to us actually works. if it 178 | # doesn't, it'll also fail later, making the verify fail, so let's 179 | # go into stateless (dumb mode) earlier if we can detect this. 180 | $cache->get("shandle:$server") 181 | or return $dumb->("cache_broken"); 182 | 183 | return $assoc; 184 | } 185 | 186 | # returns association, or undef if it can't be found 187 | sub handle_assoc { 188 | my ($csr, $server, $handle) = @_; 189 | 190 | # closure to return undef (dumb consumer mode) and log why 191 | my $dumb = sub { 192 | $csr->_debug("handle_assoc: dumb mode: $_[0]"); 193 | return undef; 194 | }; 195 | 196 | return $dumb->("no_handle") unless $handle; 197 | 198 | my $cache = $csr->cache; 199 | return $dumb->("no_cache") unless $cache; 200 | 201 | my $frozen = $cache->get("hassoc:$server:$handle"); 202 | return $dumb->("not_in_cache") unless $frozen; 203 | 204 | my $param = eval { Storable::thaw($frozen) }; 205 | return $dumb->("not_a_hashref") unless ref $param eq "HASH"; 206 | 207 | return Net::OpenID::Association->new( %$param ); 208 | } 209 | 210 | sub invalidate_handle { 211 | my ($csr, $server, $handle) = @_; 212 | my $cache = $csr->cache 213 | or return; 214 | $cache->set("hassoc:$server:$handle", ""); 215 | } 216 | 217 | sub _default_dh { 218 | my $dh = Crypt::DH->new; 219 | $dh->p("155172898181473697471232257763715539915724801966915404479707795314057629378541917580651227423698188993727816152646631438561595825688188889951272158842675419950341258706556549803580104870537681476726513255747040765857479291291572334510643245094715007229621094194349783925984760375594985848253359305585439638443"); 220 | $dh->g("2"); 221 | $dh->generate_keys; 222 | return $dh; 223 | } 224 | 225 | 226 | 227 | 1; 228 | 229 | __END__ 230 | 231 | =head1 NAME 232 | 233 | Net::OpenID::Association - a relationship with an identity server 234 | 235 | =head1 DESCRIPTION 236 | 237 | Internal class. 238 | 239 | =head1 COPYRIGHT, WARRANTY, AUTHOR 240 | 241 | See L for author, copyrignt and licensing information. 242 | 243 | =head1 SEE ALSO 244 | 245 | L 246 | 247 | L 248 | 249 | L 250 | 251 | Website: L 252 | 253 | -------------------------------------------------------------------------------- /cgi/Net/OpenID/IndirectMessage.pm: -------------------------------------------------------------------------------- 1 | 2 | package Net::OpenID::IndirectMessage; 3 | 4 | use strict; 5 | use Carp; 6 | use Net::OpenID::Consumer; 7 | 8 | sub new { 9 | my $class = shift; 10 | my $what = shift; 11 | my %opts = @_; 12 | 13 | my $self = bless {}, $class; 14 | 15 | $self->{minimum_version} = delete $opts{minimum_version}; 16 | 17 | Carp::croak("Unknown options: " . join(", ", keys %opts)) if %opts; 18 | 19 | my $getter; 20 | my $enumer; 21 | if (ref $what eq "HASH") { 22 | # In this case it's the caller's responsibility to determine 23 | # whether the method is GET or POST. 24 | $getter = sub { $what->{$_[0]}; }; 25 | $enumer = sub { keys(%$what); }; 26 | } 27 | elsif (UNIVERSAL::isa($what, "CGI")) { 28 | # CGI automatically does what we need when method is POST 29 | $getter = sub { scalar $what->param($_[0]); }; 30 | $enumer = sub { $what->param; }; 31 | } 32 | elsif (ref $what eq "Apache") { 33 | my %get; 34 | if ($what->method eq 'POST') { 35 | %get = $what->content; 36 | } 37 | else { 38 | %get = $what->args; 39 | } 40 | $getter = sub { $get{$_[0]}; }; 41 | $enumer = sub { keys(%get); }; 42 | } 43 | elsif (ref $what eq "Apache::Request") { 44 | # Apache::Request includes the POST and GET arguments in ->param 45 | # when doing a POST request, which is close enough to what 46 | # the spec requires. 47 | $getter = sub { scalar $what->param($_[0]); }; 48 | $enumer = sub { $what->param; }; 49 | } 50 | elsif (ref $what eq "CODE") { 51 | $getter = $what; 52 | # We can't enumerate with just a coderef. 53 | # OpenID 2 spec only requires enumeration to support 54 | # extension namespaces, so we don't care too much. 55 | $enumer = sub { return (); }; 56 | } 57 | else { 58 | $what = 'undef' if !defined $what; 59 | Carp::croak("Unknown parameter type ($what)"); 60 | } 61 | $self->{getter} = $getter; 62 | $self->{enumer} = $enumer; 63 | 64 | # Now some quick pre-configuration of a few bits 65 | 66 | # Is this an OpenID message at all? 67 | # All OpenID messages have an openid.mode value... 68 | return undef unless $self->get('mode'); 69 | 70 | # Is this an OpenID 2.0 message? 71 | my $ns = $self->get('ns'); 72 | 73 | 74 | # The 2.0 spec section 4.1.2 requires that we support these namespace values 75 | # but act like it's a normal 1.1 request. 76 | # We do this by just pretending that ns wasn't set at all. 77 | if ($ns && ($ns eq 'http://openid.net/signon/1.1' || $ns eq 'http://openid.net/signon/1.0')) { 78 | $ns = undef; 79 | } 80 | 81 | if (defined($ns) && $ns eq OpenID::util::version_2_namespace()) { 82 | $self->{protocol_version} = 2; 83 | } 84 | elsif (! defined($ns)) { 85 | # No namespace at all means a 1.1 message 86 | if (($self->{minimum_version}||0) <= 1) { 87 | $self->{protocol_version} = 1; 88 | } 89 | else { 90 | # Pretend we don't understand the message. 91 | return undef; 92 | } 93 | } 94 | else { 95 | # Unknown version is the same as not being an OpenID message at all 96 | return undef; 97 | } 98 | 99 | # This will be populated in on demand 100 | $self->{extension_prefixes} = undef; 101 | 102 | return $self; 103 | } 104 | 105 | sub protocol_version { 106 | return $_[0]->{protocol_version}; 107 | } 108 | 109 | sub mode { 110 | my $self = shift; 111 | return $self->get('mode'); 112 | } 113 | 114 | sub get { 115 | my $self = shift; 116 | my $key = shift or Carp::croak("No argument name supplied to get method"); 117 | 118 | # NOTE: There is intentionally no way to get all of the keys in the core 119 | # namespace because that means we don't need to be able to enumerate 120 | # to support the core protocol, and there is no requirement to enumerate 121 | # anyway. 122 | 123 | # Arguments can only contain letters, numbers, underscores and dashes 124 | Carp::croak("Invalid argument key $key") unless $key =~ /^[\w\-]+$/; 125 | Carp::croak("Too many arguments") if scalar(@_); 126 | 127 | return $self->{getter}->("openid.$key"); 128 | } 129 | 130 | sub raw_get { 131 | my $self = shift; 132 | my $key = shift or Carp::croak("No argument name supplied to raw_get method"); 133 | 134 | return $self->{getter}->($key); 135 | } 136 | 137 | sub getter { 138 | my $self = shift; 139 | 140 | return $self->{getter}; 141 | } 142 | 143 | sub get_ext { 144 | my $self = shift; 145 | my $namespace = shift or Carp::croak("No namespace URI supplied to get_ext method"); 146 | my $key = shift; 147 | 148 | Carp::croak("Too many arguments") if scalar(@_); 149 | 150 | $self->_compute_extension_prefixes() unless defined($self->{extension_prefixes}); 151 | 152 | my $alias = $self->{extension_prefixes}{$namespace}; 153 | return $key ? undef : {} unless $alias; 154 | 155 | if ($key) { 156 | return $self->{getter}->("openid.$alias.$key"); 157 | } 158 | else { 159 | my $prefix = "openid.$alias."; 160 | my $prefixlen = length($prefix); 161 | my $ret = {}; 162 | foreach my $key ($self->{enumer}->()) { 163 | next unless substr($key, 0, $prefixlen) eq $prefix; 164 | $ret->{substr($key, $prefixlen)} = $self->{getter}->($key); 165 | } 166 | return $ret; 167 | } 168 | } 169 | 170 | sub has_ext { 171 | my $self = shift; 172 | my $namespace = shift or Carp::croak("No namespace URI supplied to get_ext method"); 173 | 174 | Carp::croak("Too many arguments") if scalar(@_); 175 | 176 | $self->_compute_extension_prefixes() unless defined($self->{extension_prefixes}); 177 | 178 | return defined($self->{extension_prefixes}{$namespace}) ? 1 : 0; 179 | } 180 | 181 | sub _compute_extension_prefixes { 182 | my ($self) = @_; 183 | 184 | return unless $self->{enumer}; 185 | 186 | $self->{extension_prefixes} = {}; 187 | if ($self->protocol_version != 1) { 188 | foreach my $key ($self->{enumer}->()) { 189 | next unless $key =~ /^openid\.ns\.(\w+)$/; 190 | my $alias = $1; 191 | my $uri = $self->{getter}->($key); 192 | $self->{extension_prefixes}{$uri} = $alias; 193 | } 194 | } 195 | else { 196 | # Synthesize the SREG namespace as it was used in OpenID 1.1 197 | $self->{extension_prefixes}{"http://openid.net/extensions/sreg/1.1"} = "sreg"; 198 | } 199 | } 200 | 201 | 1; 202 | 203 | =head1 NAME 204 | 205 | Net::OpenID::IndirectMessage - Class representing a collection of namespaced arguments 206 | 207 | =head1 DESCRIPTION 208 | 209 | This class acts as an abstraction layer over a collection of flat URL arguments 210 | which supports namespaces as defined by the OpenID Auth 2.0 specification. 211 | 212 | It also recognises when its is given OpenID 1.1 non-namespaced arguments and 213 | acts as if the relevant namespaces were present. In this case, it only 214 | supports the basic OpenID 1.1 arguments and the extension arguments 215 | for Simple Registration. 216 | 217 | This class can operate on a normal hashref, a L object, an L 218 | object, an L object or an arbitrary C ref that takes 219 | a key name as its first parameter and returns a value. However, 220 | if you use a coderef then extension arguments are not supported. 221 | 222 | If you pass in a hashref or a coderef it is your responsibility as the caller 223 | to check the HTTP request method and pass in the correct set of arguments. If 224 | you use an Apache, Apache::Request or CGI object then this module will do 225 | the right thing automatically. 226 | 227 | =head1 SYNOPSIS 228 | 229 | use Net::OpenID::IndirectMessage; 230 | 231 | # Pass in something suitable for the underlying flat dictionary. 232 | # Will return an instance if the request arguments can be understood 233 | # as a supported OpenID Message format. 234 | # Will return undef if this doesn't seem to be an OpenID Auth message. 235 | # Will croak if the $argumenty_thing is not of a suitable type. 236 | my $args = Net::OpenID::IndirectMessage->new($argumenty_thing); 237 | 238 | # Determine which protocol version the message is using. 239 | # Currently this can be either 1 for 1.1 or 2 for 2.0. 240 | # Expect larger numbers for other versions in future. 241 | # Most callers don't really need to care about this. 242 | my $version = $args->protocol_version(); 243 | 244 | # Get a core argument value ("openid.mode") 245 | my $mode = $args->get("mode"); 246 | 247 | # Get an extension argument value 248 | my $nickname = $args->get_ext("http://openid.net/extensions/sreg/1.1", "nickname"); 249 | 250 | # Get hashref of all arguments in a given namespace 251 | my $sreg = $args->get_ext("http://openid.net/extensions/sreg/1.1"); 252 | 253 | Most of the time callers won't need to use this class directly, but will instead 254 | access it through a L instance. 255 | 256 | -------------------------------------------------------------------------------- /cgi/Lingua/Stem/EnBroken.pm: -------------------------------------------------------------------------------- 1 | package Lingua::Stem::EnBroken; 2 | 3 | # $RCSfile: En.pm,v $ $Revision: 1.4 $ $Date: 1999/06/24 23:33:37 $ $Author: snowhare $ 4 | 5 | =head1 NAME 6 | 7 | Lingua::Stem::EnBroken - Porter's stemming algorithm for 'generic' English 8 | 9 | =head1 SYNOPSIS 10 | 11 | use Lingua::Stem::EnBroken; 12 | my $stems = Lingua::Stem::EnBroken::stem({ -words => $word_list_reference, 13 | -locale => 'en', 14 | -exceptions => $exceptions_hash, 15 | }); 16 | 17 | =head1 DESCRIPTION 18 | 19 | This routine MIS-applies the Porter Stemming Algorithm to its parameters, 20 | returning the stemmed words. It is an intentionally broken version 21 | of Lingua::Stem::En for people needing backwards compatibility with 22 | Lingua::Stem 0.30 and Lingua::Stem 0.40. Do not use it if you aren't 23 | one of those people. 24 | 25 | It is derived from the C program "stemmer.c" 26 | as found in freewais and elsewhere, which contains these notes: 27 | 28 | Purpose: Implementation of the Porter stemming algorithm documented 29 | in: Porter, M.F., "An Algorithm For Suffix Stripping," 30 | Program 14 (3), July 1980, pp. 130-137. 31 | Provenance: Written by B. Frakes and C. Cox, 1986. 32 | 33 | I have re-interpreted areas that use Frakes and Cox's "WordSize" 34 | function. My version may misbehave on short words starting with "y", 35 | but I can't think of any examples. 36 | 37 | The step numbers correspond to Frakes and Cox, and are probably in 38 | Porter's article (which I've not seen). 39 | Porter's algorithm still has rough spots (e.g current/currency, -ings words), 40 | which I've not attempted to cure, although I have added 41 | support for the British -ise suffix. 42 | 43 | =head1 CHANGES 44 | 45 | 46 | 2003.09.28 - Documentation fix 47 | 48 | 2000.09.14 - Forked from the Lingua::Stem::En.pm module to provide 49 | a backward compatibly broken version for people needing 50 | consistent behavior with 0.30 and 0.40 more than accurate 51 | stemming. 52 | 53 | =cut 54 | 55 | ####################################################################### 56 | # Initialization 57 | ####################################################################### 58 | 59 | use strict; 60 | use Exporter; 61 | use Carp; 62 | use vars qw (@ISA @EXPORT_OK @EXPORT %EXPORT_TAGS $VERSION); 63 | BEGIN { 64 | @ISA = qw (Exporter); 65 | @EXPORT = (); 66 | @EXPORT_OK = qw (stem clear_stem_cache stem_caching); 67 | %EXPORT_TAGS = (); 68 | } 69 | $VERSION = "2.13"; 70 | 71 | my $Stem_Caching = 0; 72 | my $Stem_Cache = {}; 73 | 74 | # 75 | #V Porter.pm V2.11 25 Aug 2000 stemming cache 76 | # Porter.pm V2.1 21 Jun 1999 with '&$sub if defined' not 'eval ""' 77 | # Porter.pm V2.0 25 Nov 1994 (for Perl 5.000) 78 | # porter.pl V1.0 10 Aug 1994 (for Perl 4.036) 79 | # Jim Richardson, University of Sydney 80 | # jimr@maths.usyd.edu.au or http://www.maths.usyd.edu.au:8000/jimr.html 81 | 82 | # Find a canonical stem for a word, assumed to consist entirely of 83 | # lower-case letters. The approach is from 84 | # 85 | # M. F. Porter, An algorithm for suffix stripping, Program (Automated 86 | # Library and Information Systems) 14 (3) 130-7, July 1980. 87 | # 88 | # This algorithm is used by WAIS: for example, see freeWAIS-0.3 at 89 | # 90 | # http://kudzu.cnidr.org/cnidr_projects/cnidr_projects.html 91 | 92 | # Some additional rules are used here, mainly to allow for British spellings 93 | # like -ise. They are marked ** in the code. 94 | 95 | # Initialization required before using subroutine stem: 96 | 97 | # We count syllables slightly differently from Porter: we say the syllable 98 | # count increases on each occurrence in the word of an adjacent pair 99 | # 100 | # [aeiouy][^aeiou] 101 | # 102 | # This avoids any need to define vowels and consonants, or confusion over 103 | # 'y'. It also works slightly better: our definition gives two syllables 104 | # in 'yttrium', while Porter's gives only one because the initial 'y' is 105 | # taken to be a consonant. But it is not quite obvious: for example, 106 | # consider 'mayfly' where, when working backwards (see below), the 'yf' 107 | # matches the above pattern, even though it is the 'ay' which in Porter's 108 | # terms increments the syllable count. 109 | # 110 | # We wish to match the above in context, working backwards from the end of 111 | # the word: the appropriate regular expression is 112 | 113 | my $syl = '[aeiou]*[^aeiou][^aeiouy]*[aeiouy]'; 114 | 115 | # (This works because [^aeiouy] is a subset of [^aeiou].) If we want two 116 | # syllables ("m>1" in Porter's terminology) we can just match $syl$syl. 117 | 118 | # For step 1b we need to be able to detect the presence of a vowel: here 119 | # we revert to Porter's definition that a vowel is [aeiou], or y preceded 120 | # by a consonant. (If the . below is a vowel, then the . is the desired 121 | # vowel; if the . is a consonant the y is the desired vowel.) 122 | 123 | my $hasvow = '[^aeiouy]*([aeiou]|y.)'; 124 | 125 | =head1 METHODS 126 | 127 | =cut 128 | 129 | ####################################################################### 130 | 131 | =over 4 132 | 133 | =item stem({ -words => \@words, -locale => 'en', -exceptions => \%exceptions }); 134 | 135 | Stems a list of passed words using the rules of US English. Returns 136 | an anonymous array reference to the stemmed words. 137 | 138 | Example: 139 | 140 | my $stemmed_words = Lingua::Stem::EnBroken::stem({ -words => \@words, 141 | -locale => 'en', 142 | -exceptions => \%exceptions, 143 | }); 144 | 145 | =back 146 | 147 | =cut 148 | 149 | sub stem { 150 | return [] if ($#_ == -1); 151 | my $parm_ref; 152 | if (ref $_[0]) { 153 | $parm_ref = shift; 154 | } else { 155 | $parm_ref = { @_ }; 156 | } 157 | 158 | my $words = []; 159 | my $locale = 'en'; 160 | my $exceptions = {}; 161 | foreach (keys %$parm_ref) { 162 | my $key = lc ($_); 163 | if ($key eq '-words') { 164 | @$words = @{$parm_ref->{$key}}; 165 | } elsif ($key eq '-exceptions') { 166 | $exceptions = $parm_ref->{$key}; 167 | } elsif ($key eq '-locale') { 168 | $locale = $parm_ref->{$key}; 169 | } else { 170 | croak (__PACKAGE__ . "::stem() - Unknown parameter '$key' with value '$parm_ref->{$key}'\n"); 171 | } 172 | } 173 | 174 | local( $_ ); 175 | foreach (@$words) { 176 | 177 | # Flatten case 178 | $_ = lc $_; 179 | 180 | # Check against exceptions list 181 | if (exists $exceptions->{$_}) { 182 | $_ = $exceptions->{$_}; 183 | next; 184 | } 185 | 186 | # Check against cache of stemmed words 187 | my $original_word = $_; 188 | if ($Stem_Caching && exists $Stem_Cache->{$original_word}) { 189 | $_ = $Stem_Cache->{$original_word}; 190 | next; 191 | } 192 | 193 | # Step 0 - remove punctuation 194 | s/'s$//; s/^[^a-z]+//; s/[^a-z]+$//; 195 | next unless /^[a-z]+$/; 196 | 197 | # Reverse the word so we can easily apply pattern matching to the end: 198 | $_ = reverse $_; 199 | 200 | # Step 1a: plurals -- sses->ss, ies->i, ss->ss, s->0 201 | 202 | m!^s! && ( s!^se(ss|i)!$1! || s!^s([^s])!$1! ); 203 | 204 | # Step 1b: participles -- SYLeed->SYLee, VOWed->VOW, VOWing->VOW; 205 | # but ated->ate etc 206 | 207 | s!^dee($syl)!ee$1!o || 208 | ( 209 | s!^(de|gni)($hasvow)!$2!o && 210 | ( 211 | # at->ate, bl->ble, iz->ize, is->ise 212 | s!^(ta|lb|[sz]i)!e$1! || # ** ise as well as ize 213 | # CC->C (C consonant other than l, s, z) 214 | s!^([^aeioulsz])\1!$1! || 215 | # (m=1) CVD->CVDe (C consonant, V vowel, D consonant not w, x, y) 216 | s!^([^aeiouwxy][aeiouy][^aeiou]+)$!e$1! 217 | ) 218 | ); 219 | 220 | # Step 1c: change y to i: happy->happi, sky->sky 221 | 222 | s!^y($hasvow)!i$1!o; 223 | 224 | # Step 2: double and triple suffices (part 1) 225 | 226 | # Switch on last three letters (fails harmlessly if subroutine undefined) -- 227 | # thanks to Ian Phillipps who wrote 228 | # CPAN authors/id/IANPX/Stem-0.1.tar.gz 229 | # for suggesting the replacement of 230 | # eval( '&S2' . unpack( 'a3', $_ ) ); 231 | # (where the eval ignores undefined subroutines) by the much faster 232 | # eval { &{ 'S2' . substr( $_, 0, 3 ) } }; 233 | # But the following is slightly faster still: 234 | 235 | my $sub; 236 | 237 | &$sub if defined &{ $sub = 'S2' . substr( $_, 0, 3 ) }; 238 | 239 | # Step 3: double and triple suffices, etc (part 2) 240 | 241 | &$sub if defined &{ $sub = 'S3' . substr( $_, 0, 3 ) }; 242 | 243 | # Step 4: single suffices on polysyllables 244 | 245 | &$sub if defined &{ $sub = 'S4' . substr( $_, 0, 2 ) }; 246 | 247 | # Step 5a: tidy up final e -- probate->probat, rate->rate; cease->ceas 248 | 249 | m!^e! && ( s!^e($syl$syl)!$1!o || 250 | 251 | # Porter's ( m=1 and not *o ) E where o = cvd with d a consonant 252 | # not w, x or y: 253 | 254 | ! m!^e[^aeiouwxy][aeiouy][^aeiou]! && # not *o E 255 | s!^e($syl[aeiouy]*[^aeiou]*)$!$1!o # m=1 256 | ); 257 | 258 | # Step 5b: double l -- controll->control, roll->roll 259 | # ** Note correction: Porter has m>1 here ($syl$syl), but it seems m>0 260 | # ($syl) is wanted to strip an l off controll. 261 | 262 | s!^ll($syl)!l$1!o; 263 | 264 | $_ = scalar( reverse $_ ); 265 | 266 | $Stem_Cache->{$original_word} = $_ if $Stem_Caching; 267 | } 268 | $Stem_Cache = {} if ($Stem_Caching < 2); 269 | 270 | return $words; 271 | } 272 | 273 | ############################################################## 274 | 275 | =over 4 276 | 277 | =item stem_caching({ -level => 0|1|2 }); 278 | 279 | Sets the level of stem caching. 280 | 281 | '0' means 'no caching'. This is the default level. 282 | 283 | '1' means 'cache per run'. This caches stemming results during a single 284 | call to 'stem'. 285 | 286 | '2' means 'cache indefinitely'. This caches stemming results until 287 | either the process exits or the 'clear_stem_cache' method is called. 288 | 289 | =back 290 | 291 | =cut 292 | 293 | sub stem_caching { 294 | my $parm_ref; 295 | if (ref $_[0]) { 296 | $parm_ref = shift; 297 | } else { 298 | $parm_ref = { @_ }; 299 | } 300 | my $caching_level = $parm_ref->{-level}; 301 | if (defined $caching_level) { 302 | if ($caching_level !~ m/^[012]$/) { 303 | croak(__PACKAGE__ . "::stem_caching() - Legal values are '0','1' or '2'. '$caching_level' is not a legal value"); 304 | } 305 | $Stem_Caching = $caching_level; 306 | } 307 | return $Stem_Caching; 308 | } 309 | 310 | ############################################################## 311 | 312 | =over 4 313 | 314 | =item clear_stem_cache; 315 | 316 | Clears the cache of stemmed words 317 | 318 | =back 319 | 320 | =cut 321 | 322 | sub clear_stem_cache { 323 | $Stem_Cache = {}; 324 | } 325 | 326 | ############################################################## 327 | 328 | =head1 NOTES 329 | 330 | This code is almost entirely derived from the Porter 2.1 module 331 | written by Jim Richardson. 332 | 333 | =head1 SEE ALSO 334 | 335 | Lingua::Stem 336 | 337 | =head1 AUTHOR 338 | 339 | Jim Richardson, University of Sydney 340 | jimr@maths.usyd.edu.au or http://www.maths.usyd.edu.au:8000/jimr.html 341 | 342 | Integration in Lingua::Stem by 343 | Benjamin Franz, FreeRun Technologies, 344 | snowhare@nihongo.org or http://www.nihongo.org/snowhare/ 345 | 346 | =head1 COPYRIGHT 347 | 348 | Jim Richardson, University of Sydney 349 | Benjamin Franz, FreeRun Technologies 350 | 351 | This code is freely available under the same terms as Perl. 352 | 353 | =head1 BUGS 354 | 355 | =head1 TODO 356 | 357 | =cut 358 | 359 | 1; 360 | -------------------------------------------------------------------------------- /cgi/Net/OpenID/VerifiedIdentity.pm: -------------------------------------------------------------------------------- 1 | use strict; 2 | use Carp (); 3 | 4 | ############################################################################ 5 | package Net::OpenID::VerifiedIdentity; 6 | use fields ( 7 | 'identity', # the verified identity URL 8 | 'id_uri', # the verified identity's URI object 9 | 10 | 'claimed_identity', # The ClaimedIdentity object that we've verified 11 | 'semantic_info', # The "semantic info" (RSS URLs, etc) at the verified identity URL 12 | 13 | 'consumer', # The Net::OpenID::Consumer module which created us 14 | 15 | 'signed_fields' , # hashref of key->value of things that were signed. without "openid." prefix 16 | 'signed_message', # the signed fields as an IndirectMessage object. Created when needed. 17 | ); 18 | use URI; 19 | 20 | sub new { 21 | my Net::OpenID::VerifiedIdentity $self = shift; 22 | $self = fields::new( $self ) unless ref $self; 23 | my %opts = @_; 24 | 25 | $self->{'consumer'} = delete $opts{'consumer'}; 26 | 27 | if ($self->{'claimed_identity'} = delete $opts{'claimed_identity'}) { 28 | $self->{identity} = $self->{claimed_identity}->claimed_url; 29 | unless ($self->{'id_uri'} = URI->new($self->{identity})) { 30 | return $self->{'consumer'}->_fail("invalid_uri"); 31 | } 32 | } 33 | 34 | for my $par (qw(signed_fields)) { 35 | $self->$par(delete $opts{$par}); 36 | } 37 | 38 | Carp::croak("Unknown options: " . join(", ", keys %opts)) if %opts; 39 | return $self; 40 | } 41 | 42 | sub url { 43 | my Net::OpenID::VerifiedIdentity $self = shift; 44 | return $self->{'identity'}; 45 | } 46 | 47 | sub display { 48 | my Net::OpenID::VerifiedIdentity $self = shift; 49 | return DisplayOfURL($self->{'identity'}); 50 | } 51 | 52 | sub _semantic_info_hash { 53 | my ($self) = @_; 54 | return $self->{semantic_info} if $self->{semantic_info}; 55 | my $sem_info = $self->{claimed_identity}->semantic_info; 56 | $self->{semantic_info} = { 57 | 'foaf' => $self->_identity_relative_uri($sem_info->{"foaf"}), 58 | 'foafmaker' => $sem_info->{"foaf.maker"}, 59 | 'rss' => $self->_identity_relative_uri($sem_info->{"rss"}), 60 | 'atom' => $self->_identity_relative_uri($sem_info->{"atom"}), 61 | }; 62 | return $self->{semantic_info}; 63 | } 64 | 65 | sub _identity_relative_uri { 66 | my $self = shift; 67 | my $url = shift; 68 | 69 | return $url if ref $url; 70 | return undef unless $url; 71 | return URI->new_abs($url, $self->{'id_uri'}); 72 | } 73 | 74 | sub signed_fields { &_getset; } 75 | 76 | sub foaf { &_getset_semurl; } 77 | sub rss { &_getset_semurl; } 78 | sub atom { &_getset_semurl; } 79 | sub foafmaker { &_getset_sem; } 80 | 81 | sub declared_foaf { &_dec_semurl; } 82 | sub declared_rss { &_dec_semurl; } 83 | sub declared_atom { &_dec_semurl; } 84 | 85 | sub extension_fields { 86 | my ($self, $ns_uri) = @_; 87 | return $self->_extension_fields($ns_uri, $self->{consumer}->message); 88 | } 89 | 90 | sub signed_extension_fields { 91 | my ($self, $ns_uri) = @_; 92 | 93 | return $self->_extension_fields($ns_uri, $self->signed_message); 94 | } 95 | 96 | sub _extension_fields { 97 | my ($self, $ns_uri, $args) = @_; 98 | 99 | return $args->get_ext($ns_uri); 100 | } 101 | 102 | sub signed_message { 103 | my ($self) = @_; 104 | 105 | return $self->{signed_message} if $self->{signed_message}; 106 | 107 | # This is maybe a bit hacky. 108 | # We need to synthesize an IndirectMessage object 109 | # representing the signed fields, which means 110 | # that we need to fake up the mandatory message 111 | # arguments that probably weren't signed. 112 | 113 | my %args = map { 'openid.'.$_ => $self->{signed_fields}{$_} } keys %{$self->{signed_fields}}; 114 | 115 | my $real_message = $self->{consumer}->message; 116 | if ($real_message->protocol_version == 1) { 117 | # OpenID 1.1 just needs a mode. 118 | $args{'openid.mode'} = 'id_res'; 119 | } 120 | else { 121 | # OpenID 2.2 needs the namespace URI as well 122 | $args{'openid.ns'} = 'http://specs.openid.net/auth/2.0'; 123 | $args{'openid.mode'} = 'id_res'; 124 | } 125 | 126 | my $message = Net::OpenID::IndirectMessage->new(\%args); 127 | 128 | return $self->{signed_message} = $message; 129 | } 130 | 131 | sub _getset { 132 | my $self = shift; 133 | my $param = (caller(1))[3]; 134 | $param =~ s/.+:://; 135 | 136 | if (@_) { 137 | my $val = shift; 138 | Carp::croak("Too many parameters") if @_; 139 | $self->{$param} = $val; 140 | } 141 | return $self->{$param}; 142 | } 143 | 144 | sub _getset_sem { 145 | my $self = shift; 146 | my $param = (caller(1))[3]; 147 | $param =~ s/.+:://; 148 | 149 | my $info = $self->_semantic_info_hash; 150 | 151 | if (my $value = shift) { 152 | Carp::croak("Too many parameters") if @_; 153 | $info->{$param} = $value; 154 | } 155 | return $info->{$param}; 156 | } 157 | 158 | sub _getset_semurl { 159 | my $self = shift; 160 | my $param = (caller(1))[3]; 161 | $param =~ s/.+:://; 162 | 163 | my $info = $self->_semantic_info_hash; 164 | 165 | if (my $surl = shift) { 166 | Carp::croak("Too many parameters") if @_; 167 | 168 | # TODO: make absolute URL from possibly relative one 169 | my $abs = URI->new_abs($surl, $self->{'id_uri'}); 170 | $info->{$param} = $abs; 171 | } 172 | 173 | my $uri = $info->{$param}; 174 | return $uri && _url_is_under($self->{'id_uri'}, $uri) ? $uri->as_string : undef; 175 | } 176 | 177 | sub _dec_semurl { 178 | my $self = shift; 179 | my $param = (caller(1))[3]; 180 | $param =~ s/.+::declared_//; 181 | 182 | my $info = $self->_semantic_info_hash; 183 | 184 | my $uri = $info->{$param}; 185 | return $uri ? $uri->as_string : undef; 186 | } 187 | 188 | sub DisplayOfURL { 189 | my $url = shift; 190 | my $dev_mode = shift; 191 | 192 | return $url unless 193 | $url =~ m!^https?://([^/]+)(/.*)?$!; 194 | 195 | my ($host, $path) = ($1, $2); 196 | $host = lc($host); 197 | 198 | if ($dev_mode) { 199 | $host =~ s!^dev\.!!; 200 | $host =~ s!:\d+!!; 201 | } 202 | 203 | $host =~ s/:.+//; 204 | $host =~ s/^www\.//i; 205 | 206 | if (length($path) <= 1) { 207 | return $host; 208 | } 209 | 210 | # obvious username 211 | if ($path =~ m!^/~([^/]+)/?$! || 212 | $path =~ m!^/(?:users?|members?)/([^/]+)/?$!) { 213 | return "$1 [$host]"; 214 | } 215 | 216 | if ($host =~ m!^profile\.(.+)!i) { 217 | my $site = $1; 218 | if ($path =~ m!^/([^/]+)/?$!) { 219 | return "$1 [$site]"; 220 | } 221 | } 222 | 223 | return $url; 224 | } 225 | 226 | # FIXME: duplicated in Net::OpenID::Server 227 | sub _url_is_under { 228 | my ($root, $test, $err_ref) = @_; 229 | 230 | my $err = sub { 231 | $$err_ref = shift if $err_ref; 232 | return undef; 233 | }; 234 | 235 | my $ru = ref $root ? $root : URI->new($root); 236 | return $err->("invalid root scheme") unless $ru->scheme =~ /^https?$/; 237 | my $tu = ref $test ? $test : URI->new($test); 238 | return $err->("invalid test scheme") unless $tu->scheme =~ /^https?$/; 239 | return $err->("schemes don't match") unless $ru->scheme eq $tu->scheme; 240 | return $err->("ports don't match") unless $ru->port == $tu->port; 241 | 242 | # check hostnames 243 | my $ru_host = $ru->host; 244 | my $tu_host = $tu->host; 245 | my $wildcard_host = 0; 246 | if ($ru_host =~ s!^\*\.!!) { 247 | $wildcard_host = 1; 248 | } 249 | unless ($ru_host eq $tu_host) { 250 | if ($wildcard_host) { 251 | return $err->("host names don't match") unless 252 | $tu_host =~ /\.\Q$ru_host\E$/; 253 | } else { 254 | return $err->("host names don't match"); 255 | } 256 | } 257 | 258 | # check paths 259 | my $ru_path = $ru->path || "/"; 260 | my $tu_path = $tu->path || "/"; 261 | $ru_path .= "/" unless $ru_path =~ m!/$!; 262 | $tu_path .= "/" unless $tu_path =~ m!/$!; 263 | return $err->("path not a subpath") unless $tu_path =~ m!^\Q$ru_path\E!; 264 | 265 | return 1; 266 | } 267 | 268 | 1; 269 | 270 | __END__ 271 | 272 | =head1 NAME 273 | 274 | Net::OpenID::VerifiedIdentity - object representing a verified OpenID identity 275 | 276 | =head1 SYNOPSIS 277 | 278 | use Net::OpenID::Consumer; 279 | my $csr = Net::OpenID::Consumer->new; 280 | .... 281 | my $vident = $csr->verified_identity 282 | or die $csr->err; 283 | 284 | my $url = $vident->url; 285 | 286 | 287 | =head1 DESCRIPTION 288 | 289 | After L verifies a user's identity and does the 290 | signature checks, it gives you this Net::OpenID::VerifiedIdentity 291 | object, from which you can learn more about the user. 292 | 293 | =head1 METHODS 294 | 295 | =over 4 296 | 297 | =item $vident->B 298 | 299 | Returns the URL (as a scalar) that was verified. (Remember, an OpenID 300 | is just a URL.) 301 | 302 | =item $vident->B 303 | 304 | Returns the a short "display form" of the verified URL using a couple 305 | brain-dead patterns. For instance, the identity 306 | "http://www.foo.com/~bob/" will map to "bob [foo.com]" The www. prefix 307 | is removed, as well as http, and a username is looked for, in either 308 | the tilde form, or "/users/USERNAME" or "/members/USERNAME". If the 309 | path component is empty or just "/", then the display form is just the 310 | hostname, so "http://myblog.com/" is just "myblog.com". 311 | 312 | Suggestions for improving this function are welcome, but you'll probably 313 | get more satisfying results if you make use of the data returned by 314 | the Simple Registration (SREG) extension, which allows the user to 315 | choose a preferred nickname to use on your site. 316 | 317 | =item $vident->B($ns_uri) 318 | 319 | Return the fields from the given extension namespace, if any, that 320 | were included in the assertion request. The fields are returned in 321 | a hashref. 322 | 323 | In most cases you'll probably want to use B instead, 324 | to avoid attacks where a man-in-the-middle alters the extension fields in transit. 325 | 326 | Note that for OpenID 1.1 transactions only Simple Registration (SREG) 1.1 327 | is supported. 328 | 329 | =item $vident->B($ns_uri) 330 | 331 | The same as B except that only fields that were signed 332 | as part of the assertion are included in the returned hashref. For example, 333 | if you included a Simple Registration request in your initial message, 334 | you might fetch the results (if any) like this: 335 | 336 | $sreg = $vident->signed_extension_fields( 337 | 'http://openid.net/extensions/sreg/1.1', 338 | ); 339 | 340 | An important gotcha to bear in mind is that for OpenID 2.0 responses 341 | no extension fields can be considered signed unless the corresponding 342 | extension namespace declaration is also signed. If that is not the case, 343 | this method will behave as if no extension fields for that URI were signed. 344 | 345 | =item $vident->B 346 | 347 | =item $vident->B 348 | 349 | =item $vident->B 350 | 351 | =item $vident->B 352 | 353 | =item $vident->B 354 | 355 | =item $vident->B 356 | 357 | Returns the absolute URLs (as scalars) of the user's RSS, Atom, and 358 | FOAF XML documents that were also found in their HTML's EheadE 359 | section. The short versions will only return a URL if they're below 360 | the root URL that was verified. If you want to get at the user's 361 | declared rss/atom/foaf, even if it's on a different host or parent 362 | directory, use the delcared_* versions, which don't have the additional 363 | checks. 364 | 365 | 2005-05-24: A future module will take a Net::OpenID::VerifiedIdentity 366 | object and create an OpenID profile object so you don't have to 367 | manually parse all those documents to get profile information. 368 | 369 | =item $vident->B 370 | 371 | Returns the value of the C meta tag, if declared. 372 | 373 | =back 374 | 375 | =head1 COPYRIGHT, WARRANTY, AUTHOR 376 | 377 | See L for author, copyrignt and licensing information. 378 | 379 | =head1 SEE ALSO 380 | 381 | L 382 | 383 | L 384 | 385 | L 386 | 387 | Website: L 388 | -------------------------------------------------------------------------------- /cgi/Net/OpenID/ClaimedIdentity.pm: -------------------------------------------------------------------------------- 1 | use strict; 2 | use Carp (); 3 | 4 | ############################################################################ 5 | package Net::OpenID::ClaimedIdentity; 6 | use fields ( 7 | 'identity', # the canonical URL that was found, following redirects 8 | 'server', # author-identity identity server endpoint 9 | 'consumer', # ref up to the Net::OpenID::Consumer which generated us 10 | 'delegate', # the delegated URL actually asserted by the server 11 | 'protocol_version', # The version of the OpenID Authentication Protocol that is used 12 | 'semantic_info', # Stuff that we've discovered in the identifier page's metadata 13 | 'extension_args', # Extension arguments that the caller wants to add to the request 14 | ); 15 | 16 | sub new { 17 | my Net::OpenID::ClaimedIdentity $self = shift; 18 | $self = fields::new( $self ) unless ref $self; 19 | my %opts = @_; 20 | for my $f (qw( identity server consumer delegate protocol_version semantic_info )) { 21 | $self->{$f} = delete $opts{$f}; 22 | } 23 | 24 | $self->{protocol_version} ||= 1; 25 | unless ($self->{protocol_version} == 1 || $self->{protocol_version} == 2) { 26 | Carp::croak("Unsupported protocol version"); 27 | } 28 | 29 | # lowercase the scheme and hostname 30 | $self->{'identity'} =~ s!^(https?://.+?)(/(?:.*))?$!lc($1) . $2!ie; 31 | 32 | $self->{extension_args} = {}; 33 | 34 | Carp::croak("unknown options: " . join(", ", keys %opts)) if %opts; 35 | return $self; 36 | } 37 | 38 | sub claimed_url { 39 | my Net::OpenID::ClaimedIdentity $self = shift; 40 | Carp::croak("Too many parameters") if @_; 41 | return $self->{'identity'}; 42 | } 43 | 44 | sub delegated_url { 45 | my Net::OpenID::ClaimedIdentity $self = shift; 46 | Carp::croak("Too many parameters") if @_; 47 | return $self->{'delegate'}; 48 | } 49 | 50 | sub identity_server { 51 | my Net::OpenID::ClaimedIdentity $self = shift; 52 | Carp::croak("Too many parameters") if @_; 53 | return $self->{server}; 54 | } 55 | 56 | sub protocol_version { 57 | my Net::OpenID::ClaimedIdentity $self = shift; 58 | Carp::croak("Too many parameters") if @_; 59 | return $self->{protocol_version}; 60 | } 61 | 62 | sub semantic_info { 63 | my Net::OpenID::ClaimedIdentity $self = shift; 64 | Carp::croak("Too many parameters") if @_; 65 | return $self->{semantic_info} if $self->{semantic_info}; 66 | my $final_url = ''; 67 | my $info = $self->{consumer}->_find_semantic_info($self->claimed_url, \$final_url); 68 | # Don't return anything if the URL has changed. Something bad may be happening. 69 | $info = {} if $final_url ne $self->claimed_url; 70 | return $self->{semantic_info} = $info; 71 | } 72 | 73 | sub set_extension_args { 74 | my Net::OpenID::ClaimedIdentity $self = shift; 75 | my $ext_uri = shift; 76 | my $args = shift; 77 | Carp::croak("Too many parameters") if @_; 78 | Carp::croak("No extension URI given") unless $ext_uri; 79 | Carp::croak("Expecting hashref of args") if defined($args) && ref $args ne 'HASH'; 80 | 81 | $self->{extension_args}{$ext_uri} = $args; 82 | } 83 | 84 | sub check_url { 85 | my Net::OpenID::ClaimedIdentity $self = shift; 86 | my (%opts) = @_; 87 | 88 | my $return_to = delete $opts{'return_to'}; 89 | my $trust_root = delete $opts{'trust_root'}; 90 | my $delayed_ret = delete $opts{'delayed_return'}; 91 | my $force_reassociate = delete $opts{'force_reassociate'}; 92 | my $use_assoc_handle = delete $opts{'use_assoc_handle'}; 93 | my $actually_return_association = delete $opts{'actually_return_association'}; 94 | 95 | Carp::croak("Unknown options: " . join(", ", keys %opts)) if %opts; 96 | Carp::croak("Invalid/missing return_to") unless $return_to =~ m!^https?://!; 97 | 98 | my $csr = $self->{consumer}; 99 | 100 | my $ident_server = $self->{server} or 101 | Carp::croak("No identity server"); 102 | 103 | # get an assoc (or undef for dumb mode) 104 | my $assoc; 105 | if ($use_assoc_handle) { 106 | $assoc = Net::OpenID::Association::handle_assoc($csr, $ident_server, $use_assoc_handle); 107 | } else { 108 | $assoc = Net::OpenID::Association::server_assoc($csr, $ident_server, $force_reassociate, ( 109 | protocol_version => $self->protocol_version, 110 | )); 111 | } 112 | 113 | # for the openid-test project: (doing interop testing) 114 | if ($actually_return_association) { 115 | return $assoc; 116 | } 117 | 118 | my $identity_arg = $self->{'delegate'} || $self->{'identity'}; 119 | 120 | # make a note back to ourselves that we're using a delegate 121 | # but only in the 1.1 case because 2.0 has a core field for this 122 | if ($self->{'delegate'} && $self->protocol_version == 1) { 123 | OpenID::util::push_url_arg(\$return_to, 124 | "oic.identity", $self->{identity}); 125 | } 126 | 127 | # add a HMAC-signed time so we can verify the return_to URL wasn't spoofed 128 | my $sig_time = time(); 129 | my $c_secret = $csr->_get_consumer_secret($sig_time); 130 | my $sig = substr(OpenID::util::hmac_sha1_hex($sig_time, $c_secret), 0, 20); 131 | OpenID::util::push_url_arg(\$return_to, 132 | "oic.time", "${sig_time}-$sig"); 133 | 134 | my $curl = $ident_server; 135 | if ($self->protocol_version == 1) { 136 | OpenID::util::push_url_arg(\$curl, 137 | "openid.mode" => ($delayed_ret ? "checkid_setup" : "checkid_immediate"), 138 | "openid.identity" => $identity_arg, 139 | "openid.return_to" => $return_to, 140 | 141 | ($trust_root ? ( 142 | "openid.trust_root" => $trust_root 143 | ) : ()), 144 | 145 | ($assoc ? ( 146 | "openid.assoc_handle" => $assoc->handle 147 | ) : ()), 148 | ); 149 | } 150 | elsif ($self->protocol_version == 2) { 151 | # NOTE: OpenID Auth 2.0 uses different terminology for a bunch 152 | # of things than 1.1 did. This library still uses the 1.1 terminology 153 | # in its API. 154 | OpenID::util::push_openid2_url_arg(\$curl, 155 | "mode" => ($delayed_ret ? "checkid_setup" : "checkid_immediate"), 156 | "claimed_id" => $self->claimed_url, 157 | "identity" => $identity_arg, 158 | "return_to" => $return_to, 159 | 160 | ($trust_root ? ( 161 | "realm" => $trust_root 162 | ) : ()), 163 | 164 | ($assoc ? ( 165 | "assoc_handle" => $assoc->handle 166 | ) : ()), 167 | ); 168 | } 169 | 170 | # Finally we add in the extension arguments, if any 171 | my %ext_url_args = (); 172 | my $ext_idx = 1; 173 | foreach my $ext_uri (keys %{$self->{extension_args}}) { 174 | my $ext_alias; 175 | 176 | if ($self->protocol_version >= 2) { 177 | $ext_alias = 'e'.($ext_idx++); 178 | $ext_url_args{'openid.ns.'.$ext_alias} = $ext_uri; 179 | } 180 | else { 181 | # For OpenID 1.1 only the "SREG" extension is allowed, 182 | # and it must use the "openid.sreg." prefix. 183 | next unless $ext_uri eq "http://openid.net/extensions/sreg/1.1"; 184 | $ext_alias = "sreg"; 185 | } 186 | 187 | foreach my $k (keys %{$self->{extension_args}{$ext_uri}}) { 188 | $ext_url_args{'openid.'.$ext_alias.'.'.$k} = $self->{extension_args}{$ext_uri}{$k}; 189 | } 190 | } 191 | OpenID::util::push_url_arg(\$curl, %ext_url_args) if %ext_url_args; 192 | 193 | $self->{consumer}->_debug("check_url for (del=$self->{delegate}, id=$self->{identity}) = $curl"); 194 | return $curl; 195 | } 196 | 197 | 198 | 1; 199 | 200 | __END__ 201 | 202 | =head1 NAME 203 | 204 | Net::OpenID::ClaimedIdentity - a not-yet-verified OpenID identity 205 | 206 | =head1 SYNOPSIS 207 | 208 | use Net::OpenID::Consumer; 209 | my $csr = Net::OpenID::Consumer->new; 210 | .... 211 | my $cident = $csr->claimed_identity("bradfitz.com") 212 | or die $csr->err; 213 | 214 | if ($AJAX_mode) { 215 | my $url = $cident->claimed_url; 216 | my $openid_server = $cident->identity_server; 217 | # ... return JSON with those to user agent (whose request was 218 | # XMLHttpRequest, probably) 219 | } 220 | 221 | if ($CLASSIC_mode) { 222 | my $check_url = $cident->check_url( 223 | delayed_return => 1, 224 | return_to => "http://example.com/get-identity.app", 225 | trust_root => "http://*.example.com/", 226 | ); 227 | WebApp::redirect($check_url); 228 | } 229 | 230 | =head1 DESCRIPTION 231 | 232 | After L crawls a user's declared identity URL 233 | and finds openid.server link tags in the HTML head, you get this 234 | object. It represents an identity that can be verified with OpenID 235 | (the link tags are present), but hasn't been actually verified yet. 236 | 237 | =head1 METHODS 238 | 239 | =over 4 240 | 241 | =item $url = $cident->B 242 | 243 | The URL, now canonicalized, that the user claims to own. You can't 244 | know whether or not they do own it yet until you send them off to the 245 | check_url, though. 246 | 247 | =item $id_server = $cident->B 248 | 249 | Returns the identity server that will assert whether or not this 250 | claimed identity is valid, and sign a message saying so. 251 | 252 | =item $url = $cident->B 253 | 254 | If the claimed URL is using delegation, this returns the delegated identity that will 255 | actually be sent to the identity server. 256 | 257 | =item $version = $cident->B 258 | 259 | Determines whether this identifier is to be verified by OpenID 1.1 260 | or by OpenID 2.0. Returns C<1> or C<2> respectively. This will 261 | affect the way the C is constructed. 262 | 263 | =item $cident->B($ns_uri, $args) 264 | 265 | If called before you access C, the arguments given in the hashref 266 | $args will be added to the request in the given extension namespace. 267 | For example, to use the Simple Registration (SREG) extension: 268 | 269 | $cident->set_extension_args( 270 | 'http://openid.net/extensions/sreg/1.1', 271 | { 272 | required => 'email', 273 | optional => 'fullname,nickname', 274 | policy_url => 'http://example.com/privacypolicy.html', 275 | }, 276 | ); 277 | 278 | Note that when making an OpenID 1.1 request, only the Simple Registration 279 | extension is supported. There was no general extension mechanism defined 280 | in OpenID 1.1, so SREG (with the namespace URI as in the example above) 281 | is supported as a special case. All other extension namespaces will 282 | be silently ignored when making a 1.1 request. 283 | 284 | =item $url = $cident->B( %opts ) 285 | 286 | Makes the URL that you have to somehow send the user to in order to 287 | validate their identity. The options to put in %opts are: 288 | 289 | =over 290 | 291 | =item C 292 | 293 | The URL that the identity server should redirect the user with either 294 | a verified identity signature -or- a user_setup_url (if the assertion 295 | couldn't be made). This URL may contain query parameters, and the 296 | identity server must preserve them. 297 | 298 | =item C 299 | 300 | The URL that you want the user to actually see and declare trust for. 301 | Your C URL must be at or below your trust_root. Sending 302 | the trust_root is optional, and defaults to your C value, 303 | but it's highly recommended (and prettier for users) to see a simple 304 | trust_root. Note that the trust root may contain a wildcard at the 305 | beginning of the host, like C 306 | 307 | =item C 308 | 309 | If set to a true value, the check_url returned will indicate to the 310 | user's identity server that it has permission to control the user's 311 | user-agent for awhile, giving them real pages (not just redirects) and 312 | lets them bounce around the identity server site for awhile until 313 | the requested assertion can be made, and they can finally be redirected 314 | back to your return_to URL above. 315 | 316 | The default value, false, means that the identity server will 317 | immediately return to your return_to URL with either a "yes" or "no" 318 | answer. In the "no" case, you'll instead have control of what to do, 319 | and you'll be sent the identity server's user_setup_url where you'll 320 | have to somehow send the user (be it link, redirect, or pop-up 321 | window). 322 | 323 | When writing a dynamic "AJAX"-style application, you can't use 324 | delayed_return because the remote site can't usefully take control of 325 | a 1x1 pixel hidden IFRAME, so you'll need to get the user_setup_url 326 | and present it to the user somehow. 327 | 328 | =back 329 | 330 | =back 331 | 332 | =head1 COPYRIGHT, WARRANTY, AUTHOR 333 | 334 | See L for author, copyrignt and licensing information. 335 | 336 | =head1 SEE ALSO 337 | 338 | L 339 | 340 | L 341 | 342 | L 343 | 344 | Website: L 345 | 346 | --------------------------------------------------------------------------------