_borders/0000755002157400001440000000000010103774561017264 5ustar minnesotaviolasociety.orgusers00000000000000_borders/_vti_cnf/0000755002157400001440000000000010103774561021053 5ustar minnesotaviolasociety.orgusers00000000000000_borders/_vti_cnf/disc1_head.htm0000644002157400001440000000242510067423564023557 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|27 Jun 2004 01:40:20 -0000 vti_title:SR|Included Header for Viola Forum vti_author:SR|admin vti_modifiedby:SR|admin vti_nexttolasttimemodified:TR|27 Jun 2004 01:39:26 -0000 vti_timecreated:TR|27 Jun 2004 01:40:20 -0000 vti_extenderversion:SR|5.0.2.2623 vti_syncwith_localhost\\c\:\\documents and settings\\owner\\my documents\\my webs\\mvs maroon and white/c\:/documents and settings/owner/my documents/my webs/mvs maroon and white:TR|27 Jun 2004 01:39:26 -0000 vti_backlinkinfo:VX| vti_cacheddtm:TX|27 Jun 2004 01:40:20 -0000 vti_filesize:IR|717 vti_cachedtitle:SR|Included Header for Viola Forum vti_cachedbodystyle:SR| vti_cachedlinkinfo:VX|H|../index.html H|../disc1_frm.htm H|../disc1_srch.htm H|../disc1_post.htm vti_cachedsvcrellinks:VX|FHUS|index.html NHUS|disc1_frm.htm NHUS|disc1_srch.htm NHUS|disc1_post.htm vti_cachedneedsrewrite:BR|false vti_cachedhasbots:BR|false vti_cachedhastheme:BR|false vti_cachedhasborder:BR|false vti_metatags:VR|HTTP-EQUIV=Content-Type text/html;\\ charset=windows-1252 HTTP-EQUIV=Content-Language en-us GENERATOR Microsoft\\ FrontPage\\ 4.0 ProgId FrontPage.Editor.Document vti_charset:SR|windows-1252 vti_language:SR|en-us vti_progid:SR|FrontPage.Editor.Document vti_generator:SR|Microsoft FrontPage 4.0 _borders/_vti_cnf/disc4_head.htm0000644002157400001440000000242510103740306023545 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|03 Aug 2004 16:36:46 -0000 vti_title:SR|Included Header for Viola Forum vti_author:SR|admin vti_modifiedby:SR|admin vti_nexttolasttimemodified:TR|03 Aug 2004 16:35:22 -0000 vti_timecreated:TR|03 Aug 2004 16:36:45 -0000 vti_extenderversion:SR|5.0.2.2623 vti_syncwith_localhost\\c\:\\documents and settings\\owner\\my documents\\my webs\\mvs maroon and white/c\:/documents and settings/owner/my documents/my webs/mvs maroon and white:TR|03 Aug 2004 16:35:22 -0000 vti_backlinkinfo:VX| vti_cacheddtm:TX|03 Aug 2004 16:36:46 -0000 vti_filesize:IR|717 vti_cachedtitle:SR|Included Header for Viola Forum vti_cachedbodystyle:SR| vti_cachedlinkinfo:VX|H|../index.html H|../disc4_frm.htm H|../disc4_srch.htm H|../disc4_post.htm vti_cachedsvcrellinks:VX|FHUS|index.html NHUS|disc4_frm.htm NHUS|disc4_srch.htm NHUS|disc4_post.htm vti_cachedneedsrewrite:BR|false vti_cachedhasbots:BR|false vti_cachedhastheme:BR|false vti_cachedhasborder:BR|false vti_metatags:VR|HTTP-EQUIV=Content-Type text/html;\\ charset=windows-1252 HTTP-EQUIV=Content-Language en-us GENERATOR Microsoft\\ FrontPage\\ 4.0 ProgId FrontPage.Editor.Document vti_charset:SR|windows-1252 vti_language:SR|en-us vti_progid:SR|FrontPage.Editor.Document vti_generator:SR|Microsoft FrontPage 4.0 _borders/_vti_cnf/disc5_head.htm0000644002157400001440000000242510103774561023560 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|03 Aug 2004 16:43:49 -0000 vti_title:SR|Included Header for Viola Forum vti_author:SR|admin vti_modifiedby:SR|admin vti_nexttolasttimemodified:TR|03 Aug 2004 16:43:39 -0000 vti_timecreated:TR|03 Aug 2004 16:43:49 -0000 vti_extenderversion:SR|5.0.2.2623 vti_syncwith_localhost\\c\:\\documents and settings\\owner\\my documents\\my webs\\mvs maroon and white/c\:/documents and settings/owner/my documents/my webs/mvs maroon and white:TR|03 Aug 2004 16:43:39 -0000 vti_backlinkinfo:VX| vti_cacheddtm:TX|03 Aug 2004 16:43:49 -0000 vti_filesize:IR|717 vti_cachedtitle:SR|Included Header for Viola Forum vti_cachedbodystyle:SR| vti_cachedlinkinfo:VX|H|../index.html H|../disc5_frm.htm H|../disc5_srch.htm H|../disc5_post.htm vti_cachedsvcrellinks:VX|FHUS|index.html NHUS|disc5_frm.htm NHUS|disc5_srch.htm NHUS|disc5_post.htm vti_cachedneedsrewrite:BR|false vti_cachedhasbots:BR|false vti_cachedhastheme:BR|false vti_cachedhasborder:BR|false vti_metatags:VR|HTTP-EQUIV=Content-Type text/html;\\ charset=windows-1252 HTTP-EQUIV=Content-Language en-us GENERATOR Microsoft\\ FrontPage\\ 4.0 ProgId FrontPage.Editor.Document vti_charset:SR|windows-1252 vti_language:SR|en-us vti_progid:SR|FrontPage.Editor.Document vti_generator:SR|Microsoft FrontPage 4.0 _borders/_vti_cnf/disc6_head.htm0000644002157400001440000000242510103742070023547 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|03 Aug 2004 16:58:31 -0000 vti_title:SR|Included Header for Viola Forum vti_author:SR|admin vti_modifiedby:SR|admin vti_nexttolasttimemodified:TR|03 Aug 2004 16:52:47 -0000 vti_timecreated:TR|03 Aug 2004 16:58:31 -0000 vti_extenderversion:SR|5.0.2.2623 vti_syncwith_localhost\\c\:\\documents and settings\\owner\\my documents\\my webs\\mvs maroon and white/c\:/documents and settings/owner/my documents/my webs/mvs maroon and white:TR|03 Aug 2004 16:52:47 -0000 vti_backlinkinfo:VX| vti_cacheddtm:TX|03 Aug 2004 16:58:31 -0000 vti_filesize:IR|717 vti_cachedtitle:SR|Included Header for Viola Forum vti_cachedbodystyle:SR| vti_cachedlinkinfo:VX|H|../index.html H|../disc6_frm.htm H|../disc6_srch.htm H|../disc6_post.htm vti_cachedsvcrellinks:VX|FHUS|index.html NHUS|disc6_frm.htm NHUS|disc6_srch.htm NHUS|disc6_post.htm vti_cachedneedsrewrite:BR|false vti_cachedhasbots:BR|false vti_cachedhastheme:BR|false vti_cachedhasborder:BR|false vti_metatags:VR|HTTP-EQUIV=Content-Type text/html;\\ charset=windows-1252 HTTP-EQUIV=Content-Language en-us GENERATOR Microsoft\\ FrontPage\\ 4.0 ProgId FrontPage.Editor.Document vti_charset:SR|windows-1252 vti_language:SR|en-us vti_progid:SR|FrontPage.Editor.Document vti_generator:SR|Microsoft FrontPage 4.0 _borders/disc1_head.htm0000644002157400001440000000131510067422604021757 0ustar minnesotaviolasociety.orgusers00000000000000 Included Header for Viola Forum

Viola Forum

[ Home | Contents | Search | Post ]


_borders/disc4_head.htm0000644002157400001440000000131510103737436021765 0ustar minnesotaviolasociety.orgusers00000000000000 Included Header for Viola Forum

Viola Forum

[ Home | Contents | Search | Post ]


_borders/disc5_head.htm0000644002157400001440000000131510103740305021753 0ustar minnesotaviolasociety.orgusers00000000000000 Included Header for Viola Forum

Viola Forum

[ Home | Contents | Search | Post ]


_borders/disc6_head.htm0000644002157400001440000000131510103742067021763 0ustar minnesotaviolasociety.orgusers00000000000000 Included Header for Viola Forum

Viola Forum

[ Home | Contents | Search | Post ]


cgi-bin/0000755002157400001440000000000010366240653016776 5ustar minnesotaviolasociety.orgusers00000000000000cgi-bin/_vti_cnf/0000755002157400001440000000000010366240653020565 5ustar minnesotaviolasociety.orgusers00000000000000cgi-bin/_vti_cnf/index.html0000644002157400001440000000113107626347105022563 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|24 Feb 2003 07:48:21 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|24 Feb 2003 07:48:21 -0000 vti_filesize:IR|1133 vti_cachedtitle:SR|MOVABLE TYPE vti_cachedbodystyle:SR| vti_cachedlinkinfo:VX|H|docs/mtinstall.html vti_cachedsvcrellinks:VX|NHUS|cgi-bin/docs/mtinstall.html vti_cachedneedsrewrite:BR|false vti_cachedhasbots:BR|false vti_cachedhastheme:BR|false vti_cachedhasborder:BR|false vti_metatags:VR|HTTP-EQUIV=Content-Type text/html;\\ charset=iso-8859-1 vti_charset:SR|windows-1252 vti_title:SR|MOVABLE TYPE vti_backlinkinfo:VX| cgi-bin/_vti_cnf/LICENSE0000644002157400001440000000030307770530356021575 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|19 Dec 2003 08:03:58 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|19 Dec 2003 08:03:58 -0000 vti_filesize:IR|9261 vti_backlinkinfo:VX| cgi-bin/_vti_cnf/favicon.ico0000644002157400001440000000052510366240653022710 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_author:SR|admin vti_modifiedby:SR|admin vti_timelastmodified:TR|26 Jan 2006 21:39:55 -0000 vti_timecreated:TR|14 Jan 2006 15:05:35 -0000 vti_extenderversion:SR|5.0.2.2634 vti_nexttolasttimemodified:TR|14 Jan 2006 15:05:35 -0000 vti_cacheddtm:TX|26 Jan 2006 21:39:55 -0000 vti_filesize:IR|766 vti_backlinkinfo:VX| cgi-bin/_vti_cnf/LICENSE-COMMERCIAL0000644002157400001440000000030407770530361023203 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|19 Dec 2003 08:04:01 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|19 Dec 2003 08:04:01 -0000 vti_filesize:IR|12426 vti_backlinkinfo:VX| cgi-bin/_vti_cnf/mt-add-notify.cgi0000644002157400001440000000030207622316733023724 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|12 Feb 2003 01:05:31 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|12 Feb 2003 01:05:31 -0000 vti_filesize:IR|911 vti_backlinkinfo:VX| cgi-bin/_vti_cnf/mt-check.cgi0000644002157400001440000000030307622316733022744 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|12 Feb 2003 01:05:31 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|12 Feb 2003 01:05:31 -0000 vti_filesize:IR|4789 vti_backlinkinfo:VX| cgi-bin/_vti_cnf/mt-comments.cgi0000644002157400001440000000055310232532415023507 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|12 Feb 2003 01:05:31 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|12 Feb 2003 01:05:31 -0000 vti_filesize:IR|774 vti_backlinkinfo:VX|weblog/archives/000001.html weblog/archives/2005_04.html weblog/archives/000043.html weblog/archives/2004_01.html weblog/archives/000004.html weblog/archives/000005.html cgi-bin/_vti_cnf/mt-db-pass.cgi0000644002157400001440000000030007776361257023230 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|05 Jan 2004 22:18:23 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|05 Jan 2004 22:18:23 -0000 vti_filesize:IR|9 vti_backlinkinfo:VX| cgi-bin/_vti_cnf/mt-search.cgi0000644002157400001440000000032307622316733023136 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|12 Feb 2003 01:05:31 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|12 Feb 2003 01:05:31 -0000 vti_filesize:IR|774 vti_backlinkinfo:VX|weblog/index.html cgi-bin/_vti_cnf/mt-send-entry.cgi0000644002157400001440000000030307770464551023765 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|19 Dec 2003 02:58:49 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|19 Dec 2003 02:58:49 -0000 vti_filesize:IR|1809 vti_backlinkinfo:VX| cgi-bin/_vti_cnf/mt-tb.cgi0000644002157400001440000000030207622316733022273 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|12 Feb 2003 01:05:31 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|12 Feb 2003 01:05:31 -0000 vti_filesize:IR|771 vti_backlinkinfo:VX| cgi-bin/_vti_cnf/mt-view.cgi0000644002157400001440000000030207622316733022640 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|12 Feb 2003 01:05:31 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|12 Feb 2003 01:05:31 -0000 vti_filesize:IR|756 vti_backlinkinfo:VX| cgi-bin/_vti_cnf/mt-xmlrpc.cgi0000644002157400001440000000030207622316733023173 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|12 Feb 2003 01:05:31 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|12 Feb 2003 01:05:31 -0000 vti_filesize:IR|811 vti_backlinkinfo:VX| cgi-bin/_vti_cnf/mt.cfg0000644002157400001440000000030407776374461021703 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|05 Jan 2004 23:54:25 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|05 Jan 2004 23:54:25 -0000 vti_filesize:IR|19136 vti_backlinkinfo:VX| cgi-bin/_vti_cnf/mt.cgi0000644002157400001440000000030207622316733021670 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|12 Feb 2003 01:05:31 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|12 Feb 2003 01:05:31 -0000 vti_filesize:IR|740 vti_backlinkinfo:VX| cgi-bin/extlib/0000755002157400001440000000000007776605402020275 5ustar minnesotaviolasociety.orgusers00000000000000cgi-bin/extlib/Apache/0000755002157400001440000000000007776605372021464 5ustar minnesotaviolasociety.orgusers00000000000000cgi-bin/extlib/Apache/XMLRPC/0000755002157400001440000000000007776605372022471 5ustar minnesotaviolasociety.orgusers00000000000000cgi-bin/extlib/Apache/XMLRPC/_vti_cnf/0000755002157400001440000000000007776605372024260 5ustar minnesotaviolasociety.orgusers00000000000000cgi-bin/extlib/Apache/XMLRPC/_vti_cnf/Lite.pm0000644002157400001440000000030307771550070025474 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|22 Dec 2003 11:06:32 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|22 Dec 2003 11:06:32 -0000 vti_filesize:IR|3703 vti_backlinkinfo:VX| cgi-bin/extlib/Apache/XMLRPC/Lite.pm0000644002157400001440000000716707771550070023724 0ustar minnesotaviolasociety.orgusers00000000000000# ====================================================================== # # Copyright (C) 2000-2001 Paul Kulchenko (paulclinger@yahoo.com) # SOAP::Lite is free software; you can redistribute it # and/or modify it under the same terms as Perl itself. # # $Id: Lite.pm,v 1.3 2001/08/11 19:09:57 paulk Exp $ # # ====================================================================== package Apache::XMLRPC::Lite; use strict; use vars qw(@ISA $VERSION); use XMLRPC::Transport::HTTP; @ISA = qw(XMLRPC::Transport::HTTP::Apache); $VERSION = eval sprintf("%d.%s", q$Name: release-0_52-public $ =~ /-(\d+)_([\d_]+)/); my $server = __PACKAGE__->new; sub handler { $server->configure(@_); $server->SUPER::handler(@_); } # ====================================================================== 1; __END__ =head1 NAME Apache::XMLRPC::Lite - mod_perl-based XML-RPC server with minimum configuration =head1 SYNOPSIS =over 4 =item httpd.conf (Location), directory-based access SetHandler perl-script PerlHandler Apache::XMLRPC::Lite PerlSetVar dispatch_to "/Your/Path/To/Deployed/Modules, Module::Name, Module::method" PerlSetVar options "compress_threshold => 10000" =item httpd.conf (Files), file-based access SetHandler perl-script PerlHandler Apache::XMLRPC::Lite PerlSetVar dispatch_to "/Your/Path/To/Deployed/Modules, Module::Name, Module::method" PerlSetVar options "compress_threshold => 10000" =item .htaccess, directory-based access SetHandler perl-script PerlHandler Apache::XMLRPC::Lite PerlSetVar dispatch_to "/Your/Path/To/Deployed/Modules, Module::Name, Module::method" PerlSetVar options "compress_threshold => 10000" =back =head1 DESCRIPTION This Apache Perl module provides the ability to add support for XML-RPC protocol with easy configuration (either in .conf or in .htaccess file). This functionality should give you lightweight option for hosting SOAP services and greatly simplify configuration aspects. This module inherites functionality from XMLRPC::Transport::HTTP::Apache component of XMLRPC::Lite module. =head1 CONFIGURATION The module can be placed in , , , directives in main server configuration areas or directly in .htaccess file. All parameters should be quoted and can be separated with commas or spaces for lists ("a, b, c") and with 'wide arrows' and commas for hash parameters ("key1 => value1, key2 => value2"). All options that you can find in XMLRPC::Transport::HTTP::Apache component are available for configuration. Here is the description of most important ones. =over 4 =item dispatch_to (LIST) Specifies path to directory that contains Perl modules you'd like to give access to, or just list of modules (for preloaded modules). PerlSetVar dispatch_to "/Your/Path/To/Deployed/Modules, Module::Name, Module::method" =item options (HASH) Specifies list of options for your module, for example threshold for compression. Future versions will support more options. See XMLRPC::Transport::HTTP documentation for other options. PerlSetVar options "compress_threshold => 10000" =back =head1 DEPENDENCIES XMLRPC::Lite mod_perl =head1 SEE ALSO XMLRPC::Transport::HTTP::Apache for implementation details, XMLRPC::Lite for general information, and F for .htaccess example =head1 COPYRIGHT Copyright (C) 2000-2001 Paul Kulchenko. All rights reserved. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 AUTHOR Paul Kulchenko (paulclinger@yahoo.com) =cut cgi-bin/extlib/Apache/_vti_cnf/0000755002157400001440000000000007776605372023253 5ustar minnesotaviolasociety.orgusers00000000000000cgi-bin/extlib/Apache/_vti_cnf/SOAP.pm0000644002157400001440000000030307771550070024334 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|22 Dec 2003 11:06:32 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|22 Dec 2003 11:06:32 -0000 vti_filesize:IR|3665 vti_backlinkinfo:VX| cgi-bin/extlib/Apache/SOAP.pm0000644002157400001440000000712107771550070022552 0ustar minnesotaviolasociety.orgusers00000000000000# ====================================================================== # # Copyright (C) 2000-2001 Paul Kulchenko (paulclinger@yahoo.com) # SOAP::Lite is free software; you can redistribute it # and/or modify it under the same terms as Perl itself. # # $Id: SOAP.pm,v 1.3 2001/08/11 19:09:57 paulk Exp $ # # ====================================================================== package Apache::SOAP; use strict; use vars qw(@ISA $VERSION); use SOAP::Transport::HTTP; @ISA = qw(SOAP::Transport::HTTP::Apache); $VERSION = eval sprintf("%d.%s", q$Name: release-0_52-public $ =~ /-(\d+)_([\d_]+)/); my $server = __PACKAGE__->new; sub handler { $server->configure(@_); $server->SUPER::handler(@_); } # ====================================================================== 1; __END__ =head1 NAME Apache::SOAP - mod_perl-based SOAP server with minimum configuration =head1 SYNOPSIS =over 4 =item httpd.conf (Location), directory-based access SetHandler perl-script PerlHandler Apache::SOAP PerlSetVar dispatch_to "/Your/Path/To/Deployed/Modules, Module::Name, Module::method" PerlSetVar options "compress_threshold => 10000" =item httpd.conf (Files), file-based access SetHandler perl-script PerlHandler Apache::SOAP PerlSetVar dispatch_to "/Your/Path/To/Deployed/Modules, Module::Name, Module::method" PerlSetVar options "compress_threshold => 10000" =item .htaccess, directory-based access SetHandler perl-script PerlHandler Apache::SOAP PerlSetVar dispatch_to "/Your/Path/To/Deployed/Modules, Module::Name, Module::method" PerlSetVar options "compress_threshold => 10000" =back =head1 DESCRIPTION This Apache Perl module provides the ability to add support for SOAP (Simple Object Access Protocol) protocol with easy configuration (either in .conf or in .htaccess file). This functionality should give you lightweight option for hosting SOAP services and greatly simplify configuration aspects. This module inherites functionality from SOAP::Transport::HTTP::Apache component of SOAP::Lite module. =head1 CONFIGURATION The module can be placed in , , , directives in main server configuration areas or directly in .htaccess file. All parameters should be quoted and can be separated with commas or spaces for lists ("a, b, c") and with 'wide arrows' and commas for hash parameters ("key1 => value1, key2 => value2"). All options that you can find in SOAP::Transport::HTTP::Apache component are available for configuration. Here is the description of most important ones. =over 4 =item dispatch_to (LIST) Specifies path to directory that contains Perl modules you'd like to give access to, or just list of modules (for preloaded modules). PerlSetVar dispatch_to "/Your/Path/To/Deployed/Modules, Module::Name, Module::method" =item options (HASH) Specifies list of options for your module, for example threshold for compression. Future versions will support more options. See SOAP::Transport::HTTP documentation for other options. PerlSetVar options "compress_threshold => 10000" =back =head1 DEPENDENCIES SOAP::Lite mod_perl =head1 SEE ALSO SOAP::Transport::HTTP::Apache for implementation details, SOAP::Lite for general information, and F for .htaccess example =head1 COPYRIGHT Copyright (C) 2000-2001 Paul Kulchenko. All rights reserved. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 AUTHOR Paul Kulchenko (paulclinger@yahoo.com) =cut cgi-bin/extlib/CGI/0000755002157400001440000000000007776605372020705 5ustar minnesotaviolasociety.orgusers00000000000000cgi-bin/extlib/CGI/_vti_cnf/0000755002157400001440000000000007776605372022474 5ustar minnesotaviolasociety.orgusers00000000000000cgi-bin/extlib/CGI/_vti_cnf/Apache.pm0000644002157400001440000000030207771550070024173 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|22 Dec 2003 11:06:32 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|22 Dec 2003 11:06:32 -0000 vti_filesize:IR|271 vti_backlinkinfo:VX| cgi-bin/extlib/CGI/_vti_cnf/Carp.pm0000644002157400001440000000030407771550070023701 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|22 Dec 2003 11:06:32 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|22 Dec 2003 11:06:32 -0000 vti_filesize:IR|12290 vti_backlinkinfo:VX| cgi-bin/extlib/CGI/_vti_cnf/Cookie.pm0000644002157400001440000000030407771550070024225 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|22 Dec 2003 11:06:32 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|22 Dec 2003 11:06:32 -0000 vti_filesize:IR|13217 vti_backlinkinfo:VX| cgi-bin/extlib/CGI/_vti_cnf/Fast.pm0000644002157400001440000000030307771550070023710 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|22 Dec 2003 11:06:32 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|22 Dec 2003 11:06:32 -0000 vti_filesize:IR|7191 vti_backlinkinfo:VX| cgi-bin/extlib/CGI/_vti_cnf/Pretty.pm0000644002157400001440000000030307771550070024302 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|22 Dec 2003 11:06:32 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|22 Dec 2003 11:06:32 -0000 vti_filesize:IR|7017 vti_backlinkinfo:VX| cgi-bin/extlib/CGI/_vti_cnf/Push.pm0000644002157400001440000000030407771550070023733 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|22 Dec 2003 11:06:32 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|22 Dec 2003 11:06:32 -0000 vti_filesize:IR|10942 vti_backlinkinfo:VX| cgi-bin/extlib/CGI/_vti_cnf/Switch.pm0000644002157400001440000000030207771550070024253 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|22 Dec 2003 11:06:32 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|22 Dec 2003 11:06:32 -0000 vti_filesize:IR|285 vti_backlinkinfo:VX| cgi-bin/extlib/CGI/_vti_cnf/Util.pm0000644002157400001440000000030407771550070023731 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|22 Dec 2003 11:06:32 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|22 Dec 2003 11:06:32 -0000 vti_filesize:IR|10736 vti_backlinkinfo:VX| cgi-bin/extlib/CGI/Apache.pm0000644002157400001440000000041707771550070022413 0ustar minnesotaviolasociety.orgusers00000000000000use CGI; $VERSION = '1.00'; 1; __END__ =head1 NAME CGI::Apache - Backward compatibility module for CGI.pm =head1 SYNOPSIS Do not use this module. It is deprecated. =head1 ABSTRACT =head1 DESCRIPTION =head1 AUTHOR INFORMATION =head1 BUGS =head1 SEE ALSO =cut cgi-bin/extlib/CGI/Carp.pm0000644002157400001440000003000207771550070022110 0ustar minnesotaviolasociety.orgusers00000000000000package CGI::Carp; =head1 NAME B - CGI routines for writing to the HTTPD (or other) error log =head1 SYNOPSIS use CGI::Carp; croak "We're outta here!"; confess "It was my fault: $!"; carp "It was your fault!"; warn "I'm confused"; die "I'm dying.\n"; use CGI::Carp qw(cluck); cluck "I wouldn't do that if I were you"; use CGI::Carp qw(fatalsToBrowser); die "Fatal error messages are now sent to browser"; =head1 DESCRIPTION CGI scripts have a nasty habit of leaving warning messages in the error logs that are neither time stamped nor fully identified. Tracking down the script that caused the error is a pain. This fixes that. Replace the usual use Carp; with use CGI::Carp And the standard warn(), die (), croak(), confess() and carp() calls will automagically be replaced with functions that write out nicely time-stamped messages to the HTTP server error log. For example: [Fri Nov 17 21:40:43 1995] test.pl: I'm confused at test.pl line 3. [Fri Nov 17 21:40:43 1995] test.pl: Got an error message: Permission denied. [Fri Nov 17 21:40:43 1995] test.pl: I'm dying. =head1 REDIRECTING ERROR MESSAGES By default, error messages are sent to STDERR. Most HTTPD servers direct STDERR to the server's error log. Some applications may wish to keep private error logs, distinct from the server's error log, or they may wish to direct error messages to STDOUT so that the browser will receive them. The C function is provided for this purpose. Since carpout() is not exported by default, you must import it explicitly by saying use CGI::Carp qw(carpout); The carpout() function requires one argument, which should be a reference to an open filehandle for writing errors. It should be called in a C block at the top of the CGI application so that compiler errors will be caught. Example: BEGIN { use CGI::Carp qw(carpout); open(LOG, ">>/usr/local/cgi-logs/mycgi-log") or die("Unable to open mycgi-log: $!\n"); carpout(LOG); } carpout() does not handle file locking on the log for you at this point. The real STDERR is not closed -- it is moved to CGI::Carp::SAVEERR. Some servers, when dealing with CGI scripts, close their connection to the browser when the script closes STDOUT and STDERR. CGI::Carp::SAVEERR is there to prevent this from happening prematurely. You can pass filehandles to carpout() in a variety of ways. The "correct" way according to Tom Christiansen is to pass a reference to a filehandle GLOB: carpout(\*LOG); This looks weird to mere mortals however, so the following syntaxes are accepted as well: carpout(LOG); carpout(main::LOG); carpout(main'LOG); carpout(\LOG); carpout(\'main::LOG'); ... and so on FileHandle and other objects work as well. Use of carpout() is not great for performance, so it is recommended for debugging purposes or for moderate-use applications. A future version of this module may delay redirecting STDERR until one of the CGI::Carp methods is called to prevent the performance hit. =head1 MAKING PERL ERRORS APPEAR IN THE BROWSER WINDOW If you want to send fatal (die, confess) errors to the browser, ask to import the special "fatalsToBrowser" subroutine: use CGI::Carp qw(fatalsToBrowser); die "Bad error here"; Fatal errors will now be echoed to the browser as well as to the log. CGI::Carp arranges to send a minimal HTTP header to the browser so that even errors that occur in the early compile phase will be seen. Nonfatal errors will still be directed to the log file only (unless redirected with carpout). =head2 Changing the default message By default, the software error message is followed by a note to contact the Webmaster by e-mail with the time and date of the error. If this message is not to your liking, you can change it using the set_message() routine. This is not imported by default; you should import it on the use() line: use CGI::Carp qw(fatalsToBrowser set_message); set_message("It's not a bug, it's a feature!"); You may also pass in a code reference in order to create a custom error message. At run time, your code will be called with the text of the error message that caused the script to die. Example: use CGI::Carp qw(fatalsToBrowser set_message); BEGIN { sub handle_errors { my $msg = shift; print "

Oh gosh

"; print "Got an error: $msg"; } set_message(\&handle_errors); } In order to correctly intercept compile-time errors, you should call set_message() from within a BEGIN{} block. =head1 MAKING WARNINGS APPEAR AS HTML COMMENTS It is now also possible to make non-fatal errors appear as HTML comments embedded in the output of your program. To enable this feature, export the new "warningsToBrowser" subroutine. Since sending warnings to the browser before the HTTP headers have been sent would cause an error, any warnings are stored in an internal buffer until you call the warningsToBrowser() subroutine with a true argument: use CGI::Carp qw(fatalsToBrowser warningsToBrowser); use CGI qw(:standard); print header(); warningsToBrowser(1); You may also give a false argument to warningsToBrowser() to prevent warnings from being sent to the browser while you are printing some content where HTML comments are not allowed: warningsToBrowser(0); # disable warnings print "\n"; warningsToBrowser(1); # re-enable warnings Note: In this respect warningsToBrowser() differs fundamentally from fatalsToBrowser(), which you should never call yourself! =head1 CHANGE LOG 1.05 carpout() added and minor corrections by Marc Hedlund on 11/26/95. 1.06 fatalsToBrowser() no longer aborts for fatal errors within eval() statements. 1.08 set_message() added and carpout() expanded to allow for FileHandle objects. 1.09 set_message() now allows users to pass a code REFERENCE for really custom error messages. croak and carp are now exported by default. Thanks to Gunther Birznieks for the patches. 1.10 Patch from Chris Dean (ctdean@cogit.com) to allow module to run correctly under mod_perl. 1.11 Changed order of > and < escapes. 1.12 Changed die() on line 217 to CORE::die to avoid B<-w> warning. 1.13 Added cluck() to make the module orthogonal with Carp. More mod_perl related fixes. 1.20 Patch from Ilmari Karonen (perl@itz.pp.sci.fi): Added warningsToBrowser(). Replaced tags with
 in
     fatalsToBrowser() output.

=head1 AUTHORS

Copyright 1995-1998, Lincoln D. Stein.  All rights reserved.  

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

Address bug reports and comments to: lstein@cshl.org

=head1 SEE ALSO

Carp, CGI::Base, CGI::BasePlus, CGI::Request, CGI::MiniSvr, CGI::Form,
CGI::Response

=cut

require 5.000;
use Exporter;
use Carp;
use File::Spec;

@ISA = qw(Exporter);
@EXPORT = qw(confess croak carp);
@EXPORT_OK = qw(carpout fatalsToBrowser warningsToBrowser wrap set_message cluck);

$main::SIG{__WARN__}=\&CGI::Carp::warn;
$main::SIG{__DIE__}=\&CGI::Carp::die;
$CGI::Carp::VERSION = '1.21';
$CGI::Carp::CUSTOM_MSG = undef;

# fancy import routine detects and handles 'errorWrap' specially.
sub import {
    my $pkg = shift;
    my(%routines);
    grep($routines{$_}++,@_,@EXPORT);
    $WRAP++ if $routines{'fatalsToBrowser'} || $routines{'wrap'};
    $WARN++ if $routines{'warningsToBrowser'};
    my($oldlevel) = $Exporter::ExportLevel;
    $Exporter::ExportLevel = 1;
    Exporter::import($pkg,keys %routines);
    $Exporter::ExportLevel = $oldlevel;
}

# These are the originals
sub realwarn { CORE::warn(@_); }
sub realdie { CORE::die(@_); }

sub id {
    my $level = shift;
    my($pack,$file,$line,$sub) = caller($level);
    my($dev,$dirs,$id) = File::Spec->splitpath($file);
    return ($file,$line,$id);
}

sub stamp {
    my $time = scalar(localtime);
    my $frame = 0;
    my ($id,$pack,$file,$dev,$dirs);
    do {
	$id = $file;
	($pack,$file) = caller($frame++);
    } until !$file;
    ($dev,$dirs,$id) = File::Spec->splitpath($id);
    return "[$time] $id: ";
}

sub warn {
    my $message = shift;
    my($file,$line,$id) = id(1);
    $message .= " at $file line $line.\n" unless $message=~/\n$/;
    _warn($message) if $WARN;
    my $stamp = stamp;
    $message=~s/^/$stamp/gm;
    realwarn $message;
}

sub _warn {
    my $msg = shift;
    if ($EMIT_WARNINGS) {
	# We need to mangle the message a bit to make it a valid HTML
	# comment.  This is done by substituting similar-looking ISO
	# 8859-1 characters for <, > and -.  This is a hack.
	$msg =~ tr/<>-/\253\273\255/;
	chomp $msg;
	print STDOUT "\n";
    } else {
	push @WARNINGS, $msg;
    }
}

sub ineval { $^S }

# The mod_perl package Apache::Registry loads CGI programs by calling
# eval.  These evals don't count when looking at the stack backtrace.
sub _longmess {
    my $message = Carp::longmess();
    my $mod_perl = exists $ENV{MOD_PERL};
    $message =~ s,eval[^\n]+Apache/Registry\.pm.*,,s if $mod_perl;
    return $message;    
}

sub die {
  realdie @_ if ineval;
  my ($message) = @_;
  my $time = scalar(localtime);
  my($file,$line,$id) = id(1);
  $message .= " at $file line $line." unless $message=~/\n$/;
  &fatalsToBrowser($message) if $WRAP;
  my $stamp = stamp;
  $message=~s/^/$stamp/gm;
  realdie $message;
}

sub set_message {
    $CGI::Carp::CUSTOM_MSG = shift;
    return $CGI::Carp::CUSTOM_MSG;
}

# Avoid generating "subroutine redefined" warnings with the following
# hack:
{
    local $^W=0;
    eval <&STDERR");
    open(STDERR, ">&$no") or 
	( print SAVEERR "Unable to redirect STDERR: $!\n" and exit(1) );
}

sub warningsToBrowser {
    $EMIT_WARNINGS = @_ ? shift : 1;
    _warn(shift @WARNINGS) while $EMIT_WARNINGS and @WARNINGS;
}

# headers
sub fatalsToBrowser {
    my($msg) = @_;
    $msg=~s/&/&/g;
    $msg=~s/>/>/g;
    $msg=~s/$ENV{SERVER_ADMIN})] :
	"this site's webmaster";
    my ($outer_message) = <Software error:
$msg

$outer_message END ; if ($mod_perl && (my $r = Apache->request)) { # If bytes have already been sent, then # we print the message out directly. # Otherwise we make a custom error # handler to produce the doc for us. if ($r->bytes_sent) { $r->print($mess); $r->exit; } else { $r->status(500); $r->custom_response(500,$mess); } } else { print STDOUT $mess; } } # Cut and paste from CGI.pm so that we don't have the overhead of # always loading the entire CGI module. sub to_filehandle { my $thingy = shift; return undef unless $thingy; return $thingy if UNIVERSAL::isa($thingy,'GLOB'); return $thingy if UNIVERSAL::isa($thingy,'FileHandle'); if (!ref($thingy)) { my $caller = 1; while (my $package = caller($caller++)) { my($tmp) = $thingy=~/[\':]/ ? $thingy : "$package\:\:$thingy"; return $tmp if defined(fileno($tmp)); } } return undef; } 1; cgi-bin/extlib/CGI/Cookie.pm0000644002157400001440000003164107771550070022446 0ustar minnesotaviolasociety.orgusers00000000000000package CGI::Cookie; # See the bottom of this file for the POD documentation. Search for the # string '=head'. # You can run this file through either pod2man or pod2html to produce pretty # documentation in manual or html file format (these utilities are part of the # Perl 5 distribution). # Copyright 1995-1999, Lincoln D. Stein. All rights reserved. # It may be used and modified freely, but I do request that this copyright # notice remain attached to the file. You may modify this module as you # wish, but if you redistribute a modified version, please attach a note # listing the modifications you have made. $CGI::Cookie::VERSION='1.20'; use CGI::Util qw(rearrange unescape escape); use overload '""' => \&as_string, 'cmp' => \&compare, 'fallback'=>1; # fetch a list of cookies from the environment and # return as a hash. the cookies are parsed as normal # escaped URL data. sub fetch { my $class = shift; my $raw_cookie = $ENV{HTTP_COOKIE} || $ENV{COOKIE}; return () unless $raw_cookie; return $class->parse($raw_cookie); } # fetch a list of cookies from the environment and # return as a hash. the cookie values are not unescaped # or altered in any way. sub raw_fetch { my $class = shift; my $raw_cookie = $ENV{HTTP_COOKIE} || $ENV{COOKIE}; return () unless $raw_cookie; my %results; my($key,$value); my(@pairs) = split("; ?",$raw_cookie); foreach (@pairs) { s/\s*(.*?)\s*/$1/; if (/^([^=]+)=(.*)/) { $key = $1; $value = $2; } else { $key = $_; $value = ''; } $results{$key} = $value; } return \%results unless wantarray; return %results; } sub parse { my ($self,$raw_cookie) = @_; my %results; my(@pairs) = split("; ?",$raw_cookie); foreach (@pairs) { s/\s*(.*?)\s*/$1/; my($key,$value) = split("=",$_,2); # Some foreign cookies are not in name=value format, so ignore # them. next if !defined($value); my @values = (); if ($value ne '') { @values = map unescape($_),split(/[&;]/,$value.'&dmy'); pop @values; } $key = unescape($key); # A bug in Netscape can cause several cookies with same name to # appear. The FIRST one in HTTP_COOKIE is the most recent version. $results{$key} ||= $self->new(-name=>$key,-value=>\@values); } return \%results unless wantarray; return %results; } sub new { my $class = shift; $class = ref($class) if ref($class); my($name,$value,$path,$domain,$secure,$expires) = rearrange([NAME,[VALUE,VALUES],PATH,DOMAIN,SECURE,EXPIRES],@_); # Pull out our parameters. my @values; if (ref($value)) { if (ref($value) eq 'ARRAY') { @values = @$value; } elsif (ref($value) eq 'HASH') { @values = %$value; } } else { @values = ($value); } bless my $self = { 'name'=>$name, 'value'=>[@values], },$class; # IE requires the path and domain to be present for some reason. $path ||= "/"; # however, this breaks networks which use host tables without fully qualified # names, so we comment it out. # $domain = CGI::virtual_host() unless defined $domain; $self->path($path) if defined $path; $self->domain($domain) if defined $domain; $self->secure($secure) if defined $secure; $self->expires($expires) if defined $expires; return $self; } sub as_string { my $self = shift; return "" unless $self->name; my(@constant_values,$domain,$path,$expires,$secure); push(@constant_values,"domain=$domain") if $domain = $self->domain; push(@constant_values,"path=$path") if $path = $self->path; push(@constant_values,"expires=$expires") if $expires = $self->expires; push(@constant_values,"secure") if $secure = $self->secure; my($key) = escape($self->name); my($cookie) = join("=",$key,join("&",map escape($_),$self->value)); return join("; ",$cookie,@constant_values); } sub compare { my $self = shift; my $value = shift; return "$self" cmp $value; } # accessors sub name { my $self = shift; my $name = shift; $self->{'name'} = $name if defined $name; return $self->{'name'}; } sub value { my $self = shift; my $value = shift; if (defined $value) { my @values; if (ref($value)) { if (ref($value) eq 'ARRAY') { @values = @$value; } elsif (ref($value) eq 'HASH') { @values = %$value; } } else { @values = ($value); } $self->{'value'} = [@values]; } return wantarray ? @{$self->{'value'}} : $self->{'value'}->[0] } sub domain { my $self = shift; my $domain = shift; $self->{'domain'} = $domain if defined $domain; return $self->{'domain'}; } sub secure { my $self = shift; my $secure = shift; $self->{'secure'} = $secure if defined $secure; return $self->{'secure'}; } sub expires { my $self = shift; my $expires = shift; $self->{'expires'} = CGI::Util::expires($expires,'cookie') if defined $expires; return $self->{'expires'}; } sub path { my $self = shift; my $path = shift; $self->{'path'} = $path if defined $path; return $self->{'path'}; } 1; =head1 NAME CGI::Cookie - Interface to Netscape Cookies =head1 SYNOPSIS use CGI qw/:standard/; use CGI::Cookie; # Create new cookies and send them $cookie1 = new CGI::Cookie(-name=>'ID',-value=>123456); $cookie2 = new CGI::Cookie(-name=>'preferences', -value=>{ font => Helvetica, size => 12 } ); print header(-cookie=>[$cookie1,$cookie2]); # fetch existing cookies %cookies = fetch CGI::Cookie; $id = $cookies{'ID'}->value; # create cookies returned from an external source %cookies = parse CGI::Cookie($ENV{COOKIE}); =head1 DESCRIPTION CGI::Cookie is an interface to Netscape (HTTP/1.1) cookies, an innovation that allows Web servers to store persistent information on the browser's side of the connection. Although CGI::Cookie is intended to be used in conjunction with CGI.pm (and is in fact used by it internally), you can use this module independently. For full information on cookies see http://www.ics.uci.edu/pub/ietf/http/rfc2109.txt =head1 USING CGI::Cookie CGI::Cookie is object oriented. Each cookie object has a name and a value. The name is any scalar value. The value is any scalar or array value (associative arrays are also allowed). Cookies also have several optional attributes, including: =over 4 =item B<1. expiration date> The expiration date tells the browser how long to hang on to the cookie. If the cookie specifies an expiration date in the future, the browser will store the cookie information in a disk file and return it to the server every time the user reconnects (until the expiration date is reached). If the cookie species an expiration date in the past, the browser will remove the cookie from the disk file. If the expiration date is not specified, the cookie will persist only until the user quits the browser. =item B<2. domain> This is a partial or complete domain name for which the cookie is valid. The browser will return the cookie to any host that matches the partial domain name. For example, if you specify a domain name of ".capricorn.com", then Netscape will return the cookie to Web servers running on any of the machines "www.capricorn.com", "ftp.capricorn.com", "feckless.capricorn.com", etc. Domain names must contain at least two periods to prevent attempts to match on top level domains like ".edu". If no domain is specified, then the browser will only return the cookie to servers on the host the cookie originated from. =item B<3. path> If you provide a cookie path attribute, the browser will check it against your script's URL before returning the cookie. For example, if you specify the path "/cgi-bin", then the cookie will be returned to each of the scripts "/cgi-bin/tally.pl", "/cgi-bin/order.pl", and "/cgi-bin/customer_service/complain.pl", but not to the script "/cgi-private/site_admin.pl". By default, the path is set to "/", so that all scripts at your site will receive the cookie. =item B<4. secure flag> If the "secure" attribute is set, the cookie will only be sent to your script if the CGI request is occurring on a secure channel, such as SSL. =back =head2 Creating New Cookies $c = new CGI::Cookie(-name => 'foo', -value => 'bar', -expires => '+3M', -domain => '.capricorn.com', -path => '/cgi-bin/database', -secure => 1 ); Create cookies from scratch with the B method. The B<-name> and B<-value> parameters are required. The name must be a scalar value. The value can be a scalar, an array reference, or a hash reference. (At some point in the future cookies will support one of the Perl object serialization protocols for full generality). B<-expires> accepts any of the relative or absolute date formats recognized by CGI.pm, for example "+3M" for three months in the future. See CGI.pm's documentation for details. B<-domain> points to a domain name or to a fully qualified host name. If not specified, the cookie will be returned only to the Web server that created it. B<-path> points to a partial URL on the current server. The cookie will be returned to all URLs beginning with the specified path. If not specified, it defaults to '/', which returns the cookie to all pages at your site. B<-secure> if set to a true value instructs the browser to return the cookie only when a cryptographic protocol is in use. =head2 Sending the Cookie to the Browser Within a CGI script you can send a cookie to the browser by creating one or more Set-Cookie: fields in the HTTP header. Here is a typical sequence: my $c = new CGI::Cookie(-name => 'foo', -value => ['bar','baz'], -expires => '+3M'); print "Set-Cookie: $c\n"; print "Content-Type: text/html\n\n"; To send more than one cookie, create several Set-Cookie: fields. Alternatively, you may concatenate the cookies together with "; " and send them in one field. If you are using CGI.pm, you send cookies by providing a -cookie argument to the header() method: print header(-cookie=>$c); Mod_perl users can set cookies using the request object's header_out() method: $r->header_out('Set-Cookie',$c); Internally, Cookie overloads the "" operator to call its as_string() method when incorporated into the HTTP header. as_string() turns the Cookie's internal representation into an RFC-compliant text representation. You may call as_string() yourself if you prefer: print "Set-Cookie: ",$c->as_string,"\n"; =head2 Recovering Previous Cookies %cookies = fetch CGI::Cookie; B returns an associative array consisting of all cookies returned by the browser. The keys of the array are the cookie names. You can iterate through the cookies this way: %cookies = fetch CGI::Cookie; foreach (keys %cookies) { do_something($cookies{$_}); } In a scalar context, fetch() returns a hash reference, which may be more efficient if you are manipulating multiple cookies. CGI.pm uses the URL escaping methods to save and restore reserved characters in its cookies. If you are trying to retrieve a cookie set by a foreign server, this escaping method may trip you up. Use raw_fetch() instead, which has the same semantics as fetch(), but performs no unescaping. You may also retrieve cookies that were stored in some external form using the parse() class method: $COOKIES = `cat /usr/tmp/Cookie_stash`; %cookies = parse CGI::Cookie($COOKIES); =head2 Manipulating Cookies Cookie objects have a series of accessor methods to get and set cookie attributes. Each accessor has a similar syntax. Called without arguments, the accessor returns the current value of the attribute. Called with an argument, the accessor changes the attribute and returns its new value. =over 4 =item B Get or set the cookie's name. Example: $name = $c->name; $new_name = $c->name('fred'); =item B Get or set the cookie's value. Example: $value = $c->value; @new_value = $c->value(['a','b','c','d']); B is context sensitive. In a list context it will return the current value of the cookie as an array. In a scalar context it will return the B value of a multivalued cookie. =item B Get or set the cookie's domain. =item B Get or set the cookie's path. =item B Get or set the cookie's expiration time. =back =head1 AUTHOR INFORMATION Copyright 1997-1998, Lincoln D. Stein. All rights reserved. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. Address bug reports and comments to: lstein@cshl.org =head1 BUGS This section intentionally left blank. =head1 SEE ALSO L, L =cut cgi-bin/extlib/CGI/Fast.pm0000644002157400001440000001602707771550070022133 0ustar minnesotaviolasociety.orgusers00000000000000package CGI::Fast; # See the bottom of this file for the POD documentation. Search for the # string '=head'. # You can run this file through either pod2man or pod2html to produce pretty # documentation in manual or html file format (these utilities are part of the # Perl 5 distribution). # Copyright 1995,1996, Lincoln D. Stein. All rights reserved. # It may be used and modified freely, but I do request that this copyright # notice remain attached to the file. You may modify this module as you # wish, but if you redistribute a modified version, please attach a note # listing the modifications you have made. # The most recent version and complete docs are available at: # http://www.genome.wi.mit.edu/ftp/pub/software/WWW/cgi_docs.html # ftp://ftp-genome.wi.mit.edu/pub/software/WWW/ $CGI::Fast::VERSION='1.04'; use CGI; use FCGI; @ISA = ('CGI'); # workaround for known bug in libfcgi while (($ignore) = each %ENV) { } # override the initialization behavior so that # state is NOT maintained between invocations sub save_request { # no-op } # If ENV{FCGI_SOCKET_PATH} is specified, we maintain a FCGI Request handle # in this package variable. use vars qw($Ext_Request); BEGIN { # If ENV{FCGI_SOCKET_PATH} is given, explicitly open the socket, # and keep the request handle around from which to call Accept(). if ($ENV{FCGI_SOCKET_PATH}) { my $path = $ENV{FCGI_SOCKET_PATH}; my $backlog = $ENV{FCGI_LISTEN_QUEUE} || 100; my $socket = FCGI::OpenSocket( $path, $backlog ); $Ext_Request = FCGI::Request( \*STDIN, \*STDOUT, \*STDERR, \%ENV, $socket, 1 ); } } # New is slightly different in that it calls FCGI's # accept() method. sub new { my ($self, $initializer, @param) = @_; unless (defined $initializer) { if ($Ext_Request) { return undef unless $Ext_Request->Accept() >= 0; } else { return undef unless FCGI::accept() >= 0; } } return $CGI::Q = $self->SUPER::new($initializer, @param); } 1; =head1 NAME CGI::Fast - CGI Interface for Fast CGI =head1 SYNOPSIS use CGI::Fast qw(:standard); $COUNTER = 0; while (new CGI::Fast) { print header; print start_html("Fast CGI Rocks"); print h1("Fast CGI Rocks"), "Invocation number ",b($COUNTER++), " PID ",b($$),".", hr; print end_html; } =head1 DESCRIPTION CGI::Fast is a subclass of the CGI object created by CGI.pm. It is specialized to work well with the Open Market FastCGI standard, which greatly speeds up CGI scripts by turning them into persistently running server processes. Scripts that perform time-consuming initialization processes, such as loading large modules or opening persistent database connections, will see large performance improvements. =head1 OTHER PIECES OF THE PUZZLE In order to use CGI::Fast you'll need a FastCGI-enabled Web server. Open Market's server is FastCGI-savvy. There are also freely redistributable FastCGI modules for NCSA httpd 1.5 and Apache. FastCGI-enabling modules for Microsoft Internet Information Server and Netscape Communications Server have been announced. In addition, you'll need a version of the Perl interpreter that has been linked with the FastCGI I/O library. Precompiled binaries are available for several platforms, including DEC Alpha, HP-UX and SPARC/Solaris, or you can rebuild Perl from source with patches provided in the FastCGI developer's kit. The FastCGI Perl interpreter can be used in place of your normal Perl without ill consequences. You can find FastCGI modules for Apache and NCSA httpd, precompiled Perl interpreters, and the FastCGI developer's kit all at URL: http://www.fastcgi.com/ =head1 WRITING FASTCGI PERL SCRIPTS FastCGI scripts are persistent: one or more copies of the script are started up when the server initializes, and stay around until the server exits or they die a natural death. After performing whatever one-time initialization it needs, the script enters a loop waiting for incoming connections, processing the request, and waiting some more. A typical FastCGI script will look like this: #!/usr/local/bin/perl # must be a FastCGI version of perl! use CGI::Fast; &do_some_initialization(); while ($q = new CGI::Fast) { &process_request($q); } Each time there's a new request, CGI::Fast returns a CGI object to your loop. The rest of the time your script waits in the call to new(). When the server requests that your script be terminated, new() will return undef. You can of course exit earlier if you choose. A new version of the script will be respawned to take its place (this may be necessary in order to avoid Perl memory leaks in long-running scripts). CGI.pm's default CGI object mode also works. Just modify the loop this way: while (new CGI::Fast) { &process_request; } Calls to header(), start_form(), etc. will all operate on the current request. =head1 INSTALLING FASTCGI SCRIPTS See the FastCGI developer's kit documentation for full details. On the Apache server, the following line must be added to srm.conf: AddType application/x-httpd-fcgi .fcgi FastCGI scripts must end in the extension .fcgi. For each script you install, you must add something like the following to srm.conf: FastCgiServer /usr/etc/httpd/fcgi-bin/file_upload.fcgi -processes 2 This instructs Apache to launch two copies of file_upload.fcgi at startup time. =head1 USING FASTCGI SCRIPTS AS CGI SCRIPTS Any script that works correctly as a FastCGI script will also work correctly when installed as a vanilla CGI script. However it will not see any performance benefit. =head1 EXTERNAL FASTCGI SERVER INVOCATION FastCGI supports a TCP/IP transport mechanism which allows FastCGI scripts to run external to the webserver, perhaps on a remote machine. To configure the webserver to connect to an external FastCGI server, you would add the following to your srm.conf: FastCgiExternalServer /usr/etc/httpd/fcgi-bin/file_upload.fcgi -host sputnik:8888 Two environment variables affect how the C object is created, allowing C to be used as an external FastCGI server. (See C documentation for C for more information.) =over =item FCGI_SOCKET_PATH The address (TCP/IP) or path (UNIX Domain) of the socket the external FastCGI script to which bind an listen for incoming connections from the web server. =item FCGI_LISTEN_QUEUE Maximum length of the queue of pending connections. =back For example: #!/usr/local/bin/perl # must be a FastCGI version of perl! use CGI::Fast; &do_some_initialization(); $ENV{FCGI_SOCKET_PATH} = "sputnik:8888"; $ENV{FCGI_LISTEN_QUEUE} = 100; while ($q = new CGI::Fast) { &process_request($q); } =head1 CAVEATS I haven't tested this very much. =head1 AUTHOR INFORMATION Copyright 1996-1998, Lincoln D. Stein. All rights reserved. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. Address bug reports and comments to: lstein@cshl.org =head1 BUGS This section intentionally left blank. =head1 SEE ALSO L, L =cut cgi-bin/extlib/CGI/Pretty.pm0000644002157400001440000001555107771550070022526 0ustar minnesotaviolasociety.orgusers00000000000000package CGI::Pretty; # See the bottom of this file for the POD documentation. Search for the # string '=head'. # You can run this file through either pod2man or pod2html to produce pretty # documentation in manual or html file format (these utilities are part of the # Perl 5 distribution). use strict; use CGI (); $CGI::Pretty::VERSION = '1.05_00'; $CGI::DefaultClass = __PACKAGE__; $CGI::Pretty::AutoloadClass = 'CGI'; @CGI::Pretty::ISA = qw( CGI ); initialize_globals(); sub _prettyPrint { my $input = shift; foreach my $i ( @CGI::Pretty::AS_IS ) { if ( $$input =~ /<\/$i>/si ) { my ( $a, $b, $c, $d, $e ) = $$input =~ /(.*)<$i(\s?)(.*?)>(.*?)<\/$i>(.*)/si; _prettyPrint( \$a ); _prettyPrint( \$e ); $$input = "$a<$i$b$c>$d$e"; return; } } $$input =~ s/$CGI::Pretty::LINEBREAK/$CGI::Pretty::LINEBREAK$CGI::Pretty::INDENT/g if $CGI::Pretty::LINEBREAK; } sub comment { my($self,@p) = CGI::self_or_CGI(@_); my $s = "@p"; $s =~ s/$CGI::Pretty::LINEBREAK/$CGI::Pretty::LINEBREAK$CGI::Pretty::INDENT/g if $CGI::Pretty::LINEBREAK; return $self->SUPER::comment( "$CGI::Pretty::LINEBREAK$CGI::Pretty::INDENT$s$CGI::Pretty::LINEBREAK" ) . $CGI::Pretty::LINEBREAK; } sub _make_tag_func { my ($self,$tagname) = @_; return $self->SUPER::_make_tag_func($tagname) if $tagname=~/^(start|end)_/; # As Lincoln as noted, the last else clause is VERY hairy, and it # took me a while to figure out what I was trying to do. # What it does is look for tags that shouldn't be indented (e.g. PRE) # and makes sure that when we nest tags, those tags don't get # indented. # For an example, try print td( pre( "hello\nworld" ) ); # If we didn't care about stuff like that, the code would be # MUCH simpler. BTW: I won't claim to be a regular expression # guru, so if anybody wants to contribute something that would # be quicker, easier to read, etc, I would be more than # willing to put it in - Brian return qq{ sub $tagname { # handle various cases in which we're called # most of this bizarre stuff is to avoid -w errors shift if \$_[0] && (ref(\$_[0]) && (substr(ref(\$_[0]),0,3) eq 'CGI' || UNIVERSAL::isa(\$_[0],'CGI'))); my(\$attr) = ''; if (ref(\$_[0]) && ref(\$_[0]) eq 'HASH') { my(\@attr) = make_attributes(shift); \$attr = " \@attr" if \@attr; } my(\$tag,\$untag) = ("\L<$tagname\E\$attr>","\L\E"); return \$tag unless \@_; my \@result; my \$NON_PRETTIFY_ENDTAGS = join "", map { "" } \@CGI::Pretty::AS_IS; if ( \$NON_PRETTIFY_ENDTAGS =~ /\$untag/ ) { \@result = map { "\$tag\$_\$untag\$CGI::Pretty::LINEBREAK" } (ref(\$_[0]) eq 'ARRAY') ? \@{\$_[0]} : "\@_"; } else { my \@args; if(ref(\$_[0]) eq 'ARRAY') { \@args = \@{\$_[0]} } else { foreach (\@_) { \$args[0] .= \$_; \$args[0] .= " " unless \$args[0] =~ /\\s\$/; } chop \$args[0]; } \@result = map { chomp; if ( \$_ !~ /<\\// ) { s/\$CGI::Pretty::LINEBREAK/\$CGI::Pretty::LINEBREAK\$CGI::Pretty::INDENT/g if \$CGI::Pretty::LINEBREAK; } else { my \$tmp = \$_; CGI::Pretty::_prettyPrint( \\\$tmp ); \$_ = \$tmp; } "\$tag\$CGI::Pretty::LINEBREAK\$CGI::Pretty::INDENT\$_\$CGI::Pretty::LINEBREAK\$untag\$CGI::Pretty::LINEBREAK" } \@args; } local \$" = ""; return "\@result"; } }; } sub start_html { return CGI::start_html( @_ ) . $CGI::Pretty::LINEBREAK; } sub end_html { return CGI::end_html( @_ ) . $CGI::Pretty::LINEBREAK; } sub new { my $class = shift; my $this = $class->SUPER::new( @_ ); Apache->request->register_cleanup(\&CGI::Pretty::_reset_globals) if ($CGI::MOD_PERL); $class->_reset_globals if $CGI::PERLEX; return bless $this, $class; } sub initialize_globals { # This is the string used for indentation of tags $CGI::Pretty::INDENT = "\t"; # This is the string used for seperation between tags $CGI::Pretty::LINEBREAK = "\n"; # These tags are not prettify'd. @CGI::Pretty::AS_IS = qw( a pre code script textarea ); 1; } sub _reset_globals { initialize_globals(); } 1; =head1 NAME CGI::Pretty - module to produce nicely formatted HTML code =head1 SYNOPSIS use CGI::Pretty qw( :html3 ); # Print a table with a single data element print table( TR( td( "foo" ) ) ); =head1 DESCRIPTION CGI::Pretty is a module that derives from CGI. It's sole function is to allow users of CGI to output nicely formatted HTML code. When using the CGI module, the following code: print table( TR( td( "foo" ) ) ); produces the following output:
foo
If a user were to create a table consisting of many rows and many columns, the resultant HTML code would be quite difficult to read since it has no carriage returns or indentation. CGI::Pretty fixes this problem. What it does is add a carriage return and indentation to the HTML code so that one can easily read it. print table( TR( td( "foo" ) ) ); now produces the following output:
foo
=head2 Tags that won't be formatted The and

 tags are not formatted.  If these tags were formatted, the
user would see the extra indentation on the web browser causing the page to
look different than what would be expected.  If you wish to add more tags to
the list of tags that are not to be touched, push them onto the C<@AS_IS> array:

    push @CGI::Pretty::AS_IS,qw(CODE XMP);

=head2 Customizing the Indenting

If you wish to have your own personal style of indenting, you can change the
C<$INDENT> variable:

    $CGI::Pretty::INDENT = "\t\t";

would cause the indents to be two tabs.

Similarly, if you wish to have more space between lines, you may change the
C<$LINEBREAK> variable:

    $CGI::Pretty::LINEBREAK = "\n\n";

would create two carriage returns between lines.

If you decide you want to use the regular CGI indenting, you can easily do 
the following:

    $CGI::Pretty::INDENT = $CGI::Pretty::LINEBREAK = "";

=head1 BUGS

This section intentionally left blank.

=head1 AUTHOR

Brian Paulsen , with minor modifications by
Lincoln Stein  for incorporation into the CGI.pm
distribution.

Copyright 1999, Brian Paulsen.  All rights reserved.

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

Bug reports and comments to Brian@ThePaulsens.com.  You can also write
to lstein@cshl.org, but this code looks pretty hairy to me and I'm not
sure I understand it!

=head1 SEE ALSO

L

=cut
cgi-bin/extlib/CGI/Push.pm0000644002157400001440000002527607771550070022163 0ustar  minnesotaviolasociety.orgusers00000000000000package CGI::Push;

# See the bottom of this file for the POD documentation.  Search for the
# string '=head'.

# You can run this file through either pod2man or pod2html to produce pretty
# documentation in manual or html file format (these utilities are part of the
# Perl 5 distribution).

# Copyright 1995-2000, Lincoln D. Stein.  All rights reserved.
# It may be used and modified freely, but I do request that this copyright
# notice remain attached to the file.  You may modify this module as you 
# wish, but if you redistribute a modified version, please attach a note
# listing the modifications you have made.

# The most recent version and complete docs are available at:
#   http://stein.cshl.org/WWW/software/CGI/

$CGI::Push::VERSION='1.04';
use CGI;
use CGI::Util 'rearrange';
@ISA = ('CGI');

$CGI::DefaultClass = 'CGI::Push';
$CGI::Push::AutoloadClass = 'CGI';

# add do_push() and push_delay() to exported tags
push(@{$CGI::EXPORT_TAGS{':standard'}},'do_push','push_delay');

sub do_push {
    my ($self,@p) = CGI::self_or_default(@_);

    # unbuffer output
    $| = 1;
    srand;
    my ($random) = sprintf("%08.0f",rand()*1E8);
    my ($boundary) = "----=_NeXtPaRt$random";

    my (@header);
    my ($type,$callback,$delay,$last_page,$cookie,$target,$expires,$nph,@other) = rearrange([TYPE,NEXT_PAGE,DELAY,LAST_PAGE,[COOKIE,COOKIES],TARGET,EXPIRES,NPH],@p);
    $type = 'text/html' unless $type;
    $callback = \&simple_counter unless $callback && ref($callback) eq 'CODE';
    $delay = 1 unless defined($delay);
    $self->push_delay($delay);
    $nph = 1 unless defined($nph);

    my(@o);
    foreach (@other) { push(@o,split("=")); }
    push(@o,'-Target'=>$target) if defined($target);
    push(@o,'-Cookie'=>$cookie) if defined($cookie);
    push(@o,'-Type'=>"multipart/x-mixed-replace;boundary=\"$boundary\"");
    push(@o,'-Server'=>"CGI.pm Push Module") if $nph;
    push(@o,'-Status'=>'200 OK');
    push(@o,'-nph'=>1) if $nph;
    print $self->header(@o);

    $boundary = "$CGI::CRLF--$boundary";

    print "WARNING: YOUR BROWSER DOESN'T SUPPORT THIS SERVER-PUSH TECHNOLOGY.${boundary}$CGI::CRLF";

    my (@contents) = &$callback($self,++$COUNTER);

    # now we enter a little loop
    while (1) {
        print "Content-type: ${type}$CGI::CRLF$CGI::CRLF" unless $type =~ /^dynamic|heterogeneous$/i;
        print @contents;
        @contents = &$callback($self,++$COUNTER);
        if ((@contents) && defined($contents[0])) {
            print "${boundary}$CGI::CRLF";
            do_sleep($self->push_delay()) if $self->push_delay();
        } else {
            if ($last_page && ref($last_page) eq 'CODE') {
                print "${boundary}$CGI::CRLF";
                do_sleep($self->push_delay()) if $self->push_delay();
                print "Content-type: ${type}$CGI::CRLF$CGI::CRLF" unless $type =~ /^dynamic|heterogeneous$/i;
                print  &$last_page($self,$COUNTER);
            }
            print "${boundary}--$CGI::CRLF";
            last;
        }
    }
    print "WARNING: YOUR BROWSER DOESN'T SUPPORT THIS SERVER-PUSH TECHNOLOGY.$CGI::CRLF";
}

sub simple_counter {
    my ($self,$count) = @_;
    return $self->start_html("CGI::Push Default Counter"),
           $self->h1("CGI::Push Default Counter"),
           "This page has been updated ",$self->strong($count)," times.",
           $self->hr(),
           $self->a({'-href'=>'http://www.genome.wi.mit.edu/ftp/pub/software/WWW/cgi_docs.html'},'CGI.pm home page'),
           $self->end_html;
}

sub do_sleep {
    my $delay = shift;
    if ( ($delay >= 1) && ($delay!~/\./) ){
        sleep($delay);
    } else {
        select(undef,undef,undef,$delay);
    }
}

sub push_delay {
    my ($self,$delay) = CGI::self_or_default(@_);
    return defined($delay) ? $self->{'.delay'} = 
        $delay : $self->{'.delay'};
}

1;

=head1 NAME

CGI::Push - Simple Interface to Server Push

=head1 SYNOPSIS

    use CGI::Push qw(:standard);

    do_push(-next_page=>\&next_page,
            -last_page=>\&last_page,
            -delay=>0.5);

    sub next_page {
        my($q,$counter) = @_;
        return undef if $counter >= 10;
        return start_html('Test'),
               h1('Visible'),"\n",
               "This page has been called ", strong($counter)," times",
               end_html();
    }

    sub last_page {
        my($q,$counter) = @_;
        return start_html('Done'),
               h1('Finished'),
               strong($counter - 1),' iterations.',
               end_html;
    }

=head1 DESCRIPTION

CGI::Push is a subclass of the CGI object created by CGI.pm.  It is
specialized for server push operations, which allow you to create
animated pages whose content changes at regular intervals.

You provide CGI::Push with a pointer to a subroutine that will draw
one page.  Every time your subroutine is called, it generates a new
page.  The contents of the page will be transmitted to the browser
in such a way that it will replace what was there beforehand.  The
technique will work with HTML pages as well as with graphics files, 
allowing you to create animated GIFs.

Only Netscape Navigator supports server push.  Internet Explorer
browsers do not.

=head1 USING CGI::Push

CGI::Push adds one new method to the standard CGI suite, do_push().
When you call this method, you pass it a reference to a subroutine
that is responsible for drawing each new page, an interval delay, and
an optional subroutine for drawing the last page.  Other optional
parameters include most of those recognized by the CGI header()
method.

You may call do_push() in the object oriented manner or not, as you
prefer:

    use CGI::Push;
    $q = new CGI::Push;
    $q->do_push(-next_page=>\&draw_a_page);

        -or-

    use CGI::Push qw(:standard);
    do_push(-next_page=>\&draw_a_page);

Parameters are as follows:

=over 4

=item -next_page

    do_push(-next_page=>\&my_draw_routine);

This required parameter points to a reference to a subroutine responsible for
drawing each new page.  The subroutine should expect two parameters
consisting of the CGI object and a counter indicating the number
of times the subroutine has been called.  It should return the
contents of the page as an B of one or more items to print.  
It can return a false value (or an empty array) in order to abort the
redrawing loop and print out the final page (if any)

    sub my_draw_routine {
        my($q,$counter) = @_;
        return undef if $counter > 100;
        return start_html('testing'),
               h1('testing'),
               "This page called $counter times";
    }

You are of course free to refer to create and use global variables
within your draw routine in order to achieve special effects.

=item -last_page

This optional parameter points to a reference to the subroutine
responsible for drawing the last page of the series.  It is called
after the -next_page routine returns a false value.  The subroutine
itself should have exactly the same calling conventions as the
-next_page routine.

=item -type

This optional parameter indicates the content type of each page.  It
defaults to "text/html".  Normally the module assumes that each page
is of a homogenous MIME type.  However if you provide either of the
magic values "heterogeneous" or "dynamic" (the latter provided for the
convenience of those who hate long parameter names), you can specify
the MIME type -- and other header fields -- on a per-page basis.  See 
"heterogeneous pages" for more details.

=item -delay

This indicates the delay, in seconds, between frames.  Smaller delays
refresh the page faster.  Fractional values are allowed.

B

=item -cookie, -target, -expires, -nph

These have the same meaning as the like-named parameters in
CGI::header().

If not specified, -nph will default to 1 (as needed for many servers, see below).

=back

=head2 Heterogeneous Pages

Ordinarily all pages displayed by CGI::Push share a common MIME type.
However by providing a value of "heterogeneous" or "dynamic" in the
do_push() -type parameter, you can specify the MIME type of each page
on a case-by-case basis.  

If you use this option, you will be responsible for producing the
HTTP header for each page.  Simply modify your draw routine to
look like this:

    sub my_draw_routine {
        my($q,$counter) = @_;
        return header('text/html'),   # note we're producing the header here
               start_html('testing'),
               h1('testing'),
               "This page called $counter times";
    }

You can add any header fields that you like, but some (cookies and
status fields included) may not be interpreted by the browser.  One
interesting effect is to display a series of pages, then, after the
last page, to redirect the browser to a new URL.  Because redirect() 
does b work, the easiest way is with a -refresh header field,
as shown below:

    sub my_draw_routine {
        my($q,$counter) = @_;
        return undef if $counter > 10;
        return header('text/html'),   # note we're producing the header here
               start_html('testing'),
               h1('testing'),
               "This page called $counter times";
    }

    sub my_last_page {
        return header(-refresh=>'5; URL=http://somewhere.else/finished.html',
                      -type=>'text/html'),
               start_html('Moved'),
               h1('This is the last page'),
               'Goodbye!'
               hr,
               end_html; 
    }

=head2 Changing the Page Delay on the Fly

If you would like to control the delay between pages on a page-by-page
basis, call push_delay() from within your draw routine.  push_delay()
takes a single numeric argument representing the number of seconds you
wish to delay after the current page is displayed and before
displaying the next one.  The delay may be fractional.  Without
parameters, push_delay() just returns the current delay.

=head1 INSTALLING CGI::Push SCRIPTS

Server push scripts must be installed as no-parsed-header (NPH)
scripts in order to work correctly on many servers.  On Unix systems,
this is most often accomplished by prefixing the script's name with "nph-".  
Recognition of NPH scripts happens automatically with WebSTAR and 
Microsoft IIS.  Users of other servers should see their documentation
for help.

Apache web server from version 1.3b2 on does not need server
push scripts installed as NPH scripts: the -nph parameter to do_push()
may be set to a false value to disable the extra headers needed by an
NPH script.

=head1 AUTHOR INFORMATION

Copyright 1995-1998, Lincoln D. Stein.  All rights reserved.  

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

Address bug reports and comments to: lstein@cshl.org

=head1 BUGS

This section intentionally left blank.

=head1 SEE ALSO

L, L

=cut

cgi-bin/extlib/CGI/Switch.pm0000644002157400001440000000043507771550070022473 0ustar  minnesotaviolasociety.orgusers00000000000000use CGI;

$VERSION = '1.00';

1;

__END__

=head1 NAME

CGI::Switch - Backward compatibility module for defunct CGI::Switch

=head1 SYNOPSIS

Do not use this module.  It is deprecated.

=head1 ABSTRACT

=head1 DESCRIPTION

=head1 AUTHOR INFORMATION

=head1 BUGS

=head1 SEE ALSO

=cut
cgi-bin/extlib/CGI/Util.pm0000644002157400001440000002476007771550070022156 0ustar  minnesotaviolasociety.orgusers00000000000000package CGI::Util;

use strict;
use vars qw($VERSION @EXPORT_OK @ISA $EBCDIC @A2E @E2A);
require Exporter;
@ISA = qw(Exporter);
@EXPORT_OK = qw(rearrange make_attributes unescape escape expires);

$VERSION = '1.3';

$EBCDIC = "\t" ne "\011";
if ($EBCDIC) {
  # (ord('^') == 95) for codepage 1047 as on os390, vmesa
  @A2E = (
   0,  1,  2,  3, 55, 45, 46, 47, 22,  5, 21, 11, 12, 13, 14, 15,
  16, 17, 18, 19, 60, 61, 50, 38, 24, 25, 63, 39, 28, 29, 30, 31,
  64, 90,127,123, 91,108, 80,125, 77, 93, 92, 78,107, 96, 75, 97,
 240,241,242,243,244,245,246,247,248,249,122, 94, 76,126,110,111,
 124,193,194,195,196,197,198,199,200,201,209,210,211,212,213,214,
 215,216,217,226,227,228,229,230,231,232,233,173,224,189, 95,109,
 121,129,130,131,132,133,134,135,136,137,145,146,147,148,149,150,
 151,152,153,162,163,164,165,166,167,168,169,192, 79,208,161,  7,
  32, 33, 34, 35, 36, 37,  6, 23, 40, 41, 42, 43, 44,  9, 10, 27,
  48, 49, 26, 51, 52, 53, 54,  8, 56, 57, 58, 59,  4, 20, 62,255,
  65,170, 74,177,159,178,106,181,187,180,154,138,176,202,175,188,
 144,143,234,250,190,160,182,179,157,218,155,139,183,184,185,171,
 100,101, 98,102, 99,103,158,104,116,113,114,115,120,117,118,119,
 172,105,237,238,235,239,236,191,128,253,254,251,252,186,174, 89,
  68, 69, 66, 70, 67, 71,156, 72, 84, 81, 82, 83, 88, 85, 86, 87,
 140, 73,205,206,203,207,204,225,112,221,222,219,220,141,142,223
	 );
  @E2A = (
   0,  1,  2,  3,156,  9,134,127,151,141,142, 11, 12, 13, 14, 15,
  16, 17, 18, 19,157, 10,  8,135, 24, 25,146,143, 28, 29, 30, 31,
 128,129,130,131,132,133, 23, 27,136,137,138,139,140,  5,  6,  7,
 144,145, 22,147,148,149,150,  4,152,153,154,155, 20, 21,158, 26,
  32,160,226,228,224,225,227,229,231,241,162, 46, 60, 40, 43,124,
  38,233,234,235,232,237,238,239,236,223, 33, 36, 42, 41, 59, 94,
  45, 47,194,196,192,193,195,197,199,209,166, 44, 37, 95, 62, 63,
 248,201,202,203,200,205,206,207,204, 96, 58, 35, 64, 39, 61, 34,
 216, 97, 98, 99,100,101,102,103,104,105,171,187,240,253,254,177,
 176,106,107,108,109,110,111,112,113,114,170,186,230,184,198,164,
 181,126,115,116,117,118,119,120,121,122,161,191,208, 91,222,174,
 172,163,165,183,169,167,182,188,189,190,221,168,175, 93,180,215,
 123, 65, 66, 67, 68, 69, 70, 71, 72, 73,173,244,246,242,243,245,
 125, 74, 75, 76, 77, 78, 79, 80, 81, 82,185,251,252,249,250,255,
  92,247, 83, 84, 85, 86, 87, 88, 89, 90,178,212,214,210,211,213,
  48, 49, 50, 51, 52, 53, 54, 55, 56, 57,179,219,220,217,218,159
	 );
  if (ord('^') == 106) { # as in the BS2000 posix-bc coded character set
     $A2E[91] = 187;   $A2E[92] = 188;  $A2E[94] = 106;  $A2E[96] = 74;
     $A2E[123] = 251;  $A2E[125] = 253; $A2E[126] = 255; $A2E[159] = 95;
     $A2E[162] = 176;  $A2E[166] = 208; $A2E[168] = 121; $A2E[172] = 186;
     $A2E[175] = 161;  $A2E[217] = 224; $A2E[219] = 221; $A2E[221] = 173;
     $A2E[249] = 192;
 
     $E2A[74] = 96;   $E2A[95] = 159;  $E2A[106] = 94;  $E2A[121] = 168;
     $E2A[161] = 175; $E2A[173] = 221; $E2A[176] = 162; $E2A[186] = 172;
     $E2A[187] = 91;  $E2A[188] = 92;  $E2A[192] = 249; $E2A[208] = 166;
     $E2A[221] = 219; $E2A[224] = 217; $E2A[251] = 123; $E2A[253] = 125;
     $E2A[255] = 126;
 }
  elsif (ord('^') == 176) { # as in codepage 037 on os400
     $A2E[10] = 37;  $A2E[91] = 186;  $A2E[93] = 187; $A2E[94] = 176;
     $A2E[133] = 21; $A2E[168] = 189; $A2E[172] = 95; $A2E[221] = 173;
 
     $E2A[21] = 133; $E2A[37] = 10;  $E2A[95] = 172; $E2A[173] = 221;
     $E2A[176] = 94; $E2A[186] = 91; $E2A[187] = 93; $E2A[189] = 168;
   }
}

# Smart rearrangement of parameters to allow named parameter
# calling.  We do the rearangement if:
# the first parameter begins with a -
sub rearrange {
    my($order,@param) = @_;
    return () unless @param;

    if (ref($param[0]) eq 'HASH') {
	@param = %{$param[0]};
    } else {
	return @param 
	    unless (defined($param[0]) && substr($param[0],0,1) eq '-');
    }

    # map parameters into positional indices
    my ($i,%pos);
    $i = 0;
    foreach (@$order) {
	foreach (ref($_) eq 'ARRAY' ? @$_ : $_) { $pos{lc($_)} = $i; }
	$i++;
    }

    my (@result,%leftover);
    $#result = $#$order;  # preextend
    while (@param) {
	my $key = lc(shift(@param));
	$key =~ s/^\-//;
	if (exists $pos{$key}) {
	    $result[$pos{$key}] = shift(@param);
	} else {
	    $leftover{$key} = shift(@param);
	}
    }

    push (@result,make_attributes(\%leftover,1)) if %leftover;
    @result;
}

sub make_attributes {
    my $attr = shift;
    return () unless $attr && ref($attr) && ref($attr) eq 'HASH';
    my $escape = shift || 0;
    my(@att);
    foreach (keys %{$attr}) {
	my($key) = $_;
	$key=~s/^\-//;     # get rid of initial - if present

	# old way: breaks EBCDIC!
	# $key=~tr/A-Z_/a-z-/; # parameters are lower case, use dashes

	($key="\L$key") =~ tr/_/-/; # parameters are lower case, use dashes

	my $value = $escape ? simple_escape($attr->{$_}) : $attr->{$_};
	push(@att,defined($attr->{$_}) ? qq/$key="$value"/ : qq/$key/);
    }
    return @att;
}

sub simple_escape {
  return unless defined(my $toencode = shift);
  $toencode =~ s{&}{&}gso;
  $toencode =~ s{<}{<}gso;
  $toencode =~ s{>}{>}gso;
  $toencode =~ s{\"}{"}gso;
# Doesn't work.  Can't work.  forget it.
#  $toencode =~ s{\x8b}{‹}gso;
#  $toencode =~ s{\x9b}{›}gso;
  $toencode;
}

sub utf8_chr ($) {
        my $c = shift(@_);

        if ($c < 0x80) {
                return sprintf("%c", $c);
        } elsif ($c < 0x800) {
                return sprintf("%c%c", 0xc0 | ($c >> 6), 0x80 | ($c & 0x3f));
        } elsif ($c < 0x10000) {
                return sprintf("%c%c%c",
                                           0xe0 |  ($c >> 12),
                                           0x80 | (($c >>  6) & 0x3f),
                                           0x80 | ( $c          & 0x3f));
        } elsif ($c < 0x200000) {
                return sprintf("%c%c%c%c",
                                           0xf0 |  ($c >> 18),
                                           0x80 | (($c >> 12) & 0x3f),
                                           0x80 | (($c >>  6) & 0x3f),
                                           0x80 | ( $c          & 0x3f));
        } elsif ($c < 0x4000000) {
                return sprintf("%c%c%c%c%c",
                                           0xf8 |  ($c >> 24),
                                           0x80 | (($c >> 18) & 0x3f),
                                           0x80 | (($c >> 12) & 0x3f),
                                           0x80 | (($c >>  6) & 0x3f),
                                           0x80 | ( $c          & 0x3f));

        } elsif ($c < 0x80000000) {
                return sprintf("%c%c%c%c%c%c",
                                           0xfe |  ($c >> 30),
                                           0x80 | (($c >> 24) & 0x3f),
                                           0x80 | (($c >> 18) & 0x3f),
                                           0x80 | (($c >> 12) & 0x3f),
                                           0x80 | (($c >> 6)  & 0x3f),
                                           0x80 | ( $c          & 0x3f));
        } else {
                return utf8(0xfffd);
        }
}

# unescape URL-encoded data
sub unescape {
  shift() if @_ > 1 and (ref($_[0]) || (defined $_[1] && $_[0] eq $CGI::DefaultClass));
  my $todecode = shift;
  return undef unless defined($todecode);
  $todecode =~ tr/+/ /;       # pluses become spaces
    $EBCDIC = "\t" ne "\011";
    if ($EBCDIC) {
      $todecode =~ s/%([0-9a-fA-F]{2})/chr $A2E[hex($1)]/ge;
    } else {
      $todecode =~ s/%(?:([0-9a-fA-F]{2})|u([0-9a-fA-F]{4}))/
	defined($1)? chr hex($1) : utf8_chr(hex($2))/ge;
    }
  return $todecode;
}

# URL-encode data
sub escape {
  shift() if @_ > 1 and ( ref($_[0]) || (defined $_[1] && $_[0] eq $CGI::DefaultClass));
  my $toencode = shift;
  return undef unless defined($toencode);
    if ($EBCDIC) {
      $toencode=~s/([^a-zA-Z0-9_.-])/uc sprintf("%%%02x",$E2A[ord($1)])/eg;
    } else {
      $toencode=~s/([^a-zA-Z0-9_.-])/uc sprintf("%%%02x",ord($1))/eg;
    }
  return $toencode;
}

# This internal routine creates date strings suitable for use in
# cookies and HTTP headers.  (They differ, unfortunately.)
# Thanks to Mark Fisher for this.
sub expires {
    my($time,$format) = @_;
    $format ||= 'http';

    my(@MON)=qw/Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec/;
    my(@WDAY) = qw/Sun Mon Tue Wed Thu Fri Sat/;

    # pass through preformatted dates for the sake of expire_calc()
    $time = expire_calc($time);
    return $time unless $time =~ /^\d+$/;

    # make HTTP/cookie date string from GMT'ed time
    # (cookies use '-' as date separator, HTTP uses ' ')
    my($sc) = ' ';
    $sc = '-' if $format eq "cookie";
    my($sec,$min,$hour,$mday,$mon,$year,$wday) = gmtime($time);
    $year += 1900;
    return sprintf("%s, %02d$sc%s$sc%04d %02d:%02d:%02d GMT",
                   $WDAY[$wday],$mday,$MON[$mon],$year,$hour,$min,$sec);
}

# This internal routine creates an expires time exactly some number of
# hours from the current time.  It incorporates modifications from 
# Mark Fisher.
sub expire_calc {
    my($time) = @_;
    my(%mult) = ('s'=>1,
                 'm'=>60,
                 'h'=>60*60,
                 'd'=>60*60*24,
                 'M'=>60*60*24*30,
                 'y'=>60*60*24*365);
    # format for time can be in any of the forms...
    # "now" -- expire immediately
    # "+180s" -- in 180 seconds
    # "+2m" -- in 2 minutes
    # "+12h" -- in 12 hours
    # "+1d"  -- in 1 day
    # "+3M"  -- in 3 months
    # "+2y"  -- in 2 years
    # "-3m"  -- 3 minutes ago(!)
    # If you don't supply one of these forms, we assume you are
    # specifying the date yourself
    my($offset);
    if (!$time || (lc($time) eq 'now')) {
        $offset = 0;
    } elsif ($time=~/^\d+/) {
        return $time;
    } elsif ($time=~/^([+-]?(?:\d+|\d*\.\d*))([mhdMy]?)/) {
        $offset = ($mult{$2} || 1)*$1;
    } else {
        return $time;
    }
    return (time+$offset);
}

1;

__END__

=head1 NAME

CGI::Util - Internal utilities used by CGI module

=head1 SYNOPSIS

none

=head1 DESCRIPTION

no public subroutines

=head1 AUTHOR INFORMATION

Copyright 1995-1998, Lincoln D. Stein.  All rights reserved.  

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

Address bug reports and comments to: lstein@cshl.org.  When sending
bug reports, please provide the version of CGI.pm, the version of
Perl, the name and version of your Web server, and the name and
version of the operating system you are using.  If the problem is even
remotely browser dependent, please provide information about the
affected browers as well.

=head1 SEE ALSO

L

=cut
cgi-bin/extlib/File/0000755002157400001440000000000007776605372021162 5ustar  minnesotaviolasociety.orgusers00000000000000cgi-bin/extlib/File/Spec/0000755002157400001440000000000007776605372022054 5ustar  minnesotaviolasociety.orgusers00000000000000cgi-bin/extlib/File/Spec/_vti_cnf/0000755002157400001440000000000007776605372023643 5ustar  minnesotaviolasociety.orgusers00000000000000cgi-bin/extlib/File/Spec/_vti_cnf/Functions.pm0000644002157400001440000000030307771550070026132 0ustar  minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl
vti_timelastmodified:TR|22 Dec 2003 11:06:32 -0000
vti_extenderversion:SR|5.0.2.2623
vti_cacheddtm:TX|22 Dec 2003 11:06:32 -0000
vti_filesize:IR|1625
vti_backlinkinfo:VX|
cgi-bin/extlib/File/Spec/_vti_cnf/Mac.pm0000644002157400001440000000030307771550070024662 0ustar  minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl
vti_timelastmodified:TR|22 Dec 2003 11:06:32 -0000
vti_extenderversion:SR|5.0.2.2623
vti_cacheddtm:TX|22 Dec 2003 11:06:32 -0000
vti_filesize:IR|8864
vti_backlinkinfo:VX|
cgi-bin/extlib/File/Spec/_vti_cnf/OS2.pm0000644002157400001440000000030307771550070024565 0ustar  minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl
vti_timelastmodified:TR|22 Dec 2003 11:06:32 -0000
vti_extenderversion:SR|5.0.2.2623
vti_cacheddtm:TX|22 Dec 2003 11:06:32 -0000
vti_filesize:IR|1142
vti_backlinkinfo:VX|
cgi-bin/extlib/File/Spec/_vti_cnf/Unix.pm0000644002157400001440000000030407771550070025106 0ustar  minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl
vti_timelastmodified:TR|22 Dec 2003 11:06:32 -0000
vti_extenderversion:SR|5.0.2.2623
vti_cacheddtm:TX|22 Dec 2003 11:06:32 -0000
vti_filesize:IR|10953
vti_backlinkinfo:VX|
cgi-bin/extlib/File/Spec/_vti_cnf/VMS.pm0000644002157400001440000000030407771550070024630 0ustar  minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl
vti_timelastmodified:TR|22 Dec 2003 11:06:32 -0000
vti_extenderversion:SR|5.0.2.2623
vti_cacheddtm:TX|22 Dec 2003 11:06:32 -0000
vti_filesize:IR|13396
vti_backlinkinfo:VX|
cgi-bin/extlib/File/Spec/_vti_cnf/Win32.pm0000644002157400001440000000030307771550070025064 0ustar  minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl
vti_timelastmodified:TR|22 Dec 2003 11:06:32 -0000
vti_extenderversion:SR|5.0.2.2623
vti_cacheddtm:TX|22 Dec 2003 11:06:32 -0000
vti_filesize:IR|8723
vti_backlinkinfo:VX|
cgi-bin/extlib/File/Spec/Functions.pm0000644002157400001440000000313107771550070024345 0ustar  minnesotaviolasociety.orgusers00000000000000package File::Spec::Functions;

use File::Spec;
use strict;

use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);

$VERSION = '1.1';

require Exporter;

@ISA = qw(Exporter);

@EXPORT = qw(
	canonpath
	catdir
	catfile
	curdir
	rootdir
	updir
	no_upwards
	file_name_is_absolute
	path
);

@EXPORT_OK = qw(
	devnull
	tmpdir
	splitpath
	splitdir
	catpath
	abs2rel
	rel2abs
);

%EXPORT_TAGS = ( ALL => [ @EXPORT_OK, @EXPORT ] );

foreach my $meth (@EXPORT, @EXPORT_OK) {
    my $sub = File::Spec->can($meth);
    no strict 'refs';
    *{$meth} = sub {&$sub('File::Spec', @_)};
}


1;
__END__

=head1 NAME

File::Spec::Functions - portably perform operations on file names

=head1 SYNOPSIS

	use File::Spec::Functions;
	$x = catfile('a','b');

=head1 DESCRIPTION

This module exports convenience functions for all of the class methods
provided by File::Spec.

For a reference of available functions, please consult L,
which contains the entire set, and which is inherited by the modules for
other platforms. For further information, please see L,
L, L, or L.

=head2 Exports

The following functions are exported by default.

	canonpath
	catdir
	catfile
	curdir
	rootdir
	updir
	no_upwards
	file_name_is_absolute
	path


The following functions are exported only by request.

	devnull
	tmpdir
	splitpath
	splitdir
	catpath
	abs2rel
	rel2abs

All the functions may be imported using the C<:ALL> tag.

=head1 SEE ALSO

File::Spec, File::Spec::Unix, File::Spec::Mac, File::Spec::OS2,
File::Spec::Win32, File::Spec::VMS, ExtUtils::MakeMaker
cgi-bin/extlib/File/Spec/Mac.pm0000644002157400001440000002124007771550070023076 0ustar  minnesotaviolasociety.orgusers00000000000000package File::Spec::Mac;

use strict;
use vars qw(@ISA $VERSION);
require File::Spec::Unix;

$VERSION = '1.2';

@ISA = qw(File::Spec::Unix);

=head1 NAME

File::Spec::Mac - File::Spec for MacOS

=head1 SYNOPSIS

 require File::Spec::Mac; # Done internally by File::Spec if needed

=head1 DESCRIPTION

Methods for manipulating file specifications.

=head1 METHODS

=over 2

=item canonpath

On MacOS, there's nothing to be done.  Returns what it's given.

=cut

sub canonpath {
    my ($self,$path) = @_;
    return $path;
}

=item catdir

Concatenate two or more directory names to form a complete path ending with 
a directory.  Put a trailing : on the end of the complete path if there 
isn't one, because that's what's done in MacPerl's environment.

The fundamental requirement of this routine is that

	  File::Spec->catdir(split(":",$path)) eq $path

But because of the nature of Macintosh paths, some additional 
possibilities are allowed to make using this routine give reasonable results 
for some common situations.  Here are the rules that are used.  Each 
argument has its trailing ":" removed.  Each argument, except the first,
has its leading ":" removed.  They are then joined together by a ":".

So

	  File::Spec->catdir("a","b") = "a:b:"
	  File::Spec->catdir("a:",":b") = "a:b:"
	  File::Spec->catdir("a:","b") = "a:b:"
	  File::Spec->catdir("a",":b") = "a:b"
	  File::Spec->catdir("a","","b") = "a::b"

etc.

To get a relative path (one beginning with :), begin the first argument with :
or put a "" as the first argument.

If you don't want to worry about these rules, never allow a ":" on the ends 
of any of the arguments except at the beginning of the first.

Under MacPerl, there is an additional ambiguity.  Does the user intend that

	  File::Spec->catfile("LWP","Protocol","http.pm")

be relative or absolute?  There's no way of telling except by checking for the
existence of LWP: or :LWP, and even there he may mean a dismounted volume or
a relative path in a different directory (like in @INC).   So those checks
aren't done here. This routine will treat this as absolute.

=cut

sub catdir {
    shift;
    my @args = @_;
    my $result = shift @args;
    $result =~ s/:\Z(?!\n)//;
    foreach (@args) {
	s/:\Z(?!\n)//;
	s/^://s;
	$result .= ":$_";
    }
    return "$result:";
}

=item catfile

Concatenate one or more directory names and a filename to form a
complete path ending with a filename.  Since this uses catdir, the
same caveats apply.  Note that the leading : is removed from the filename,
so that 

	  File::Spec->catfile($ENV{HOME},"file");

and

	  File::Spec->catfile($ENV{HOME},":file");

give the same answer, as one might expect.

=cut

sub catfile {
    my $self = shift;
    my $file = pop @_;
    return $file unless @_;
    my $dir = $self->catdir(@_);
    $file =~ s/^://s;
    return $dir.$file;
}

=item curdir

Returns a string representing the current directory.

=cut

sub curdir {
    return ":";
}

=item devnull

Returns a string representing the null device.

=cut

sub devnull {
    return "Dev:Null";
}

=item rootdir

Returns a string representing the root directory.  Under MacPerl,
returns the name of the startup volume, since that's the closest in
concept, although other volumes aren't rooted there.

=cut

sub rootdir {
#
#  There's no real root directory on MacOS.  The name of the startup
#  volume is returned, since that's the closest in concept.
#
    require Mac::Files;
    my $system =  Mac::Files::FindFolder(&Mac::Files::kOnSystemDisk,
					 &Mac::Files::kSystemFolderType);
    $system =~ s/:.*\Z(?!\n)/:/s;
    return $system;
}

=item tmpdir

Returns a string representation of the first existing directory
from the following list or '' if none exist:

    $ENV{TMPDIR}

=cut

my $tmpdir;
sub tmpdir {
    return $tmpdir if defined $tmpdir;
    $tmpdir = $ENV{TMPDIR} if -d $ENV{TMPDIR};
    $tmpdir = '' unless defined $tmpdir;
    return $tmpdir;
}

=item updir

Returns a string representing the parent directory.

=cut

sub updir {
    return "::";
}

=item file_name_is_absolute

Takes as argument a path and returns true, if it is an absolute path.  In 
the case where a name can be either relative or absolute (for example, a 
folder named "HD" in the current working directory on a drive named "HD"), 
relative wins.  Use ":" in the appropriate place in the path if you want to
distinguish unambiguously.

As a special case, the file name '' is always considered to be absolute.

=cut

sub file_name_is_absolute {
    my ($self,$file) = @_;
    if ($file =~ /:/) {
	return ($file !~ m/^:/s);
    } elsif ( $file eq '' ) {
        return 1 ;
    } else {
	return (! -e ":$file");
    }
}

=item path

Returns the null list for the MacPerl application, since the concept is 
usually meaningless under MacOS. But if you're using the MacPerl tool under 
MPW, it gives back $ENV{Commands} suitably split, as is done in 
:lib:ExtUtils:MM_Mac.pm.

=cut

sub path {
#
#  The concept is meaningless under the MacPerl application.
#  Under MPW, it has a meaning.
#
    return unless exists $ENV{Commands};
    return split(/,/, $ENV{Commands});
}

=item splitpath

=cut

sub splitpath {
    my ($self,$path, $nofile) = @_;

    my ($volume,$directory,$file) = ('','','');

    if ( $nofile ) {
        ( $volume, $directory ) = $path =~ m@((?:[^:]+(?::|\Z(?!\n)))?)(.*)@s;
    }
    else {
        $path =~ 
            m@^( (?: [^:]+: )? ) 
                ( (?: .*: )? )
                ( .* )
             @xs;
        $volume    = $1;
        $directory = $2;
        $file      = $3;
    }

    # Make sure non-empty volumes and directories end in ':'
    $volume    .= ':' if $volume    =~ m@[^:]\Z(?!\n)@ ;
    $directory .= ':' if $directory =~ m@[^:]\Z(?!\n)@ ;
    return ($volume,$directory,$file);
}


=item splitdir

=cut

sub splitdir {
    my ($self,$directories) = @_ ;
    #
    # split() likes to forget about trailing null fields, so here we
    # check to be sure that there will not be any before handling the
    # simple case.
    #
    if ( $directories !~ m@:\Z(?!\n)@ ) {
        return split( m@:@, $directories );
    }
    else {
        #
        # since there was a trailing separator, add a file name to the end, 
        # then do the split, then replace it with ''.
        #
        my( @directories )= split( m@:@, "${directories}dummy" ) ;
        $directories[ $#directories ]= '' ;
        return @directories ;
    }
}


=item catpath

=cut

sub catpath {
    my $self = shift ;

    my $result = shift ;
    $result =~ s@^([^/])@/$1@s ;

    my $segment ;
    for $segment ( @_ ) {
        if ( $result =~ m@[^/]\Z(?!\n)@ && $segment =~ m@^[^/]@s ) {
            $result .= "/$segment" ;
        }
        elsif ( $result =~ m@/\Z(?!\n)@ && $segment =~ m@^/@s ) {
            $result  =~ s@/+\Z(?!\n)@/@;
            $segment =~ s@^/+@@s;
            $result  .= "$segment" ;
        }
        else {
            $result  .= $segment ;
        }
    }

    return $result ;
}

=item abs2rel

See L for general documentation.

Unlike Cabs2rel()>, this function will make
checks against the local filesystem if necessary.  See
L for details.

=cut

sub abs2rel {
    my($self,$path,$base) = @_;

    # Clean up $path
    if ( ! $self->file_name_is_absolute( $path ) ) {
        $path = $self->rel2abs( $path ) ;
    }

    # Figure out the effective $base and clean it up.
    if ( !defined( $base ) || $base eq '' ) {
        $base = cwd() ;
    }
    elsif ( ! $self->file_name_is_absolute( $base ) ) {
        $base = $self->rel2abs( $base ) ;
    }

    # Now, remove all leading components that are the same
    my @pathchunks = $self->splitdir( $path );
    my @basechunks = $self->splitdir( $base );

    while (@pathchunks && @basechunks && $pathchunks[0] eq $basechunks[0]) {
        shift @pathchunks ;
        shift @basechunks ;
    }

    $path = join( ':', @pathchunks );

    # @basechunks now contains the number of directories to climb out of.
    $base = ':' x @basechunks ;

    return "$base:$path" ;
}

=item rel2abs

See L for general documentation.

Unlike Crel2abs()>, this function will make
checks against the local filesystem if necessary.  See
L for details.

=cut

sub rel2abs {
    my ($self,$path,$base ) = @_;

    if ( ! $self->file_name_is_absolute( $path ) ) {
        if ( !defined( $base ) || $base eq '' ) {
            $base = cwd() ;
        }
        elsif ( ! $self->file_name_is_absolute( $base ) ) {
            $base = $self->rel2abs( $base ) ;
        }
        else {
            $base = $self->canonpath( $base ) ;
        }

        $path = $self->canonpath("$base$path") ;
    }

    return $path ;
}


=back

=head1 SEE ALSO

L

=cut

1;
cgi-bin/extlib/File/Spec/OS2.pm0000644002157400001440000000216607771550070023007 0ustar  minnesotaviolasociety.orgusers00000000000000package File::Spec::OS2;

use strict;
use vars qw(@ISA $VERSION);
require File::Spec::Unix;

$VERSION = '1.1';

@ISA = qw(File::Spec::Unix);

sub devnull {
    return "/dev/nul";
}

sub case_tolerant {
    return 1;
}

sub file_name_is_absolute {
    my ($self,$file) = @_;
    return scalar($file =~ m{^([a-z]:)?[\\/]}is);
}

sub path {
    my $path = $ENV{PATH};
    $path =~ s:\\:/:g;
    my @path = split(';',$path);
    foreach (@path) { $_ = '.' if $_ eq '' }
    return @path;
}

my $tmpdir;
sub tmpdir {
    return $tmpdir if defined $tmpdir;
    my $self = shift;
    foreach (@ENV{qw(TMPDIR TEMP TMP)}, qw(/tmp /)) {
	next unless defined && -d;
	$tmpdir = $_;
	last;
    }
    $tmpdir = '' unless defined $tmpdir;
    $tmpdir =~ s:\\:/:g;
    $tmpdir = $self->canonpath($tmpdir);
    return $tmpdir;
}

1;
__END__

=head1 NAME

File::Spec::OS2 - methods for OS/2 file specs

=head1 SYNOPSIS

 require File::Spec::OS2; # Done internally by File::Spec if needed

=head1 DESCRIPTION

See File::Spec::Unix for a documentation of the methods provided
there. This package overrides the implementation of these methods, not
the semantics.
cgi-bin/extlib/File/Spec/Unix.pm0000644002157400001440000002531107771550070023324 0ustar  minnesotaviolasociety.orgusers00000000000000package File::Spec::Unix;

use strict;
use vars qw($VERSION);

$VERSION = '1.2';

use Cwd;

=head1 NAME

File::Spec::Unix - methods used by File::Spec

=head1 SYNOPSIS

 require File::Spec::Unix; # Done automatically by File::Spec

=head1 DESCRIPTION

Methods for manipulating file specifications.

=head1 METHODS

=over 2

=item canonpath

No physical check on the filesystem, but a logical cleanup of a
path. On UNIX eliminated successive slashes and successive "/.".

    $cpath = File::Spec->canonpath( $path ) ;

=cut

sub canonpath {
    my ($self,$path) = @_;
    $path =~ s|/+|/|g unless($^O eq 'cygwin');     # xx////xx  -> xx/xx
    $path =~ s|(/\.)+/|/|g;                        # xx/././xx -> xx/xx
    $path =~ s|^(\./)+||s unless $path eq "./";    # ./xx      -> xx
    $path =~ s|^/(\.\./)+|/|s;                     # /../../xx -> xx
    $path =~ s|/\Z(?!\n)|| unless $path eq "/";          # xx/       -> xx
    return $path;
}

=item catdir

Concatenate two or more directory names to form a complete path ending
with a directory. But remove the trailing slash from the resulting
string, because it doesn't look good, isn't necessary and confuses
OS2. Of course, if this is the root directory, don't cut off the
trailing slash :-)

=cut

sub catdir {
    my $self = shift;
    my @args = @_;
    foreach (@args) {
	# append a slash to each argument unless it has one there
	$_ .= "/" if $_ eq '' || substr($_,-1) ne "/";
    }
    return $self->canonpath(join('', @args));
}

=item catfile

Concatenate one or more directory names and a filename to form a
complete path ending with a filename

=cut

sub catfile {
    my $self = shift;
    my $file = pop @_;
    return $file unless @_;
    my $dir = $self->catdir(@_);
    $dir .= "/" unless substr($dir,-1) eq "/";
    return $dir.$file;
}

=item curdir

Returns a string representation of the current directory.  "." on UNIX.

=cut

sub curdir {
    return ".";
}

=item devnull

Returns a string representation of the null device. "/dev/null" on UNIX.

=cut

sub devnull {
    return "/dev/null";
}

=item rootdir

Returns a string representation of the root directory.  "/" on UNIX.

=cut

sub rootdir {
    return "/";
}

=item tmpdir

Returns a string representation of the first writable directory
from the following list or "" if none are writable:

    $ENV{TMPDIR}
    /tmp

=cut

my $tmpdir;
sub tmpdir {
    return $tmpdir if defined $tmpdir;
    foreach ($ENV{TMPDIR}, "/tmp") {
	next unless defined && -d && -w _;
	$tmpdir = $_;
	last;
    }
    $tmpdir = '' unless defined $tmpdir;
    return $tmpdir;
}

=item updir

Returns a string representation of the parent directory.  ".." on UNIX.

=cut

sub updir {
    return "..";
}

=item no_upwards

Given a list of file names, strip out those that refer to a parent
directory. (Does not strip symlinks, only '.', '..', and equivalents.)

=cut

sub no_upwards {
    my $self = shift;
    return grep(!/^\.{1,2}\Z(?!\n)/s, @_);
}

=item case_tolerant

Returns a true or false value indicating, respectively, that alphabetic
is not or is significant when comparing file specifications.

=cut

sub case_tolerant {
    return 0;
}

=item file_name_is_absolute

Takes as argument a path and returns true if it is an absolute path.

This does not consult the local filesystem on Unix, Win32, or OS/2.  It
does sometimes on MacOS (see L).
It does consult the working environment for VMS (see
L).

=cut

sub file_name_is_absolute {
    my ($self,$file) = @_;
    return scalar($file =~ m:^/:s);
}

=item path

Takes no argument, returns the environment variable PATH as an array.

=cut

sub path {
    my @path = split(':', $ENV{PATH});
    foreach (@path) { $_ = '.' if $_ eq '' }
    return @path;
}

=item join

join is the same as catfile.

=cut

sub join {
    my $self = shift;
    return $self->catfile(@_);
}

=item splitpath

    ($volume,$directories,$file) = File::Spec->splitpath( $path );
    ($volume,$directories,$file) = File::Spec->splitpath( $path, $no_file );

Splits a path in to volume, directory, and filename portions. On systems
with no concept of volume, returns undef for volume. 

For systems with no syntax differentiating filenames from directories, 
assumes that the last file is a path unless $no_file is true or a 
trailing separator or /. or /.. is present. On Unix this means that $no_file
true makes this return ( '', $path, '' ).

The directory portion may or may not be returned with a trailing '/'.

The results can be passed to L to get back a path equivalent to
(usually identical to) the original path.

=cut

sub splitpath {
    my ($self,$path, $nofile) = @_;

    my ($volume,$directory,$file) = ('','','');

    if ( $nofile ) {
        $directory = $path;
    }
    else {
        $path =~ m|^ ( (?: .* / (?: \.\.?\Z(?!\n) )? )? ) ([^/]*) |xs;
        $directory = $1;
        $file      = $2;
    }

    return ($volume,$directory,$file);
}


=item splitdir

The opposite of L.

    @dirs = File::Spec->splitdir( $directories );

$directories must be only the directory portion of the path on systems 
that have the concept of a volume or that have path syntax that differentiates
files from directories.

Unlike just splitting the directories on the separator, empty
directory names (C<''>) can be returned, because these are significant
on some OSs (e.g. MacOS).

On Unix,

    File::Spec->splitdir( "/a/b//c/" );

Yields:

    ( '', 'a', 'b', '', 'c', '' )

=cut

sub splitdir {
    my ($self,$directories) = @_ ;
    #
    # split() likes to forget about trailing null fields, so here we
    # check to be sure that there will not be any before handling the
    # simple case.
    #
    if ( $directories !~ m|/\Z(?!\n)| ) {
        return split( m|/|, $directories );
    }
    else {
        #
        # since there was a trailing separator, add a file name to the end, 
        # then do the split, then replace it with ''.
        #
        my( @directories )= split( m|/|, "${directories}dummy" ) ;
        $directories[ $#directories ]= '' ;
        return @directories ;
    }
}


=item catpath

Takes volume, directory and file portions and returns an entire path. Under
Unix, $volume is ignored, and directory and file are catenated.  A '/' is
inserted if need be.  On other OSs, $volume is significant.

=cut

sub catpath {
    my ($self,$volume,$directory,$file) = @_;

    if ( $directory ne ''                && 
         $file ne ''                     && 
         substr( $directory, -1 ) ne '/' && 
         substr( $file, 0, 1 ) ne '/' 
    ) {
        $directory .= "/$file" ;
    }
    else {
        $directory .= $file ;
    }

    return $directory ;
}

=item abs2rel

Takes a destination path and an optional base path returns a relative path
from the base path to the destination path:

    $rel_path = File::Spec->abs2rel( $path ) ;
    $rel_path = File::Spec->abs2rel( $path, $base ) ;

If $base is not present or '', then L is used. If $base is relative, 
then it is converted to absolute form using L. This means that it
is taken to be relative to L.

On systems with the concept of a volume, this assumes that both paths 
are on the $destination volume, and ignores the $base volume. 

On systems that have a grammar that indicates filenames, this ignores the 
$base filename as well. Otherwise all path components are assumed to be
directories.

If $path is relative, it is converted to absolute form using L.
This means that it is taken to be relative to L.

No checks against the filesystem are made on most systems.  On MacOS,
the filesystem may be consulted (see
L).  On VMS, there is
interaction with the working environment, as logicals and
macros are expanded.

Based on code written by Shigio Yamaguchi.

=cut

sub abs2rel {
    my($self,$path,$base) = @_;

    # Clean up $path
    if ( ! $self->file_name_is_absolute( $path ) ) {
        $path = $self->rel2abs( $path ) ;
    }
    else {
        $path = $self->canonpath( $path ) ;
    }

    # Figure out the effective $base and clean it up.
    if ( !defined( $base ) || $base eq '' ) {
        $base = cwd() ;
    }
    elsif ( ! $self->file_name_is_absolute( $base ) ) {
        $base = $self->rel2abs( $base ) ;
    }
    else {
        $base = $self->canonpath( $base ) ;
    }

    # Now, remove all leading components that are the same
    my @pathchunks = $self->splitdir( $path);
    my @basechunks = $self->splitdir( $base);

    while (@pathchunks && @basechunks && $pathchunks[0] eq $basechunks[0]) {
        shift @pathchunks ;
        shift @basechunks ;
    }

    $path = CORE::join( '/', @pathchunks );
    $base = CORE::join( '/', @basechunks );

    # $base now contains the directories the resulting relative path 
    # must ascend out of before it can descend to $path_directory.  So, 
    # replace all names with $parentDir
    $base =~ s|[^/]+|..|g ;

    # Glue the two together, using a separator if necessary, and preventing an
    # empty result.
    if ( $path ne '' && $base ne '' ) {
        $path = "$base/$path" ;
    } else {
        $path = "$base$path" ;
    }

    return $self->canonpath( $path ) ;
}

=item rel2abs

Converts a relative path to an absolute path. 

    $abs_path = File::Spec->rel2abs( $path ) ;
    $abs_path = File::Spec->rel2abs( $path, $base ) ;

If $base is not present or '', then L is used. If $base is relative, 
then it is converted to absolute form using L. This means that it
is taken to be relative to L.

On systems with the concept of a volume, this assumes that both paths 
are on the $base volume, and ignores the $path volume. 

On systems that have a grammar that indicates filenames, this ignores the 
$base filename as well. Otherwise all path components are assumed to be
directories.

If $path is absolute, it is cleaned up and returned using L.

No checks against the filesystem are made on most systems.  On MacOS,
the filesystem may be consulted (see
L).  On VMS, there is
interaction with the working environment, as logicals and
macros are expanded.

Based on code written by Shigio Yamaguchi.

=cut

sub rel2abs {
    my ($self,$path,$base ) = @_;

    # Clean up $path
    if ( ! $self->file_name_is_absolute( $path ) ) {
        # Figure out the effective $base and clean it up.
        if ( !defined( $base ) || $base eq '' ) {
            $base = cwd() ;
        }
        elsif ( ! $self->file_name_is_absolute( $base ) ) {
            $base = $self->rel2abs( $base ) ;
        }
        else {
            $base = $self->canonpath( $base ) ;
        }

        # Glom them together
        $path = $self->catdir( $base, $path ) ;
    }

    return $self->canonpath( $path ) ;
}


=back

=head1 SEE ALSO

L

=cut

1;
cgi-bin/extlib/File/Spec/VMS.pm0000644002157400001440000003212407771550070023046 0ustar  minnesotaviolasociety.orgusers00000000000000package File::Spec::VMS;

use strict;
use vars qw(@ISA $VERSION);
require File::Spec::Unix;

$VERSION = '1.1';

@ISA = qw(File::Spec::Unix);

use Cwd;
use File::Basename;
use VMS::Filespec;

=head1 NAME

File::Spec::VMS - methods for VMS file specs

=head1 SYNOPSIS

 require File::Spec::VMS; # Done internally by File::Spec if needed

=head1 DESCRIPTION

See File::Spec::Unix for a documentation of the methods provided
there. This package overrides the implementation of these methods, not
the semantics.

=over

=item eliminate_macros

Expands MM[KS]/Make macros in a text string, using the contents of
identically named elements of C<%$self>, and returns the result
as a file specification in Unix syntax.

=cut

sub eliminate_macros {
    my($self,$path) = @_;
    return '' unless $path;
    $self = {} unless ref $self;

    if ($path =~ /\s/) {
      return join ' ', map { $self->eliminate_macros($_) } split /\s+/, $path;
    }

    my($npath) = unixify($path);
    my($complex) = 0;
    my($head,$macro,$tail);

    # perform m##g in scalar context so it acts as an iterator
    while ($npath =~ m#(.*?)\$\((\S+?)\)(.*)#gs) { 
        if ($self->{$2}) {
            ($head,$macro,$tail) = ($1,$2,$3);
            if (ref $self->{$macro}) {
                if (ref $self->{$macro} eq 'ARRAY') {
                    $macro = join ' ', @{$self->{$macro}};
                }
                else {
                    print "Note: can't expand macro \$($macro) containing ",ref($self->{$macro}),
                          "\n\t(using MMK-specific deferred substitutuon; MMS will break)\n";
                    $macro = "\cB$macro\cB";
                    $complex = 1;
                }
            }
            else { ($macro = unixify($self->{$macro})) =~ s#/\Z(?!\n)##; }
            $npath = "$head$macro$tail";
        }
    }
    if ($complex) { $npath =~ s#\cB(.*?)\cB#\${$1}#gs; }
    $npath;
}

=item fixpath

Catchall routine to clean up problem MM[SK]/Make macros.  Expands macros
in any directory specification, in order to avoid juxtaposing two
VMS-syntax directories when MM[SK] is run.  Also expands expressions which
are all macro, so that we can tell how long the expansion is, and avoid
overrunning DCL's command buffer when MM[KS] is running.

If optional second argument has a TRUE value, then the return string is
a VMS-syntax directory specification, if it is FALSE, the return string
is a VMS-syntax file specification, and if it is not specified, fixpath()
checks to see whether it matches the name of a directory in the current
default directory, and returns a directory or file specification accordingly.

=cut

sub fixpath {
    my($self,$path,$force_path) = @_;
    return '' unless $path;
    $self = bless {} unless ref $self;
    my($fixedpath,$prefix,$name);

    if ($path =~ /\s/) {
      return join ' ',
             map { $self->fixpath($_,$force_path) }
	     split /\s+/, $path;
    }

    if ($path =~ m#^\$\([^\)]+\)\Z(?!\n)#s || $path =~ m#[/:>\]]#) { 
        if ($force_path or $path =~ /(?:DIR\)|\])\Z(?!\n)/) {
            $fixedpath = vmspath($self->eliminate_macros($path));
        }
        else {
            $fixedpath = vmsify($self->eliminate_macros($path));
        }
    }
    elsif ((($prefix,$name) = ($path =~ m#^\$\(([^\)]+)\)(.+)#s)) && $self->{$prefix}) {
        my($vmspre) = $self->eliminate_macros("\$($prefix)");
        # is it a dir or just a name?
        $vmspre = ($vmspre =~ m|/| or $prefix =~ /DIR\Z(?!\n)/) ? vmspath($vmspre) : '';
        $fixedpath = ($vmspre ? $vmspre : $self->{$prefix}) . $name;
        $fixedpath = vmspath($fixedpath) if $force_path;
    }
    else {
        $fixedpath = $path;
        $fixedpath = vmspath($fixedpath) if $force_path;
    }
    # No hints, so we try to guess
    if (!defined($force_path) and $fixedpath !~ /[:>(.\]]/) {
        $fixedpath = vmspath($fixedpath) if -d $fixedpath;
    }

    # Trim off root dirname if it's had other dirs inserted in front of it.
    $fixedpath =~ s/\.000000([\]>])/$1/;
    # Special case for VMS absolute directory specs: these will have had device
    # prepended during trip through Unix syntax in eliminate_macros(), since
    # Unix syntax has no way to express "absolute from the top of this device's
    # directory tree".
    if ($path =~ /^[\[>][^.\-]/) { $fixedpath =~ s/^[^\[<]+//; }
    $fixedpath;
}

=back

=head2 Methods always loaded

=over

=item canonpath (override)

Removes redundant portions of file specifications according to VMS syntax.

=cut

sub canonpath {
    my($self,$path) = @_;

    if ($path =~ m|/|) { # Fake Unix
      my $pathify = $path =~ m|/\Z(?!\n)|;
      $path = $self->SUPER::canonpath($path);
      if ($pathify) { return vmspath($path); }
      else          { return vmsify($path);  }
    }
    else {
      $path =~ s-\]\[--g;  $path =~ s/> foo.bar
      $path =~ s/([\[<])000000\./$1/;                   # [000000.foo     ==> foo
      1 while $path =~ s{([\[<-])\.-}{$1-};             # [.-.-           ==> [--
      $path =~ s/\.[^\[<\.]+\.-([\]\>])/$1/;            # bar.foo.-]      ==> bar]
      $path =~ s/([\[<])(-+)/$1 . "\cx" x length($2)/e; # encode leading '-'s
      $path =~ s/([\[<\.])([^\[<\.\cx]+)\.-\.?/$1/g;    # bar.-.foo       ==> foo
      $path =~ s/([\[<])(\cx+)/$1 . '-' x length($2)/e; # then decode
      return $path;
    }
}

=item catdir

Concatenates a list of file specifications, and returns the result as a
VMS-syntax directory specification.  No check is made for "impossible"
cases (e.g. elements other than the first being absolute filespecs).

=cut

sub catdir {
    my ($self,@dirs) = @_;
    my $dir = pop @dirs;
    @dirs = grep($_,@dirs);
    my $rslt;
    if (@dirs) {
	my $path = (@dirs == 1 ? $dirs[0] : $self->catdir(@dirs));
	my ($spath,$sdir) = ($path,$dir);
	$spath =~ s/\.dir\Z(?!\n)//; $sdir =~ s/\.dir\Z(?!\n)//; 
	$sdir = $self->eliminate_macros($sdir) unless $sdir =~ /^[\w\-]+\Z(?!\n)/s;
	$rslt = $self->fixpath($self->eliminate_macros($spath)."/$sdir",1);

	# Special case for VMS absolute directory specs: these will have had device
	# prepended during trip through Unix syntax in eliminate_macros(), since
	# Unix syntax has no way to express "absolute from the top of this device's
	# directory tree".
	if ($spath =~ /^[\[<][^.\-]/s) { $rslt =~ s/^[^\[<]+//s; }
    }
    else {
	if    (not defined $dir or not length $dir) { $rslt = ''; }
	elsif ($dir =~ /^\$\([^\)]+\)\Z(?!\n)/s)          { $rslt = $dir; }
	else                                        { $rslt = vmspath($dir); }
    }
    return $self->canonpath($rslt);
}

=item catfile

Concatenates a list of file specifications, and returns the result as a
VMS-syntax file specification.

=cut

sub catfile {
    my ($self,@files) = @_;
    my $file = pop @files;
    @files = grep($_,@files);
    my $rslt;
    if (@files) {
	my $path = (@files == 1 ? $files[0] : $self->catdir(@files));
	my $spath = $path;
	$spath =~ s/\.dir\Z(?!\n)//;
	if ($spath =~ /^[^\)\]\/:>]+\)\Z(?!\n)/s && basename($file) eq $file) {
	    $rslt = "$spath$file";
	}
	else {
	    $rslt = $self->eliminate_macros($spath);
	    $rslt = vmsify($rslt.($rslt ? '/' : '').unixify($file));
	}
    }
    else { $rslt = (defined($file) && length($file)) ? vmsify($file) : ''; }
    return $self->canonpath($rslt);
}


=item curdir (override)

Returns a string representation of the current directory: '[]'

=cut

sub curdir {
    return '[]';
}

=item devnull (override)

Returns a string representation of the null device: '_NLA0:'

=cut

sub devnull {
    return "_NLA0:";
}

=item rootdir (override)

Returns a string representation of the root directory: 'SYS$DISK:[000000]'

=cut

sub rootdir {
    return 'SYS$DISK:[000000]';
}

=item tmpdir (override)

Returns a string representation of the first writable directory
from the following list or '' if none are writable:

    sys$scratch
    $ENV{TMPDIR}

=cut

my $tmpdir;
sub tmpdir {
    return $tmpdir if defined $tmpdir;
    foreach ('sys$scratch', $ENV{TMPDIR}) {
	next unless defined && -d && -w _;
	$tmpdir = $_;
	last;
    }
    $tmpdir = '' unless defined $tmpdir;
    return $tmpdir;
}

=item updir (override)

Returns a string representation of the parent directory: '[-]'

=cut

sub updir {
    return '[-]';
}

=item case_tolerant (override)

VMS file specification syntax is case-tolerant.

=cut

sub case_tolerant {
    return 1;
}

=item path (override)

Translate logical name DCL$PATH as a searchlist, rather than trying
to C string value of C<$ENV{'PATH'}>.

=cut

sub path {
    my (@dirs,$dir,$i);
    while ($dir = $ENV{'DCL$PATH;' . $i++}) { push(@dirs,$dir); }
    return @dirs;
}

=item file_name_is_absolute (override)

Checks for VMS directory spec as well as Unix separators.

=cut

sub file_name_is_absolute {
    my ($self,$file) = @_;
    # If it's a logical name, expand it.
    $file = $ENV{$file} while $file =~ /^[\w\$\-]+\Z(?!\n)/s && $ENV{$file};
    return scalar($file =~ m!^/!s             ||
		  $file =~ m![<\[][^.\-\]>]!  ||
		  $file =~ /:[^<\[]/);
}

=item splitpath (override)

Splits using VMS syntax.

=cut

sub splitpath {
    my($self,$path) = @_;
    my($dev,$dir,$file) = ('','','');

    vmsify($path) =~ /(.+:)?([\[<].*[\]>])?(.*)/s;
    return ($1 || '',$2 || '',$3);
}

=item splitdir (override)

Split dirspec using VMS syntax.

=cut

sub splitdir {
    my($self,$dirspec) = @_;
    $dirspec =~ s/\]\[//g;  $dirspec =~ s/\-\-/-.-/g;
    $dirspec = "[$dirspec]" unless $dirspec =~ /[\[<]/; # make legal
    my(@dirs) = split('\.', vmspath($dirspec));
    $dirs[0] =~ s/^[\[<]//s;  $dirs[-1] =~ s/[\]>]\Z(?!\n)//s;
    @dirs;
}


=item catpath (override)

Construct a complete filespec using VMS syntax

=cut

sub catpath {
    my($self,$dev,$dir,$file) = @_;
    if ($dev =~ m|^/+([^/]+)|) { $dev = "$1:"; }
    else { $dev .= ':' unless $dev eq '' or $dev =~ /:\Z(?!\n)/; }
    if (length($dev) or length($dir)) {
      $dir = "[$dir]" unless $dir =~ /[\[<\/]/;
      $dir = vmspath($dir);
    }
    "$dev$dir$file";
}

=item abs2rel (override)

Use VMS syntax when converting filespecs.

=cut

sub abs2rel {
    my $self = shift;

    return vmspath(File::Spec::Unix::abs2rel( $self, @_ ))
        if ( join( '', @_ ) =~ m{/} ) ;

    my($path,$base) = @_;

    # Note: we use '/' to glue things together here, then let canonpath()
    # clean them up at the end.

    # Clean up $path
    if ( ! $self->file_name_is_absolute( $path ) ) {
        $path = $self->rel2abs( $path ) ;
    }
    else {
        $path = $self->canonpath( $path ) ;
    }

    # Figure out the effective $base and clean it up.
    if ( !defined( $base ) || $base eq '' ) {
        $base = cwd() ;
    }
    elsif ( ! $self->file_name_is_absolute( $base ) ) {
        $base = $self->rel2abs( $base ) ;
    }
    else {
        $base = $self->canonpath( $base ) ;
    }

    # Split up paths
    my ( $path_directories, $path_file ) =
        ($self->splitpath( $path, 1 ))[1,2] ;

    $path_directories = $1
        if $path_directories =~ /^\[(.*)\]\Z(?!\n)/s ;

    my $base_directories = ($self->splitpath( $base, 1 ))[1] ;

    $base_directories = $1
        if $base_directories =~ /^\[(.*)\]\Z(?!\n)/s ;

    # Now, remove all leading components that are the same
    my @pathchunks = $self->splitdir( $path_directories );
    my @basechunks = $self->splitdir( $base_directories );

    while ( @pathchunks && 
            @basechunks && 
            lc( $pathchunks[0] ) eq lc( $basechunks[0] ) 
          ) {
        shift @pathchunks ;
        shift @basechunks ;
    }

    # @basechunks now contains the directories to climb out of,
    # @pathchunks now has the directories to descend in to.
    $path_directories = '-.' x @basechunks . join( '.', @pathchunks ) ;
    $path_directories =~ s{\.\Z(?!\n)}{} ;
    return $self->canonpath( $self->catpath( '', $path_directories, $path_file ) ) ;
}


=item rel2abs (override)

Use VMS syntax when converting filespecs.

=cut

sub rel2abs {
    my $self = shift ;
    return vmspath(File::Spec::Unix::rel2abs( $self, @_ ))
        if ( join( '', @_ ) =~ m{/} ) ;

    my ($path,$base ) = @_;
    # Clean up and split up $path
    if ( ! $self->file_name_is_absolute( $path ) ) {
        # Figure out the effective $base and clean it up.
        if ( !defined( $base ) || $base eq '' ) {
            $base = cwd() ;
        }
        elsif ( ! $self->file_name_is_absolute( $base ) ) {
            $base = $self->rel2abs( $base ) ;
        }
        else {
            $base = $self->canonpath( $base ) ;
        }

        # Split up paths
        my ( $path_directories, $path_file ) =
            ($self->splitpath( $path ))[1,2] ;

        my ( $base_volume, $base_directories ) =
            $self->splitpath( $base ) ;

        $path_directories = '' if $path_directories eq '[]' ||
                                  $path_directories eq '<>';
        my $sep = '' ;
        $sep = '.'
            if ( $base_directories =~ m{[^.\]>]\Z(?!\n)} &&
                 $path_directories =~ m{^[^.\[<]}s
            ) ;
        $base_directories = "$base_directories$sep$path_directories";
        $base_directories =~ s{\.?[\]>][\[<]\.?}{.};

        $path = $self->catpath( $base_volume, $base_directories, $path_file );
   }

    return $self->canonpath( $path ) ;
}


=back

=head1 SEE ALSO

L

=cut

1;
cgi-bin/extlib/File/Spec/Win32.pm0000644002157400001440000002102307771550070023277 0ustar  minnesotaviolasociety.orgusers00000000000000package File::Spec::Win32;

use strict;
use Cwd;
use vars qw(@ISA $VERSION);
require File::Spec::Unix;

$VERSION = '1.2';

@ISA = qw(File::Spec::Unix);

=head1 NAME

File::Spec::Win32 - methods for Win32 file specs

=head1 SYNOPSIS

 require File::Spec::Win32; # Done internally by File::Spec if needed

=head1 DESCRIPTION

See File::Spec::Unix for a documentation of the methods provided
there. This package overrides the implementation of these methods, not
the semantics.

=over

=item devnull

Returns a string representation of the null device.

=cut

sub devnull {
    return "nul";
}

=item tmpdir

Returns a string representation of the first existing directory
from the following list:

    $ENV{TMPDIR}
    $ENV{TEMP}
    $ENV{TMP}
    /tmp
    /

=cut

my $tmpdir;
sub tmpdir {
    return $tmpdir if defined $tmpdir;
    my $self = shift;
    foreach (@ENV{qw(TMPDIR TEMP TMP)}, qw(/tmp /)) {
	next unless defined && -d;
	$tmpdir = $_;
	last;
    }
    $tmpdir = '' unless defined $tmpdir;
    $tmpdir = $self->canonpath($tmpdir);
    return $tmpdir;
}

sub case_tolerant {
    return 1;
}

sub file_name_is_absolute {
    my ($self,$file) = @_;
    return scalar($file =~ m{^([a-z]:)?[\\/]}is);
}

=item catfile

Concatenate one or more directory names and a filename to form a
complete path ending with a filename

=cut

sub catfile {
    my $self = shift;
    my $file = pop @_;
    return $file unless @_;
    my $dir = $self->catdir(@_);
    $dir .= "\\" unless substr($dir,-1) eq "\\";
    return $dir.$file;
}

sub path {
    my $path = $ENV{'PATH'} || $ENV{'Path'} || $ENV{'path'};
    my @path = split(';',$path);
    foreach (@path) { $_ = '.' if $_ eq '' }
    return @path;
}

=item canonpath

No physical check on the filesystem, but a logical cleanup of a
path. On UNIX eliminated successive slashes and successive "/.".

=cut

sub canonpath {
    my ($self,$path) = @_;
    $path =~ s/^([a-z]:)/\u$1/s;
    $path =~ s|/|\\|g;
    $path =~ s|([^\\])\\+|$1\\|g;                  # xx////xx  -> xx/xx
    $path =~ s|(\\\.)+\\|\\|g;                     # xx/././xx -> xx/xx
    $path =~ s|^(\.\\)+||s unless $path eq ".\\";  # ./xx      -> xx
    $path =~ s|\\\Z(?!\n)||
             unless $path =~ m#^([A-Z]:)?\\\Z(?!\n)#s;   # xx/       -> xx
    return $path;
}

=item splitpath

    ($volume,$directories,$file) = File::Spec->splitpath( $path );
    ($volume,$directories,$file) = File::Spec->splitpath( $path, $no_file );

Splits a path in to volume, directory, and filename portions. Assumes that 
the last file is a path unless the path ends in '\\', '\\.', '\\..'
or $no_file is true.  On Win32 this means that $no_file true makes this return 
( $volume, $path, undef ).

Separators accepted are \ and /.

Volumes can be drive letters or UNC sharenames (\\server\share).

The results can be passed to L to get back a path equivalent to
(usually identical to) the original path.

=cut

sub splitpath {
    my ($self,$path, $nofile) = @_;
    my ($volume,$directory,$file) = ('','','');
    if ( $nofile ) {
        $path =~ 
            m{^( (?:[a-zA-Z]:|(?:\\\\|//)[^\\/]+[\\/][^\\/]+)? ) 
                 (.*)
             }xs;
        $volume    = $1;
        $directory = $2;
    }
    else {
        $path =~ 
            m{^ ( (?: [a-zA-Z]: |
                      (?:\\\\|//)[^\\/]+[\\/][^\\/]+
                  )?
                )
                ( (?:.*[\\\\/](?:\.\.?\Z(?!\n))?)? )
                (.*)
             }xs;
        $volume    = $1;
        $directory = $2;
        $file      = $3;
    }

    return ($volume,$directory,$file);
}


=item splitdir

The opposite of L.

    @dirs = File::Spec->splitdir( $directories );

$directories must be only the directory portion of the path on systems 
that have the concept of a volume or that have path syntax that differentiates
files from directories.

Unlike just splitting the directories on the separator, leading empty and 
trailing directory entries can be returned, because these are significant
on some OSs. So,

    File::Spec->splitdir( "/a/b/c" );

Yields:

    ( '', 'a', 'b', '', 'c', '' )

=cut

sub splitdir {
    my ($self,$directories) = @_ ;
    #
    # split() likes to forget about trailing null fields, so here we
    # check to be sure that there will not be any before handling the
    # simple case.
    #
    if ( $directories !~ m|[\\/]\Z(?!\n)| ) {
        return split( m|[\\/]|, $directories );
    }
    else {
        #
        # since there was a trailing separator, add a file name to the end, 
        # then do the split, then replace it with ''.
        #
        my( @directories )= split( m|[\\/]|, "${directories}dummy" ) ;
        $directories[ $#directories ]= '' ;
        return @directories ;
    }
}


=item catpath

Takes volume, directory and file portions and returns an entire path. Under
Unix, $volume is ignored, and this is just like catfile(). On other OSs,
the $volume become significant.

=cut

sub catpath {
    my ($self,$volume,$directory,$file) = @_;

    # If it's UNC, make sure the glue separator is there, reusing
    # whatever separator is first in the $volume
    $volume .= $1
        if ( $volume =~ m@^([\\/])[\\/][^\\/]+[\\/][^\\/]+\Z(?!\n)@s &&
             $directory =~ m@^[^\\/]@s
           ) ;

    $volume .= $directory ;

    # If the volume is not just A:, make sure the glue separator is 
    # there, reusing whatever separator is first in the $volume if possible.
    if ( $volume !~ m@^[a-zA-Z]:\Z(?!\n)@s &&
         $volume =~ m@[^\\/]\Z(?!\n)@      &&
         $file   =~ m@[^\\/]@
       ) {
        $volume =~ m@([\\/])@ ;
        my $sep = $1 ? $1 : '\\' ;
        $volume .= $sep ;
    }

    $volume .= $file ;

    return $volume ;
}


sub abs2rel {
    my($self,$path,$base) = @_;

    # Clean up $path
    if ( ! $self->file_name_is_absolute( $path ) ) {
        $path = $self->rel2abs( $path ) ;
    }
    else {
        $path = $self->canonpath( $path ) ;
    }

    # Figure out the effective $base and clean it up.
    if ( ! $self->file_name_is_absolute( $base ) ) {
        $base = $self->rel2abs( $base ) ;
    }
    elsif ( !defined( $base ) || $base eq '' ) {
        $base = cwd() ;
    }
    else {
        $base = $self->canonpath( $base ) ;
    }

    # Split up paths
    my ( $path_volume, $path_directories, $path_file ) =
        $self->splitpath( $path, 1 ) ;

    my $base_directories = ($self->splitpath( $base, 1 ))[1] ;

    # Now, remove all leading components that are the same
    my @pathchunks = $self->splitdir( $path_directories );
    my @basechunks = $self->splitdir( $base_directories );

    while ( @pathchunks && 
            @basechunks && 
            lc( $pathchunks[0] ) eq lc( $basechunks[0] ) 
          ) {
        shift @pathchunks ;
        shift @basechunks ;
    }

    # No need to catdir, we know these are well formed.
    $path_directories = CORE::join( '\\', @pathchunks );
    $base_directories = CORE::join( '\\', @basechunks );

    # $base_directories now contains the directories the resulting relative
    # path must ascend out of before it can descend to $path_directory.  So, 
    # replace all names with $parentDir

    #FA Need to replace between backslashes...
    $base_directories =~ s|[^\\]+|..|g ;

    # Glue the two together, using a separator if necessary, and preventing an
    # empty result.

    #FA Must check that new directories are not empty.
    if ( $path_directories ne '' && $base_directories ne '' ) {
        $path_directories = "$base_directories\\$path_directories" ;
    } else {
        $path_directories = "$base_directories$path_directories" ;
    }

    # It makes no sense to add a relative path to a UNC volume
    $path_volume = '' unless $path_volume =~ m{^[A-Z]:}is ;

    return $self->canonpath( 
        $self->catpath($path_volume, $path_directories, $path_file ) 
    ) ;
}


sub rel2abs {
    my ($self,$path,$base ) = @_;

    if ( ! $self->file_name_is_absolute( $path ) ) {

        if ( !defined( $base ) || $base eq '' ) {
            $base = cwd() ;
        }
        elsif ( ! $self->file_name_is_absolute( $base ) ) {
            $base = $self->rel2abs( $base ) ;
        }
        else {
            $base = $self->canonpath( $base ) ;
        }

        my ( $path_directories, $path_file ) =
            ($self->splitpath( $path, 1 ))[1,2] ;

        my ( $base_volume, $base_directories ) =
            $self->splitpath( $base, 1 ) ;

        $path = $self->catpath( 
            $base_volume, 
            $self->catdir( $base_directories, $path_directories ), 
            $path_file
        ) ;
    }

    return $self->canonpath( $path ) ;
}

=back

=head1 SEE ALSO

L

=cut

1;
cgi-bin/extlib/File/_vti_cnf/0000755002157400001440000000000007776605372022751 5ustar  minnesotaviolasociety.orgusers00000000000000cgi-bin/extlib/File/_vti_cnf/Listing.pm0000644002157400001440000000030307771550070024701 0ustar  minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl
vti_timelastmodified:TR|22 Dec 2003 11:06:32 -0000
vti_extenderversion:SR|5.0.2.2623
vti_cacheddtm:TX|22 Dec 2003 11:06:32 -0000
vti_filesize:IR|8380
vti_backlinkinfo:VX|
cgi-bin/extlib/File/_vti_cnf/Spec.pm0000644002157400001440000000030307771550070024162 0ustar  minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl
vti_timelastmodified:TR|22 Dec 2003 11:06:32 -0000
vti_extenderversion:SR|5.0.2.2623
vti_cacheddtm:TX|22 Dec 2003 11:06:32 -0000
vti_filesize:IR|2813
vti_backlinkinfo:VX|
cgi-bin/extlib/File/_vti_cnf/Temp.pm0000644002157400001440000000030407771550070024176 0ustar  minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl
vti_timelastmodified:TR|22 Dec 2003 11:06:32 -0000
vti_extenderversion:SR|5.0.2.2623
vti_cacheddtm:TX|22 Dec 2003 11:06:32 -0000
vti_filesize:IR|53650
vti_backlinkinfo:VX|
cgi-bin/extlib/File/Listing.pm0000644002157400001440000002027407771550070023123 0ustar  minnesotaviolasociety.orgusers00000000000000#
# $Id: Listing.pm,v 1.11 1999/03/20 07:37:35 gisle Exp $

package File::Listing;

sub Version { $VERSION; }
$VERSION = sprintf("%d.%02d", q$Revision: 1.11 $ =~ /(\d+)\.(\d+)/);

=head1 NAME

parse_dir - parse directory listing

=head1 SYNOPSIS

 use File::Listing;
 for (parse_dir(`ls -l`)) {
     ($name, $type, $size, $mtime, $mode) = @$_;
     next if $type ne 'f'; # plain file
     #...
 }

 # directory listing can also be read from a file
 open(LISTING, "zcat ls-lR.gz|");
 $dir = parse_dir(\*LISTING, '+0000');

=head1 DESCRIPTION

The parse_dir() routine can be used to parse directory
listings. Currently it only understand Unix C<'ls -l'> and C<'ls -lR'>
format.  It should eventually be able to most things you might get
back from a ftp server file listing (LIST command), i.e. VMS listings,
NT listings, DOS listings,...

The first parameter to parse_dir() is the directory listing to parse.
It can be a scalar, a reference to an array of directory lines or a
glob representing a filehandle to read the directory listing from.

The second parameter is the time zone to use when parsing time stamps
in the listing. If this value is undefined, then the local time zone is
assumed.

The third parameter is the type of listing to assume.  The values will
be strings like 'unix', 'vms', 'dos'.  Currently only 'unix' is
implemented and this is also the default value.  Ideally, the listing
type should be determined automatically.

The fourth parameter specifies how unparseable lines should be treated.
Values can be 'ignore', 'warn' or a code reference.  Warn means that
the perl warn() function will be called.  If a code reference is
passed, then this routine will be called and the return value from it
will be incorporated in the listing.  The default is 'ignore'.

Only the first parameter is mandatory.

The return value from parse_dir() is a list of directory entries.  In
a scalar context the return value is a reference to the list.  The
directory entries are represented by an array consisting of [
$filename, $filetype, $filesize, $filetime, $filemode ].  The
$filetype value is one of the letters 'f', 'd', 'l' or '?'.  The
$filetime value is the seconds since Jan 1, 1970.  The
$filemode is a bitmask like the mode returned by stat().

=head1 CREDITS

Based on lsparse.pl (from Lee McLoughlin's ftp mirror package) and
Net::FTP's parse_dir (Graham Barr).

=cut

require Exporter;
@ISA = qw(Exporter);

@EXPORT = qw(parse_dir);

use strict;

use Carp ();
use HTTP::Date qw(str2time);

sub parse_dir ($;$$$)
{
   my($dir, $tz, $fstype, $error) = @_;

   $fstype ||= 'unix';
   $fstype = "File::Listing::" . lc $fstype;

   my @args = $_[0];
   push(@args, $tz) if(@_ >= 2);
   push(@args, $error) if(@_ >= 4);

   $fstype->parse(@args);
}

sub line { Carp::croak("Not implemented yet"); }
sub init { } # Dummy sub

sub file_mode ($)
{
    # This routine was originally borrowed from Graham Barr's
    # Net::FTP package.

    local $_ = shift;
    my $mode = 0;
    my($type,$ch);

    s/^(.)// and $type = $1;

    while (/(.)/g) {
	$mode <<= 1;
	$mode |= 1 if $1 ne "-" &&
		      $1 ne 'S' &&
		      $1 ne 't' &&
		      $1 ne 'T';
    }

    $type eq "d" and $mode |= 0040000 or	# Directory
      $type eq "l" and $mode |= 0120000 or	# Symbolic Link
	$mode |= 0100000;			# Regular File

    $mode |= 0004000 if /^...s....../i;
    $mode |= 0002000 if /^......s.../i;
    $mode |= 0001000 if /^.........t/i;

    $mode;
}

sub parse
{
   my($pkg, $dir, $tz, $error) = @_;

   # First let's try to determine what kind of dir parameter we have
   # received.  We allow both listings, reference to arrays and
   # file handles to read from.

   if (ref($dir) eq 'ARRAY') {
       # Already splitted up
   } elsif (ref($dir) eq 'GLOB') {
       # A file handle
   } elsif (ref($dir)) {
      Carp::croak("Illegal argument to parse_dir()");
   } elsif ($dir =~ /^\*\w+(::\w+)+$/) {
      # This scalar looks like a file handle, so we assume it is
   } else {
      # A normal scalar listing
      $dir = [ split(/\n/, $dir) ];
   }

   $pkg->init();

   my @files = ();
   if (ref($dir) eq 'ARRAY') {
       for (@$dir) {
	   push(@files, $pkg->line($_, $tz, $error));
       }
   } else {
       local($_);
       while (<$dir>) {
	   chomp;
	   push(@files, $pkg->line($_, $tz, $error));
       }
   }
   wantarray ? @files : \@files;
}


package File::Listing::unix;

use HTTP::Date qw(str2time);

# A place to remember current directory from last line parsed.
use vars qw($curdir);
no strict qw(vars);

@ISA = qw(File::Listing);


sub init
{
    $curdir = '';
}

sub line
{
    shift; # package name
    local($_) = shift;
    my($tz, $error) = @_;

    s/\015//g;
    #study;

    my ($kind, $size, $date, $name);
    if (($kind, $size, $date, $name) =
	/^([\-FlrwxsStTdD]{10})                   # Type and permission bits
	 .*                                       # Graps
	 \D(\d+)                                  # File size
	 \s+                                      # Some space
	 (\w{3}\s+\d+\s+(?:\d{1,2}:\d{2}|\d{4}))  # Date
	 \s+                                      # Some more space
	 (.*)$                                    # File name
	/x )

    {
	return if $name eq '.' || $name eq '..';
	$name = "$curdir/$name" if length $curdir;
	my $type = '?';
	if ($kind =~ /^l/ && $name =~ /(.*) -> (.*)/ ) {
	    $name = $1;
	    $type = "l $2";
	} elsif ($kind =~ /^[\-F]/) { # (hopefully) a regular file
	    $type = 'f';
	} elsif ($kind =~ /^[dD]/) {
	    $type = 'd';
	    $size = undef;  # Don't believe the reported size
	}
	return [$name, $type, $size, str2time($date, $tz), 
              File::Listing::file_mode($kind)];

    } elsif (/^(.+):$/ && !/^[dcbsp].*\s.*\s.*:$/ ) {
	my $dir = $1;
	return () if $dir eq '.';
	$curdir = $dir;
	return ();
    } elsif (/^[Tt]otal\s+(\d+)$/ || /^\s*$/) {
	return ();
    } elsif (/not found/    || # OSF1, HPUX, and SunOS return
             # "$file not found"
             /No such file/ || # IRIX returns
             # "UX:ls: ERROR: Cannot access $file: No such file or directory"
                               # Solaris returns
             # "$file: No such file or directory"
             /cannot find/     # Windows NT returns
             # "The system cannot find the path specified."
             ) {
	return () unless defined $error;
	&$error($_) if ref($error) eq 'CODE';
	warn "Error: $_\n" if $error eq 'warn';
	return ();
    } elsif ($_ eq '') {       # AIX, and Linux return nothing
	return () unless defined $error;
	&$error("No such file or directory") if ref($error) eq 'CODE';
	warn "Warning: No such file or directory\n" if $error eq 'warn';
	return ();
    } else {
        # parse failed, check if the dosftp parse understands it
        return(File::Listing::dosftp->line($_,$tz,$error));
    }

}

package File::Listing::dosftp;

use HTTP::Date qw(str2time);

# A place to remember current directory from last line parsed.
use vars qw($curdir);
no strict qw(vars);

@ISA = qw(File::Listing);

sub init
{
    $curdir = '';
}

sub line
{
    shift; # package name
    local($_) = shift;
    my($tz, $error) = @_;

    s/\015//g;

    my ($kind, $size, $date, $name);

    # 02-05-96  10:48AM                 1415 src.slf
    # 09-10-96  09:18AM                 sl_util
    if (($date,$size_or_dir,$name) =
        /^(\d\d-\d\d-\d\d\s+\d\d:\d\d\wM)         # Date and time info
         \s+                                      # Some space
         (<\w{3}>|\d+)                            # Dir or Size
         \s+                                      # Some more space
         (.+)$                                    # File name
        /x )
    {
	return if $name eq '.' || $name eq '..';
	$name = "$curdir/$name" if length $curdir;
	my $type = '?';
	if ($size_or_dir eq '') {
	    $type = "d";
            $size = ""; # directories have no size in the pc listing
        } else {
	    $type = 'f';
            $size = $size_or_dir;
	}
	return [$name, $type, $size, str2time($date, $tz),
              File::Listing::file_mode($kind)];

    } else {
	return () unless defined $error;
	&$error($_) if ref($error) eq 'CODE';
	warn "Can't parse: $_\n" if $error eq 'warn';
	return ();
    }

}

package File::Listing::vms;
@File::Listing::unix::ISA = qw(File::Listing);

package File::Listing::netware;
@File::Listing::unix::ISA = qw(File::Listing);

1;
cgi-bin/extlib/File/Spec.pm0000644002157400001440000000537507771550070022411 0ustar  minnesotaviolasociety.orgusers00000000000000package File::Spec;

use strict;
use vars qw(@ISA $VERSION);

$VERSION = 0.82 ;

my %module = (MacOS   => 'Mac',
	      MSWin32 => 'Win32',
	      os2     => 'OS2',
	      VMS     => 'VMS');

my $module = $module{$^O} || 'Unix';
require "File/Spec/$module.pm";
@ISA = ("File::Spec::$module");

1;
__END__

=head1 NAME

File::Spec - portably perform operations on file names

=head1 SYNOPSIS

	use File::Spec;

	$x=File::Spec->catfile('a', 'b', 'c');

which returns 'a/b/c' under Unix. Or:

	use File::Spec::Functions;

	$x = catfile('a', 'b', 'c');

=head1 DESCRIPTION

This module is designed to support operations commonly performed on file
specifications (usually called "file names", but not to be confused with the
contents of a file, or Perl's file handles), such as concatenating several
directory and file names into a single path, or determining whether a path
is rooted. It is based on code directly taken from MakeMaker 5.17, code
written by Andreas KEnig, Andy Dougherty, Charles Bailey, Ilya
Zakharevich, Paul Schinder, and others.

Since these functions are different for most operating systems, each set of
OS specific routines is available in a separate module, including:

	File::Spec::Unix
	File::Spec::Mac
	File::Spec::OS2
	File::Spec::Win32
	File::Spec::VMS

The module appropriate for the current OS is automatically loaded by
File::Spec. Since some modules (like VMS) make use of facilities available
only under that OS, it may not be possible to load all modules under all
operating systems.

Since File::Spec is object oriented, subroutines should not called directly,
as in:

	File::Spec::catfile('a','b');

but rather as class methods:

	File::Spec->catfile('a','b');

For simple uses, L provides convenient functional
forms of these methods.

For a list of available methods, please consult L,
which contains the entire set, and which is inherited by the modules for
other platforms. For further information, please see L,
L, L, or L.

=head1 SEE ALSO

File::Spec::Unix, File::Spec::Mac, File::Spec::OS2, File::Spec::Win32,
File::Spec::VMS, File::Spec::Functions, ExtUtils::MakeMaker

=head1 AUTHORS

Kenneth Albanowski >, Andy Dougherty
>, Andreas KEnig
>, Tim Bunce >. VMS
support by Charles Bailey >.  OS/2 support by
Ilya Zakharevich >. Mac support by Paul Schinder
>.  abs2rel() and rel2abs() written by
Shigio Yamaguchi >, modified by Barrie Slaymaker
>.  splitpath(), splitdir(), catpath() and catdir()
by Barrie Slaymaker.
cgi-bin/extlib/File/Temp.pm0000644002157400001440000015062207771550070022420 0ustar  minnesotaviolasociety.orgusers00000000000000package File::Temp;

=head1 NAME

File::Temp - return name and handle of a temporary file safely

=begin __INTERNALS

=head1 PORTABILITY

This module is designed to be portable across operating systems
and it currently supports Unix, VMS, DOS, OS/2 and Windows. When
porting to a new OS there are generally three main issues
that have to be solved:

=over 4

=item *

Can the OS unlink an open file? If it can not then the
C<_can_unlink_opened_file> method should be modified.

=item *

Are the return values from C reliable? By default all the
return values from C are compared when unlinking a temporary
file using the filename and the handle. Operating systems other than
unix do not always have valid entries in all fields. If C fails
then the C comparison should be modified accordingly.

=item *

Security. Systems that can not support a test for the sticky bit
on a directory can not use the MEDIUM and HIGH security tests.
The C<_can_do_level> method should be modified accordingly.

=back

=end __INTERNALS

=head1 SYNOPSIS

  use File::Temp qw/ tempfile tempdir /; 

  $dir = tempdir( CLEANUP => 1 );
  ($fh, $filename) = tempfile( DIR => $dir );

  ($fh, $filename) = tempfile( $template, DIR => $dir);
  ($fh, $filename) = tempfile( $template, SUFFIX => '.dat');

  $fh = tempfile();

MkTemp family:

  use File::Temp qw/ :mktemp  /;

  ($fh, $file) = mkstemp( "tmpfileXXXXX" );
  ($fh, $file) = mkstemps( "tmpfileXXXXXX", $suffix);

  $tmpdir = mkdtemp( $template );

  $unopened_file = mktemp( $template );

POSIX functions:

  use File::Temp qw/ :POSIX /;

  $file = tmpnam();
  $fh = tmpfile();

  ($fh, $file) = tmpnam();
  ($fh, $file) = tmpfile();


Compatibility functions:

  $unopened_file = File::Temp::tempnam( $dir, $pfx );

=begin later

Objects (NOT YET IMPLEMENTED):

  require File::Temp;

  $fh = new File::Temp($template);
  $fname = $fh->filename;

=end later

=head1 DESCRIPTION

C can be used to create and open temporary files in a safe way.
The tempfile() function can be used to return the name and the open
filehandle of a temporary file.  The tempdir() function can 
be used to create a temporary directory.

The security aspect of temporary file creation is emphasized such that
a filehandle and filename are returned together.  This helps guarantee
that a race condition can not occur where the temporary file is
created by another process between checking for the existence of the
file and its opening.  Additional security levels are provided to
check, for example, that the sticky bit is set on world writable
directories.  See L<"safe_level"> for more information.

For compatibility with popular C library functions, Perl implementations of
the mkstemp() family of functions are provided. These are, mkstemp(),
mkstemps(), mkdtemp() and mktemp().

Additionally, implementations of the standard L
tmpnam() and tmpfile() functions are provided if required.

Implementations of mktemp(), tmpnam(), and tempnam() are provided,
but should be used with caution since they return only a filename
that was valid when function was called, so cannot guarantee
that the file will not exist by the time the caller opens the filename.

=cut

# 5.6.0 gives us S_IWOTH, S_IWGRP, our and auto-vivifying filehandls
# People would like a version on 5.005 so give them what they want :-)
use 5.005;
use strict;
use Carp;
use File::Spec 0.8;
use File::Path qw/ rmtree /;
use Fcntl 1.03;
use Errno;
require VMS::Stdio if $^O eq 'VMS';

# Need the Symbol package if we are running older perl
require Symbol if $] < 5.006;


# use 'our' on v5.6.0
use vars qw($VERSION @EXPORT_OK %EXPORT_TAGS $DEBUG);

$DEBUG = 0;

# We are exporting functions

use base qw/Exporter/;

# Export list - to allow fine tuning of export table

@EXPORT_OK = qw{
	      tempfile
	      tempdir
	      tmpnam
	      tmpfile
	      mktemp
	      mkstemp
	      mkstemps
	      mkdtemp
	      unlink0
		};

# Groups of functions for export

%EXPORT_TAGS = (
		'POSIX' => [qw/ tmpnam tmpfile /],
		'mktemp' => [qw/ mktemp mkstemp mkstemps mkdtemp/],
	       );

# add contents of these tags to @EXPORT
Exporter::export_tags('POSIX','mktemp');

# Version number 

$VERSION = '0.12';

# This is a list of characters that can be used in random filenames

my @CHARS = (qw/ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
	         a b c d e f g h i j k l m n o p q r s t u v w x y z
	         0 1 2 3 4 5 6 7 8 9 _
	     /);

# Maximum number of tries to make a temp file before failing

use constant MAX_TRIES => 10;

# Minimum number of X characters that should be in a template
use constant MINX => 4;

# Default template when no template supplied

use constant TEMPXXX => 'X' x 10;

# Constants for the security level

use constant STANDARD => 0;
use constant MEDIUM   => 1;
use constant HIGH     => 2;

# OPENFLAGS. If we defined the flag to use with Sysopen here this gives
# us an optimisation when many temporary files are requested

my $OPENFLAGS = O_CREAT | O_EXCL | O_RDWR;

for my $oflag (qw/ FOLLOW BINARY LARGEFILE EXLOCK NOINHERIT /) {
  my ($bit, $func) = (0, "Fcntl::O_" . $oflag);
  no strict 'refs';
  $OPENFLAGS |= $bit if eval {
    # Make sure that redefined die handlers do not cause problems
    # eg CGI::Carp
    local $SIG{__DIE__} = sub {};
    local $SIG{__WARN__} = sub {};
    $bit = &$func();
    1;
  };
}

# On some systems the O_TEMPORARY flag can be used to tell the OS
# to automatically remove the file when it is closed. This is fine
# in most cases but not if tempfile is called with UNLINK=>0 and
# the filename is requested -- in the case where the filename is to
# be passed to another routine. This happens on windows. We overcome
# this by using a second open flags variable

my $OPENTEMPFLAGS = $OPENFLAGS;
for my $oflag (qw/ TEMPORARY /) {
  my ($bit, $func) = (0, "Fcntl::O_" . $oflag);
  no strict 'refs';
  $OPENTEMPFLAGS |= $bit if eval {
    # Make sure that redefined die handlers do not cause problems
    # eg CGI::Carp
    local $SIG{__DIE__} = sub {};
    local $SIG{__WARN__} = sub {};
    $bit = &$func();
    1;
  };
}

# INTERNAL ROUTINES - not to be used outside of package

# Generic routine for getting a temporary filename
# modelled on OpenBSD _gettemp() in mktemp.c

# The template must contain X's that are to be replaced
# with the random values

#  Arguments:

#  TEMPLATE   - string containing the XXXXX's that is converted
#           to a random filename and opened if required

# Optionally, a hash can also be supplied containing specific options
#   "open" => if true open the temp file, else just return the name
#             default is 0
#   "mkdir"=> if true, we are creating a temp directory rather than tempfile
#             default is 0
#   "suffixlen" => number of characters at end of PATH to be ignored.
#                  default is 0.
#   "unlink_on_close" => indicates that, if possible,  the OS should remove
#                        the file as soon as it is closed. Usually indicates
#                        use of the O_TEMPORARY flag to sysopen. 
#                        Usually irrelevant on unix

# Optionally a reference to a scalar can be passed into the function
# On error this will be used to store the reason for the error
#   "ErrStr"  => \$errstr

# "open" and "mkdir" can not both be true
# "unlink_on_close" is not used when "mkdir" is true.

# The default options are equivalent to mktemp().

# Returns:
#   filehandle - open file handle (if called with doopen=1, else undef)
#   temp name  - name of the temp file or directory

# For example:
#   ($fh, $name) = _gettemp($template, "open" => 1);

# for the current version, failures are associated with
# stored in an error string and returned to give the reason whilst debugging
# This routine is not called by any external function
sub _gettemp {

  croak 'Usage: ($fh, $name) = _gettemp($template, OPTIONS);'
    unless scalar(@_) >= 1;

  # the internal error string - expect it to be overridden
  # Need this in case the caller decides not to supply us a value
  # need an anonymous scalar
  my $tempErrStr;

  # Default options
  my %options = (
		 "open" => 0,
		 "mkdir" => 0,
		 "suffixlen" => 0,
		 "unlink_on_close" => 0,
		 "ErrStr" => \$tempErrStr,
		);

  # Read the template
  my $template = shift;
  if (ref($template)) {
    # Use a warning here since we have not yet merged ErrStr
    carp "File::Temp::_gettemp: template must not be a reference";
    return ();
  }

  # Check that the number of entries on stack are even
  if (scalar(@_) % 2 != 0) {
    # Use a warning here since we have not yet merged ErrStr
    carp "File::Temp::_gettemp: Must have even number of options";
    return ();
  }

  # Read the options and merge with defaults
  %options = (%options, @_)  if @_;

  # Make sure the error string is set to undef
  ${$options{ErrStr}} = undef;

  # Can not open the file and make a directory in a single call
  if ($options{"open"} && $options{"mkdir"}) {
    ${$options{ErrStr}} = "doopen and domkdir can not both be true\n";
    return ();
  }

  # Find the start of the end of the  Xs (position of last X)
  # Substr starts from 0
  my $start = length($template) - 1 - $options{"suffixlen"};

  # Check that we have at least MINX x X (eg 'XXXX") at the end of the string
  # (taking suffixlen into account). Any fewer is insecure.

  # Do it using substr - no reason to use a pattern match since
  # we know where we are looking and what we are looking for

  if (substr($template, $start - MINX + 1, MINX) ne 'X' x MINX) {
    ${$options{ErrStr}} = "The template must contain at least ".
      MINX . " 'X' characters\n";
    return ();
  }

  # Replace all the X at the end of the substring with a
  # random character or just all the XX at the end of a full string.
  # Do it as an if, since the suffix adjusts which section to replace
  # and suffixlen=0 returns nothing if used in the substr directly
  # and generate a full path from the template

  my $path = _replace_XX($template, $options{"suffixlen"});


  # Split the path into constituent parts - eventually we need to check
  # whether the directory exists
  # We need to know whether we are making a temp directory
  # or a tempfile

  my ($volume, $directories, $file);
  my $parent; # parent directory
  if ($options{"mkdir"}) {
    # There is no filename at the end
    ($volume, $directories, $file) = File::Spec->splitpath( $path, 1);

    # The parent is then $directories without the last directory
    # Split the directory and put it back together again
    my @dirs = File::Spec->splitdir($directories);

    # If @dirs only has one entry that means we are in the current
    # directory
    if ($#dirs == 0) {
      $parent = File::Spec->curdir;
    } else {

      if ($^O eq 'VMS') {  # need volume to avoid relative dir spec
        $parent = File::Spec->catdir($volume, @dirs[0..$#dirs-1]);
        $parent = 'sys$disk:[]' if $parent eq '';
      } else {

	# Put it back together without the last one
	$parent = File::Spec->catdir(@dirs[0..$#dirs-1]);

	# ...and attach the volume (no filename)
	$parent = File::Spec->catpath($volume, $parent, '');
      }

    }

  } else {

    # Get rid of the last filename (use File::Basename for this?)
    ($volume, $directories, $file) = File::Spec->splitpath( $path );

    # Join up without the file part
    $parent = File::Spec->catpath($volume,$directories,'');

    # If $parent is empty replace with curdir
    $parent = File::Spec->curdir
      unless $directories ne '';

  }

  # Check that the parent directories exist 
  # Do this even for the case where we are simply returning a name
  # not a file -- no point returning a name that includes a directory
  # that does not exist or is not writable

  unless (-d $parent) {
    ${$options{ErrStr}} = "Parent directory ($parent) is not a directory";
    return ();
  }
  unless (-w _) {
    ${$options{ErrStr}} = "Parent directory ($parent) is not writable\n";
      return ();
  }


  # Check the stickiness of the directory and chown giveaway if required
  # If the directory is world writable the sticky bit
  # must be set

  if (File::Temp->safe_level == MEDIUM) {
    my $safeerr;
    unless (_is_safe($parent,\$safeerr)) {
      ${$options{ErrStr}} = "Parent directory ($parent) is not safe ($safeerr)";
      return ();
    }
  } elsif (File::Temp->safe_level == HIGH) {
    my $safeerr;
    unless (_is_verysafe($parent, \$safeerr)) {
      ${$options{ErrStr}} = "Parent directory ($parent) is not safe ($safeerr)";
      return ();
    }
  }


  # Now try MAX_TRIES time to open the file
  for (my $i = 0; $i < MAX_TRIES; $i++) {

    # Try to open the file if requested
    if ($options{"open"}) {
      my $fh;

      # If we are running before perl5.6.0 we can not auto-vivify
      if ($] < 5.006) {
	$fh = &Symbol::gensym;
      }

      # Try to make sure this will be marked close-on-exec
      # XXX: Win32 doesn't respect this, nor the proper fcntl,
      #      but may have O_NOINHERIT. This may or may not be in Fcntl.
      local $^F = 2;

      # Store callers umask
      my $umask = umask();

      # Set a known umask
      umask(066);

      # Attempt to open the file
      my $open_success = undef;
      if ( $^O eq 'VMS' and $options{"unlink_on_close"} ) {
        # make it auto delete on close by setting FAB$V_DLT bit
	$fh = VMS::Stdio::vmssysopen($path, $OPENFLAGS, 0600, 'fop=dlt');
	$open_success = $fh;
      } else {
	my $flags = ( $options{"unlink_on_close"} ?
		      $OPENTEMPFLAGS :
		      $OPENFLAGS );
	$open_success = sysopen($fh, $path, $flags, 0600);
      }
      if ( $open_success ) {

	# Reset umask
	umask($umask);
	
	# Opened successfully - return file handle and name
	return ($fh, $path);

      } else {
	# Reset umask
	umask($umask);

	# Error opening file - abort with error
	# if the reason was anything but EEXIST
	unless ($!{EEXIST}) {
	  ${$options{ErrStr}} = "Could not create temp file $path: $!";
	  return ();
	}

	# Loop round for another try
	
      }
    } elsif ($options{"mkdir"}) {

      # Store callers umask
      my $umask = umask();

      # Set a known umask
      umask(066);

      # Open the temp directory
      if (mkdir( $path, 0700)) {
	# created okay
	# Reset umask
	umask($umask);

	return undef, $path;
      } else {

	# Reset umask
	umask($umask);

	# Abort with error if the reason for failure was anything
	# except EEXIST
	unless ($!{EEXIST}) {
	  ${$options{ErrStr}} = "Could not create directory $path: $!";
	  return ();
	}

	# Loop round for another try

      }

    } else {

      # Return true if the file can not be found
      # Directory has been checked previously

      return (undef, $path) unless -e $path;

      # Try again until MAX_TRIES

    }

    # Did not successfully open the tempfile/dir
    # so try again with a different set of random letters
    # No point in trying to increment unless we have only
    # 1 X say and the randomness could come up with the same
    # file MAX_TRIES in a row.

    # Store current attempt - in principal this implies that the
    # 3rd time around the open attempt that the first temp file
    # name could be generated again. Probably should store each
    # attempt and make sure that none are repeated

    my $original = $path;
    my $counter = 0;  # Stop infinite loop
    my $MAX_GUESS = 50;

    do {

      # Generate new name from original template
      $path = _replace_XX($template, $options{"suffixlen"});

      $counter++;

    } until ($path ne $original || $counter > $MAX_GUESS);

    # Check for out of control looping
    if ($counter > $MAX_GUESS) {
      ${$options{ErrStr}} = "Tried to get a new temp name different to the previous value $MAX_GUESS times.\nSomething wrong with template?? ($template)";
      return ();
    }

  }

  # If we get here, we have run out of tries
  ${ $options{ErrStr} } = "Have exceeded the maximum number of attempts ("
    . MAX_TRIES . ") to open temp file/dir";

  return ();

}

# Internal routine to return a random character from the
# character list. Does not do an srand() since rand()
# will do one automatically

# No arguments. Return value is the random character

# No longer called since _replace_XX runs a few percent faster if
# I inline the code. This is important if we are creating thousands of
# temporary files.

sub _randchar {

  $CHARS[ int( rand( $#CHARS ) ) ];

}

# Internal routine to replace the XXXX... with random characters
# This has to be done by _gettemp() every time it fails to 
# open a temp file/dir

# Arguments:  $template (the template with XXX), 
#             $ignore   (number of characters at end to ignore)

# Returns:    modified template

sub _replace_XX {

  croak 'Usage: _replace_XX($template, $ignore)'
    unless scalar(@_) == 2;

  my ($path, $ignore) = @_;

  # Do it as an if, since the suffix adjusts which section to replace
  # and suffixlen=0 returns nothing if used in the substr directly
  # Alternatively, could simply set $ignore to length($path)-1
  # Don't want to always use substr when not required though.

  if ($ignore) {
    substr($path, 0, - $ignore) =~ s/X(?=X*\z)/$CHARS[ int( rand( $#CHARS ) ) ]/ge;
  } else {
    $path =~ s/X(?=X*\z)/$CHARS[ int( rand( $#CHARS ) ) ]/ge;
  }

  return $path;
}

# internal routine to check to see if the directory is safe
# First checks to see if the directory is not owned by the
# current user or root. Then checks to see if anyone else
# can write to the directory and if so, checks to see if
# it has the sticky bit set

# Will not work on systems that do not support sticky bit

#Args:  directory path to check
#       Optionally: reference to scalar to contain error message
# Returns true if the path is safe and false otherwise.
# Returns undef if can not even run stat() on the path

# This routine based on version written by Tom Christiansen

# Presumably, by the time we actually attempt to create the
# file or directory in this directory, it may not be safe
# anymore... Have to run _is_safe directly after the open.

sub _is_safe {

  my $path = shift;
  my $err_ref = shift;

  # Stat path
  my @info = stat($path);
  unless (scalar(@info)) {
    $$err_ref = "stat(path) returned no values";
    return 0;
  };
  return 1 if $^O eq 'VMS';  # owner delete control at file level

  # Check to see whether owner is neither superuser (or a system uid) nor me
  # Use the real uid from the $< variable
  # UID is in [4]
  if ($info[4] > File::Temp->top_system_uid() && $info[4] != $<) {

    Carp::cluck(sprintf "uid=$info[4] topuid=%s \$<=$< path='$path'",
		File::Temp->top_system_uid());

    $$err_ref = "Directory owned neither by root nor the current user"
      if ref($err_ref);
    return 0;
  }

  # check whether group or other can write file
  # use 066 to detect either reading or writing
  # use 022 to check writability
  # Do it with S_IWOTH and S_IWGRP for portability (maybe)
  # mode is in info[2]
  if (($info[2] & &Fcntl::S_IWGRP) ||   # Is group writable?
      ($info[2] & &Fcntl::S_IWOTH) ) {  # Is world writable?
    # Must be a directory
    unless (-d _) {
      $$err_ref = "Path ($path) is not a directory"
      if ref($err_ref);
      return 0;
    }
    # Must have sticky bit set
    unless (-k _) {
      $$err_ref = "Sticky bit not set on $path when dir is group|world writable"
	if ref($err_ref);
      return 0;
    }
  }

  return 1;
}

# Internal routine to check whether a directory is safe
# for temp files. Safer than _is_safe since it checks for 
# the possibility of chown giveaway and if that is a possibility
# checks each directory in the path to see if it is safe (with _is_safe)

# If _PC_CHOWN_RESTRICTED is not set, does the full test of each
# directory anyway.

# Takes optional second arg as scalar ref to error reason

sub _is_verysafe {

  # Need POSIX - but only want to bother if really necessary due to overhead
  require POSIX;

  my $path = shift;
  print "_is_verysafe testing $path\n" if $DEBUG;
  return 1 if $^O eq 'VMS';  # owner delete control at file level

  my $err_ref = shift;

  # Should Get the value of _PC_CHOWN_RESTRICTED if it is defined
  # and If it is not there do the extensive test
  my $chown_restricted;
  $chown_restricted = &POSIX::_PC_CHOWN_RESTRICTED()
    if eval { &POSIX::_PC_CHOWN_RESTRICTED(); 1};

  # If chown_resticted is set to some value we should test it
  if (defined $chown_restricted) {

    # Return if the current directory is safe
    return _is_safe($path,$err_ref) if POSIX::sysconf( $chown_restricted );

  }

  # To reach this point either, the _PC_CHOWN_RESTRICTED symbol
  # was not avialable or the symbol was there but chown giveaway
  # is allowed. Either way, we now have to test the entire tree for
  # safety.

  # Convert path to an absolute directory if required
  unless (File::Spec->file_name_is_absolute($path)) {
    $path = File::Spec->rel2abs($path);
  }

  # Split directory into components - assume no file
  my ($volume, $directories, undef) = File::Spec->splitpath( $path, 1);

  # Slightly less efficient than having a a function in File::Spec
  # to chop off the end of a directory or even a function that
  # can handle ../ in a directory tree
  # Sometimes splitdir() returns a blank at the end
  # so we will probably check the bottom directory twice in some cases
  my @dirs = File::Spec->splitdir($directories);

  # Concatenate one less directory each time around
  foreach my $pos (0.. $#dirs) {
    # Get a directory name
    my $dir = File::Spec->catpath($volume,
				  File::Spec->catdir(@dirs[0.. $#dirs - $pos]),
				  ''
				  );

    print "TESTING DIR $dir\n" if $DEBUG;

    # Check the directory
    return 0 unless _is_safe($dir,$err_ref);

  }

  return 1;
}



# internal routine to determine whether unlink works on this
# platform for files that are currently open.
# Returns true if we can, false otherwise.

# Currently WinNT, OS/2 and VMS can not unlink an opened file
# On VMS this is because the O_EXCL flag is used to open the
# temporary file. Currently I do not know enough about the issues
# on VMS to decide whether O_EXCL is a requirement.

sub _can_unlink_opened_file {

  if ($^O eq 'MSWin32' || $^O eq 'os2' || $^O eq 'VMS' || $^O eq 'dos') {
    return 0;
  } else {
    return 1;
  }

}

# internal routine to decide which security levels are allowed
# see safe_level() for more information on this

# Controls whether the supplied security level is allowed

#   $cando = _can_do_level( $level )

sub _can_do_level {

  # Get security level
  my $level = shift;

  # Always have to be able to do STANDARD
  return 1 if $level == STANDARD;

  # Currently, the systems that can do HIGH or MEDIUM are identical
  if ( $^O eq 'MSWin32' || $^O eq 'os2' || $^O eq 'cygwin' || $^O eq 'dos') {
    return 0;
  } else {
    return 1;
  }

}

# This routine sets up a deferred unlinking of a specified
# filename and filehandle. It is used in the following cases:
#  - Called by unlink0 if an opened file can not be unlinked
#  - Called by tempfile() if files are to be removed on shutdown
#  - Called by tempdir() if directories are to be removed on shutdown

# Arguments:
#   _deferred_unlink( $fh, $fname, $isdir );
#
#   - filehandle (so that it can be expclicitly closed if open
#   - filename   (the thing we want to remove)
#   - isdir      (flag to indicate that we are being given a directory)
#                 [and hence no filehandle]

# Status is not referred to since all the magic is done with an END block

{
  # Will set up two lexical variables to contain all the files to be
  # removed. One array for files, another for directories
  # They will only exist in this block
  # This means we only have to set up a single END block to remove all files
  # @files_to_unlink contains an array ref with the filehandle and filename
  my (@files_to_unlink, @dirs_to_unlink);

  # Set up an end block to use these arrays
  END {
    # Files
    foreach my $file (@files_to_unlink) {
      # close the filehandle without checking its state
      # in order to make real sure that this is closed
      # if its already closed then I dont care about the answer
      # probably a better way to do this
      close($file->[0]);  # file handle is [0]

      if (-f $file->[1]) {  # file name is [1]
	unlink $file->[1] or warn "Error removing ".$file->[1];
      }
    }
    # Dirs
    foreach my $dir (@dirs_to_unlink) {
      if (-d $dir) {
	rmtree($dir, $DEBUG, 1);
      }
    }

  }

  # This is the sub called to register a file for deferred unlinking
  # This could simply store the input parameters and defer everything
  # until the END block. For now we do a bit of checking at this
  # point in order to make sure that (1) we have a file/dir to delete
  # and (2) we have been called with the correct arguments.
  sub _deferred_unlink {

    croak 'Usage:  _deferred_unlink($fh, $fname, $isdir)'
      unless scalar(@_) == 3;

    my ($fh, $fname, $isdir) = @_;

    warn "Setting up deferred removal of $fname\n"
      if $DEBUG;

    # If we have a directory, check that it is a directory
    if ($isdir) {

      if (-d $fname) {

	# Directory exists so store it
	# first on VMS turn []foo into [.foo] for rmtree
	$fname = VMS::Filespec::vmspath($fname) if $^O eq 'VMS';
	push (@dirs_to_unlink, $fname);

      } else {
	carp "Request to remove directory $fname could not be completed since it does not exist!\n" if $^W;
      }

    } else {

      if (-f $fname) {

	# file exists so store handle and name for later removal
	push(@files_to_unlink, [$fh, $fname]);

      } else {
	carp "Request to remove file $fname could not be completed since it is not there!\n" if $^W;
      }

    }

  }


}

=head1 FUNCTIONS

This section describes the recommended interface for generating
temporary files and directories.

=over 4

=item B

This is the basic function to generate temporary files.
The behaviour of the file can be changed using various options:

  ($fh, $filename) = tempfile();

Create a temporary file in  the directory specified for temporary
files, as specified by the tmpdir() function in L.

  ($fh, $filename) = tempfile($template);

Create a temporary file in the current directory using the supplied
template.  Trailing `X' characters are replaced with random letters to
generate the filename.  At least four `X' characters must be present
in the template.

  ($fh, $filename) = tempfile($template, SUFFIX => $suffix)

Same as previously, except that a suffix is added to the template
after the `X' translation.  Useful for ensuring that a temporary
filename has a particular extension when needed by other applications.
But see the WARNING at the end.

  ($fh, $filename) = tempfile($template, DIR => $dir);

Translates the template as before except that a directory name
is specified.

  ($fh, $filename) = tempfile($template, UNLINK => 1);

Return the filename and filehandle as before except that the file is
automatically removed when the program exits. Default is for the file
to be removed if a file handle is requested and to be kept if the
filename is requested. In a scalar context (where no filename is 
returned) the file is always deleted either on exit or when it is closed.

If the template is not specified, a template is always
automatically generated. This temporary file is placed in tmpdir()
(L) unless a directory is specified explicitly with the 
DIR option.

  $fh = tempfile( $template, DIR => $dir );

If called in scalar context, only the filehandle is returned
and the file will automatically be deleted when closed (see 
the description of tmpfile() elsewhere in this document).
This is the preferred mode of operation, as if you only 
have a filehandle, you can never create a race condition
by fumbling with the filename. On systems that can not unlink
an open file or can not mark a file as temporary when it is opened
(for example, Windows NT uses the C flag))
the file is marked for deletion when the program ends (equivalent
to setting UNLINK to 1). The C flag is ignored if present.

  (undef, $filename) = tempfile($template, OPEN => 0);

This will return the filename based on the template but
will not open this file.  Cannot be used in conjunction with
UNLINK set to true. Default is to always open the file 
to protect from possible race conditions. A warning is issued
if warnings are turned on. Consider using the tmpnam()
and mktemp() functions described elsewhere in this document
if opening the file is not required.

Options can be combined as required.

=cut

sub tempfile {

  # Can not check for argument count since we can have any
  # number of args

  # Default options
  my %options = (
		 "DIR"    => undef,  # Directory prefix
                "SUFFIX" => '',     # Template suffix
                "UNLINK" => 0,      # Do not unlink file on exit
                "OPEN"   => 1,      # Open file
		);

  # Check to see whether we have an odd or even number of arguments
  my $template = (scalar(@_) % 2 == 1 ? shift(@_) : undef);

  # Read the options and merge with defaults
  %options = (%options, @_)  if @_;

  # First decision is whether or not to open the file
  if (! $options{"OPEN"}) {

    warn "tempfile(): temporary filename requested but not opened.\nPossibly unsafe, consider using tempfile() with OPEN set to true\n"
      if $^W;

  }

  if ($options{"DIR"} and $^O eq 'VMS') {

      # on VMS turn []foo into [.foo] for concatenation
      $options{"DIR"} = VMS::Filespec::vmspath($options{"DIR"});
  }

  # Construct the template

  # Have a choice of trying to work around the mkstemp/mktemp/tmpnam etc
  # functions or simply constructing a template and using _gettemp()
  # explicitly. Go for the latter

  # First generate a template if not defined and prefix the directory
  # If no template must prefix the temp directory
  if (defined $template) {
    if ($options{"DIR"}) {

      $template = File::Spec->catfile($options{"DIR"}, $template);

    }

  } else {

    if ($options{"DIR"}) {

      $template = File::Spec->catfile($options{"DIR"}, TEMPXXX);

    } else {

      $template = File::Spec->catfile(File::Spec->tmpdir, TEMPXXX);

    }

  }

  # Now add a suffix
  $template .= $options{"SUFFIX"};

  # Determine whether we should tell _gettemp to unlink the file
  # On unix this is irrelevant and can be worked out after the file is
  # opened (simply by unlinking the open filehandle). On Windows or VMS
  # we have to indicate temporary-ness when we open the file. In general
  # we only want a true temporary file if we are returning just the 
  # filehandle - if the user wants the filename they probably do not
  # want the file to disappear as soon as they close it.
  # For this reason, tie unlink_on_close to the return context regardless
  # of OS.
  my $unlink_on_close = ( wantarray ? 0 : 1);

  # Create the file
  my ($fh, $path, $errstr);
  croak "Error in tempfile() using $template: $errstr"
    unless (($fh, $path) = _gettemp($template,
				    "open" => $options{'OPEN'},
				    "mkdir"=> 0 ,
                                    "unlink_on_close" => $unlink_on_close,
				    "suffixlen" => length($options{'SUFFIX'}),
				    "ErrStr" => \$errstr,
				   ) );

  # Set up an exit handler that can do whatever is right for the
  # system. This removes files at exit when requested explicitly or when
  # system is asked to unlink_on_close but is unable to do so because
  # of OS limitations.
  # The latter should be achieved by using a tied filehandle.
  # Do not check return status since this is all done with END blocks.
  _deferred_unlink($fh, $path, 0) if $options{"UNLINK"};

  # Return
  if (wantarray()) {

    if ($options{'OPEN'}) {
      return ($fh, $path);
    } else {
      return (undef, $path);
    }

  } else {

    # Unlink the file. It is up to unlink0 to decide what to do with
    # this (whether to unlink now or to defer until later)
    unlink0($fh, $path) or croak "Error unlinking file $path using unlink0";

    # Return just the filehandle.
    return $fh;
  }


}

=item B

This is the recommended interface for creation of temporary directories.
The behaviour of the function depends on the arguments:

  $tempdir = tempdir();

Create a directory in tmpdir() (see L).

  $tempdir = tempdir( $template );

Create a directory from the supplied template. This template is
similar to that described for tempfile(). `X' characters at the end
of the template are replaced with random letters to construct the
directory name. At least four `X' characters must be in the template.

  $tempdir = tempdir ( DIR => $dir );

Specifies the directory to use for the temporary directory.
The temporary directory name is derived from an internal template.

  $tempdir = tempdir ( $template, DIR => $dir );

Prepend the supplied directory name to the template. The template
should not include parent directory specifications itself. Any parent
directory specifications are removed from the template before
prepending the supplied directory.

  $tempdir = tempdir ( $template, TMPDIR => 1 );

Using the supplied template, creat the temporary directory in 
a standard location for temporary files. Equivalent to doing

  $tempdir = tempdir ( $template, DIR => File::Spec->tmpdir);

but shorter. Parent directory specifications are stripped from the
template itself. The C option is ignored if C is set
explicitly.  Additionally, C is implied if neither a template
nor a directory are supplied.

  $tempdir = tempdir( $template, CLEANUP => 1);

Create a temporary directory using the supplied template, but 
attempt to remove it (and all files inside it) when the program
exits. Note that an attempt will be made to remove all files from
the directory even if they were not created by this module (otherwise
why ask to clean it up?). The directory removal is made with
the rmtree() function from the L module.
Of course, if the template is not specified, the temporary directory
will be created in tmpdir() and will also be removed at program exit.

=cut

# '

sub tempdir  {

  # Can not check for argument count since we can have any
  # number of args

  # Default options
  my %options = (
		 "CLEANUP"    => 0,  # Remove directory on exit
		 "DIR"        => '', # Root directory
		 "TMPDIR"     => 0,  # Use tempdir with template
		);

  # Check to see whether we have an odd or even number of arguments
  my $template = (scalar(@_) % 2 == 1 ? shift(@_) : undef );

  # Read the options and merge with defaults
  %options = (%options, @_)  if @_;

  # Modify or generate the template

  # Deal with the DIR and TMPDIR options
  if (defined $template) {

    # Need to strip directory path if using DIR or TMPDIR
    if ($options{'TMPDIR'} || $options{'DIR'}) {

      # Strip parent directory from the filename
      #
      # There is no filename at the end
      $template = VMS::Filespec::vmspath($template) if $^O eq 'VMS';
      my ($volume, $directories, undef) = File::Spec->splitpath( $template, 1);

      # Last directory is then our template
      $template = (File::Spec->splitdir($directories))[-1];

      # Prepend the supplied directory or temp dir
      if ($options{"DIR"}) {

        $template = File::Spec->catdir($options{"DIR"}, $template);

      } elsif ($options{TMPDIR}) {

	# Prepend tmpdir
	$template = File::Spec->catdir(File::Spec->tmpdir, $template);

      }

    }

  } else {

    if ($options{"DIR"}) {

      $template = File::Spec->catdir($options{"DIR"}, TEMPXXX);

    } else {

      $template = File::Spec->catdir(File::Spec->tmpdir, TEMPXXX);

    }

  }

  # Create the directory
  my $tempdir;
  my $suffixlen = 0;
  if ($^O eq 'VMS') {  # dir names can end in delimiters
    $template =~ m/([\.\]:>]+)$/;
    $suffixlen = length($1);
  }

  my $errstr;
  croak "Error in tempdir() using $template: $errstr"
    unless ((undef, $tempdir) = _gettemp($template,
				    "open" => 0,
				    "mkdir"=> 1 ,
				    "suffixlen" => $suffixlen,
				    "ErrStr" => \$errstr,
				   ) );

  # Install exit handler; must be dynamic to get lexical
  if ( $options{'CLEANUP'} && -d $tempdir) {
    _deferred_unlink(undef, $tempdir, 1);
  }

  # Return the dir name
  return $tempdir;

}

=back

=head1 MKTEMP FUNCTIONS

The following functions are Perl implementations of the 
mktemp() family of temp file generation system calls.

=over 4

=item B

Given a template, returns a filehandle to the temporary file and the name
of the file.

  ($fh, $name) = mkstemp( $template );

In scalar context, just the filehandle is returned.

The template may be any filename with some number of X's appended
to it, for example F. The trailing X's are replaced
with unique alphanumeric combinations.

=cut



sub mkstemp {

  croak "Usage: mkstemp(template)"
    if scalar(@_) != 1;

  my $template = shift;

  my ($fh, $path, $errstr);
  croak "Error in mkstemp using $template: $errstr"
    unless (($fh, $path) = _gettemp($template,
				    "open" => 1,
				    "mkdir"=> 0 ,
				    "suffixlen" => 0,
				    "ErrStr" => \$errstr,
				   ) );

  if (wantarray()) {
    return ($fh, $path);
  } else {
    return $fh;
  }

}


=item B

Similar to mkstemp(), except that an extra argument can be supplied
with a suffix to be appended to the template.

  ($fh, $name) = mkstemps( $template, $suffix );

For example a template of C and suffix of C<.dat>
would generate a file similar to F.

Returns just the filehandle alone when called in scalar context.

=cut

sub mkstemps {

  croak "Usage: mkstemps(template, suffix)"
    if scalar(@_) != 2;


  my $template = shift;
  my $suffix   = shift;

  $template .= $suffix;

  my ($fh, $path, $errstr);
  croak "Error in mkstemps using $template: $errstr"
    unless (($fh, $path) = _gettemp($template,
				    "open" => 1,
				    "mkdir"=> 0 ,
				    "suffixlen" => length($suffix),
				    "ErrStr" => \$errstr,
				   ) );

  if (wantarray()) {
    return ($fh, $path);
  } else {
    return $fh;
  }

}

=item B

Create a directory from a template. The template must end in
X's that are replaced by the routine.

  $tmpdir_name = mkdtemp($template);

Returns the name of the temporary directory created.
Returns undef on failure.

Directory must be removed by the caller.

=cut

#' # for emacs

sub mkdtemp {

  croak "Usage: mkdtemp(template)"
    if scalar(@_) != 1;

  my $template = shift;
  my $suffixlen = 0;
  if ($^O eq 'VMS') {  # dir names can end in delimiters
    $template =~ m/([\.\]:>]+)$/;
    $suffixlen = length($1);
  }
  my ($junk, $tmpdir, $errstr);
  croak "Error creating temp directory from template $template\: $errstr"
    unless (($junk, $tmpdir) = _gettemp($template,
					"open" => 0,
					"mkdir"=> 1 ,
					"suffixlen" => $suffixlen,
					"ErrStr" => \$errstr,
				       ) );

  return $tmpdir;

}

=item B

Returns a valid temporary filename but does not guarantee
that the file will not be opened by someone else.

  $unopened_file = mktemp($template);

Template is the same as that required by mkstemp().

=cut

sub mktemp {

  croak "Usage: mktemp(template)"
    if scalar(@_) != 1;

  my $template = shift;

  my ($tmpname, $junk, $errstr);
  croak "Error getting name to temp file from template $template: $errstr"
    unless (($junk, $tmpname) = _gettemp($template,
					 "open" => 0,
					 "mkdir"=> 0 ,
					 "suffixlen" => 0,
					 "ErrStr" => \$errstr,
					 ) );

  return $tmpname;
}

=back

=head1 POSIX FUNCTIONS

This section describes the re-implementation of the tmpnam()
and tmpfile() functions described in L 
using the mkstemp() from this module.

Unlike the L implementations, the directory used
for the temporary file is not specified in a system include
file (C) but simply depends on the choice of tmpdir()
returned by L. On some implementations this
location can be set using the C environment variable, which
may not be secure.
If this is a problem, simply use mkstemp() and specify a template.

=over 4

=item B

When called in scalar context, returns the full name (including path)
of a temporary file (uses mktemp()). The only check is that the file does
not already exist, but there is no guarantee that that condition will
continue to apply.

  $file = tmpnam();

When called in list context, a filehandle to the open file and
a filename are returned. This is achieved by calling mkstemp()
after constructing a suitable template.

  ($fh, $file) = tmpnam();

If possible, this form should be used to prevent possible
race conditions.

See L for information on the choice of temporary
directory for a particular operating system.

=cut

sub tmpnam {

   # Retrieve the temporary directory name
   my $tmpdir = File::Spec->tmpdir;

   croak "Error temporary directory is not writable"
     if $tmpdir eq '';

   # Use a ten character template and append to tmpdir
   my $template = File::Spec->catfile($tmpdir, TEMPXXX);

   if (wantarray() ) {
       return mkstemp($template);
   } else {
       return mktemp($template);
   }

}

=item B

In scalar context, returns the filehandle of a temporary file.

  $fh = tmpfile();

The file is removed when the filehandle is closed or when the program
exits. No access to the filename is provided.

If the temporary file can not be created undef is returned.
Currently this command will probably not work when the temporary
directory is on an NFS file system.

=cut

sub tmpfile {

  # Simply call tmpnam() in a list context
  my ($fh, $file) = tmpnam();

  # Make sure file is removed when filehandle is closed
  # This will fail on NFS
  unlink0($fh, $file)
    or return undef;

  return $fh;

}

=back

=head1 ADDITIONAL FUNCTIONS

These functions are provided for backwards compatibility
with common tempfile generation C library functions.

They are not exported and must be addressed using the full package
name. 

=over 4

=item B

Return the name of a temporary file in the specified directory
using a prefix. The file is guaranteed not to exist at the time
the function was called, but such guarantees are good for one 
clock tick only.  Always use the proper form of C
with C if you must open such a filename.

  $filename = File::Temp::tempnam( $dir, $prefix );

Equivalent to running mktemp() with $dir/$prefixXXXXXXXX
(using unix file convention as an example) 

Because this function uses mktemp(), it can suffer from race conditions.

=cut

sub tempnam {

  croak 'Usage tempnam($dir, $prefix)' unless scalar(@_) == 2;

  my ($dir, $prefix) = @_;

  # Add a string to the prefix
  $prefix .= 'XXXXXXXX';

  # Concatenate the directory to the file
  my $template = File::Spec->catfile($dir, $prefix);

  return mktemp($template);

}

=back

=head1 UTILITY FUNCTIONS

Useful functions for dealing with the filehandle and filename.

=over 4

=item B

Given an open filehandle and the associated filename, make a safe
unlink. This is achieved by first checking that the filename and
filehandle initially point to the same file and that the number of
links to the file is 1 (all fields returned by stat() are compared).
Then the filename is unlinked and the filehandle checked once again to
verify that the number of links on that file is now 0.  This is the
closest you can come to making sure that the filename unlinked was the
same as the file whose descriptor you hold.

  unlink0($fh, $path) or die "Error unlinking file $path safely";

Returns false on error. The filehandle is not closed since on some
occasions this is not required.

On some platforms, for example Windows NT, it is not possible to
unlink an open file (the file must be closed first). On those
platforms, the actual unlinking is deferred until the program ends and
good status is returned. A check is still performed to make sure that
the filehandle and filename are pointing to the same thing (but not at
the time the end block is executed since the deferred removal may not
have access to the filehandle).

Additionally, on Windows NT not all the fields returned by stat() can
be compared. For example, the C and C fields seem to be
different.  Also, it seems that the size of the file returned by stat()
does not always agree, with C being more accurate than
C, presumably because of caching issues even when
using autoflush (this is usually overcome by waiting a while after
writing to the tempfile before attempting to C it).

Finally, on NFS file systems the link count of the file handle does
not always go to zero immediately after unlinking. Currently, this
command is expected to fail on NFS disks.

=cut

sub unlink0 {

  croak 'Usage: unlink0(filehandle, filename)'
    unless scalar(@_) == 2;

  # Read args
  my ($fh, $path) = @_;

  warn "Unlinking $path using unlink0\n"
    if $DEBUG;

  # Stat the filehandle
  my @fh = stat $fh;

  if ($fh[3] > 1 && $^W) {
    carp "unlink0: fstat found too many links; SB=@fh" if $^W;
  }

  # Stat the path
  my @path = stat $path;

  unless (@path) {
    carp "unlink0: $path is gone already" if $^W;
    return;
  }

  # this is no longer a file, but may be a directory, or worse
  unless (-f _) {
    confess "panic: $path is no longer a file: SB=@fh";
  }

  # Do comparison of each member of the array
  # On WinNT dev and rdev seem to be different
  # depending on whether it is a file or a handle.
  # Cannot simply compare all members of the stat return
  # Select the ones we can use
  my @okstat = (0..$#fh);  # Use all by default
  if ($^O eq 'MSWin32') {
    @okstat = (1,2,3,4,5,7,8,9,10);
  } elsif ($^O eq 'os2') {
    @okstat = (0, 2..$#fh);
  } elsif ($^O eq 'VMS') { # device and file ID are sufficient
    @okstat = (0, 1);
  } elsif ($^O eq 'dos') {
     @okstat = (0,2..7,11..$#fh);
  }

  # Now compare each entry explicitly by number
  for (@okstat) {
    print "Comparing: $_ : $fh[$_] and $path[$_]\n" if $DEBUG;
    # Use eq rather than == since rdev, blksize, and blocks (6, 11,
    # and 12) will be '' on platforms that do not support them.  This
    # is fine since we are only comparing integers.
    unless ($fh[$_] eq $path[$_]) {
      warn "Did not match $_ element of stat\n" if $DEBUG;
      return 0;
    }
  }

  # attempt remove the file (does not work on some platforms)
  if (_can_unlink_opened_file()) {
    # XXX: do *not* call this on a directory; possible race
    #      resulting in recursive removal
    croak "unlink0: $path has become a directory!" if -d $path;
    unlink($path) or return 0;

    # Stat the filehandle
    @fh = stat $fh;

    print "Link count = $fh[3] \n" if $DEBUG;

    # Make sure that the link count is zero
    # - Cygwin provides deferred unlinking, however,
    #   on Win9x the link count remains 1
    # On NFS the link count may still be 1 but we cant know that
    # we are on NFS
    return ( $fh[3] == 0 or $^O eq 'cygwin' ? 1 : 0);

  } else {
    _deferred_unlink($fh, $path, 0);
    return 1;
  }

}

=back

=head1 PACKAGE VARIABLES

These functions control the global state of the package.

=over 4

=item B

Controls the lengths to which the module will go to check the safety of the
temporary file or directory before proceeding.
Options are:

=over 8

=item STANDARD

Do the basic security measures to ensure the directory exists and
is writable, that the umask() is fixed before opening of the file,
that temporary files are opened only if they do not already exist, and
that possible race conditions are avoided.  Finally the L
function is used to remove files safely.

=item MEDIUM

In addition to the STANDARD security, the output directory is checked
to make sure that it is owned either by root or the user running the
program. If the directory is writable by group or by other, it is then
checked to make sure that the sticky bit is set.

Will not work on platforms that do not support the C<-k> test
for sticky bit.

=item HIGH

In addition to the MEDIUM security checks, also check for the
possibility of ``chown() giveaway'' using the L
sysconf() function. If this is a possibility, each directory in the
path is checked in turn for safeness, recursively walking back to the 
root directory.

For platforms that do not support the L
C<_PC_CHOWN_RESTRICTED> symbol (for example, Windows NT) it is 
assumed that ``chown() giveaway'' is possible and the recursive test
is performed.

=back

The level can be changed as follows:

  File::Temp->safe_level( File::Temp::HIGH );

The level constants are not exported by the module.

Currently, you must be running at least perl v5.6.0 in order to
run with MEDIUM or HIGH security. This is simply because the 
safety tests use functions from L that are not
available in older versions of perl. The problem is that the version
number for Fcntl is the same in perl 5.6.0 and in 5.005_03 even though
they are different versions.

On systems that do not support the HIGH or MEDIUM safety levels
(for example Win NT or OS/2) any attempt to change the level will
be ignored. The decision to ignore rather than raise an exception
allows portable programs to be written with high security in mind
for the systems that can support this without those programs failing
on systems where the extra tests are irrelevant.

If you really need to see whether the change has been accepted
simply examine the return value of C.

  $newlevel = File::Temp->safe_level( File::Temp::HIGH );
  die "Could not change to high security" 
      if $newlevel != File::Temp::HIGH;

=cut

{
  # protect from using the variable itself
  my $LEVEL = STANDARD;
  sub safe_level {
    my $self = shift;
    if (@_) { 
      my $level = shift;
      if (($level != STANDARD) && ($level != MEDIUM) && ($level != HIGH)) {
	carp "safe_level: Specified level ($level) not STANDARD, MEDIUM or HIGH - ignoring\n" if $^W;
      } else {
	# Dont allow this on perl 5.005 or earlier
	if ($] < 5.006 && $level != STANDARD) {
	  # Cant do MEDIUM or HIGH checks
	  croak "Currently requires perl 5.006 or newer to do the safe checks";
	}
	# Check that we are allowed to change level
	# Silently ignore if we can not.
        $LEVEL = $level if _can_do_level($level);
      }
    }
    return $LEVEL;
  }
}

=item TopSystemUID

This is the highest UID on the current system that refers to a root
UID. This is used to make sure that the temporary directory is 
owned by a system UID (C, C, C etc) rather than 
simply by root.

This is required since on many unix systems C is not owned
by root.

Default is to assume that any UID less than or equal to 10 is a root
UID.

  File::Temp->top_system_uid(10);
  my $topid = File::Temp->top_system_uid;

This value can be adjusted to reduce security checking if required.
The value is only relevant when C is set to MEDIUM or higher.

=back

=cut

{
  my $TopSystemUID = 10;
  sub top_system_uid {
    my $self = shift;
    if (@_) {
      my $newuid = shift;
      croak "top_system_uid: UIDs should be numeric"
        unless $newuid =~ /^\d+$/s;
      $TopSystemUID = $newuid;
    }
    return $TopSystemUID;
  }
}

=head1 WARNING

For maximum security, endeavour always to avoid ever looking at,
touching, or even imputing the existence of the filename.  You do not
know that that filename is connected to the same file as the handle
you have, and attempts to check this can only trigger more race
conditions.  It's far more secure to use the filehandle alone and
dispense with the filename altogether.

If you need to pass the handle to something that expects a filename
then, on a unix system, use C<"/dev/fd/" . fileno($fh)> for arbitrary
programs, or more generally C<< "+<=&" . fileno($fh) >> for Perl
programs.  You will have to clear the close-on-exec bit on that file
descriptor before passing it to another process.

    use Fcntl qw/F_SETFD F_GETFD/;
    fcntl($tmpfh, F_SETFD, 0)
        or die "Can't clear close-on-exec flag on temp fh: $!\n";

=head2 Temporary files and NFS

Some problems are associated with using temporary files that reside
on NFS file systems and it is recommended that a local filesystem
is used whenever possible. Some of the security tests will most probably
fail when the temp file is not local. Additionally, be aware that
the performance of I/O operations over NFS will not be as good as for
a local disk.

=head1 HISTORY

Originally began life in May 1999 as an XS interface to the system
mkstemp() function. In March 2000, the OpenBSD mkstemp() code was
translated to Perl for total control of the code's
security checking, to ensure the presence of the function regardless of
operating system and to help with portability.

=head1 SEE ALSO

L, L, L, L

See L and L for different implementations of 
temporary file handling.

=head1 AUTHOR

Tim Jenness Et.jenness@jach.hawaii.eduE

Copyright (C) 1999-2001 Tim Jenness and the UK Particle Physics and
Astronomy Research Council. All Rights Reserved.  This program is free
software; you can redistribute it and/or modify it under the same
terms as Perl itself.

Original Perl implementation loosely based on the OpenBSD C code for 
mkstemp(). Thanks to Tom Christiansen for suggesting that this module
should be written and providing ideas for code improvements and
security enhancements.

=cut


1;
cgi-bin/extlib/HTML/0000755002157400001440000000000007776605372021047 5ustar  minnesotaviolasociety.orgusers00000000000000cgi-bin/extlib/HTML/_vti_cnf/0000755002157400001440000000000007776605372022636 5ustar  minnesotaviolasociety.orgusers00000000000000cgi-bin/extlib/HTML/_vti_cnf/Template.pm0000644002157400001440000000030507771550070024732 0ustar  minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl
vti_timelastmodified:TR|22 Dec 2003 11:06:32 -0000
vti_extenderversion:SR|5.0.2.2623
vti_cacheddtm:TX|22 Dec 2003 11:06:32 -0000
vti_filesize:IR|106940
vti_backlinkinfo:VX|
cgi-bin/extlib/HTML/_vti_cnf/Form.pm0000644002157400001440000000030407771550070024061 0ustar  minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl
vti_timelastmodified:TR|22 Dec 2003 11:06:32 -0000
vti_extenderversion:SR|5.0.2.2623
vti_cacheddtm:TX|22 Dec 2003 11:06:32 -0000
vti_filesize:IR|15876
vti_backlinkinfo:VX|
cgi-bin/extlib/HTML/Template.pm0000644002157400001440000032067407771550070023161 0ustar  minnesotaviolasociety.orgusers00000000000000package HTML::Template;

$HTML::Template::VERSION = '2.4';

=head1 NAME

HTML::Template - Perl module to use HTML Templates from CGI scripts

=head1 SYNOPSIS

First you make a template - this is just a normal HTML file with a few
extra tags, the simplest being 

For example, test.tmpl:

  
  Test Template
  
  My Home Directory is 
  

My Path is set to Now create a small CGI program: use HTML::Template; # open the html template my $template = HTML::Template->new(filename => 'test.tmpl'); # fill in some parameters $template->param( HOME => $ENV{HOME}, PATH => $ENV{PATH}, ); # send the obligatory Content-Type print "Content-Type: text/html\n\n"; # print the template print $template->output; If all is well in the universe this should show something like this in your browser when visiting the CGI: My Home Directory is /home/some/directory My Path is set to /bin;/usr/bin =head1 DESCRIPTION This module attempts to make using HTML templates simple and natural. It extends standard HTML with a few new HTML-esque tags - , , , and . The file written with HTML and these new tags is called a template. It is usually saved separate from your script - possibly even created by someone else! Using this module you fill in the values for the variables, loops and branches declared in the template. This allows you to separate design - the HTML - from the data, which you generate in the Perl script. A Japanese translation of the documentation is available at: http://member.nifty.ne.jp/hippo2000/perltips/html/template.htm This module is licensed under the GPL. See the LICENSE section below for more details. =head1 MOTIVATION It is true that there are a number of packages out there to do HTML templates. On the one hand you have things like HTML::Embperl which allows you freely mix Perl with HTML. On the other hand lie home-grown variable substitution solutions. Hopefully the module can find a place between the two. One advantage of this module over a full HTML::Embperl-esque solution is that it enforces an important divide - design and programming. By limiting the programmer to just using simple variables and loops in the HTML, the template remains accessible to designers and other non-perl people. The use of HTML-esque syntax goes further to make the format understandable to others. In the future this similarity could be used to extend existing HTML editors/analyzers to support HTML::Template. An advantage of this module over home-grown tag-replacement schemes is the support for loops. In my work I am often called on to produce tables of data in html. Producing them using simplistic HTML templates results in CGIs containing lots of HTML since the HTML itself cannot represent loops. The introduction of loop statements in the HTML simplifies this situation considerably. The designer can layout a single row and the programmer can fill it in as many times as necessary - all they must agree on is the parameter names. For all that, I think the best thing about this module is that it does just one thing and it does it quickly and carefully. It doesn't try to replace Perl and HTML, it just augments them to interact a little better. And it's pretty fast. =head1 The Tags Note: even though these tags look like HTML they are a little different - they're allowed to "break the rules". Something like: is not really valid HTML, but it is a perfectly valid use and will work as planned. The "NAME=" in the tag is optional, although for extensibility's sake I recommend using it. Example - "" is acceptable. If you're a fanatic about valid HTML and would like your templates to conform to valid HTML syntax, you may optionally type template tags in the form of HTML comments. This may be of use to HTML authors who would like to validate their templates' HTML syntax prior to HTML::Template processing, or who use DTD-savvy editing tools. In order to realize a dramatic savings in bandwidth, the standard (non-comment) tags will be used throughout the rest of this documentation. =head2 The tag is very simple. For each tag in the template you call $template->param(PARAMETER_NAME => "VALUE"). When the template is output the is replaced with the VALUE text you specified. If you don't set a parameter it just gets skipped in the output. Optionally you can use the "ESCAPE=HTML" option in the tag to indicate that you want the value to be HTML-escaped before being returned from output (the old ESCAPE=1 syntax is still supported). This means that the ", <, >, and & characters get translated into ", <, > and & respectively. This is useful when you want to use a TMPL_VAR in a context where those characters would cause trouble. Example: "> If you called param() with a value like sam"my you'll get in trouble with HTML's idea of a double-quote. On the other hand, if you use ESCAPE=HTML, like this: "> You'll get what you wanted no matter what value happens to be passed in for param. You can also write ESCAPE="HTML", ESCAPE='HTML' and ESCAPE='1'. Substitute a 0 for the HTML and you turn off escaping, which is the default anyway. There is also the "ESCAPE=URL" option which may be used for VARs that populate a URL. It will do URL escaping, like replacing ' ' with '+' and '/' with '%2F'. =head2 The tag is a bit more complicated. The tag allows you to delimit a section of text and give it a name. Inside the you place s. Now you pass to param() a list (an array ref) of parameter assignments (hash refs). The loop iterates over this list and produces output from the text block for each pass. Unset parameters are skipped. Here's an example: In the template: Name:

Job:

In the script: $template->param(EMPLOYEE_INFO => [ { name => 'Sam', job => 'programmer' }, { name => 'Steve', job => 'soda jerk' }, ] ); print $template->output(); The output: Name: Sam

Job: programmer

Name: Steve

Job: soda jerk

As you can see above the takes a list of variable assignments and then iterates over the loop body producing output. Often you'll want to generate a 's contents programmatically. Here's an example of how this can be done (many other ways are possible!): # a couple of arrays of data to put in a loop: my @words = qw(I Am Cool); my @numbers = qw(1 2 3); my @loop_data = (); # initialize an array to hold your loop while (@words and @numbers) { my %row_data; # get a fresh hash for the row data # fill in this row $row_data{WORD} = shift @words; $row_data{NUMBER} = shift @numbers; # the crucial step - push a reference to this row into the loop! push(@loop_data, \%row_data); } # finally, assign the loop data to the loop param, again with a # reference: $template->param(THIS_LOOP => \@loop_data); The above example would work with a template like: Word:
Number:

It would produce output like: Word: I Number: 1 Word: Am Number: 2 Word: Cool Number: 3 s within s are fine and work as you would expect. If the syntax for the param() call has you stumped, here's an example of a param call with one nested loop: $template->param('ROW',[ { name => 'Bobby', nicknames => [ { name => 'the big bad wolf' }, { name => 'He-Man' }, ], }, ], ); Basically, each gets an array reference. Inside the array are any number of hash references. These hashes contain the name=>value pairs for a single pass over the loop template. Inside a , the only variables that are usable are the ones from the . The variables in the outer blocks are not visible within a template loop. For the computer-science geeks among you, a introduces a new scope much like a perl subroutine call. If you want your variables to be global you can use 'global_vars' option to new described below. =head2 This tag includes a template directly into the current template at the point where the tag is found. The included template contents are used exactly as if its contents were physically included in the master template. The file specified can be a full path - beginning with a '/'. If it isn't a full path, the path to the enclosing file is tried first. After that the path in the environment variable HTML_TEMPLATE_ROOT is tried next, if it exists. Next, the "path" new() option is consulted. As a final attempt, the filename is passed to open() directly. See below for more information on HTML_TEMPLATE_ROOT and the "path" option to new(). As a protection against infinitly recursive includes, an arbitary limit of 10 levels deep is imposed. You can alter this limit with the "max_includes" option. See the entry for the "max_includes" option below for more details. =head2 The tag allows you to include or not include a block of the template based on the value of a given parameter name. If the parameter is given a value that is true for Perl - like '1' - then the block is included in the output. If it is not defined, or given a false value - like '0' - then it is skipped. The parameters are specified the same way as with TMPL_VAR. Example Template: Some text that only gets displayed if BOOL is true! Now if you call $template->param(BOOL => 1) then the above block will be included by output. blocks can include any valid HTML::Template construct - VARs and LOOPs and other IF/ELSE blocks. Note, however, that intersecting a and a is invalid. Not going to work: If the name of a TMPL_LOOP is used in a TMPL_IF, the IF block will output if the loop has at least one row. Example: This will output if the loop is not empty. .... WARNING: Much of the benefit of HTML::Template is in decoupling your Perl and HTML. If you introduce numerous cases where you have TMPL_IFs and matching Perl if()s, you will create a maintenance problem in keeping the two synchronized. I suggest you adopt the practice of only using TMPL_IF if you can do so without requiring a matching if() in your Perl code. =head2 You can include an alternate block in your TMPL_IF block by using TMPL_ELSE. NOTE: You still end the block with , not ! Example: Some text that is included only if BOOL is true Some text that is included only if BOOL is false =head2 This tag is the opposite of . The block is output if the CONTROL_PARAMETER is set false or not defined. You can use with just as you can with . Example: Some text that is output only if BOOL is FALSE. Some text that is output only if BOOL is TRUE. If the name of a TMPL_LOOP is used in a TMPL_UNLESS, the UNLESS block output if the loop has zero rows. This will output if the loop is empty. .... =cut =head1 Methods =head2 new() Call new() to create a new Template object: my $template = HTML::Template->new( filename => 'file.tmpl', option => 'value' ); You must call new() with at least one name => value pair specifying how to access the template text. You can use "filename => 'file.tmpl'" to specify a filename to be opened as the template. Alternately you can use: my $t = HTML::Template->new( scalarref => $ref_to_template_text, option => 'value' ); and my $t = HTML::Template->new( arrayref => $ref_to_array_of_lines , option => 'value' ); These initialize the template from in-memory resources. In almost every case you'll want to use the filename parameter. If you're worried about all the disk access from reading a template file just use mod_perl and the cache option detailed below. You can also read the template from an already opened filehandle, either traditionally as a glob or as a FileHandle: my $t = HTML::Template->new( filehandle => *FH, option => 'value'); The four new() calling methods can also be accessed as below, if you prefer. my $t = HTML::Template->new_file('file.tmpl', option => 'value'); my $t = HTML::Template->new_scalar_ref($ref_to_template_text, option => 'value'); my $t = HTML::Template->new_array_ref($ref_to_array_of_lines, option => 'value'); my $t = HTML::Template->new_filehandle($fh, option => 'value'); And as a final option, for those that might prefer it, you can call new as: my $t = HTML::Template->new(type => 'filename', source => 'file.tmpl'); Which works for all three of the source types. If the environment variable HTML_TEMPLATE_ROOT is set and your filename doesn't begin with /, then the path will be relative to the value of $HTML_TEMPLATE_ROOT. Example - if the environment variable HTML_TEMPLATE_ROOT is set to "/home/sam" and I call HTML::Template->new() with filename set to "sam.tmpl", the HTML::Template will try to open "/home/sam/sam.tmpl" to access the template file. You can also affect the search path for files with the "path" option to new() - see below for more information. You can modify the Template object's behavior with new. These options are available: =over 4 =item * die_on_bad_params - if set to 0 the module will let you call $template->param(param_name => 'value') even if 'param_name' doesn't exist in the template body. Defaults to 1. =item * strict - if set to 0 the module will allow things that look like they might be TMPL_* tags to get by without dieing. Example: Would normally cause an error, but if you call new with strict => 0, HTML::Template will ignore it. Defaults to 1. =item * cache - if set to 1 the module will cache in memory the parsed templates based on the filename parameter and modification date of the file. This only applies to templates opened with the filename parameter specified, not scalarref or arrayref templates. Caching also looks at the modification times of any files included using tags, but again, only if the template is opened with filename parameter. This is mainly of use in a persistent environment like Apache/mod_perl. It has absolutely no benefit in a normal CGI environment since the script is unloaded from memory after every request. For a cache that does work for normal CGIs see the 'shared_cache' option below. Note that different new() parameter settings do not cause a cache refresh, only a change in the modification time of the template will trigger a cache refresh. For most usages this is fine. My simplistic testing shows that using cache yields a 90% performance increase under mod_perl. Cache defaults to 0. =item * shared_cache - if set to 1 the module will store its cache in shared memory using the IPC::SharedCache module (available from CPAN). The effect of this will be to maintain a single shared copy of each parsed template for all instances of HTML::Template to use. This can be a significant reduction in memory usage in a multiple server environment. As an example, on one of our systems we use 4MB of template cache and maintain 25 httpd processes - shared_cache results in saving almost 100MB! Of course, some reduction in speed versus normal caching is to be expected. Another difference between normal caching and shared_cache is that shared_cache will work in a CGI environment - normal caching is only useful in a persistent environment like Apache/mod_perl. By default HTML::Template uses the IPC key 'TMPL' as a shared root segment (0x4c504d54 in hex), but this can be changed by setting the 'ipc_key' new() parameter to another 4-character or integer key. Other options can be used to affect the shared memory cache correspond to IPC::SharedCache options - ipc_mode, ipc_segment_size and ipc_max_size. See L for a description of how these work - in most cases you shouldn't need to change them from the defaults. For more information about the shared memory cache system used by HTML::Template see L. =item * double_cache - if set to 1 the module will use a combination of shared_cache and normal cache mode for the best possible caching. Of course, it also uses the most memory of all the cache modes. All the same ipc_* options that work with shared_cache apply to double_cache as well. By default double_cache is off. =item * blind_cache - if set to 1 the module behaves exactly as with normal caching but does not check to see if the file has changed on each request. This option should be used with caution, but could be of use on high-load servers. My tests show blind_cache performing only 1 to 2 percent faster than cache under mod_perl. NOTE: Combining this option with shared_cache can result in stale templates stuck permanently in shared memory! =item * file_cache - if set to 1 the module will store its cache in a file using the Storable module. It uses no additional memory, and my simplistic testing shows that it yields a 50% performance advantage. Like shared_cache, it will work in a CGI environment. Default is 0. If you set this option you must set the "file_cache_dir" option. See below for details. NOTE: Storable using flock() to ensure safe access to cache files. Using file_cache on a system or filesystem (NFS) without flock() support is dangerous. =item * file_cache_dir - sets the directory where the module will store the cache files if file_cache is enabled. Your script will need write permissions to this directory. You'll also need to make sure the sufficient space is available to store the cache files. =item * file_cache_dir_mode - sets the file mode for newly created file_cache directories and subdirectories. Defaults to 0700 for security but this may be inconvenient if you do not have access to the account running the webserver. =item * double_file_cache - if set to 1 the module will use a combination of file_cache and normal cache mode for the best possible caching. The file_cache_* options that work with file_cache apply to double_file_cache as well. By default double_file_cache is 0. =item * associate - this option allows you to inherit the parameter values from other objects. The only requirement for the other object is that it have a param() method that works like HTML::Template's param(). A good candidate would be a CGI.pm query object. Example: my $query = new CGI; my $template = HTML::Template->new(filename => 'template.tmpl', associate => $query); Now, $template->output() will act as though $template->param('FormField', $cgi->param('FormField')); had been specified for each key/value pair that would be provided by the $cgi->param() method. Parameters you set directly take precedence over associated parameters. You can specify multiple objects to associate by passing an anonymous array to the associate option. They are searched for parameters in the order they appear: my $template = HTML::Template->new(filename => 'template.tmpl', associate => [$query, $other_obj]); The old associateCGI() call is still supported, but should be considered obsolete. NOTE: The parameter names are matched in a case-insensitve manner. If you have two parameters in a CGI object like 'NAME' and 'Name' one will be chosen randomly by associate. This behavior can be changed by the following option. =item * case_sensitive - setting this option to true causes HTML::Template to treat template variable names case-sensitively. The following example would only set one parameter without the "case_sensitive" option: my $template = HTML::Template->new(filename => 'template.tmpl', case_sensitive => 1); $template->param( FieldA => 'foo', fIELDa => 'bar', ); This option defaults to off. =item * loop_context_vars - when this parameter is set to true (it is false by default) four loop context variables are made available inside a loop: __FIRST__, __LAST__, __INNER__, __ODD__. They can be used with , and to control how a loop is output. Example: This only outputs on the first pass. This outputs every other pass, on the odd passes. This outputs every other pass, on the even passes. This outputs on passes that are neither first nor last. This only outputs on the last pass. One use of this feature is to provide a "separator" similar in effect to the perl function join(). Example: and , . Would output (in a browser) something like: Apples, Oranges, Brains, Toes, and Kiwi. Given an appropriate param() call, of course. NOTE: A loop with only a single pass will get both __FIRST__ and __LAST__ set to true, but not __INNER__. =item * path - you can set this variable with a list of paths to search for files specified with the "filename" option to new() and for files included with the tag. This list is only consulted when the filename is relative. The HTML_TEMPLATE_ROOT environment variable is always tried first if it exists. In the case of a file, the path to the including file is also tried before path is consulted. Example: my $template = HTML::Template->new( filename => 'file.tmpl', path => [ '/path/to/templates', '/alternate/path' ] ); NOTE: the paths in the path list must be expressed as UNIX paths, separated by the forward-slash character ('/'). =item * no_includes - set this option to 1 to disallow the tag in the template file. This can be used to make opening untrusted templates B less dangerous. Defaults to 0. =item * max_includes - set this variable to determine the maximum depth that includes can reach. Set to 10 by default. Including files to a depth greater than this value causes an error message to be displayed. Set to 0 to disable this protection. =item * search_path_on_include - if set to a true value the module will search from the top of the array of paths specified by the path option on every and use the first matching template found. The normal behavior is to look only in the current directory for a template to include. Defaults to 0. =item * global_vars - normally variables declared outside a loop are not available inside a loop. This option makes s like global variables in Perl - they have unlimited scope. This option also affects and . Example: This is a normal variable: .

Here it is inside the loop:

Normally this wouldn't work as expected, since 's value outside the loop is not available inside the loop. =item * filter - this option allows you to specify a filter for your template files. A filter is a subroutine that will be called after HTML::Template reads your template file but before it starts parsing template tags. In the most simple usage, you simply assign a code reference to the filter parameter. This subroutine will recieve a single arguement - a reference to a string containing the template file text. Here is an example that accepts templates with tags that look like "!!!ZAP_VAR FOO!!!" and transforms them into HTML::Template tags: my $filter = sub { my $text_ref = shift; $$text_ref =~ s/!!!ZAP_(.*?)!!!//g; } # open zap.tmpl using the above filter my $template = HTML::Template->new(filename => 'zap.tmpl', filter => $filter); More complicated usages are possible. You can request that your filter receieve the template text as an array of lines rather than as a single scalar. To do that you need to specify your filter using a hash-ref. In this form you specify the filter using the "sub" key and the desired argument format using the "format" key. The available formats are "scalar" and "array". Using the "array" format will incur a performance penalty but may be more convenient in some situations. my $template = HTML::Template->new(filename => 'zap.tmpl', filter => { sub => $filter, format => 'array' }); You may also have multiple filters. This allows simple filters to be combined for more elaborate functionality. To do this you specify an array of filters. The filters are applied in the order they are specified. my $template = HTML::Template->new(filename => 'zap.tmpl', filter => [ { sub => \&decompress, format => 'scalar' }, { sub => \&remove_spaces, format => 'array' } ]); The specified filters will be called for any TMPL_INCLUDEed files just as they are for the main template file. =item * vanguard_compatibility_mode - if set to 1 the module will expect to see s that look like %NAME% in addition to the standard syntax. Also sets die_on_bad_params => 0. If you're not at Vanguard Media trying to use an old format template don't worry about this one. Defaults to 0. =item * debug - if set to 1 the module will write random debugging information to STDERR. Defaults to 0. =item * stack_debug - if set to 1 the module will use Data::Dumper to print out the contents of the parse_stack to STDERR. Defaults to 0. =item * cache_debug - if set to 1 the module will send information on cache loads, hits and misses to STDERR. Defaults to 0. =item * shared_cache_debug - if set to 1 the module will turn on the debug option in IPC::SharedCache - see L for details. Defaults to 0. =item * memory_debug - if set to 1 the module will send information on cache memory usage to STDERR. Requires the GTop module. Defaults to 0. =back 4 =cut use integer; # no floating point math so far! use strict; # and no funny business, either. use Carp; # generate better errors with more context use File::Spec; # generate paths that work on all platforms # define accessor constants used to improve readability of array # accesses into "objects". I used to use 'use constant' but that # seems to cause occasional irritating warnings in older Perls. package HTML::Template::LOOP; sub TEMPLATE_HASH { 0; } sub PARAM_SET { 1 }; package HTML::Template::COND; sub VARIABLE { 0 }; sub VARIABLE_TYPE { 1 }; sub VARIABLE_TYPE_VAR { 0 }; sub VARIABLE_TYPE_LOOP { 1 }; sub JUMP_IF_TRUE { 2 }; sub JUMP_ADDRESS { 3 }; sub WHICH { 4 }; sub WHICH_IF { 0 }; sub WHICH_UNLESS { 1 }; # back to the main package scope. package HTML::Template; # open a new template and return an object handle sub new { my $pkg = shift; my $self; { my %hash; $self = bless(\%hash, $pkg); } # the options hash my $options = {}; $self->{options} = $options; # set default parameters in options hash %$options = ( debug => 0, stack_debug => 0, timing => 0, search_path_on_include => 0, cache => 0, blind_cache => 0, file_cache => 0, file_cache_dir => '', file_cache_dir_mode => 0700, cache_debug => 0, shared_cache_debug => 0, memory_debug => 0, die_on_bad_params => 1, vanguard_compatibility_mode => 0, associate => [], path => [], strict => 1, loop_context_vars => 0, max_includes => 10, shared_cache => 0, double_cache => 0, double_file_cache => 0, ipc_key => 'TMPL', ipc_mode => 0666, ipc_segment_size => 65536, ipc_max_size => 0, global_vars => 0, no_includes => 0, case_sensitive => 0, filter => [], ); # load in options supplied to new() for (my $x = 0; $x <= $#_; $x += 2) { defined($_[($x + 1)]) or croak("HTML::Template->new() called with odd number of option parameters - should be of the form option => value"); $options->{lc($_[$x])} = $_[($x + 1)]; } # blind_cache = 1 implies cache = 1 $options->{blind_cache} and $options->{cache} = 1; # shared_cache = 1 implies cache = 1 $options->{shared_cache} and $options->{cache} = 1; # file_cache = 1 implies cache = 1 $options->{file_cache} and $options->{cache} = 1; # double_cache is a combination of shared_cache and cache. $options->{double_cache} and $options->{cache} = 1; $options->{double_cache} and $options->{shared_cache} = 1; # double_file_cache is a combination of file_cache and cache. $options->{double_file_cache} and $options->{cache} = 1; $options->{double_file_cache} and $options->{file_cache} = 1; # vanguard_compatibility_mode implies die_on_bad_params = 0 $options->{vanguard_compatibility_mode} and $options->{die_on_bad_params} = 0; # handle the "type", "source" parameter format (does anyone use it?) if (exists($options->{type})) { exists($options->{source}) or croak("HTML::Template->new() called with 'type' parameter set, but no 'source'!"); ($options->{type} eq 'filename' or $options->{type} eq 'scalarref' or $options->{type} eq 'arrayref' or $options->{type} eq 'filehandle') or croak("HTML::Template->new() : type parameter must be set to 'filename', 'arrayref', 'scalarref' or 'filehandle'!"); $options->{$options->{type}} = $options->{source}; delete $options->{type}; delete $options->{source}; } # associate should be an array of one element if it's not # already an array. if (ref($options->{associate}) ne 'ARRAY') { $options->{associate} = [ $options->{associate} ]; } # path should be an array if it's not already if (ref($options->{path}) ne 'ARRAY') { $options->{path} = [ $options->{path} ]; } # filter should be an array if it's not already if (ref($options->{filter}) ne 'ARRAY') { $options->{filter} = [ $options->{filter} ]; } # make sure objects in associate area support param() foreach my $object (@{$options->{associate}}) { defined($object->can('param')) or croak("HTML::Template->new called with associate option, containing object of type " . ref($object) . " which lacks a param() method!"); } # check for syntax errors: my $source_count = 0; exists($options->{filename}) and $source_count++; exists($options->{filehandle}) and $source_count++; exists($options->{arrayref}) and $source_count++; exists($options->{scalarref}) and $source_count++; if ($source_count != 1) { croak("HTML::Template->new called with multiple (or no) template sources specified! A valid call to new() has exactly one filename => 'file' OR exactly one scalarref => \\\$scalar OR exactly one arrayref => \\\@array OR exactly one filehandle => \*FH"); } # do some memory debugging - this is best started as early as possible if ($options->{memory_debug}) { # memory_debug needs GTop eval { require GTop; }; croak("Could not load GTop. You must have GTop installed to use HTML::Template in memory_debug mode. The error was: $@") if ($@); $self->{gtop} = GTop->new(); $self->{proc_mem} = $self->{gtop}->proc_mem($$); print STDERR "\n### HTML::Template Memory Debug ### START ", $self->{proc_mem}->size(), "\n"; } if ($options->{file_cache}) { # make sure we have a file_cache_dir option croak("You must specify the file_cache_dir option if you want to use file_cache.") unless defined $options->{file_cache_dir} and length $options->{file_cache_dir}; # file_cache needs some extra modules loaded eval { require Storable; }; croak("Could not load Storable. You must have Storable installed to use HTML::Template in file_cache mode. The error was: $@") if ($@); eval { require Digest::MD5; }; croak("Could not load Digest::MD5. You must have Digest::MD5 installed to use HTML::Template in file_cache mode. The error was: $@") if ($@); } if ($options->{shared_cache}) { # shared_cache needs some extra modules loaded eval { require IPC::SharedCache; }; croak("Could not load IPC::SharedCache. You must have IPC::SharedCache installed to use HTML::Template in shared_cache mode. The error was: $@") if ($@); # initialize the shared cache my %cache; tie %cache, 'IPC::SharedCache', ipc_key => $options->{ipc_key}, load_callback => [\&_load_shared_cache, $self], validate_callback => [\&_validate_shared_cache, $self], debug => $options->{shared_cache_debug}, ipc_mode => $options->{ipc_mode}, max_size => $options->{ipc_max_size}, ipc_segment_size => $options->{ipc_segment_size}; $self->{cache} = \%cache; } print STDERR "### HTML::Template Memory Debug ### POST CACHE INIT ", $self->{proc_mem}->size(), "\n" if $options->{memory_debug}; # initialize data structures $self->_init; print STDERR "### HTML::Template Memory Debug ### POST _INIT CALL ", $self->{proc_mem}->size(), "\n" if $options->{memory_debug}; # drop the shared cache - leaving out this step results in the # template object evading garbage collection since the callbacks in # the shared cache tie hold references to $self! This was not easy # to find, by the way. delete $self->{cache} if $options->{shared_cache}; return $self; } # an internally used new that receives its parse_stack and param_map as input sub _new_from_loop { my $pkg = shift; my $self; { my %hash; $self = bless(\%hash, $pkg); } # the options hash my $options = {}; $self->{options} = $options; # set default parameters in options hash - a subset of the options # valid in a normal new(). Since _new_from_loop never calls _init, # many options have no relevance. %$options = ( debug => 0, stack_debug => 0, die_on_bad_params => 1, associate => [], loop_context_vars => 0, ); # load in options supplied to new() for (my $x = 0; $x <= $#_; $x += 2) { defined($_[($x + 1)]) or croak("HTML::Template->new() called with odd number of option parameters - should be of the form option => value"); $options->{lc($_[$x])} = $_[($x + 1)]; } $self->{param_map} = $options->{param_map}; $self->{parse_stack} = $options->{parse_stack}; delete($options->{param_map}); delete($options->{parse_stack}); return $self; } # a few shortcuts to new(), of possible use... sub new_file { my $pkg = shift; return $pkg->new('filename', @_); } sub new_filehandle { my $pkg = shift; return $pkg->new('filehandle', @_); } sub new_array_ref { my $pkg = shift; return $pkg->new('arrayref', @_); } sub new_scalar_ref { my $pkg = shift; return $pkg->new('scalarref', @_); } # initializes all the object data structures, either from cache or by # calling the appropriate routines. sub _init { my $self = shift; my $options = $self->{options}; if ($options->{double_cache}) { # try the normal cache, return if we have it. $self->_fetch_from_cache(); return if (defined $self->{param_map} and defined $self->{parse_stack}); # try the shared cache $self->_fetch_from_shared_cache(); # put it in the local cache if we got it. $self->_commit_to_cache() if (defined $self->{param_map} and defined $self->{parse_stack}); } elsif ($options->{double_file_cache}) { # try the normal cache, return if we have it. $self->_fetch_from_cache(); return if (defined $self->{param_map} and defined $self->{parse_stack}); # try the file cache $self->_fetch_from_file_cache(); # put it in the local cache if we got it. $self->_commit_to_cache() if (defined $self->{param_map} and defined $self->{parse_stack}); } elsif ($options->{shared_cache}) { # try the shared cache $self->_fetch_from_shared_cache(); } elsif ($options->{file_cache}) { # try the file cache $self->_fetch_from_file_cache(); } elsif ($options->{cache}) { # try the normal cache $self->_fetch_from_cache(); } # if we got a cache hit, return return if (defined $self->{param_map} and defined $self->{parse_stack}); # if we're here, then we didn't get a cached copy, so do a full # init. $self->_init_template(); $self->_parse(); # now that we have a full init, cache the structures if cacheing is # on. shared cache is already cool. if($options->{file_cache}){ $self->_commit_to_file_cache(); } $self->_commit_to_cache() if (($options->{cache} and not $options->{shared_cache} and not $options->{file_cache}) or ($options->{double_cache}) or ($options->{double_file_cache})); } # Caching subroutines - they handle getting and validating cache # records from either the in-memory or shared caches. # handles the normal in memory cache use vars qw( %CACHE ); sub _fetch_from_cache { my $self = shift; my $options = $self->{options}; # return if there's no cache entry for this filename return unless exists($options->{filename}); my $filepath = $self->_find_file($options->{filename}); return unless (defined($filepath) and exists $CACHE{$filepath}); $options->{filepath} = $filepath; # validate the cache my $mtime = $self->_mtime($filepath); if (defined $mtime) { # return if the mtime doesn't match the cache if (defined($CACHE{$filepath}{mtime}) and ($mtime != $CACHE{$filepath}{mtime})) { $options->{cache_debug} and print STDERR "CACHE MISS : $filepath : $mtime\n"; return; } # if the template has includes, check each included file's mtime # and return if different if (exists($CACHE{$filepath}{included_mtimes})) { foreach my $filename (keys %{$CACHE{$filepath}{included_mtimes}}) { next unless defined($CACHE{$filepath}{included_mtimes}{$filename}); my $included_mtime = (stat($filename))[9]; if ($included_mtime != $CACHE{$filepath}{included_mtimes}{$filename}) { $options->{cache_debug} and print STDERR "### HTML::Template Cache Debug ### CACHE MISS : $filepath : INCLUDE $filename : $included_mtime\n"; return; } } } } # got a cache hit! $options->{cache_debug} and print STDERR "### HTML::Template Cache Debug ### CACHE HIT : $filepath\n"; $self->{param_map} = $CACHE{$filepath}{param_map}; $self->{parse_stack} = $CACHE{$filepath}{parse_stack}; exists($CACHE{$filepath}{included_mtimes}) and $self->{included_mtimes} = $CACHE{$filepath}{included_mtimes}; # clear out values from param_map from last run $self->_normalize_options(); $self->clear_params(); } sub _commit_to_cache { my $self = shift; my $options = $self->{options}; my $filepath = $options->{filepath}; if (not defined $filepath) { $filepath = $self->_find_file($options->{filename}); confess("HTML::Template->new() : Cannot open included file $options->{filename} : file not found.") unless defined($filepath); $options->{filepath} = $filepath; } $options->{cache_debug} and print STDERR "### HTML::Template Cache Debug ### CACHE LOAD : $filepath\n"; $options->{blind_cache} or $CACHE{$filepath}{mtime} = $self->_mtime($filepath); $CACHE{$filepath}{param_map} = $self->{param_map}; $CACHE{$filepath}{parse_stack} = $self->{parse_stack}; exists($self->{included_mtimes}) and $CACHE{$filepath}{included_mtimes} = $self->{included_mtimes}; } # generates MD5 from filepath to determine filename for cache file sub _get_cache_filename { my ($self, $filepath) = @_; # hash the filename ... my $hash = Digest::MD5->md5_hex($filepath); # ... and build a path out of it. Using the first two charcters # gives us 255 buckets. This means you can have 255,000 templates # in the cache before any one directory gets over a few thousand # files in it. That's probably pretty good for this planet. If not # then it should be configurable. if (wantarray) { return (substr($hash,0,2), substr($hash,2)) } else { return File::Spec->join($self->{options}{file_cache_dir}, substr($hash,0,2), substr($hash,2)); } } # handles the file cache sub _fetch_from_file_cache { my $self = shift; my $options = $self->{options}; return unless exists($options->{filename}); # return if there's no cache entry for this filename my $filepath = $self->_find_file($options->{filename}); return unless defined $filepath; my $cache_filename = $self->_get_cache_filename($filepath); return unless -e $cache_filename; eval { $self->{record} = Storable::lock_retrieve($cache_filename); }; croak("HTML::Template::new() - Problem reading cache file $cache_filename (file_cache => 1) : $@") if $@; croak("HTML::Template::new() - Problem reading cache file $cache_filename (file_cache => 1) : $!") unless defined $self->{record}; ($self->{mtime}, $self->{included_mtimes}, $self->{param_map}, $self->{parse_stack}) = @{$self->{record}}; $options->{filepath} = $filepath; # validate the cache my $mtime = $self->_mtime($filepath); if (defined $mtime) { # return if the mtime doesn't match the cache if (defined($self->{mtime}) and ($mtime != $self->{mtime})) { $options->{cache_debug} and print STDERR "### HTML::Template Cache Debug ### FILE CACHE MISS : $filepath : $mtime\n"; ($self->{mtime}, $self->{included_mtimes}, $self->{param_map}, $self->{parse_stack}) = (undef, undef, undef, undef); return; } # if the template has includes, check each included file's mtime # and return if different if (exists($self->{included_mtimes})) { foreach my $filename (keys %{$self->{included_mtimes}}) { next unless defined($self->{included_mtimes}{$filename}); my $included_mtime = (stat($filename))[9]; if ($included_mtime != $self->{included_mtimes}{$filename}) { $options->{cache_debug} and print STDERR "### HTML::Template Cache Debug ### FILE CACHE MISS : $filepath : INCLUDE $filename : $included_mtime\n"; ($self->{mtime}, $self->{included_mtimes}, $self->{param_map}, $self->{parse_stack}) = (undef, undef, undef, undef); return; } } } } # got a cache hit! $options->{cache_debug} and print STDERR "### HTML::Template Cache Debug ### FILE CACHE HIT : $filepath\n"; # clear out values from param_map from last run $self->_normalize_options(); $self->clear_params(); } sub _commit_to_file_cache { my $self = shift; my $options = $self->{options}; my $filepath = $options->{filepath}; if (not defined $filepath) { $filepath = $self->_find_file($options->{filename}); confess("HTML::Template->new() : Cannot open included file $options->{filename} : file not found.") unless defined($filepath); $options->{filepath} = $filepath; } my ($cache_dir, $cache_file) = $self->_get_cache_filename($filepath); $cache_dir = File::Spec->join($options->{file_cache_dir}, $cache_dir); if (not -d $cache_dir) { if (not -d $options->{file_cache_dir}) { mkdir($options->{file_cache_dir},$options->{file_cache_dir_mode}) or croak("HTML::Template->new() : can't mkdir $options->{file_cache_dir} (file_cache => 1): $!"); } mkdir($cache_dir,$options->{file_cache_dir_mode}) or croak("HTML::Template->new() : can't mkdir $cache_dir (file_cache => 1): $!"); } $options->{cache_debug} and print STDERR "### HTML::Template Cache Debug ### FILE CACHE LOAD : $options->{filepath}\n"; my $result; eval { $result = Storable::lock_store([ $self->{mtime}, $self->{included_mtimes}, $self->{param_map}, $self->{parse_stack} ], scalar File::Spec->join($cache_dir, $cache_file) ); }; croak("HTML::Template::new() - Problem writing cache file $cache_dir/$cache_file (file_cache => 1) : $@") if $@; croak("HTML::Template::new() - Problem writing cache file $cache_dir/$cache_file (file_cache => 1) : $!") unless defined $result; } # Shared cache routines. sub _fetch_from_shared_cache { my $self = shift; my $options = $self->{options}; my $filepath = $self->_find_file($options->{filename}); return unless defined $filepath; # fetch from the shared cache. $self->{record} = $self->{cache}{$filepath}; ($self->{mtime}, $self->{included_mtimes}, $self->{param_map}, $self->{parse_stack}) = @{$self->{record}} if defined($self->{record}); $options->{cache_debug} and defined($self->{record}) and print STDERR "### HTML::Template Cache Debug ### CACHE HIT : $filepath\n"; # clear out values from param_map from last run $self->_normalize_options(), $self->clear_params() if (defined($self->{record})); delete($self->{record}); return $self; } sub _validate_shared_cache { my ($self, $filename, $record) = @_; my $options = $self->{options}; $options->{shared_cache_debug} and print STDERR "### HTML::Template Cache Debug ### SHARED CACHE VALIDATE : $filename\n"; return 1 if $options->{blind_cache}; my ($c_mtime, $included_mtimes, $param_map, $parse_stack) = @$record; # if the modification time has changed return false my $mtime = $self->_mtime($filename); if (defined $mtime and defined $c_mtime and $mtime != $c_mtime) { $options->{cache_debug} and print STDERR "### HTML::Template Cache Debug ### SHARED CACHE MISS : $filename : $mtime\n"; return 0; } # if the template has includes, check each included file's mtime # and return false if different if (defined $mtime and defined $included_mtimes) { foreach my $fname (keys %$included_mtimes) { next unless defined($included_mtimes->{$fname}); if ($included_mtimes->{$fname} != (stat($fname))[9]) { $options->{cache_debug} and print STDERR "### HTML::Template Cache Debug ### SHARED CACHE MISS : $filename : INCLUDE $fname\n"; return 0; } } } # all done - return true return 1; } sub _load_shared_cache { my ($self, $filename) = @_; my $options = $self->{options}; my $cache = $self->{cache}; $self->_init_template(); $self->_parse(); $options->{cache_debug} and print STDERR "### HTML::Template Cache Debug ### SHARED CACHE LOAD : $options->{filepath}\n"; print STDERR "### HTML::Template Memory Debug ### END CACHE LOAD ", $self->{proc_mem}->size(), "\n" if $options->{memory_debug}; return [ $self->{mtime}, $self->{included_mtimes}, $self->{param_map}, $self->{parse_stack} ]; } # utility function - given a filename performs documented search and # returns a full path of undef if the file cannot be found. sub _find_file { my ($self, $filename, $extra_path) = @_; my $options = $self->{options}; my $filepath; # first check for a full path return File::Spec->canonpath($filename) if (File::Spec->file_name_is_absolute($filename) and (-e $filename)); # try the extra_path if one was specified if (defined($extra_path)) { $extra_path->[$#{$extra_path}] = $filename; $filepath = File::Spec->canonpath(File::Spec->catfile(@$extra_path)); return File::Spec->canonpath($filepath) if -e $filepath; } # try pre-prending HTML_Template_Root if (exists($ENV{HTML_TEMPLATE_ROOT})) { $filepath = File::Spec->catfile($ENV{HTML_TEMPLATE_ROOT}, $filename); return File::Spec->canonpath($filepath) if -e $filepath; } # try "path" option list.. foreach my $path (@{$options->{path}}) { $filepath = File::Spec->canonpath(File::Spec->catfile($path, $filename)); return File::Spec->canonpath($filepath) if -e $filepath; } # try even a relative path from the current directory... return File::Spec->canonpath($filename) if -e $filename; return undef; } # utility function - computes the mtime for $filename sub _mtime { my ($self, $filepath) = @_; my $options = $self->{options}; return(undef) if ($options->{blind_cache}); # make sure it still exists in the filesystem (-r $filepath) or Carp::confess("HTML::Template : template file $filepath does not exist or is unreadable."); # get the modification time return (stat(_))[9]; } # utility function - enforces new() options across LOOPs that have # come from a cache. Otherwise they would have stale options hashes. sub _normalize_options { my $self = shift; my $options = $self->{options}; my @pstacks = ($self->{parse_stack}); while(@pstacks) { my $pstack = pop(@pstacks); foreach my $item (@$pstack) { next unless (ref($item) eq 'HTML::Template::LOOP'); foreach my $template (values %{$item->[HTML::Template::LOOP::TEMPLATE_HASH]}) { # must be the same list as the call to _new_from_loop... $template->{options}{debug} = $options->{debug}; $template->{options}{stack_debug} = $options->{stack_debug}; $template->{options}{die_on_bad_params} = $options->{die_on_bad_params}; $template->{options}{case_sensitive} = $options->{case_sensitive}; push(@pstacks, $template->{parse_stack}); } } } } # initialize the template buffer sub _init_template { my $self = shift; my $options = $self->{options}; print STDERR "### HTML::Template Memory Debug ### START INIT_TEMPLATE ", $self->{proc_mem}->size(), "\n" if $options->{memory_debug}; if (exists($options->{filename})) { my $filepath = $options->{filepath}; if (not defined $filepath) { $filepath = $self->_find_file($options->{filename}); confess("HTML::Template->new() : Cannot open included file $options->{filename} : file not found.") unless defined($filepath); # we'll need this for future reference - to call stat() for example. $options->{filepath} = $filepath; } confess("HTML::Template->new() : Cannot open included file $options->{filename} : $!") unless defined(open(TEMPLATE, $filepath)); $self->{mtime} = $self->_mtime($filepath); # read into scalar, note the mtime for the record $self->{template} = ""; while (read(TEMPLATE, $self->{template}, 10240, length($self->{template}))) {} close(TEMPLATE); } elsif (exists($options->{scalarref})) { # copy in the template text $self->{template} = ${$options->{scalarref}}; delete($options->{scalarref}); } elsif (exists($options->{arrayref})) { # if we have an array ref, join and store the template text $self->{template} = join("", @{$options->{arrayref}}); delete($options->{arrayref}); } elsif (exists($options->{filehandle})) { # just read everything in in one go local $/ = undef; $self->{template} = readline($options->{filehandle}); delete($options->{filehandle}); } else { confess("HTML::Template : Need to call new with filename, filehandle, scalarref or arrayref parameter specified."); } print STDERR "### HTML::Template Memory Debug ### END INIT_TEMPLATE ", $self->{proc_mem}->size(), "\n" if $options->{memory_debug}; # handle filters if necessary $self->_call_filters(\$self->{template}) if @{$options->{filter}}; return $self; } # handle calling user defined filters sub _call_filters { my $self = shift; my $template_ref = shift; my $options = $self->{options}; my ($format, $sub); foreach my $filter (@{$options->{filter}}) { croak("HTML::Template->new() : bad value set for filter parameter - must be a code ref or a hash ref.") unless ref $filter; # translate into CODE->HASH $filter = { 'format' => 'scalar', 'sub' => $filter } if (ref $filter eq 'CODE'); if (ref $filter eq 'HASH') { $format = $filter->{'format'}; $sub = $filter->{'sub'}; # check types and values croak("HTML::Template->new() : bad value set for filter parameter - hash must contain \"format\" key and \"sub\" key.") unless defined $format and defined $sub; croak("HTML::Template->new() : bad value set for filter parameter - \"format\" must be either 'array' or 'scalar'") unless $format eq 'array' or $format eq 'scalar'; croak("HTML::Template->new() : bad value set for filter parameter - \"sub\" must be a code ref") unless ref $sub and ref $sub eq 'CODE'; # catch errors eval { if ($format eq 'scalar') { # call $sub->($template_ref); } else { # modulate my @array = map { $_."\n" } split("\n", $$template_ref); # call $sub->(\@array); # demodulate $$template_ref = join("", @array); } }; croak("HTML::Template->new() : fatal error occured during filter call: $@") if $@; } else { croak("HTML::Template->new() : bad value set for filter parameter - must be code ref or hash ref"); } } # all done return $template_ref; } # _parse sifts through a template building up the param_map and # parse_stack structures. # # The end result is a Template object that is fully ready for # output(). sub _parse { my $self = shift; my $options = $self->{options}; $options->{debug} and print STDERR "### HTML::Template Debug ### In _parse:\n"; # setup the stacks and maps - they're accessed by typeglobs that # reference the top of the stack. They are masked so that a loop # can transparently have its own versions. use vars qw(@pstack %pmap @ifstack @ucstack %top_pmap); local (*pstack, *ifstack, *pmap, *ucstack, *top_pmap); # the pstack is the array of scalar refs (plain text from the # template file), VARs, LOOPs, IFs and ELSEs that output() works on # to produce output. Looking at output() should make it clear what # _parse is trying to accomplish. my @pstacks = ([]); *pstack = $pstacks[0]; $self->{parse_stack} = $pstacks[0]; # the pmap binds names to VARs, LOOPs and IFs. It allows param() to # access the right variable. NOTE: output() does not look at the # pmap at all! my @pmaps = ({}); *pmap = $pmaps[0]; *top_pmap = $pmaps[0]; $self->{param_map} = $pmaps[0]; # the ifstack is a temporary stack containing pending ifs and elses # waiting for a /if. my @ifstacks = ([]); *ifstack = $ifstacks[0]; # the ucstack is a temporary stack containing conditions that need # to be bound to param_map entries when their block is finished. # This happens when a conditional is encountered before any other # reference to its NAME. Since a conditional can reference VARs and # LOOPs it isn't possible to make the link right away. my @ucstacks = ([]); *ucstack = $ucstacks[0]; # the loopstack is another temp stack for closing loops. unlike # those above it doesn't get scoped inside loops, therefore it # doesn't need the typeglob magic. my @loopstack = (); # the fstack is a stack of filenames and counters that keeps track # of which file we're in and where we are in it. This allows # accurate error messages even inside included files! # fcounter, fmax and fname are aliases for the current file's info use vars qw($fcounter $fname $fmax); local (*fcounter, *fname, *fmax); my @fstack = ([$options->{filename} || "main template", 1, scalar @{[$self->{template} =~ m/(\n)/g]} + 1 ]); (*fname, *fcounter, *fmax) = \ ( @{$fstack[0]} ); my $NOOP = HTML::Template::NOOP->new(); my $ESCAPE = HTML::Template::ESCAPE->new(); my $URLESCAPE = HTML::Template::URLESCAPE->new(); # all the tags that need NAMEs: my %need_names = map { $_ => 1 } qw(TMPL_VAR TMPL_LOOP TMPL_IF TMPL_UNLESS TMPL_INCLUDE); # variables used below that don't need to be my'd in the loop my ($name, $which, $escape); # handle the old vanguard format $options->{vanguard_compatibility_mode} and $self->{template} =~ s/%([-\w\/\.+]+)%//g; # now split up template on '<', leaving them in my @chunks = split(m/(?=<)/, $self->{template}); # all done with template delete $self->{template}; # loop through chunks, filling up pstack my $last_chunk = $#chunks; CHUNK: for (my $chunk_number = 0; $chunk_number <= $last_chunk; $chunk_number++) { next unless defined $chunks[$chunk_number]; my $chunk = $chunks[$chunk_number]; # a general regex to match any and all TMPL_* tags if ($chunk =~ /^< (?:!--\s*)? ( \/?[Tt][Mm][Pp][Ll]_ (?: (?:[Vv][Aa][Rr]) | (?:[Ll][Oo][Oo][Pp]) | (?:[Ii][Ff]) | (?:[Ee][Ll][Ss][Ee]) | (?:[Uu][Nn][Ll][Ee][Ss][Ss]) | (?:[Ii][Nn][Cc][Ll][Uu][Dd][Ee]) ) ) # $1 => $which - start of the tag \s* # ESCAPE attribute (?: [Ee][Ss][Cc][Aa][Pp][Ee] \s*=\s* (?: ( 0 | (?:"0") | (?:'0') ) # $2 => ESCAPE off | ( 1 | (?:"1") | (?:'1') | (?:[Hh][Tt][Mm][Ll]) | (?:"[Hh][Tt][Mm][Ll]") | (?:'[Hh][Tt][Mm][Ll]') | (?:[Uu][Rr][Ll]) | (?:"[Uu][Rr][Ll]") | (?:'[Uu][Rr][Ll]') | ) # $3 => ESCAPE on ) )* # allow multiple ESCAPEs \s* # NAME attribute (?: (?: [Nn][Aa][Mm][Ee] \s*=\s* )? (?: "([^">]*)" # $4 => double-quoted NAME value " | '([^'>]*)' # $5 => single-quoted NAME value | ([^\s=>]*) # $6 => unquoted NAME value ) )? \s* # ESCAPE attribute (?: [Ee][Ss][Cc][Aa][Pp][Ee] \s*=\s* (?: ( 0 | (?:"0") | (?:'0') ) # $7 => ESCAPE off | ( 1 | (?:"1") | (?:'1') | (?:[Hh][Tt][Mm][Ll]) | (?:"[Hh][Tt][Mm][Ll]") | (?:'[Hh][Tt][Mm][Ll]') | (?:[Uu][Rr][Ll]) | (?:"[Uu][Rr][Ll]") | (?:'[Uu][Rr][Ll]') | ) # $8 => ESCAPE on ) )* # allow multiple ESCAPEs \s* (?:--)?> (.*) # $9 => $post - text that comes after the tag $/sx) { $which = uc($1); # which tag is it $escape = $3 || $8; $escape = 0 if $2 || $7; # ESCAPE=0 $escape = 0 unless defined($escape); # what name for the tag? undef for a /tag at most, one of the # following three will be defined undef $name; $name = $4 if defined($4); $name = $5 if defined($5); $name = $6 if defined($6); # allow mixed case in filenames, otherwise flatten $name = lc($name) unless ($which eq 'TMPL_INCLUDE' or $options->{case_sensitive}); my $post = $9; # what comes after on the line # die if we need a name and didn't get one die "HTML::Template->new() : No NAME given to a $which tag at $fname : line $fcounter." if (!defined($name) and $need_names{$which}); # die if we got an escape but can't use one die "HTML::Template->new() : ESCAPE option invalid in a $which tag at $fname : line $fcounter." if ( $escape and ($which ne 'TMPL_VAR')); # take actions depending on which tag found if ($which eq 'TMPL_VAR') { $options->{debug} and print STDERR "### HTML::Template Debug ### $fname : line $fcounter : parsed VAR $name\n"; # if we already have this var, then simply link to the existing # HTML::Template::VAR, else create a new one. my $var; if (exists $pmap{$name}) { $var = $pmap{$name}; (ref($var) eq 'HTML::Template::VAR') or die "HTML::Template->new() : Already used param name $name as a TMPL_LOOP, found in a TMPL_VAR at $fname : line $fcounter."; } else { $var = HTML::Template::VAR->new(); $pmap{$name} = $var; $top_pmap{$name} = HTML::Template::VAR->new() if $options->{global_vars} and not exists $top_pmap{$name}; } # if ESCAPE was set, push an ESCAPE op on the stack before # the variable. output will handle the actual work. if ($escape) { if ($escape =~ /^"?[Uu][Rr][Ll]"?$/) { push(@pstack, $URLESCAPE); } else { push(@pstack, $ESCAPE); } } push(@pstack, $var); } elsif ($which eq 'TMPL_LOOP') { # we've got a loop start $options->{debug} and print STDERR "### HTML::Template Debug ### $fname : line $fcounter : LOOP $name start\n"; # if we already have this loop, then simply link to the existing # HTML::Template::LOOP, else create a new one. my $loop; if (exists $pmap{$name}) { $loop = $pmap{$name}; (ref($loop) eq 'HTML::Template::LOOP') or die "HTML::Template->new() : Already used param name $name as a TMPL_VAR, TMPL_IF or TMPL_UNLESS, found in a TMP_LOOP at $fname : line $fcounter!"; } else { # store the results in a LOOP object - actually just a # thin wrapper around another HTML::Template object. $loop = HTML::Template::LOOP->new(); $pmap{$name} = $loop; } # get it on the loopstack, pstack of the enclosing block push(@pstack, $loop); push(@loopstack, [$loop, $#pstack]); # magic time - push on a fresh pmap and pstack, adjust the typeglobs. # this gives the loop a separate namespace (i.e. pmap and pstack). push(@pstacks, []); *pstack = $pstacks[$#pstacks]; push(@pmaps, {}); *pmap = $pmaps[$#pmaps]; push(@ifstacks, []); *ifstack = $ifstacks[$#ifstacks]; push(@ucstacks, []); *ucstack = $ucstacks[$#ucstacks]; # auto-vivify __FIRST__, __LAST__ and __INNER__ if # loop_context_vars is set. Otherwise, with # die_on_bad_params set output() will might cause errors # when it tries to set them. if ($options->{loop_context_vars}) { $pmap{__first__} = HTML::Template::VAR->new(); $pmap{__inner__} = HTML::Template::VAR->new(); $pmap{__last__} = HTML::Template::VAR->new(); $pmap{__odd__} = HTML::Template::VAR->new(); } } elsif ($which eq '/TMPL_LOOP') { $options->{debug} and print STDERR "### HTML::Template Debug ### $fname : line $fcounter : LOOP end\n"; my $loopdata = pop(@loopstack); die "HTML::Template->new() : found with no matching at $fname : line $fcounter!" unless defined $loopdata; my ($loop, $starts_at) = @$loopdata; # resolve pending conditionals foreach my $uc (@ucstack) { my $var = $uc->[HTML::Template::COND::VARIABLE]; if (exists($pmap{$var})) { $uc->[HTML::Template::COND::VARIABLE] = $pmap{$var}; } else { $pmap{$var} = HTML::Template::VAR->new(); $top_pmap{$var} = HTML::Template::VAR->new() if $options->{global_vars} and not exists $top_pmap{$var}; $uc->[HTML::Template::COND::VARIABLE] = $pmap{$var}; } if (ref($pmap{$var}) eq 'HTML::Template::VAR') { $uc->[HTML::Template::COND::VARIABLE_TYPE] = HTML::Template::COND::VARIABLE_TYPE_VAR; } else { $uc->[HTML::Template::COND::VARIABLE_TYPE] = HTML::Template::COND::VARIABLE_TYPE_LOOP; } } # get pmap and pstack for the loop, adjust the typeglobs to # the enclosing block. my $param_map = pop(@pmaps); *pmap = $pmaps[$#pmaps]; my $parse_stack = pop(@pstacks); *pstack = $pstacks[$#pstacks]; scalar(@ifstack) and die "HTML::Template->new() : Dangling or in loop ending at $fname : line $fcounter."; pop(@ifstacks); *ifstack = $ifstacks[$#ifstacks]; pop(@ucstacks); *ucstack = $ucstacks[$#ucstacks]; # instantiate the sub-Template, feeding it parse_stack and # param_map. This means that only the enclosing template # does _parse() - sub-templates get their parse_stack and # param_map fed to them already filled in. $loop->[HTML::Template::LOOP::TEMPLATE_HASH]{$starts_at} = HTML::Template->_new_from_loop( parse_stack => $parse_stack, param_map => $param_map, debug => $options->{debug}, die_on_bad_params => $options->{die_on_bad_params}, loop_context_vars => $options->{loop_context_vars}, case_sensitive => $options->{case_sensitive}, ); } elsif ($which eq 'TMPL_IF' or $which eq 'TMPL_UNLESS' ) { $options->{debug} and print STDERR "### HTML::Template Debug ### $fname : line $fcounter : $which $name start\n"; # if we already have this var, then simply link to the existing # HTML::Template::VAR/LOOP, else defer the mapping my $var; if (exists $pmap{$name}) { $var = $pmap{$name}; } else { $var = $name; } # connect the var to a conditional my $cond = HTML::Template::COND->new($var); if ($which eq 'TMPL_IF') { $cond->[HTML::Template::COND::WHICH] = HTML::Template::COND::WHICH_IF; $cond->[HTML::Template::COND::JUMP_IF_TRUE] = 0; } else { $cond->[HTML::Template::COND::WHICH] = HTML::Template::COND::WHICH_UNLESS; $cond->[HTML::Template::COND::JUMP_IF_TRUE] = 1; } # push unconnected conditionals onto the ucstack for # resolution later. Otherwise, save type information now. if ($var eq $name) { push(@ucstack, $cond); } else { if (ref($var) eq 'HTML::Template::VAR') { $cond->[HTML::Template::COND::VARIABLE_TYPE] = HTML::Template::COND::VARIABLE_TYPE_VAR; } else { $cond->[HTML::Template::COND::VARIABLE_TYPE] = HTML::Template::COND::VARIABLE_TYPE_LOOP; } } # push what we've got onto the stacks push(@pstack, $cond); push(@ifstack, $cond); } elsif ($which eq '/TMPL_IF' or $which eq '/TMPL_UNLESS') { $options->{debug} and print STDERR "### HTML::Template Debug ###$fname : line $fcounter : $which end\n"; my $cond = pop(@ifstack); die "HTML::Template->new() : found with no matching at $fname : line $fcounter." unless defined $cond; if ($which eq '/TMPL_IF') { die "HTML::Template->new() : found incorrectly terminating a (use ) at $fname : line $fcounter.\n" if ($cond->[HTML::Template::COND::WHICH] == HTML::Template::COND::WHICH_UNLESS); } else { die "HTML::Template->new() : found incorrectly terminating a (use ) at $fname : line $fcounter.\n" if ($cond->[HTML::Template::COND::WHICH] == HTML::Template::COND::WHICH_IF); } # connect the matching to this "address" - place a NOOP to # hold the spot. This allows output() to treat an IF in the # assembler-esque "Conditional Jump" mode. push(@pstack, $NOOP); $cond->[HTML::Template::COND::JUMP_ADDRESS] = $#pstack; } elsif ($which eq 'TMPL_ELSE') { $options->{debug} and print STDERR "### HTML::Template Debug ### $fname : line $fcounter : ELSE\n"; my $cond = pop(@ifstack); die "HTML::Template->new() : found with no matching or at $fname : line $fcounter." unless defined $cond; my $else = HTML::Template::COND->new($cond->[HTML::Template::COND::VARIABLE]); $else->[HTML::Template::COND::WHICH] = $cond->[HTML::Template::COND::WHICH]; $else->[HTML::Template::COND::JUMP_IF_TRUE] = not $cond->[HTML::Template::COND::JUMP_IF_TRUE]; # need end-block resolution? if (defined($cond->[HTML::Template::COND::VARIABLE_TYPE])) { $else->[HTML::Template::COND::VARIABLE_TYPE] = $cond->[HTML::Template::COND::VARIABLE_TYPE]; } else { push(@ucstack, $else); } push(@pstack, $else); push(@ifstack, $else); # connect the matching to this "address" - thus the if, # failing jumps to the ELSE address. The else then gets # elaborated, and of course succeeds. On the other hand, if # the IF fails and falls though, output will reach the else # and jump to the /if address. $cond->[HTML::Template::COND::JUMP_ADDRESS] = $#pstack; } elsif ($which eq 'TMPL_INCLUDE') { # handle TMPL_INCLUDEs $options->{debug} and print STDERR "### HTML::Template Debug ### $fname : line $fcounter : INCLUDE $name \n"; # no includes here, bub $options->{no_includes} and croak("HTML::Template : Illegal attempt to use TMPL_INCLUDE in template file : (no_includes => 1)"); my $filename = $name; # look for the included file... my @path = split('/', $options->{filepath}); my $filepath; if ($options->{search_path_on_include} or not @path) { $filepath = $self->_find_file($filename); } else { $filepath = $self->_find_file($filename, \@path); } die "HTML::Template->new() : Cannot open included file $filename : file not found." unless defined($filepath); die "HTML::Template->new() : Cannot open included file $filename : $!" unless defined(open(TEMPLATE, $filepath)); # read into the array my $included_template = ""; while(read(TEMPLATE, $included_template, 10240, length($included_template))) {} close(TEMPLATE); # call filters if necessary $self->_call_filters(\$included_template) if @{$options->{filter}}; if ($included_template) { # not empty # handle the old vanguard format - this needs to happen here # since we're not about to do a next CHUNKS. $options->{vanguard_compatibility_mode} and $included_template =~ s/%([-\w\/\.+]+)%//g; # collect mtimes for included files if ($options->{cache} and !$options->{blind_cache}) { $self->{included_mtimes}{$filepath} = (stat($filepath))[9]; } # adjust the fstack to point to the included file info push(@fstack, [$filepath, 1, scalar @{[$included_template =~ m/(\n)/g]} + 1]); (*fname, *fcounter, *fmax) = \ ( @{$fstack[$#fstack]} ); # make sure we aren't infinitely recursing die "HTML::Template->new() : likely recursive includes - parsed $options->{max_includes} files deep and giving up (set max_includes higher to allow deeper recursion)." if ($options->{max_includes} and (scalar(@fstack) > $options->{max_includes})); # stick the remains of this chunk onto the bottom of the # included text. $included_template .= $post; $post = undef; # move the new chunks into place. splice(@chunks, $chunk_number, 1, split(m/(?=<)/, $included_template)); # recalculate stopping point $last_chunk = $#chunks; # start in on the first line of the included text - nothing # else to do on this line. $chunk = $chunks[$chunk_number]; redo CHUNK; } } else { # zuh!? die "HTML::Template->new() : Unknown or unmatched TMPL construct at $fname : line $fcounter."; } # push the rest after the tag if (defined($post)) { if (ref($pstack[$#pstack]) eq 'SCALAR') { ${$pstack[$#pstack]} .= $post; } else { push(@pstack, \$post); } } } else { # just your ordinary markup # make sure we didn't reject something TMPL_* but badly formed if ($options->{strict}) { die "HTML::Template->new() : Syntax error in tag at $fname : $fcounter." if ($chunk =~ /<(?:!--\s*)?\/?[Tt][Mm][Pp][Ll]_/); } # push the rest and get next chunk if (defined($chunk)) { if (ref($pstack[$#pstack]) eq 'SCALAR') { ${$pstack[$#pstack]} .= $chunk; } else { push(@pstack, \$chunk); } } } # count newlines in chunk and advance line count $fcounter += scalar(@{[$chunk =~ m/(\n)/g]}); # if we just crossed the end of an included file # pop off the record and re-alias to the enclosing file's info pop(@fstack), (*fname, *fcounter, *fmax) = \ ( @{$fstack[$#fstack]} ) if ($fcounter > $fmax); } # next CHUNK # make sure we don't have dangling IF or LOOP blocks scalar(@ifstack) and die "HTML::Template->new() : At least one or not terminated at end of file!"; scalar(@loopstack) and die "HTML::Template->new() : At least one not terminated at end of file!"; # resolve pending conditionals foreach my $uc (@ucstack) { my $var = $uc->[HTML::Template::COND::VARIABLE]; if (exists($pmap{$var})) { $uc->[HTML::Template::COND::VARIABLE] = $pmap{$var}; } else { $pmap{$var} = HTML::Template::VAR->new(); $top_pmap{$var} = HTML::Template::VAR->new() if $options->{global_vars} and not exists $top_pmap{$var}; $uc->[HTML::Template::COND::VARIABLE] = $pmap{$var}; } if (ref($pmap{$var}) eq 'HTML::Template::VAR') { $uc->[HTML::Template::COND::VARIABLE_TYPE] = HTML::Template::COND::VARIABLE_TYPE_VAR; } else { $uc->[HTML::Template::COND::VARIABLE_TYPE] = HTML::Template::COND::VARIABLE_TYPE_LOOP; } } # want a stack dump? if ($options->{stack_debug}) { require 'Data/Dumper.pm'; print STDERR "### HTML::Template _param Stack Dump ###\n\n", Data::Dumper::Dumper($self->{parse_stack}), "\n"; } # get rid of filters - they cause runtime errors if Storable tries # to store them. This can happen under global_vars. delete $options->{filter}; } # a recursive sub that associates each loop with the loops above # (treating the top-level as a loop) sub _globalize_vars { my $self = shift; # associate with the loop (and top-level templates) above in the tree. push(@{$self->{options}{associate}}, @_); # recurse down into the template tree, adding ourself to the end of # list. push(@_, $self); map { $_->_globalize_vars(@_) } map {values %{$_->[HTML::Template::LOOP::TEMPLATE_HASH]}} grep { ref($_) eq 'HTML::Template::LOOP'} @{$self->{parse_stack}}; } # method used to recursively un-hook associate sub _unglobalize_vars { my $self = shift; # disassociate $self->{options}{associate} = undef; # recurse down into the template tree disassociating map { $_->_unglobalize_vars() } map {values %{$_->[HTML::Template::LOOP::TEMPLATE_HASH]}} grep { ref($_) eq 'HTML::Template::LOOP'} @{$self->{parse_stack}}; } =head2 param param() can be called in a number of ways 1) To return a list of parameters in the template : my @parameter_names = $self->param(); 2) To return the value set to a param : my $value = $self->param('PARAM'); 3) To set the value of a parameter : # For simple TMPL_VARs: $self->param(PARAM => 'value'); # with a subroutine reference that gets called to get the value # of the scalar. The sub will recieve the template object as a # parameter. $self->param(PARAM => sub { return 'value' }); # And TMPL_LOOPs: $self->param(LOOP_PARAM => [ { PARAM => VALUE_FOR_FIRST_PASS, ... }, { PARAM => VALUE_FOR_SECOND_PASS, ... } ... ] ); 4) To set the value of a a number of parameters : # For simple TMPL_VARs: $self->param(PARAM => 'value', PARAM2 => 'value' ); # And with some TMPL_LOOPs: $self->param(PARAM => 'value', PARAM2 => 'value', LOOP_PARAM => [ { PARAM => VALUE_FOR_FIRST_PASS, ... }, { PARAM => VALUE_FOR_SECOND_PASS, ... } ... ], ANOTHER_LOOP_PARAM => [ { PARAM => VALUE_FOR_FIRST_PASS, ... }, { PARAM => VALUE_FOR_SECOND_PASS, ... } ... ] ); 5) To set the value of a a number of parameters using a hash-ref : $self->param( { PARAM => 'value', PARAM2 => 'value', LOOP_PARAM => [ { PARAM => VALUE_FOR_FIRST_PASS, ... }, { PARAM => VALUE_FOR_SECOND_PASS, ... } ... ], ANOTHER_LOOP_PARAM => [ { PARAM => VALUE_FOR_FIRST_PASS, ... }, { PARAM => VALUE_FOR_SECOND_PASS, ... } ... ] } ); =cut sub param { my $self = shift; my $options = $self->{options}; my $param_map = $self->{param_map}; # the no-parameter case - return list of parameters in the template. return keys(%$param_map) unless scalar(@_); my $first = shift; my $type = ref $first; # the one-parameter case - could be a parameter value request or a # hash-ref. if (!scalar(@_) and !length($type)) { my $param = $options->{case_sensitive} ? $first : lc $first; # check for parameter existence $options->{die_on_bad_params} and !exists($param_map->{$param}) and croak("HTML::Template : Attempt to get nonexistent parameter '$param' - this parameter name doesn't match any declarations in the template file : (die_on_bad_params set => 1)"); return undef unless (exists($param_map->{$param}) and defined($param_map->{$param})); return ${$param_map->{$param}} if (ref($param_map->{$param}) eq 'HTML::Template::VAR'); return $param_map->{$param}[HTML::Template::LOOP::PARAM_SET]; } if (!scalar(@_)) { croak("HTML::Template->param() : Single reference arg to param() must be a hash-ref! You gave me a $type.") unless $type eq 'HASH' or (ref($first) and UNIVERSAL::isa($first, 'HASH')); push(@_, %$first); } else { unshift(@_, $first); } croak("HTML::Template->param() : You gave me an odd number of parameters to param()!") unless ((@_ % 2) == 0); # strangely, changing this to a "while(@_) { shift, shift }" type # loop causes perl 5.004_04 to die with some nonsense about a # read-only value. for (my $x = 0; $x <= $#_; $x += 2) { my $param = $options->{case_sensitive} ? $_[$x] : lc $_[$x]; my $value = $_[($x + 1)]; # check that this param exists in the template $options->{die_on_bad_params} and !exists($param_map->{$param}) and croak("HTML::Template : Attempt to set nonexistent parameter '$param' - this parameter name doesn't match any declarations in the template file : (die_on_bad_params => 1)"); # if we're not going to die from bad param names, we need to ignore # them... next unless (exists($param_map->{$param})); # figure out what we've got, taking special care to allow for # objects that are compatible underneath. my $value_type = ref($value); if (defined($value_type) and length($value_type) and ($value_type eq 'ARRAY' or ((ref($value) !~ /^(CODE)|(HASH)|(SCALAR)$/) and $value->isa('ARRAY')))) { (ref($param_map->{$param}) eq 'HTML::Template::LOOP') or croak("HTML::Template::param() : attempt to set parameter '$param' with an array ref - parameter is not a TMPL_LOOP!"); $param_map->{$param}[HTML::Template::LOOP::PARAM_SET] = [@{$value}]; } else { (ref($param_map->{$param}) eq 'HTML::Template::VAR') or croak("HTML::Template::param() : attempt to set parameter '$param' with a scalar - parameter is not a TMPL_VAR!"); ${$param_map->{$param}} = $value; } } } =pod =head2 clear_params() Sets all the parameters to undef. Useful internally, if nowhere else! =cut sub clear_params { my $self = shift; my $type; foreach my $name (keys %{$self->{param_map}}) { $type = ref($self->{param_map}{$name}); undef(${$self->{param_map}{$name}}) if ($type eq 'HTML::Template::VAR'); undef($self->{param_map}{$name}[HTML::Template::LOOP::PARAM_SET]) if ($type eq 'HTML::Template::LOOP'); } } # obsolete implementation of associate sub associateCGI { my $self = shift; my $cgi = shift; (ref($cgi) eq 'CGI') or croak("Warning! non-CGI object was passed to HTML::Template::associateCGI()!\n"); push(@{$self->{options}{associate}}, $cgi); return 1; } =head2 output() output() returns the final result of the template. In most situations you'll want to print this, like: print $template->output(); When output is called each occurrence of is replaced with the value assigned to "name" via param(). If a named parameter is unset it is simply replaced with ''. are evaluated once per parameter set, accumlating output on each pass. Calling output() is guaranteed not to change the state of the Template object, in case you were wondering. This property is mostly important for the internal implementation of loops. You may optionally supply a filehandle to print to automatically as the template is generated. This may improve performance and lower memory consumption. Example: $template->output(print_to => *STDOUT); The return value is undefined when using the "print_to" option. =cut use vars qw(%URLESCAPE_MAP); sub output { my $self = shift; my $options = $self->{options}; croak("HTML::Template->output() : You gave me an odd number of parameters to output()!") unless ((@_ % 2) == 0); my %args = @_; print STDERR "### HTML::Template Memory Debug ### START OUTPUT ", $self->{proc_mem}->size(), "\n" if $options->{memory_debug}; $options->{debug} and print STDERR "### HTML::Template Debug ### In output\n"; # want a stack dump? if ($options->{stack_debug}) { require 'Data/Dumper.pm'; print STDERR "### HTML::Template output Stack Dump ###\n\n", Data::Dumper::Dumper($self->{parse_stack}), "\n"; } # globalize vars - this happens here to localize the circular # references created by global_vars. $self->_globalize_vars() if ($options->{global_vars}); # support the associate magic, searching for undefined params and # attempting to fill them from the associated objects. if (scalar(@{$options->{associate}})) { # prepare case-mapping hashes to do case-insensitive matching # against associated objects. This allows CGI.pm to be # case-sensitive and still work with asssociate. my (%case_map, $lparam); foreach my $associated_object (@{$options->{associate}}) { # what a hack! This should really be optimized out for case_sensitive. if ($options->{case_sensitive}) { map { $case_map{$associated_object}{$_} = $_ } $associated_object->param(); } else { map { $case_map{$associated_object}{lc($_)} = $_ } $associated_object->param(); } } foreach my $param (keys %{$self->{param_map}}) { unless (defined($self->param($param))) { OBJ: foreach my $associated_object (@{$options->{associate}}) { $self->param($param, scalar $associated_object->param($case_map{$associated_object}{$param})), last OBJ if (exists($case_map{$associated_object}{$param})); } } } } use vars qw($line @parse_stack); local(*line, *parse_stack); # walk the parse stack, accumulating output in $result *parse_stack = $self->{parse_stack}; my $result = ''; tie $result, 'HTML::Template::PRINTSCALAR', $args{print_to} if (defined $args{print_to}); my $type; my $parse_stack_length = $#parse_stack; for (my $x = 0; $x <= $parse_stack_length; $x++) { *line = \$parse_stack[$x]; $type = ref($line); if ($type eq 'SCALAR') { $result .= $$line; } elsif ($type eq 'HTML::Template::VAR' and ref($$line) eq 'CODE') { defined($$line) and $result .= $$line->($self); } elsif ($type eq 'HTML::Template::VAR') { defined($$line) and $result .= $$line; } elsif ($type eq 'HTML::Template::LOOP') { if (defined($line->[HTML::Template::LOOP::PARAM_SET])) { eval { $result .= $line->output($x, $options->{loop_context_vars}); }; croak("HTML::Template->output() : fatal error in loop output : $@") if $@; } } elsif ($type eq 'HTML::Template::COND') { if ($line->[HTML::Template::COND::JUMP_IF_TRUE]) { if ($line->[HTML::Template::COND::VARIABLE_TYPE] == HTML::Template::COND::VARIABLE_TYPE_VAR) { if (defined ${$line->[HTML::Template::COND::VARIABLE]}) { if (ref(${$line->[HTML::Template::COND::VARIABLE]}) eq 'CODE') { $x = $line->[HTML::Template::COND::JUMP_ADDRESS] if ${$line->[HTML::Template::COND::VARIABLE]}->($self); } else { $x = $line->[HTML::Template::COND::JUMP_ADDRESS] if ${$line->[HTML::Template::COND::VARIABLE]}; } } } else { $x = $line->[HTML::Template::COND::JUMP_ADDRESS] if (defined $line->[HTML::Template::COND::VARIABLE][HTML::Template::LOOP::PARAM_SET] and scalar @{$line->[HTML::Template::COND::VARIABLE][HTML::Template::LOOP::PARAM_SET]}); } } else { if ($line->[HTML::Template::COND::VARIABLE_TYPE] == HTML::Template::COND::VARIABLE_TYPE_VAR) { if (defined ${$line->[HTML::Template::COND::VARIABLE]}) { if (ref(${$line->[HTML::Template::COND::VARIABLE]}) eq 'CODE') { $x = $line->[HTML::Template::COND::JUMP_ADDRESS] unless ${$line->[HTML::Template::COND::VARIABLE]}->($self); } else { $x = $line->[HTML::Template::COND::JUMP_ADDRESS] unless ${$line->[HTML::Template::COND::VARIABLE]}; } } else { $x = $line->[HTML::Template::COND::JUMP_ADDRESS]; } } else { $x = $line->[HTML::Template::COND::JUMP_ADDRESS] if (not defined $line->[HTML::Template::COND::VARIABLE][HTML::Template::LOOP::PARAM_SET] or not scalar @{$line->[HTML::Template::COND::VARIABLE][HTML::Template::LOOP::PARAM_SET]}); } } } elsif ($type eq 'HTML::Template::NOOP') { next; } elsif ($type eq 'HTML::Template::ESCAPE') { $x++; *line = \$parse_stack[$x]; if (defined($$line)) { my $toencode = $$line; # straight from the CGI.pm bible. $toencode=~s/&/&/g; $toencode=~s/\"/"/g; #" $toencode=~s/>/>/g; $toencode=~s/hex map if one isn't already available unless (exists($URLESCAPE_MAP{chr(1)})) { for (0..255) { $URLESCAPE_MAP{chr($_)} = sprintf('%%%02X', $_); } } # do the translation (RFC 2396 ^uric) $toencode =~ s!([^a-zA-Z0-9_.\-])!$URLESCAPE_MAP{$1}!g; $result .= $toencode; } } else { confess("HTML::Template::output() : Unknown item in parse_stack : " . $type); } } # undo the globalization circular refs $self->_unglobalize_vars() if ($options->{global_vars}); print STDERR "### HTML::Template Memory Debug ### END OUTPUT ", $self->{proc_mem}->size(), "\n" if $options->{memory_debug}; return undef if defined $args{print_to}; return $result; } =pod =head2 query() This method allow you to get information about the template structure. It can be called in a number of ways. The simplest usage of query is simply to check whether a parameter name exists in the template, using the C option: if ($template->query(name => 'foo')) { # do something if a varaible of any type # named FOO is in the template } This same usage returns the type of the parameter. The type is the same as the tag minus the leading 'TMPL_'. So, for example, a TMPL_VAR parameter returns 'VAR' from query(). if ($template->query(name => 'foo') eq 'VAR') { # do something if FOO exists and is a TMPL_VAR } Note that the variables associated with TMPL_IFs and TMPL_UNLESSs will be identified as 'VAR' unless they are also used in a TMPL_LOOP, in which case they will return 'LOOP'. C also allows you to get a list of parameters inside a loop (and inside loops inside loops). Example loop: And some query calls: # returns 'LOOP' $type = $template->query(name => 'EXAMPLE_LOOP'); # returns ('bop', 'bee', 'example_inner_loop') @param_names = $template->query(loop => 'EXAMPLE_LOOP'); # both return 'VAR' $type = $template->query(name => ['EXAMPLE_LOOP', 'BEE']); $type = $template->query(name => ['EXAMPLE_LOOP', 'BOP']); # and this one returns 'LOOP' $type = $template->query(name => ['EXAMPLE_LOOP', 'EXAMPLE_INNER_LOOP']); # and finally, this returns ('inner_bee', 'inner_bop') @inner_param_names = $template->query(loop => ['EXAMPLE_LOOP', 'EXAMPLE_INNER_LOOP']); # for non existent parameter names you get undef # this returns undef. $type = $template->query(name => 'DWEAZLE_ZAPPA'); # calling loop on a non-loop parameter name will cause an error. # this dies: $type = $template->query(loop => 'DWEAZLE_ZAPPA'); As you can see above the C option returns a list of parameter names and both C and C take array refs in order to refer to parameters inside loops. It is an error to use C with a parameter that is not a loop. Note that all the names are returned in lowercase and the types are uppercase. Just like C, C with no arguements returns all the parameter names in the template at the top level. =cut sub query { my $self = shift; $self->{options}{debug} and print STDERR "### HTML::Template Debug ### query(", join(', ', @_), ")\n"; # the no-parameter case - return $self->param() return $self->param() unless scalar(@_); croak("HTML::Template::query() : Odd number of parameters passed to query!") if (scalar(@_) % 2); croak("HTML::Template::query() : Wrong number of parameters passed to query - should be 2.") if (scalar(@_) != 2); my ($opt, $path) = (lc shift, shift); croak("HTML::Template::query() : invalid parameter ($opt)") unless ($opt eq 'name' or $opt eq 'loop'); # make path an array unless it already is $path = [$path] unless (ref $path); # find the param in question. my @objs = $self->_find_param(@$path); return undef unless scalar(@objs); my ($obj, $type); # do what the user asked with the object if ($opt eq 'name') { # we only look at the first one. new() should make sure they're # all the same. ($obj, $type) = (shift(@objs), shift(@objs)); return undef unless defined $obj; return 'VAR' if $type eq 'HTML::Template::VAR'; return 'LOOP' if $type eq 'HTML::Template::LOOP'; croak("HTML::Template::query() : unknown object ($type) in param_map!"); } elsif ($opt eq 'loop') { my %results; while(@objs) { ($obj, $type) = (shift(@objs), shift(@objs)); croak("HTML::Template::query() : Search path [", join(', ', @$path), "] doesn't end in a TMPL_LOOP - it is an error to use the 'loop' option on a non-loop parameter. To avoid this problem you can use the 'name' option to query() to check the type first.") unless ((defined $obj) and ($type eq 'HTML::Template::LOOP')); # SHAZAM! This bit extracts all the parameter names from all the # loop objects for this name. map {$results{$_} = 1} map { keys(%{$_->{'param_map'}}) } values(%{$obj->[HTML::Template::LOOP::TEMPLATE_HASH]}); } # this is our loop list, return it. return keys(%results); } } # a function that returns the object(s) corresponding to a given path and # its (their) ref()(s). Used by query() in the obvious way. sub _find_param { my $self = shift; my $spot = $self->{options}{case_sensitive} ? shift : lc shift; # get the obj and type for this spot my $obj = $self->{'param_map'}{$spot}; return unless defined $obj; my $type = ref $obj; # return if we're here or if we're not but this isn't a loop return ($obj, $type) unless @_; return unless ($type eq 'HTML::Template::LOOP'); # recurse. this is a depth first seach on the template tree, for # the algorithm geeks in the audience. return map { $_->_find_param(@_) } values(%{$obj->[HTML::Template::LOOP::TEMPLATE_HASH]}); } # HTML::Template::VAR, LOOP, etc are *light* objects - their internal # spec is used above. No encapsulation or information hiding is to be # assumed. package HTML::Template::VAR; sub new { my ($pkg) = @_; my $value; my $self = \$value; bless($self, $pkg); return $self; } package HTML::Template::LOOP; sub new { my ($pkg) = shift; my $self = []; bless($self, $pkg); return $self; } sub output { my $self = shift; my $index = shift; my $loop_context_vars = shift; my $template = $self->[TEMPLATE_HASH]{$index}; my $value_sets_array = $self->[PARAM_SET]; return unless defined($value_sets_array); my $result = ''; my $count = 0; my $odd = 0; foreach my $value_set (@$value_sets_array) { if ($loop_context_vars) { if ($count == 0) { @{$value_set}{qw(__first__ __inner__ __last__)} = (1,0,$#{$value_sets_array} == 0); } elsif ($count == $#{$value_sets_array}) { @{$value_set}{qw(__first__ __inner__ __last__)} = (0,0,1); } else { @{$value_set}{qw(__first__ __inner__ __last__)} = (0,1,0); } $odd = $value_set->{__odd__} = not $odd; } $template->param($value_set); $result .= $template->output; $template->clear_params; @{$value_set}{qw(__first__ __last__ __inner__ __odd__)} = (0,0,0,0) if ($loop_context_vars); $count++; } return $result; } package HTML::Template::COND; sub new { my $pkg = shift; my $var = shift; my $self = []; $self->[VARIABLE] = $var; bless($self, $pkg); return $self; } package HTML::Template::NOOP; sub new { my $unused; my $self = \$unused; bless($self, $_[0]); return $self; } package HTML::Template::ESCAPE; sub new { my $unused; my $self = \$unused; bless($self, $_[0]); return $self; } package HTML::Template::URLESCAPE; sub new { my $unused; my $self = \$unused; bless($self, $_[0]); return $self; } # scalar-tying package for output(print_to => *HANDLE) implementation package HTML::Template::PRINTSCALAR; use strict; sub TIESCALAR { bless \$_[1], $_[0]; } sub FETCH { } sub STORE { my $self = shift; local *FH = $$self; print FH @_; } 1; __END__ =head1 FREQUENTLY ASKED QUESTIONS In the interest of greater understanding I've started a FAQ section of the perldocs. Please look in here before you send me email. 1) Is there a place to go to discuss HTML::Template and/or get help? There's a mailing-list for HTML::Template at htmltmpl@lists.vm.com. Send a blank message to htmltmpl-subscribe@lists.vm.com to join! 2) I want support for ! How about it? Maybe. I definitely encourage people to discuss their ideas for HTML::Template on the mailing list. Please be ready to explain to me how the new tag fits in with HTML::Template's mission to provide a fast, lightweight system for using HTML templates. NOTE: Offering to program said addition and provide it in the form of a patch to the most recent version of HTML::Template will definitely have a softening effect on potential opponents! 3) I found a bug, can you fix it? That depends. Did you send me the VERSION of HTML::Template, a test script and a test template? If so, then almost certainly. If you're feeling really adventurous, HTML::Template has a publically available CVS server. See below for more information in the PUBLIC CVS SERVER section. 4) s from the main template aren't working inside a ! Why? This is the intended behavior. introduces a separate scope for s much like a subroutine call in Perl introduces a separate scope for "my" variables. If you want your s to be global you can set the 'global_vars' option when you call new(). See above for documentation of the 'global_vars' new() option. 5) Why do you use /[Tt]/ instead of /t/i? It's so ugly! Simple - the case-insensitive match switch is very inefficient. According to _Mastering_Regular_Expressions_ from O'Reilly Press, /[Tt]/ is faster and more space efficient than /t/i - by as much as double against long strings. //i essentially does a lc() on the string and keeps a temporary copy in memory. When this changes, and it is in the 5.6 development series, I will gladly use //i. Believe me, I realize [Tt] is hideously ugly. 6) How can I pre-load my templates using cache-mode and mod_perl? Add something like this to your startup.pl: use HTML::Template; use File::Find; print STDERR "Pre-loading HTML Templates...\n"; find( sub { return unless /\.tmpl$/; HTML::Template->new( filename => "$File::Find::dir/$_", cache => 1, ); }, '/path/to/templates', '/another/path/to/templates/' ); Note that you'll need to modify the "return unless" line to specify the extension you use for your template files - I use .tmpl, as you can see. You'll also need to specify the path to your template files. One potential problem: the "/path/to/templates/" must be EXACTLY the same path you use when you call HTML::Template->new(). Otherwise the cache won't know they're the same file and will load a new copy - instead getting a speed increase, you'll double your memory usage. To find out if this is happening set cache_debug => 1 in your application code and look for "CACHE MISS" messages in the logs. 7) What characters are allowed in TMPL_* NAMEs? Numbers, letters, '.', '/', '+', '-' and '_'. 8) How can I execute a program from inside my template? Short answer: you can't. Longer answer: you shouldn't since this violates the fundamental concept behind HTML::Template - that design and code should be seperate. But, inevitably some people still want to do it. At times it has even seemed that HTML::Template development might split over this issue, so I will attempt a compromise. Here is a method you can use to allow your template authors to evaluate arbitrary perl scripts from within the template. First, tell all your designers that when they want to run a perl script named "program.pl" they should use a tag like: Then, have all your programmers call this subroutine instead of calling HTML::Template::new directly. They still use the same parameters, but they also get the program execution. sub new_template { # get the template object my $template = HTML::Template->new(@_); # find program parameters and fill them in my @params = $template->param(); for my $param (@params) { if ($param =~ /^__execute_(.*)__$/) { $template->param($param, do($1)); } } # return the template object return $template; } The programs called in this way should return a string containing their output. A more complicated subroutine could be written to capture STDOUT from the scripts, but this one is simple enough to include in the FAQ. Another improvement would be to use query() to enable program execution inside loops. 9) Can I get a copy of these docs in Japanese? Yes you can. See Kawai Takanori's translation at: http://member.nifty.ne.jp/hippo2000/perltips/html/template.htm =head1 BUGS I am aware of no bugs - if you find one, join the mailing list and tell us about it (htmltmpl@lists.vm.com). You can join the HTML::Template mailing-list by sending a blank email to htmltmpl-subscribe@lists.vm.com. Of course, you can still email me directly (sam@tregar.com) with bugs, but I reserve the right to forward bug reports to the mailing list. When submitting bug reports, be sure to include full details, including the VERSION of the module, a test script and a test template demonstrating the problem! If you're feeling really adventurous, HTML::Template has a publically available CVS server. See below for more information in the PUBLIC CVS SERVER section. =head1 CREDITS This module was the brain child of my boss, Jesse Erlbaum (jesse@vm.com) here at Vanguard Media. The most original idea in this module - the - was entirely his. Fixes, Bug Reports, Optimizations and Ideas have been generously provided by: Richard Chen Mike Blazer Adriano Nagelschmidt Rodrigues Andrej Mikus Ilya Obshadko Kevin Puetz Steve Reppucci Richard Dice Tom Hukins Eric Zylberstejn David Glasser Peter Marelas James William Carlson Frank D. Cringle Winfried Koenig Matthew Wickline Doug Steinwand Drew Taylor Tobias Brox Michael Lloyd Simran Gambhir Chris Houser Larry Moore Todd Larason Jody Biggs T.J. Mather Martin Schroth Dave Wolfe uchum Kawai Takanori Peter Guelich Chris Nokleberg Ralph Corderoy William Ward Ade Olonoh Mark Stosberg Lance Thomas Roland Giersig Jere Julian Peter Leonard Thanks! =head1 PUBLIC CVS SERVER HTML::Template now has a publicly accessible CVS server provided by SourceForge (www.sourceforge.net). You can access it by going to http://sourceforge.net/cvs/?group_id=1075. Give it a try! =head1 AUTHOR Sam Tregar, sam@tregar.com (you can also find me on the mailing list at htmltmpl@lists.vm.com - join it by sending a blank message to htmltmpl-subscribe@lists.vm.com). =head1 LICENSE HTML::Template : A module for using HTML Templates with Perl Copyright (C) 2000 Sam Tregar (sam@tregar.com) This module is free software; you can redistribute it and/or modify it under the terms of either: a) the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version, or b) the "Artistic License" which comes with this module. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the Artistic License for more details. You should have received a copy of the Artistic License with this module, in the file ARTISTIC. If not, I'll be glad to provide one. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA =cut cgi-bin/extlib/HTML/Form.pm0000644002157400001440000003700407771550070022301 0ustar minnesotaviolasociety.orgusers00000000000000package HTML::Form; use strict; use URI; use Carp (); use vars qw($VERSION); $VERSION='0.03'; my %form_tags = map {$_ => 1} qw(input textarea button select option); my %type2class = ( text => "TextInput", password => "TextInput", file => "TextInput", hidden => "TextInput", textarea => "TextInput", button => "IgnoreInput", "reset" => "IgnoreInput", radio => "ListInput", checkbox => "ListInput", option => "ListInput", submit => "SubmitInput", image => "ImageInput", ); =head1 NAME HTML::Form - Class that represents HTML forms =head1 SYNOPSIS use HTML::Form; $form = HTML::Form->parse($html, $base_uri); $form->value(query => "Perl"); use LWP; LWP::UserAgent->new->request($form->click); =head1 DESCRIPTION Objects of the C class represents a single HTML

...
instance. A form consist of a sequence of inputs that usually have names, and which can take on various values. The following methods are available: =over 4 =item $form = HTML::Form->new($method, $action_uri, [[$enctype], $input,...]) The constructor takes a $method and a $uri as argument. The $enctype and and initial inputs are optional. You will normally use HTML::Form->parse() to create new HTML::Form objects. =cut sub new { my $class = shift; my $self = bless {}, $class; $self->{method} = uc(shift || "GET"); $self->{action} = shift || Carp::croak("No action defined"); $self->{enctype} = shift || "application/x-www-form-urlencoded"; $self->{inputs} = [@_]; $self; } =item @forms = HTML::Form->parse($html_document, $base_uri) The parse() class method will parse an HTML document and build up C objects for each
found. If called in scalar context only returns the first . Returns an empty list if there are no forms to be found. The $base_uri is (usually) the URI used to access the $html_document. It is needed to resolve relative action URIs. For LWP this parameter is obtained from the $response->base() method. =cut sub parse { my($class, $html, $base_uri) = @_; require HTML::TokeParser; my $p = HTML::TokeParser->new(\$html); eval { # optimization $p->report_tags(qw(form input textarea select optgroup option)); }; my @forms; my $f; # current form while (my $t = $p->get_tag) { my($tag,$attr) = @$t; if ($tag eq "form") { my $action = delete $attr->{'action'}; $action = "" unless defined $action; $action = URI->new_abs($action, $base_uri); $f = $class->new(delete $attr->{'method'}, $action, delete $attr->{'enctype'}); $f->{extra_attr} = $attr; push(@forms, $f); while (my $t = $p->get_tag) { my($tag, $attr) = @$t; last if $tag eq "/form"; if ($tag eq "input") { my $type = delete $attr->{type} || "text"; $f->push_input($type, $attr); } elsif ($tag eq "textarea") { $attr->{textarea_value} = $attr->{value} if exists $attr->{value}; my $text = $p->get_text("/textarea"); $attr->{value} = $text; $f->push_input("textarea", $attr); } elsif ($tag eq "select") { $attr->{select_value} = $attr->{value} if exists $attr->{value}; while ($t = $p->get_tag) { my $tag = shift @$t; last if $tag eq "/select"; next if $tag =~ m,/?optgroup,; next if $tag eq "/option"; if ($tag eq "option") { my %a = (%$attr, %{$t->[0]}); $a{value} = $p->get_trimmed_text unless defined $a{value}; $f->push_input("option", \%a); } else { Carp::carp("Bad HTML tags, we also have "textarea" and "option". The $no is the sequence number of the input with the indicated $name and/or $type (where 1 is the first). =cut sub find_input { my($self, $name, $type, $no) = @_; $no ||= 1; for (@{$self->{'inputs'}}) { if (defined $name) { next unless exists $_->{name}; next if $name ne $_->{name}; } next if $type && $type ne $_->{type}; next if --$no; return $_; } return; } sub fixup { my $self = shift; for (@{$self->{'inputs'}}) { $_->fixup; } } =item $form->value($name, [$value]) The value() method can be used to get/set the value of some input. If no input have the indicated name, then this method will croak. =cut sub value { my $self = shift; my $key = shift; my $input = $self->find_input($key); Carp::croak("No such field '$key'") unless $input; local $Carp::CarpLevel = 1; $input->value(@_); } =item $form->try_others(\&callback) This method will iterate over all permutations of unvisited enumerated values ( or C), with the indicated $name, if specified. You can optinally specify a coordinate clicked, which only makes a difference if you clicked on an image. The default coordinate is (1,1). =cut sub click { my $self = shift; my $name; $name = shift if (@_ % 2) == 1; # odd number of arguments # try to find first submit button to activate for (@{$self->{'inputs'}}) { next unless $_->can("click"); next if $name && $_->name ne $name; return $_->click($self, @_); } Carp::croak("No clickable input with name $name") if $name; $self->make_request; } =item $form->form Returns the current setting as a sequence of key/value pairs. =cut sub form { my $self = shift; map {$_->form_name_value} @{$self->{'inputs'}}; } =item $form->dump Returns a textual representation of the form. Mainly useful for debugging. If called in void context, then the dump is printed on STDERR. =cut sub dump { my $self = shift; my $method = $self->{'method'}; my $uri = $self->{'action'}; my $enctype = $self->{'enctype'}; my $dump = "$method $uri"; $dump .= " ($enctype)" if $enctype eq "application/xxx-www-form-urlencoded"; $dump .= "\n"; for ($self->inputs) { $dump .= " " . $_->dump . "\n"; } print STDERR $dump unless defined wantarray; $dump; } #--------------------------------------------------- package HTML::Form::Input; =back =head1 INPUTS An C contains a sequence of inputs. References to the inputs can be obtained with the $form->inputs or $form->find_input methods. Once you have such a reference, then one of the following methods can be used on it: =over 4 =cut sub new { my $class = shift; my $self = bless {@_}, $class; $self; } sub add_to_form { my($self, $form) = @_; push(@{$form->{'inputs'}}, $self); $self; } sub fixup {} =item $input->type Returns the type of this input. Types are stuff like "text", "password", "hidden", "textarea", "image", "submit", "radio", "checkbox", "option"... =cut sub type { shift->{type}; } =item $input->name([$new]) =item $input->value([$new]) These methods can be used to set/get the current name or value of an input. If the input only can take an enumerated list of values, then it is an error to try to set it to something else and the method will croak if you try. =cut sub name { my $self = shift; my $old = $self->{name}; $self->{name} = shift if @_; $old; } sub value { my $self = shift; my $old = $self->{value}; $self->{value} = shift if @_; $old; } =item $input->possible_values Returns a list of all values that and input can take. For inputs that does not have discrete values this returns an empty list. =cut sub possible_values { return; } =item $input->other_possible_values Returns a list of all values not tried yet. =cut sub other_possible_values { return; } =item $input->form_name_value Returns a (possible empty) list of key/value pairs that should be incorporated in the form value from this input. =cut sub form_name_value { my $self = shift; my $name = $self->{'name'}; return unless defined $name; my $value = $self->value; return unless defined $value; return ($name => $value); } sub dump { my $self = shift; my $name = $self->name; $name = "" unless defined $name; my $value = $self->value; $value = "" unless defined $value; my $dump = "$name=$value"; my $type = $self->type; return $dump if $type eq "text"; $type = ($type eq "text") ? "" : " ($type)"; my $menu = $self->{menu} || ""; if ($menu) { my @menu; for (0 .. @$menu-1) { my $opt = $menu->[$_]; $opt = "" unless defined $opt; substr($opt,0,0) = "*" if $self->{seen}[$_]; push(@menu, $opt); } $menu = "[" . join("|", @menu) . "]"; } sprintf "%-30s %-10s %s", $dump, $type, $menu; } #--------------------------------------------------- package HTML::Form::TextInput; @HTML::Form::TextInput::ISA=qw(HTML::Form::Input); #input/text #input/password #input/file #input/hidden #textarea sub value { my $self = shift; if (@_) { if (exists($self->{readonly}) || $self->{type} eq "hidden") { Carp::carp("Input '$self->{name}' is readonly") if $^W; } } $self->SUPER::value(@_); } #--------------------------------------------------- package HTML::Form::IgnoreInput; @HTML::Form::IgnoreInput::ISA=qw(HTML::Form::Input); #input/button #input/reset sub value { return } #--------------------------------------------------- package HTML::Form::ListInput; @HTML::Form::ListInput::ISA=qw(HTML::Form::Input); #select/option (val1, val2, ....) #input/radio (undef, val1, val2,...) #input/checkbox (undef, value) sub new { my $class = shift; my $self = $class->SUPER::new(@_); if ($self->type eq "checkbox") { my $value = delete $self->{value}; $value = "on" unless defined $value; $self->{menu} = [undef, $value]; $self->{current} = (exists $self->{checked}) ? 1 : 0; delete $self->{checked}; } else { $self->{menu} = [delete $self->{value}]; my $checked = exists $self->{checked} || exists $self->{selected}; delete $self->{checked}; delete $self->{selected}; if (exists $self->{multiple}) { unshift(@{$self->{menu}}, undef); $self->{current} = $checked ? 1 : 0; } else { $self->{current} = 0 if $checked; } } $self; } sub add_to_form { my($self, $form) = @_; my $type = $self->type; return $self->SUPER::add_to_form($form) if $type eq "checkbox" || ($type eq "option" && exists $self->{multiple}); my $prev = $form->find_input($self->{name}, $self->{type}); return $self->SUPER::add_to_form($form) unless $prev; # merge menues push(@{$prev->{menu}}, @{$self->{menu}}); $prev->{current} = @{$prev->{menu}} - 1 if exists $self->{current}; } sub fixup { my $self = shift; if ($self->{type} eq "option" && !(exists $self->{current})) { $self->{current} = 0; } $self->{seen} = [(0) x @{$self->{menu}}]; $self->{seen}[$self->{current}] = 1 if exists $self->{current}; } sub value { my $self = shift; my $old; $old = $self->{menu}[$self->{current}] if exists $self->{current}; if (@_) { my $i = 0; my $val = shift; my $cur; for (@{$self->{menu}}) { if ((defined($val) && defined($_) && $val eq $_) || (!defined($val) && !defined($_)) ) { $cur = $i; last; } $i++; } Carp::croak("Illegal value '$val'") unless defined $cur; $self->{current} = $cur; $self->{seen}[$cur] = 1; } $old; } sub possible_values { my $self = shift; @{$self->{menu}}; } sub other_possible_values { my $self = shift; map { $self->{menu}[$_] } grep {!$self->{seen}[$_]} 0 .. (@{$self->{seen}} - 1); } #--------------------------------------------------- package HTML::Form::SubmitInput; @HTML::Form::SubmitInput::ISA=qw(HTML::Form::Input); #input/image #input/submit =item $input->click($form, $x, $y) Some input types (currently "sumbit" buttons and "images") can be clicked to submit the form. The click() method returns the corrsponding C object. =cut sub click { my($self,$form,$x,$y) = @_; for ($x, $y) { $_ = 1 unless defined; } local($self->{clicked}) = [$x,$y]; return $form->make_request; } sub form_name_value { my $self = shift; return unless $self->{clicked}; return $self->SUPER::form_name_value(@_); } #--------------------------------------------------- package HTML::Form::ImageInput; @HTML::Form::ImageInput::ISA=qw(HTML::Form::SubmitInput); sub form_name_value { my $self = shift; my $clicked = $self->{clicked}; return unless $clicked; my $name = $self->{name}; return unless defined $name; return ("$name.x" => $clicked->[0], "$name.y" => $clicked->[1] ); } 1; __END__ =back =head1 SEE ALSO L, L, L =head1 COPYRIGHT Copyright 1998-2000 Gisle Aas. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut cgi-bin/extlib/HTTP/0000755002157400001440000000000007776605372021062 5ustar minnesotaviolasociety.orgusers00000000000000cgi-bin/extlib/HTTP/Headers/0000755002157400001440000000000007776605372022435 5ustar minnesotaviolasociety.orgusers00000000000000cgi-bin/extlib/HTTP/Headers/_vti_cnf/0000755002157400001440000000000007776605372024224 5ustar minnesotaviolasociety.orgusers00000000000000cgi-bin/extlib/HTTP/Headers/_vti_cnf/Auth.pm0000644002157400001440000000030307771550070025444 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|22 Dec 2003 11:06:32 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|22 Dec 2003 11:06:32 -0000 vti_filesize:IR|2028 vti_backlinkinfo:VX| cgi-bin/extlib/HTTP/Headers/_vti_cnf/ETag.pm0000644002157400001440000000030307771550070025363 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|22 Dec 2003 11:06:32 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|22 Dec 2003 11:06:32 -0000 vti_filesize:IR|2180 vti_backlinkinfo:VX| cgi-bin/extlib/HTTP/Headers/_vti_cnf/Util.pm0000644002157400001440000000030307771550070025460 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|22 Dec 2003 11:06:32 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|22 Dec 2003 11:06:32 -0000 vti_filesize:IR|4431 vti_backlinkinfo:VX| cgi-bin/extlib/HTTP/Headers/Auth.pm0000644002157400001440000000375407771550070023672 0ustar minnesotaviolasociety.orgusers00000000000000package HTTP::Headers::Auth; use strict; use vars qw($VERSION); $VERSION = sprintf("%d.%02d", q$Revision: 1.2 $ =~ /(\d+)\.(\d+)/); require HTTP::Headers; package HTTP::Headers; BEGIN { # we provide a new (and better) implementations below undef(&www_authenticate); undef(&proxy_authenticate); } require HTTP::Headers::Util; sub _parse_authenticate { my @ret; for (HTTP::Headers::Util::split_header_words(@_)) { if (!defined($_->[1])) { # this is a new auth scheme push(@ret, lc(shift @$_) => {}); shift @$_; } if (@ret) { # this a new parameter pair for the last auth scheme while (@$_) { my $k = lc(shift @$_); my $v = shift @$_; $ret[-1]{$k} = $v; } } else { # something wrong, parameter pair without any scheme seen # IGNORE } } @ret; } sub _authenticate { my $self = shift; my $header = shift; my @old = $self->_header($header); if (@_) { $self->remove_header($header); my @new = @_; while (@new) { my $a_scheme = shift(@new); if ($a_scheme =~ /\s/) { # assume complete valid value, pass it through $self->push_header($header, $a_scheme); } else { my @param; if (@new) { my $p = $new[0]; if (ref($p) eq "ARRAY") { @param = @$p; shift(@new); } elsif (ref($p) eq "HASH") { @param = %$p; shift(@new); } } my $val = ucfirst(lc($a_scheme)); if (@param) { my $sep = " "; while (@param) { my $k = shift @param; my $v = shift @param; if ($v =~ /[^0-9a-zA-Z]/ || lc($k) eq "realm") { # must quote the value $v =~ s,([\\\"]),\\$1,g; $v = qq("$v"); } $val .= "$sep$k=$v"; $sep = ", "; } } $self->push_header($header, $val); } } } return unless defined wantarray; wantarray ? _parse_authenticate(@old) : join(", ", @old); } sub www_authenticate { shift->_authenticate("WWW-Authenticate", @_) } sub proxy_authenticate { shift->_authenticate("Proxy-Authenticate", @_) } 1; cgi-bin/extlib/HTTP/Headers/ETag.pm0000644002157400001440000000420407771550070023600 0ustar minnesotaviolasociety.orgusers00000000000000package HTTP::Headers::ETag; use strict; use vars qw($VERSION); $VERSION = sprintf("%d.%02d", q$Revision: 1.3 $ =~ /(\d+)\.(\d+)/); require HTTP::Date; require HTTP::Headers; package HTTP::Headers; sub _etags { my $self = shift; my $header = shift; my @old = _split_etag_list($self->_header($header)); if (@_) { $self->_header($header => join(", ", _split_etag_list(@_))); } wantarray ? @old : join(", ", @old); } sub etag { shift->_etags("ETag", @_); } sub if_match { shift->_etags("If-Match", @_); } sub if_none_match { shift->_etags("If-None-Match", @_); } sub if_range { # Either a date or an entity-tag my $self = shift; my @old = $self->_header("If-Range"); if (@_) { my $new = shift; if (!defined $new) { $self->remove_header("If-Range"); } elsif ($new =~ /^\d+$/) { $self->_date_header("If-Range", $new); } else { $self->_etags("If-Range", $new); } } return unless defined(wantarray); for (@old) { my $t = HTTP::Date::str2time($_); $_ = $t if $t; } wantarray ? @old : join(", ", @old); } # Split a list of entity tag values. The return value is a list # consisting of one element per entity tag. Suitable for parsing # headers like C, C. You might even want to # use it on C and C entity tag values, because it will # normalize them to the common form. # # entity-tag = [ weak ] opaque-tag # weak = "W/" # opaque-tag = quoted-string sub _split_etag_list { my(@val) = @_; my @res; for (@val) { while (length) { my $weak = ""; $weak = "W/" if s,^\s*[wW]/,,; my $etag = ""; if (s/^\s*(\"[^\"\\]*(?:\\.[^\"\\]*)*\")//) { push(@res, "$weak$1"); } elsif (s/^\s*,//) { push(@res, qq(W/"")) if $weak; } elsif (s/^\s*([^,\s]+)//) { $etag = $1; $etag =~ s/([\"\\])/\\$1/g; push(@res, qq($weak"$etag")); } elsif (s/^\s+// || !length) { push(@res, qq(W/"")) if $weak; } else { die "This should not happen: '$_'"; } } } @res; } 1; cgi-bin/extlib/HTTP/Headers/Util.pm0000644002157400001440000001051707771550070023701 0ustar minnesotaviolasociety.orgusers00000000000000package HTTP::Headers::Util; use strict; use vars qw($VERSION @ISA @EXPORT_OK); $VERSION = sprintf("%d.%02d", q$Revision: 1.9 $ =~ /(\d+)\.(\d+)/); require Exporter; @ISA=qw(Exporter); @EXPORT_OK=qw(split_header_words join_header_words); =head1 NAME HTTP::Headers::Util - Header value parsing utility functions =head1 SYNOPSIS use HTTP::Headers::Util qw(split_header_words); @values = split_header_words($h->header("Content-Type")); =head1 DESCRIPTION This module provides a few functions that helps parsing and construction of valid HTTP header values. None of the functions are exported by default. The following functions are available: =over 4 =item split_header_words( @header_values ) This function will parse the header values given as argument into a list of anonymous arrays containing key/value pairs. The function knows how to deal with ",", ";" and "=" as well as quoted values after "=". A list of space separated tokens are parsed as if they were separated by ";". If the @header_values passed as argument contains multiple values, then they are treated as if they were a single value separated by comma ",". This means that this function is useful for parsing header fields that follow this syntax (BNF as from the HTTP/1.1 specification, but we relax the requirement for tokens). headers = #header header = (token | parameter) *( [";"] (token | parameter)) token = 1* separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <"> | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT quoted-string = ( <"> *(qdtext | quoted-pair ) <"> ) qdtext = > quoted-pair = "\" CHAR parameter = attribute "=" value attribute = token value = token | quoted-string Each I
is represented by an anonymous array of key/value pairs. The value for a simple token (not part of a parameter) is C. Syntactically incorrect headers will not necessary be parsed as you would want. This is easier to describe with some examples: split_header_words('foo="bar"; port="80,81"; discard, bar=baz') split_header_words('text/html; charset="iso-8859-1"); split_header_words('Basic realm="\"foo\\bar\""'); will return [foo=>'bar', port=>'80,81', discard=> undef], [bar=>'baz' ] ['text/html' => undef, charset => 'iso-8859-1'] [Basic => undef, realm => '"foo\bar"'] =cut sub split_header_words { my(@val) = @_; my @res; for (@val) { my @cur; while (length) { if (s/^\s*(=*[^\s=;,]+)//) { # 'token' or parameter 'attribute' push(@cur, $1); # a quoted value if (s/^\s*=\s*\"([^\"\\]*(?:\\.[^\"\\]*)*)\"//) { my $val = $1; $val =~ s/\\(.)/$1/g; push(@cur, $val); # some unquoted value } elsif (s/^\s*=\s*([^;,\s]*)//) { my $val = $1; $val =~ s/\s+$//; push(@cur, $val); # no value, a lone token } else { push(@cur, undef); } } elsif (s/^\s*,//) { push(@res, [@cur]) if @cur; @cur = (); } elsif (s/^\s*;// || s/^\s+//) { # continue } else { die "This should not happen: '$_'"; } } push(@res, \@cur) if @cur; } @res; } =item join_header_words( @arrays ) This will do the opposite of the conversion done by split_header_words(). It takes a list of anonymous arrays as arguments (or a list of key/value pairs) and produces a single header value. Attribute values are quoted if needed. Example: join_header_words(["text/plain" => undef, charset => "iso-8859/1"]); join_header_words("text/plain" => undef, charset => "iso-8859/1"); will both return the string: text/plain; charset="iso-8859/1" =cut sub join_header_words { @_ = ([@_]) if @_ && !ref($_[0]); my @res; for (@_) { my @cur = @$_; my @attr; while (@cur) { my $k = shift @cur; my $v = shift @cur; if (defined $v) { if ($v =~ /^\w+$/) { $k .= "=$v"; } else { $v =~ s/([\"\\])/\\$1/g; # escape " and \ $k .= qq(="$v"); } } push(@attr, $k); } push(@res, join("; ", @attr)) if @attr; } join(", ", @res); } 1; __END__ =back =head1 COPYRIGHT Copyright 1997-1998, Gisle Aas This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut cgi-bin/extlib/HTTP/Request/0000755002157400001440000000000007776605372022512 5ustar minnesotaviolasociety.orgusers00000000000000cgi-bin/extlib/HTTP/Request/_vti_cnf/0000755002157400001440000000000007776605372024301 5ustar minnesotaviolasociety.orgusers00000000000000cgi-bin/extlib/HTTP/Request/_vti_cnf/Common.pm0000644002157400001440000000030407771550070026051 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|22 Dec 2003 11:06:32 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|22 Dec 2003 11:06:32 -0000 vti_filesize:IR|12306 vti_backlinkinfo:VX| cgi-bin/extlib/HTTP/Request/Common.pm0000644002157400001440000003002207771550070024262 0ustar minnesotaviolasociety.orgusers00000000000000# $Id: Common.pm,v 1.19 2001/01/05 18:53:11 gisle Exp $ # package HTTP::Request::Common; use strict; use vars qw(@EXPORT @EXPORT_OK $VERSION $DYNAMIC_FILE_UPLOAD); $DYNAMIC_FILE_UPLOAD ||= 0; # make it defined (don't know why) require Exporter; *import = \&Exporter::import; @EXPORT =qw(GET HEAD PUT POST); @EXPORT_OK = qw($DYNAMIC_FILE_UPLOAD); require HTTP::Request; use Carp(); $VERSION = sprintf("%d.%02d", q$Revision: 1.19 $ =~ /(\d+)\.(\d+)/); my $CRLF = "\015\012"; # "\r\n" is not portable sub GET { _simple_req('GET', @_); } sub HEAD { _simple_req('HEAD', @_); } sub PUT { _simple_req('PUT' , @_); } sub POST { my $url = shift; my $req = HTTP::Request->new(POST => $url); my $content; $content = shift if @_ and ref $_[0]; my($k, $v); while (($k,$v) = splice(@_, 0, 2)) { if (lc($k) eq 'content') { $content = $v; } else { $req->push_header($k, $v); } } my $ct = $req->header('Content-Type'); unless ($ct) { $ct = 'application/x-www-form-urlencoded'; } elsif ($ct eq 'form-data') { $ct = 'multipart/form-data'; } if (ref $content) { if ($ct =~ m,^multipart/form-data\s*(;|$),i) { require HTTP::Headers::Util; my @v = HTTP::Headers::Util::split_header_words($ct); Carp::carp("Multiple Content-Type headers") if @v > 1; @v = @{$v[0]}; my $boundary; my $boundary_index; for (my @tmp = @v; @tmp;) { my($k, $v) = splice(@tmp, 0, 2); if (lc($k) eq "boundary") { $boundary = $v; $boundary_index = @v - @tmp - 1; last; } } ($content, $boundary) = form_data($content, $boundary, $req); if ($boundary_index) { $v[$boundary_index] = $boundary; } else { push(@v, boundary => $boundary); } $ct = HTTP::Headers::Util::join_header_words(@v); } else { # We use a temporary URI object to format # the application/x-www-form-urlencoded content. require URI; my $url = URI->new('http:'); $url->query_form(ref($content) eq "HASH" ? %$content : @$content); $content = $url->query; } } $req->header('Content-Type' => $ct); # might be redundant if (defined($content)) { $req->header('Content-Length' => length($content)) unless ref($content); $req->content($content); } $req; } sub _simple_req { my($method, $url) = splice(@_, 0, 2); my $req = HTTP::Request->new($method => $url); my($k, $v); while (($k,$v) = splice(@_, 0, 2)) { if (lc($k) eq 'content') { $req->add_content($v); } else { $req->push_header($k, $v); } } $req; } sub form_data # RFC1867 { my($data, $boundary, $req) = @_; my @data = ref($data) eq "HASH" ? %$data : @$data; # copy my $fhparts; my @parts; my($k,$v); while (($k,$v) = splice(@data, 0, 2)) { if (!ref($v)) { $k =~ s/([\\\"])/\\$1/g; # escape quotes and backslashes push(@parts, qq(Content-Disposition: form-data; name="$k"$CRLF$CRLF$v)); } else { my($file, $usename, @headers) = @$v; unless (defined $usename) { $usename = $file; $usename =~ s,.*/,, if defined($usename); } my $disp = qq(form-data; name="$k"); $disp .= qq(; filename="$usename") if $usename; my $content = ""; my $h = HTTP::Headers->new(@headers); my $ct = $h->header("Content-Type"); if ($file) { require Symbol; my $fh = Symbol::gensym(); open($fh, $file) or Carp::croak("Can't open file $file: $!"); binmode($fh); if ($DYNAMIC_FILE_UPLOAD) { # will read file later $content = $fh; } else { local($/) = undef; # slurp files $content = <$fh>; close($fh); $h->header("Content-Length" => length($content)); } unless ($ct) { require LWP::MediaTypes; $ct = LWP::MediaTypes::guess_media_type($file, $h); } } if ($h->header("Content-Disposition")) { # just to get it sorted first $disp = $h->header("Content-Disposition"); $h->remove_header("Content-Disposition"); } if ($h->header("Content")) { $content = $h->header("Content"); $h->remove_header("Content"); } my $head = join($CRLF, "Content-Disposition: $disp", $h->as_string($CRLF), ""); if (ref $content) { push(@parts, [$head, $content]); $fhparts++; } else { push(@parts, $head . $content); } } } return "" unless @parts; my $content; if ($fhparts) { $boundary = boundary(10) # hopefully enough randomness unless $boundary; # add the boundaries to the @parts array for (1..@parts-1) { splice(@parts, $_*2-1, 0, "$CRLF--$boundary$CRLF"); } unshift(@parts, "--$boundary$CRLF"); push(@parts, "$CRLF--$boundary--$CRLF"); # See if we can generate Content-Length header my $length = 0; for (@parts) { if (ref $_) { my ($head, $f) = @$_; my $file_size; unless ( -f $f && ($file_size = -s _) ) { # The file is either a dynamic file like /dev/audio # or perhaps a file in the /proc file system where # stat may return a 0 size even though reading it # will produce data. So we cannot make # a Content-Length header. undef $length; last; } $length += $file_size + length $head; } else { $length += length; } } $length && $req->header('Content-Length' => $length); # set up a closure that will return content piecemeal $content = sub { for (;;) { unless (@parts) { defined $length && $length != 0 && Carp::croak "length of data sent did not match calculated Content-Length header. Probably because uploaded file changed in size during transfer."; return; } my $p = shift @parts; unless (ref $p) { $p .= shift @parts while @parts && !ref($parts[0]); defined $length && ($length -= length $p); return $p; } my($buf, $fh) = @$p; my $buflength = length $buf; my $n = read($fh, $buf, 2048, $buflength); if ($n) { $buflength += $n; unshift(@parts, ["", $fh]); } else { close($fh); } if ($buflength) { defined $length && ($length -= $buflength); return $buf } } }; } else { $boundary = boundary() unless $boundary; my $bno = 0; CHECK_BOUNDARY: { for (@parts) { if (index($_, $boundary) >= 0) { # must have a better boundary $boundary = boundary(++$bno); redo CHECK_BOUNDARY; } } last; } $content = "--$boundary$CRLF" . join("$CRLF--$boundary$CRLF", @parts) . "$CRLF--$boundary--$CRLF"; } wantarray ? ($content, $boundary) : $content; } sub boundary { my $size = shift || return "xYzZY"; require MIME::Base64; my $b = MIME::Base64::encode(join("", map chr(rand(256)), 1..$size*3), ""); $b =~ s/[\W]/X/g; # ensure alnum only $b; } 1; __END__ =head1 NAME HTTP::Request::Common - Construct common HTTP::Request objects =head1 SYNOPSIS use HTTP::Request::Common; $ua = LWP::UserAgent->new; $ua->request(GET 'http://www.sn.no/'); $ua->request(POST 'http://somewhere/foo', [foo => bar, bar => foo]); =head1 DESCRIPTION This module provide functions that return newly created HTTP::Request objects. These functions are usually more convenient to use than the standard HTTP::Request constructor for these common requests. The following functions are provided. =over 4 =item GET $url, Header => Value,... The GET() function returns a HTTP::Request object initialized with the GET method and the specified URL. Without additional arguments it is exactly equivalent to the following call HTTP::Request->new(GET => $url) but is less cluttered. It also reads better when used together with the LWP::UserAgent->request() method: my $ua = new LWP::UserAgent; my $res = $ua->request(GET 'http://www.sn.no') if ($res->is_success) { ... You can also initialize header values in the request by specifying some key/value pairs as optional arguments. For instance: $ua->request(GET 'http://www.sn.no', If_Match => 'foo', From => 'gisle@aas.no', ); A header key called 'Content' is special and when seen the value will initialize the content part of the request instead of setting a header. =item HEAD $url, [Header => Value,...] Like GET() but the method in the request is HEAD. =item PUT $url, [Header => Value,...] Like GET() but the method in the request is PUT. =item POST $url, [$form_ref], [Header => Value,...] This works mostly like GET() with POST as the method, but this function also takes a second optional array or hash reference parameter ($form_ref). This argument can be used to pass key/value pairs for the form content. By default we will initialize a request using the C content type. This means that you can emulate a HTML Eform> POSTing like this: POST 'http://www.perl.org/survey.cgi', [ name => 'Gisle Aas', email => 'gisle@aas.no', gender => 'M', born => '1964', perc => '3%', ]; This will create a HTTP::Request object that looks like this: POST http://www.perl.org/survey.cgi Content-Length: 66 Content-Type: application/x-www-form-urlencoded name=Gisle%20Aas&email=gisle%40aas.no&gender=M&born=1964&perc=3%25 The POST method also supports the C content used for I as specified in RFC 1867. You trigger this content format by specifying a content type of C<'form-data'> as one of the request headers. If one of the values in the $form_ref is an array reference, then it is treated as a file part specification with the following interpretation: [ $file, $filename, Header => Value... ] The first value in the array ($file) is the name of a file to open. This file will be read and its content placed in the request. The routine will croak if the file can't be opened. Use an C as $file value if you want to specify the content directly. The $filename is the filename to report in the request. If this value is undefined, then the basename of the $file will be used. You can specify an empty string as $filename if you don't want any filename in the request. Sending my F<~/.profile> to the survey used as example above can be achieved by this: POST 'http://www.perl.org/survey.cgi', Content_Type => 'form-data', Content => [ name => 'Gisle Aas', email => 'gisle@aas.no', gender => 'M', born => '1964', init => ["$ENV{HOME}/.profile"], ] This will create a HTTP::Request object that almost looks this (the boundary and the content of your F<~/.profile> is likely to be different): POST http://www.perl.org/survey.cgi Content-Length: 388 Content-Type: multipart/form-data; boundary="6G+f" --6G+f Content-Disposition: form-data; name="name" Gisle Aas --6G+f Content-Disposition: form-data; name="email" gisle@aas.no --6G+f Content-Disposition: form-data; name="gender" M --6G+f Content-Disposition: form-data; name="born" 1964 --6G+f Content-Disposition: form-data; name="init"; filename=".profile" Content-Type: text/plain PATH=/local/perl/bin:$PATH export PATH --6G+f-- If you set the $DYNAMIC_FILE_UPLOAD variable (exportable) to some TRUE value, then you get back a request object with a subroutine closure as the content attribute. This subroutine will read the content of any files on demand and return it in suitable chunks. This allow you to upload arbitrary big files without using lots of memory. You can even upload infinite files like F if you wish; however, if the file is not a plain file, there will be no Content-Length header defined for the request. Not all servers (or server applications) like this. Also, if the file(s) change in size between the time the Content-Length is calculated and the time that the last chunk is delivered, the subroutine will C. =back =head1 SEE ALSO L, L =head1 COPYRIGHT Copyright 1997-2000, Gisle Aas This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut cgi-bin/extlib/HTTP/_vti_cnf/0000755002157400001440000000000007776605372022651 5ustar minnesotaviolasociety.orgusers00000000000000cgi-bin/extlib/HTTP/_vti_cnf/Cookies.pm0000644002157400001440000000030407771550070024565 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|22 Dec 2003 11:06:32 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|22 Dec 2003 11:06:32 -0000 vti_filesize:IR|21391 vti_backlinkinfo:VX| cgi-bin/extlib/HTTP/_vti_cnf/Daemon.pm0000644002157400001440000000030407771550070024374 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|22 Dec 2003 11:06:32 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|22 Dec 2003 11:06:32 -0000 vti_filesize:IR|21577 vti_backlinkinfo:VX| cgi-bin/extlib/HTTP/_vti_cnf/Date.pm0000644002157400001440000000030407771550070024046 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|22 Dec 2003 11:06:32 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|22 Dec 2003 11:06:32 -0000 vti_filesize:IR|10592 vti_backlinkinfo:VX| cgi-bin/extlib/HTTP/_vti_cnf/Headers.pm0000644002157400001440000000030407771550070024544 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|22 Dec 2003 11:06:32 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|22 Dec 2003 11:06:32 -0000 vti_filesize:IR|17977 vti_backlinkinfo:VX| cgi-bin/extlib/HTTP/_vti_cnf/Message.pm0000644002157400001440000000030307771550070024554 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|22 Dec 2003 11:06:32 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|22 Dec 2003 11:06:32 -0000 vti_filesize:IR|4794 vti_backlinkinfo:VX| cgi-bin/extlib/HTTP/_vti_cnf/Negotiate.pm0000644002157400001440000000030407771550070025110 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|22 Dec 2003 11:06:32 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|22 Dec 2003 11:06:32 -0000 vti_filesize:IR|16217 vti_backlinkinfo:VX| cgi-bin/extlib/HTTP/_vti_cnf/Request.pm0000644002157400001440000000030307771550070024620 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|22 Dec 2003 11:06:32 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|22 Dec 2003 11:06:32 -0000 vti_filesize:IR|4309 vti_backlinkinfo:VX| cgi-bin/extlib/HTTP/_vti_cnf/Response.pm0000644002157400001440000000030307771550070024766 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|22 Dec 2003 11:06:32 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|22 Dec 2003 11:06:32 -0000 vti_filesize:IR|9706 vti_backlinkinfo:VX| cgi-bin/extlib/HTTP/_vti_cnf/Status.pm0000644002157400001440000000030307771550070024453 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|22 Dec 2003 11:06:32 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|22 Dec 2003 11:06:32 -0000 vti_filesize:IR|6725 vti_backlinkinfo:VX| cgi-bin/extlib/HTTP/Cookies.pm0000644002157400001440000005161707771550070023013 0ustar minnesotaviolasociety.orgusers00000000000000package HTTP::Cookies; use strict; use HTTP::Date qw(str2time time2str); use HTTP::Headers::Util qw(split_header_words join_header_words); use LWP::Debug (); use vars qw($VERSION); $VERSION = sprintf("%d.%02d", q$Revision: 1.24 $ =~ /(\d+)\.(\d+)/); my $EPOCH_OFFSET = 0; # difference from Unix epoch if ($^O eq "MacOS") { require Time::Local; $EPOCH_OFFSET = Time::Local::timelocal(0,0,0,1,0,70); } =head1 NAME HTTP::Cookies - Cookie storage and management =head1 SYNOPSIS use HTTP::Cookies; $cookie_jar = HTTP::Cookies->new; $cookie_jar->add_cookie_header($request); $cookie_jar->extract_cookies($response); =head1 DESCRIPTION Cookies are a general mechanism which server side connections can use to both store and retrieve information on the client side of the connection. For more information about cookies refer to and . This module also implements the new style cookies described in I. The two variants of cookies are supposed to be able to coexist happily. Instances of the class I are able to store a collection of Set-Cookie2: and Set-Cookie: headers and are able to use this information to initialize Cookie-headers in I objects. The state of a I object can be saved in and restored from files. =head1 METHODS The following methods are provided: =over 4 =cut # A HTTP::Cookies object is a hash. The main attribute is the # COOKIES 3 level hash: $self->{COOKIES}{$domain}{$path}{$key}. =item $cookie_jar = HTTP::Cookies->new; The constructor takes hash style parameters. The following parameters are recognized: file: name of the file to restore cookies from and save cookies to autosave: save during destruction (bool) ignore_discard: save even cookies that are requested to be discarded (bool) hide_cookie2: don't add Cookie2 header to requests Future parameters might include (not yet implemented): max_cookies 300 max_cookies_per_domain 20 max_cookie_size 4096 no_cookies list of domain names that we never return cookies to =cut sub new { my $class = shift; my $self = bless { COOKIES => {}, }, $class; my %cnf = @_; for (keys %cnf) { $self->{lc($_)} = $cnf{$_}; } $self->load; $self; } =item $cookie_jar->add_cookie_header($request); The add_cookie_header() method will set the appropriate Cookie:-header for the I object given as argument. The $request must have a valid url attribute before this method is called. =cut sub add_cookie_header { my $self = shift; my $request = shift || return; my $url = $request->url; my $domain = _host($request, $url); $domain = "$domain.local" unless $domain =~ /\./; my $secure_request = ($url->scheme eq "https"); my $req_path = _url_path($url); my $req_port = $url->port; my $now = time(); _normalize_path($req_path) if $req_path =~ /%/; my @cval; # cookie values for the "Cookie" header my $set_ver; my $netscape_only = 0; # An exact domain match applies to any cookie while ($domain =~ /\./) { LWP::Debug::debug("Checking $domain for cookies"); my $cookies = $self->{COOKIES}{$domain}; next unless $cookies; # Want to add cookies corresponding to the most specific paths # first (i.e. longest path first) my $path; for $path (sort {length($b) <=> length($a) } keys %$cookies) { LWP::Debug::debug("- checking cookie path=$path"); if (index($req_path, $path) != 0) { LWP::Debug::debug(" path $path:$req_path does not fit"); next; } my($key,$array); while (($key,$array) = each %{$cookies->{$path}}) { my($version,$val,$port,$path_spec,$secure,$expires) = @$array; LWP::Debug::debug(" - checking cookie $key=$val"); if ($secure && !$secure_request) { LWP::Debug::debug(" not a secure requests"); next; } if ($expires && $expires < $now) { LWP::Debug::debug(" expired"); next; } if ($port) { my $found; if ($port =~ s/^_//) { # The correponding Set-Cookie attribute was empty $found++ if $port eq $req_port; $port = ""; } else { my $p; for $p (split(/,/, $port)) { $found++, last if $p eq $req_port; } } unless ($found) { LWP::Debug::debug(" port $port:$req_port does not fit"); next; } } if ($version > 0 && $netscape_only) { LWP::Debug::debug(" domain $domain applies to " . "Netscape-style cookies only"); next; } LWP::Debug::debug(" it's a match"); # set version number of cookie header. # XXX: What should it be if multiple matching # Set-Cookie headers have different versions themselves if (!$set_ver++) { if ($version >= 1) { push(@cval, "\$Version=$version"); } elsif (!$self->{hide_cookie2}) { $request->header(Cookie2 => '$Version="1"'); } } # do we need to quote the value if ($val =~ /\W/ && $version) { $val =~ s/([\\\"])/\\$1/g; $val = qq("$val"); } # and finally remember this cookie push(@cval, "$key=$val"); if ($version >= 1) { push(@cval, qq(\$Path="$path")) if $path_spec; push(@cval, qq(\$Domain="$domain")) if $domain =~ /^\./; if (defined $port) { my $p = '$Port'; $p .= qq(="$port") if length $port; push(@cval, $p); } } } } } continue { # Try with a more general domain, alternately stripping # leading name components and leading dots. When this # results in a domain with no leading dot, it is for # Netscape cookie compatibility only: # # a.b.c.net Any cookie # .b.c.net Any cookie # b.c.net Netscape cookie only # .c.net Any cookie if ($domain =~ s/^\.+//) { $netscape_only = 1; } else { $domain =~ s/[^.]*//; $netscape_only = 0; } } $request->header(Cookie => join("; ", @cval)) if @cval; $request; } =item $cookie_jar->extract_cookies($response); The extract_cookies() method will look for Set-Cookie: and Set-Cookie2: headers in the I object passed as argument. Any of these headers that are found are used to update the state of the $cookie_jar. =cut sub extract_cookies { my $self = shift; my $response = shift || return; my @set = split_header_words($response->_header("Set-Cookie2")); my @ns_set = $response->_header("Set-Cookie"); return $response unless @set || @ns_set; # quick exit my $request = $response->request; my $url = $request->url; my $req_host = _host($request, $url); $req_host = "$req_host.local" unless $req_host =~ /\./; my $req_port = $url->port; my $req_path = _url_path($url); _normalize_path($req_path) if $req_path =~ /%/; if (@ns_set) { # The old Netscape cookie format for Set-Cookie # http://www.netscape.com/newsref/std/cookie_spec.html # can for instance contain an unquoted "," in the expires # field, so we have to use this ad-hoc parser. my $now = time(); # Build a hash of cookies that was present in Set-Cookie2 # headers. We need to skip them if we also find them in a # Set-Cookie header. my %in_set2; for (@set) { $in_set2{$_->[0]}++; } my $set; for $set (@ns_set) { my @cur; my $param; my $expires; for $param (split(/;\s*/, $set)) { my($k,$v) = split(/\s*=\s*/, $param, 2); $v =~ s/\s+$//; #print "$k => $v\n"; my $lc = lc($k); if ($lc eq "expires") { my $etime = str2time($v); if ($etime) { push(@cur, "Max-Age" => str2time($v) - $now); $expires++; } } else { push(@cur, $k => $v); } } next if $in_set2{$cur[0]}; # push(@cur, "Port" => $req_port); push(@cur, "Discard" => undef) unless $expires; push(@cur, "Version" => 0); push(@cur, "ns-cookie" => 1); push(@set, \@cur); } } SET_COOKIE: for my $set (@set) { next unless @$set >= 2; my $key = shift @$set; my $val = shift @$set; LWP::Debug::debug("Set cookie $key => $val"); my %hash; while (@$set) { my $k = shift @$set; my $v = shift @$set; my $lc = lc($k); # don't loose case distinction for unknown fields $k = $lc if $lc =~ /^(?:discard|domain|max-age| path|port|secure|version)$/x; if ($k eq "discard" || $k eq "secure") { $v = 1 unless defined $v; } next if exists $hash{$k}; # only first value is signigicant $hash{$k} = $v; }; my %orig_hash = %hash; my $version = delete $hash{version}; $version = 1 unless defined($version); my $discard = delete $hash{discard}; my $secure = delete $hash{secure}; my $maxage = delete $hash{'max-age'}; my $ns_cookie = delete $hash{'ns-cookie'}; # Check domain my $domain = delete $hash{domain}; if (defined($domain) && $domain ne $req_host && $domain ne ".$req_host") { if ($domain !~ /\./ && $domain ne "local") { LWP::Debug::debug("Domain $domain contains no dot"); next SET_COOKIE; } $domain = ".$domain" unless $domain =~ /^\./; if ($domain =~ /\.\d+$/) { LWP::Debug::debug("IP-address $domain illeagal as domain"); next SET_COOKIE; } my $len = length($domain); unless (substr($req_host, -$len) eq $domain) { LWP::Debug::debug("Domain $domain does not match host $req_host"); next SET_COOKIE; } my $hostpre = substr($req_host, 0, length($req_host) - $len); if ($hostpre =~ /\./ && !$ns_cookie) { LWP::Debug::debug("Host prefix contain a dot: $hostpre => $domain"); next SET_COOKIE; } } else { $domain = $req_host; } my $path = delete $hash{path}; my $path_spec; if (defined $path && $path ne '') { $path_spec++; _normalize_path($path) if $path =~ /%/; if (!$ns_cookie && substr($req_path, 0, length($path)) ne $path) { LWP::Debug::debug("Path $path is not a prefix of $req_path"); next SET_COOKIE; } } else { $path = $req_path; $path =~ s,/[^/]*$,,; $path = "/" unless length($path); } my $port; if (exists $hash{port}) { $port = delete $hash{port}; if (defined $port) { $port =~ s/\s+//g; my $found; for my $p (split(/,/, $port)) { unless ($p =~ /^\d+$/) { LWP::Debug::debug("Bad port $port (not numeric)"); next SET_COOKIE; } $found++ if $p eq $req_port; } unless ($found) { LWP::Debug::debug("Request port ($req_port) not found in $port"); next SET_COOKIE; } } else { $port = "_$req_port"; } } $self->set_cookie($version,$key,$val,$path,$domain,$port,$path_spec,$secure,$maxage,$discard, \%hash) if $self->set_cookie_ok(\%orig_hash); } $response; } sub set_cookie_ok { 1 }; =item $cookie_jar->set_cookie($version, $key, $val, $path, $domain, $port, $path_spec, $secure, $maxage, $discard, \%rest) The set_cookie() method updates the state of the $cookie_jar. The $key, $val, $domain, $port and $path arguments are strings. The $path_spec, $secure, $discard arguments are boolean values. The $maxage value is a number indicating number of seconds that this cookie will live. A value <= 0 will delete this cookie. %rest defines various other attributes like "Comment" and "CommentURL". =cut sub set_cookie { my $self = shift; my($version, $key, $val, $path, $domain, $port, $path_spec, $secure, $maxage, $discard, $rest) = @_; # path and key can not be empty (key can't start with '$') return $self if !defined($path) || $path !~ m,^/, || !defined($key) || $key !~ m,[^\$],; # ensure legal port if (defined $port) { return $self unless $port =~ /^_?\d+(?:,\d+)*$/; } my $expires; if (defined $maxage) { if ($maxage <= 0) { delete $self->{COOKIES}{$domain}{$path}{$key}; return $self; } $expires = time() + $maxage; } $version = 0 unless defined $version; my @array = ($version, $val,$port, $path_spec, $secure, $expires, $discard); push(@array, {%$rest}) if defined($rest) && %$rest; # trim off undefined values at end pop(@array) while !defined $array[-1]; $self->{COOKIES}{$domain}{$path}{$key} = \@array; $self; } =item $cookie_jar->save( [$file] ); This method file saves the state of the $cookie_jar to a file. The state can then be restored later using the load() method. If a filename is not specified we will use the name specified during construction. If the attribute I is set, then we will even save cookies that are marked to be discarded. The default is to save a sequence of "Set-Cookie3" lines. "Set-Cookie3" is a proprietary LWP format, not known to be compatible with any browser. The I sub-class can be used to save in a format compatible with Netscape. =cut sub save { my $self = shift; my $file = shift || $self->{'file'} || return; local(*FILE); open(FILE, ">$file") or die "Can't open $file: $!"; print FILE "#LWP-Cookies-1.0\n"; print FILE $self->as_string(!$self->{ignore_discard}); close(FILE); 1; } =item $cookie_jar->load( [$file] ); This method reads the cookies from the file and adds them to the $cookie_jar. The file must be in the format written by the save() method. =cut sub load { my $self = shift; my $file = shift || $self->{'file'} || return; local(*FILE, $_); local $/ = "\n"; # make sure we got standard record separator open(FILE, $file) or return; my $magic = ; unless ($magic =~ /^\#LWP-Cookies-(\d+\.\d+)/) { warn "$file does not seem to contain cookies"; return; } while () { next unless s/^Set-Cookie3:\s*//; chomp; my $cookie; for $cookie (split_header_words($_)) { my($key,$val) = splice(@$cookie, 0, 2); my %hash; while (@$cookie) { my $k = shift @$cookie; my $v = shift @$cookie; $hash{$k} = $v; } my $version = delete $hash{version}; my $path = delete $hash{path}; my $domain = delete $hash{domain}; my $port = delete $hash{port}; my $expires = str2time(delete $hash{expires}); my $path_spec = exists $hash{path_spec}; delete $hash{path_spec}; my $secure = exists $hash{secure}; delete $hash{secure}; my $discard = exists $hash{discard}; delete $hash{discard}; my @array = ($version,$val,$port, $path_spec,$secure,$expires,$discard); push(@array, \%hash) if %hash; $self->{COOKIES}{$domain}{$path}{$key} = \@array; } } close(FILE); 1; } =item $cookie_jar->revert; This method empties the $cookie_jar and re-loads the $cookie_jar from the last save file. =cut sub revert { my $self = shift; $self->clear->load; $self; } =item $cookie_jar->clear( [$domain, [$path, [$key] ] ]); Invoking this method without arguments will empty the whole $cookie_jar. If given a single argument only cookies belonging to that domain will be removed. If given two arguments, cookies belonging to the specified path within that domain are removed. If given three arguments, then the cookie with the specified key, path and domain is removed. =cut sub clear { my $self = shift; if (@_ == 0) { $self->{COOKIES} = {}; } elsif (@_ == 1) { delete $self->{COOKIES}{$_[0]}; } elsif (@_ == 2) { delete $self->{COOKIES}{$_[0]}{$_[1]}; } elsif (@_ == 3) { delete $self->{COOKIES}{$_[0]}{$_[1]}{$_[2]}; } else { require Carp; Carp::carp('Usage: $c->clear([domain [,path [,key]]])'); } $self; } =item $cookie_jar->clear_temporary_cookies( ); Discard all temporary cookies. Scans for all cookies in the jar with either no expire field or a true C flag. To be called when the user agent shuts down according to RFC 2965. =cut sub clear_temporary_cookies { my($self) = @_; $self->scan(sub { if($_[9] or # "Discard" flag set not $_[8]) { # No expire field? $_[8] = -1; # Set the expire/max_age field $self->set_cookie(@_); # Clear the cookie } }); } sub DESTROY { my $self = shift; $self->save if $self->{'autosave'}; } =item $cookie_jar->scan( \&callback ); The argument is a subroutine that will be invoked for each cookie stored in the $cookie_jar. The subroutine will be invoked with the following arguments: 0 version 1 key 2 val 3 path 4 domain 5 port 6 path_spec 7 secure 8 expires 9 discard 10 hash =cut sub scan { my($self, $cb) = @_; my($domain,$path,$key); for $domain (sort keys %{$self->{COOKIES}}) { for $path (sort keys %{$self->{COOKIES}{$domain}}) { for $key (sort keys %{$self->{COOKIES}{$domain}{$path}}) { my($version,$val,$port,$path_spec, $secure,$expires,$discard,$rest) = @{$self->{COOKIES}{$domain}{$path}{$key}}; $rest = {} unless defined($rest); &$cb($version,$key,$val,$path,$domain,$port, $path_spec,$secure,$expires,$discard,$rest); } } } } =item $cookie_jar->as_string( [$skip_discard] ); The as_string() method will return the state of the $cookie_jar represented as a sequence of "Set-Cookie3" header lines separated by "\n". If $skip_discard is TRUE, it will not return lines for cookies with the I attribute. =cut sub as_string { my($self, $skip_discard) = @_; my @res; $self->scan(sub { my($version,$key,$val,$path,$domain,$port, $path_spec,$secure,$expires,$discard,$rest) = @_; return if $discard && $skip_discard; my @h = ($key, $val); push(@h, "path", $path); push(@h, "domain" => $domain); push(@h, "port" => $port) if defined $port; push(@h, "path_spec" => undef) if $path_spec; push(@h, "secure" => undef) if $secure; push(@h, "expires" => HTTP::Date::time2isoz($expires)) if $expires; push(@h, "discard" => undef) if $discard; my $k; for $k (sort keys %$rest) { push(@h, $k, $rest->{$k}); } push(@h, "version" => $version); push(@res, "Set-Cookie3: " . join_header_words(\@h)); }); join("\n", @res, ""); } sub _host { my($request, $url) = @_; if (my $h = $request->header("Host")) { $h =~ s/:\d+$//; # might have a port as well return $h; } return $url->host; } sub _url_path { my $url = shift; my $path; if($url->can('epath')) { $path = $url->epath; # URI::URL method } else { $path = $url->path; # URI::_generic method } $path = "/" unless length $path; $path; } sub _normalize_path # so that plain string compare can be used { my $x; $_[0] =~ s/%([0-9a-fA-F][0-9a-fA-F])/ $x = uc($1); $x eq "2F" || $x eq "25" ? "%$x" : pack("C", hex($x)); /eg; $_[0] =~ s/([\0-\x20\x7f-\xff])/sprintf("%%%02X",ord($1))/eg; } =back =head1 SUB CLASSES We also provide a subclass called I which loads and saves Netscape compatible cookie files. You should be able to have LWP share Netscape's cookies by constructing your $cookie_jar like this: $cookie_jar = HTTP::Cookies::Netscape->new( File => "$ENV{HOME}/.netscape/cookies", AutoSave => 1, ); Please note that the Netscape cookie file format is not able to store all the information available in the Set-Cookie2 headers, so you will probably loose some information if you save in this format. =cut package HTTP::Cookies::Netscape; use vars qw(@ISA); @ISA=qw(HTTP::Cookies); sub load { my($self, $file) = @_; $file ||= $self->{'file'} || return; local(*FILE, $_); local $/ = "\n"; # make sure we got standard record separator my @cookies; open(FILE, $file) || return; my $magic = ; unless ($magic =~ /^\# Netscape HTTP Cookie File/) { warn "$file does not look like a netscape cookies file" if $^W; close(FILE); return; } my $now = time() - $EPOCH_OFFSET; while () { next if /^\s*\#/; next if /^\s*$/; chomp; my($domain,$bool1,$path,$secure, $expires,$key,$val) = split(/\t/, $_); $secure = ($secure eq "TRUE"); $self->set_cookie(undef,$key,$val,$path,$domain,undef, 0,$secure,$expires-$now, 0); } close(FILE); 1; } sub save { my($self, $file) = @_; $file ||= $self->{'file'} || return; local(*FILE, $_); open(FILE, ">$file") || return; print FILE <scan(sub { my($version,$key,$val,$path,$domain,$port, $path_spec,$secure,$expires,$discard,$rest) = @_; return if $discard && !$self->{ignore_discard}; $expires = $expires ? $expires - $EPOCH_OFFSET : 0; return if $now > $expires; $secure = $secure ? "TRUE" : "FALSE"; my $bool = $domain =~ /^\./ ? "TRUE" : "FALSE"; print FILE join("\t", $domain, $bool, $path, $secure, $expires, $key, $val), "\n"; }); close(FILE); 1; } 1; __END__ =head1 COPYRIGHT Copyright 1997-1999 Gisle Aas This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut cgi-bin/extlib/HTTP/Daemon.pm0000644002157400001440000005211107771550070022610 0ustar minnesotaviolasociety.orgusers00000000000000# $Id: Daemon.pm,v 1.25 2001/08/07 19:32:40 gisle Exp $ # use strict; package HTTP::Daemon; =head1 NAME HTTP::Daemon - a simple http server class =head1 SYNOPSIS use HTTP::Daemon; use HTTP::Status; my $d = HTTP::Daemon->new || die; print "Please contact me at: url, ">\n"; while (my $c = $d->accept) { while (my $r = $c->get_request) { if ($r->method eq 'GET' and $r->url->path eq "/xyzzy") { # remember, this is *not* recommened practice :-) $c->send_file_response("/etc/passwd"); } else { $c->send_error(RC_FORBIDDEN) } } $c->close; undef($c); } =head1 DESCRIPTION Instances of the I class are HTTP/1.1 servers that listen on a socket for incoming requests. The I is a sub-class of I, so you can perform socket operations directly on it too. The accept() method will return when a connection from a client is available. In a scalar context the returned value will be a reference to a object of the I class which is another I subclass. In a list context a two-element array is returned containing the new I reference and the peer address; the list will be empty upon failure. Calling the get_request() method on the I object will read data from the client and return an I object reference. This HTTP daemon does not fork(2) for you. Your application, i.e. the user of the I is reponsible for forking if that is desirable. Also note that the user is responsible for generating responses that conform to the HTTP/1.1 protocol. The I class provides some methods that make this easier. =head1 METHODS The following is a list of methods that are new (or enhanced) relative to the I base class. =over 4 =cut use vars qw($VERSION @ISA $PROTO $DEBUG); $VERSION = sprintf("%d.%02d", q$Revision: 1.25 $ =~ /(\d+)\.(\d+)/); use IO::Socket qw(AF_INET INADDR_ANY inet_ntoa); @ISA=qw(IO::Socket::INET); $PROTO = "HTTP/1.1"; =item $d = new HTTP::Daemon The constructor takes the same parameters as the I constructor. It can also be called without specifying any parameters. The daemon will then set up a listen queue of 5 connections and allocate some random port number. A server that wants to bind to some specific address on the standard HTTP port will be constructed like this: $d = new HTTP::Daemon LocalAddr => 'www.someplace.com', LocalPort => 80; =cut sub new { my($class, %args) = @_; $args{Listen} ||= 5; $args{Proto} ||= 'tcp'; return $class->SUPER::new(%args); } =item $c = $d->accept([$pkg]) This method is the same as I but returns an I reference by default. It returns undef if you specify a timeout and no connection is made within that time. In a scalar context the returned value will be a reference to a object of the I class which is another I subclass. In a list context a two-element array is returned containing the new I reference and the peer address; the list will be empty upon failure. =cut sub accept { my $self = shift; my $pkg = shift || "HTTP::Daemon::ClientConn"; my ($sock, $peer) = $self->SUPER::accept($pkg); if ($sock) { ${*$sock}{'httpd_daemon'} = $self; return wantarray ? ($sock, $peer) : $sock; } else { return; } } =item $d->url Returns a URL string that can be used to access the server root. =cut sub url { my $self = shift; my $url = "http://"; my $addr = $self->sockaddr; if ($addr eq INADDR_ANY) { require Sys::Hostname; $url .= lc Sys::Hostname::hostname(); } else { $url .= gethostbyaddr($addr, AF_INET) || inet_ntoa($addr); } my $port = $self->sockport; $url .= ":$port" if $port != 80; $url .= "/"; $url; } =item $d->product_tokens Returns the name that this server will use to identify itself. This is the string that is sent with the I response header. The main reason to have this method is that subclasses can override it if they want to use another product name. =cut sub product_tokens { "libwww-perl-daemon/$HTTP::Daemon::VERSION"; } package HTTP::Daemon::ClientConn; use vars qw(@ISA $DEBUG); use IO::Socket (); @ISA=qw(IO::Socket::INET); *DEBUG = \$HTTP::Daemon::DEBUG; use HTTP::Request (); use HTTP::Response (); use HTTP::Status; use HTTP::Date qw(time2str); use LWP::MediaTypes qw(guess_media_type); use Carp (); my $CRLF = "\015\012"; # "\r\n" is not portable my $HTTP_1_0 = _http_version("HTTP/1.0"); my $HTTP_1_1 = _http_version("HTTP/1.1"); =back The I is also a I subclass. Instances of this class are returned by the accept() method of I. The following additional methods are provided: =over 4 =item $c->get_request([$headers_only]) Read data from the client and turn it into an I object which is then returned. It returns C if reading of the request fails. If it fails, then the I object ($c) should be discarded, and you should not call this method again. The $c->reason method might give you some information about why $c->get_request returned C. The $c->get_request method supports HTTP/1.1 request content bodies, including I transfer encoding with footer and self delimiting I content types. The $c->get_request method will normally not return until the whole request has been received from the client. This might not be what you want if the request is an upload of a multi-mega-byte file (and with chunked transfer encoding HTTP can even support infinite request messages - uploading live audio for instance). If you pass a TRUE value as the $headers_only argument, then $c->get_request will return immediately after parsing the request headers and you are responsible for reading the rest of the request content. If you are going to call $c->get_request again on the same connection you better read the correct number of bytes. =cut sub get_request { my($self, $only_headers) = @_; if (${*$self}{'httpd_nomore'}) { $self->reason("No more requests from this connection"); return; } $self->reason(""); my $buf = ${*$self}{'httpd_rbuf'}; $buf = "" unless defined $buf; my $timeout = $ {*$self}{'io_socket_timeout'}; my $fdset = ""; vec($fdset, $self->fileno, 1) = 1; local($_); READ_HEADER: while (1) { # loop until we have the whole header in $buf $buf =~ s/^(?:\015?\012)+//; # ignore leading blank lines if ($buf =~ /\012/) { # potential, has at least one line if ($buf =~ /^\w+[^\012]+HTTP\/\d+\.\d+\015?\012/) { if ($buf =~ /\015?\012\015?\012/) { last READ_HEADER; # we have it } elsif (length($buf) > 16*1024) { $self->send_error(413); # REQUEST_ENTITY_TOO_LARGE $self->reason("Very long header"); return; } } else { last READ_HEADER; # HTTP/0.9 client } } elsif (length($buf) > 16*1024) { $self->send_error(414); # REQUEST_URI_TOO_LARGE $self->reason("Very long first line"); return; } print STDERR "Need more data for complete header\n" if $DEBUG; return unless $self->_need_more($buf, $timeout, $fdset); } if ($buf !~ s/^(\S+)[ \t]+(\S+)(?:[ \t]+(HTTP\/\d+\.\d+))?[^\012]*\012//) { ${*$self}{'httpd_client_proto'} = _http_version("HTTP/1.0"); $self->send_error(400); # BAD_REQUEST $self->reason("Bad request line: $buf"); return; } my $method = $1; my $uri = $2; my $proto = $3 || "HTTP/0.9"; $uri = "http://$uri" if $method eq "CONNECT"; $uri = $HTTP::URI_CLASS->new($uri, $self->daemon->url); my $r = HTTP::Request->new($method, $uri); $r->protocol($proto); ${*$self}{'httpd_client_proto'} = $proto = _http_version($proto); if ($proto >= $HTTP_1_0) { # we expect to find some headers my($key, $val); HEADER: while ($buf =~ s/^([^\012]*)\012//) { $_ = $1; s/\015$//; if (/^([\w\-]+)\s*:\s*(.*)/) { $r->push_header($key, $val) if $key; ($key, $val) = ($1, $2); } elsif (/^\s+(.*)/) { $val .= " $1"; } else { last HEADER; } } $r->push_header($key, $val) if $key; } my $conn = $r->header('Connection'); if ($proto >= $HTTP_1_1) { ${*$self}{'httpd_nomore'}++ if $conn && lc($conn) =~ /\bclose\b/; } else { ${*$self}{'httpd_nomore'}++ unless $conn && lc($conn) =~ /\bkeep-alive\b/; } if ($only_headers) { ${*$self}{'httpd_rbuf'} = $buf; return $r; } # Find out how much content to read my $te = $r->header('Transfer-Encoding'); my $ct = $r->header('Content-Type'); my $len = $r->header('Content-Length'); if ($te && lc($te) eq 'chunked') { # Handle chunked transfer encoding my $body = ""; CHUNK: while (1) { print STDERR "Chunked\n" if $DEBUG; if ($buf =~ s/^([^\012]*)\012//) { my $chunk_head = $1; unless ($chunk_head =~ /^([0-9A-Fa-f]+)/) { $self->send_error(400); $self->reason("Bad chunk header $chunk_head"); return; } my $size = hex($1); last CHUNK if $size == 0; my $missing = $size - length($buf) + 2; # 2=CRLF at chunk end # must read until we have a complete chunk while ($missing > 0) { print STDERR "Need $missing more bytes\n" if $DEBUG; my $n = $self->_need_more($buf, $timeout, $fdset); return unless $n; $missing -= $n; } $body .= substr($buf, 0, $size); substr($buf, 0, $size+2) = ''; } else { # need more data in order to have a complete chunk header return unless $self->_need_more($buf, $timeout, $fdset); } } $r->content($body); # pretend it was a normal entity body $r->remove_header('Transfer-Encoding'); $r->header('Content-Length', length($body)); my($key, $val); FOOTER: while (1) { if ($buf !~ /\012/) { # need at least one line to look at return unless $self->_need_more($buf, $timeout, $fdset); } else { $buf =~ s/^([^\012]*)\012//; $_ = $1; s/\015$//; if (/^([\w\-]+)\s*:\s*(.*)/) { $r->push_header($key, $val) if $key; ($key, $val) = ($1, $2); } elsif (/^\s+(.*)/) { $val .= " $1"; } elsif (!length) { last FOOTER; } else { $self->reason("Bad footer syntax"); return; } } } $r->push_header($key, $val) if $key; } elsif ($te) { $self->send_error(501); # Unknown transfer encoding $self->reason("Unknown transfer encoding '$te'"); return; } elsif ($ct && lc($ct) =~ m/^multipart\/\w+\s*;.*boundary\s*=\s*(\w+)/) { # Handle multipart content type my $boundary = "$CRLF--$1--$CRLF"; my $index; while (1) { $index = index($buf, $boundary); last if $index >= 0; # end marker not yet found return unless $self->_need_more($buf, $timeout, $fdset); } $index += length($boundary); $r->content(substr($buf, 0, $index)); substr($buf, 0, $index) = ''; } elsif ($len) { # Plain body specified by "Content-Length" my $missing = $len - length($buf); while ($missing > 0) { print "Need $missing more bytes of content\n" if $DEBUG; my $n = $self->_need_more($buf, $timeout, $fdset); return unless $n; $missing -= $n; } if (length($buf) > $len) { $r->content(substr($buf,0,$len)); substr($buf, 0, $len) = ''; } else { $r->content($buf); $buf=''; } } ${*$self}{'httpd_rbuf'} = $buf; $r; } sub _need_more { my $self = shift; #my($buf,$timeout,$fdset) = @_; if ($_[1]) { my($timeout, $fdset) = @_[1,2]; print STDERR "select(,,,$timeout)\n" if $DEBUG; my $n = select($fdset,undef,undef,$timeout); unless ($n) { $self->reason(defined($n) ? "Timeout" : "select: $!"); return; } } print STDERR "sysread()\n" if $DEBUG; my $n = sysread($self, $_[0], 2048, length($_[0])); $self->reason(defined($n) ? "Client closed" : "sysread: $!") unless $n; $n; } =item $c->read_buffer([$new_value]) Bytes read by $c->get_request, but not used are placed in the I. The next time $c->get_request is called it will consume the bytes in this buffer before reading more data from the network connection itself. The read buffer is invalid after $c->get_request has returned an undefined value. If you handle the reading of the request content yourself you need to empty this buffer before you read more and you need to place unconsumed bytes here. You also need this buffer if you implement services like I<101 Switching Protocols>. This method always return the old buffer content and can optionally replace the buffer content if you pass it an argument. =cut sub read_buffer { my $self = shift; my $old = ${*$self}{'httpd_rbuf'}; if (@_) { ${*$self}{'httpd_rbuf'} = shift; } $old; } =item $c->reason When $c->get_request returns C you can obtain a short string describing why it happened by calling $c->reason. =cut sub reason { my $self = shift; my $old = ${*$self}{'httpd_reason'}; if (@_) { ${*$self}{'httpd_reason'} = shift; } $old; } =item $c->proto_ge($proto) Return TRUE if the client announced a protocol with version number greater or equal to the given argument. The $proto argument can be a string like "HTTP/1.1" or just "1.1". =cut sub proto_ge { my $self = shift; ${*$self}{'httpd_client_proto'} >= _http_version(shift); } sub _http_version { local($_) = shift; return 0 unless m,^(?:HTTP/)?(\d+)\.(\d+)$,i; $1 * 1000 + $2; } =item $c->antique_client Return TRUE if the client speaks the HTTP/0.9 protocol. No status code and no headers should be returned to such a client. This should be the same as !$c->proto_ge("HTTP/1.0"). =cut sub antique_client { my $self = shift; ${*$self}{'httpd_client_proto'} < $HTTP_1_0; } =item $c->force_last_request Make sure that $c->get_request will not try to read more requests off this connection. If you generate a response that is not self delimiting, then you should signal this fact by calling this method. This attribute is turned on automatically if the client announces protocol HTTP/1.0 or worse and does not include a "Connection: Keep-Alive" header. It is also turned on automatically when HTTP/1.1 or better clients send the "Connection: close" request header. =cut sub force_last_request { my $self = shift; ${*$self}{'httpd_nomore'}++; } =item $c->send_status_line( [$code, [$mess, [$proto]]] ) Send the status line back to the client. If $code is omitted 200 is assumed. If $mess is omitted, then a message corresponding to $code is inserted. If $proto is missing the content of the $HTTP::Daemon::PROTO variable is used. =cut sub send_status_line { my($self, $status, $message, $proto) = @_; return if $self->antique_client; $status ||= RC_OK; $message ||= status_message($status) || ""; $proto ||= $HTTP::Daemon::PROTO || "HTTP/1.1"; print $self "$proto $status $message$CRLF"; } =item $c->send_crlf Send the CRLF sequence to the client. =cut sub send_crlf { my $self = shift; print $self $CRLF; } =item $c->send_basic_header( [$code, [$mess, [$proto]]] ) Send the status line and the "Date:" and "Server:" headers back to the client. This header is assumed to be continued and does not end with an empty CRLF line. =cut sub send_basic_header { my $self = shift; return if $self->antique_client; $self->send_status_line(@_); print $self "Date: ", time2str(time), $CRLF; my $product = $self->daemon->product_tokens; print $self "Server: $product$CRLF" if $product; } =item $c->send_response( [$res] ) Write a I object to the client as a response. We try hard to make sure that the response is self delimiting so that the connection can stay persistent for further request/response exchanges. The content attribute of the I object can be a normal string or a subroutine reference. If it is a subroutine, then whatever this callback routine returns is written back to the client as the response content. The routine will be called until it return an undefined or empty value. If the client is HTTP/1.1 aware then we will use chunked transfer encoding for the response. =cut sub send_response { my $self = shift; my $res = shift; if (!ref $res) { $res ||= RC_OK; $res = HTTP::Response->new($res, @_); } my $content = $res->content; my $chunked; unless ($self->antique_client) { my $code = $res->code; $self->send_basic_header($code, $res->message, $res->protocol); if ($code =~ /^(1\d\d|[23]04)$/) { # make sure content is empty $res->remove_header("Content-Length"); $content = ""; } elsif ($res->request && $res->request->method eq "HEAD") { # probably OK } elsif (ref($content) eq "CODE") { if ($self->proto_ge("HTTP/1.1")) { $res->push_header("Transfer-Encoding" => "chunked"); $chunked++; } else { $self->force_last_request; } } elsif (length($content)) { $res->header("Content-Length" => length($content)); } else { $self->force_last_request; } print $self $res->headers_as_string($CRLF); print $self $CRLF; # separates headers and content } if (ref($content) eq "CODE") { while (1) { my $chunk = &$content(); last unless defined($chunk) && length($chunk); if ($chunked) { printf $self "%x%s%s%s", length($chunk), $CRLF, $chunk, $CRLF; } else { print $self $chunk; } } print $self "0$CRLF$CRLF" if $chunked; # no trailers either } elsif (length $content) { print $self $content; } } =item $c->send_redirect( $loc, [$code, [$entity_body]] ) Send a redirect response back to the client. The location ($loc) can be an absolute or relative URL. The $code must be one the redirect status codes, and defaults to "301 Moved Permanently" =cut sub send_redirect { my($self, $loc, $status, $content) = @_; $status ||= RC_MOVED_PERMANENTLY; Carp::croak("Status '$status' is not redirect") unless is_redirect($status); $self->send_basic_header($status); my $base = $self->daemon->url; $loc = $HTTP::URI_CLASS->new($loc, $base) unless ref($loc); $loc = $loc->abs($base); print $self "Location: $loc$CRLF"; if ($content) { my $ct = $content =~ /^\s*force_last_request; # no use keeping the connection open } =item $c->send_error( [$code, [$error_message]] ) Send an error response back to the client. If the $code is missing a "Bad Request" error is reported. The $error_message is a string that is incorporated in the body of the HTML entity body. =cut sub send_error { my($self, $status, $error) = @_; $status ||= RC_BAD_REQUEST; Carp::croak("Status '$status' is not an error") unless is_error($status); my $mess = status_message($status); $error ||= ""; $mess = <$status $mess

$status $mess

$error EOT unless ($self->antique_client) { $self->send_basic_header($status); print $self "Content-Type: text/html$CRLF"; print $self "Content-Length: " . length($mess) . $CRLF; print $self $CRLF; } print $self $mess; $status; } =item $c->send_file_response($filename) Send back a response with the specified $filename as content. If the file is a directory we try to generate an HTML index of it. =cut sub send_file_response { my($self, $file) = @_; if (-d $file) { $self->send_dir($file); } elsif (-f _) { # plain file local(*F); sysopen(F, $file, 0) or return $self->send_error(RC_FORBIDDEN); binmode(F); my($ct,$ce) = guess_media_type($file); my($size,$mtime) = (stat _)[7,9]; unless ($self->antique_client) { $self->send_basic_header; print $self "Content-Type: $ct$CRLF"; print $self "Content-Encoding: $ce$CRLF" if $ce; print $self "Content-Length: $size$CRLF" if $size; print $self "Last-Modified: ", time2str($mtime), "$CRLF" if $mtime; print $self $CRLF; } $self->send_file(\*F); return RC_OK; } else { $self->send_error(RC_NOT_FOUND); } } sub send_dir { my($self, $dir) = @_; $self->send_error(RC_NOT_FOUND) unless -d $dir; $self->send_error(RC_NOT_IMPLEMENTED); } =item $c->send_file($fd); Copy the file to the client. The file can be a string (which will be interpreted as a filename) or a reference to an I or glob. =cut sub send_file { my($self, $file) = @_; my $opened = 0; if (!ref($file)) { local(*F); open(F, $file) || return undef; binmode(F); $file = \*F; $opened++; } my $cnt = 0; my $buf = ""; my $n; while ($n = sysread($file, $buf, 8*1024)) { last if !$n; $cnt += $n; print $self $buf; } close($file) if $opened; $cnt; } =item $c->daemon Return a reference to the corresponding I object. =cut sub daemon { my $self = shift; ${*$self}{'httpd_daemon'}; } =back =head1 SEE ALSO RFC 2068 L, L =head1 COPYRIGHT Copyright 1996-2001, Gisle Aas This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut 1; cgi-bin/extlib/HTTP/Date.pm0000644002157400001440000002454007771550070022267 0ustar minnesotaviolasociety.orgusers00000000000000package HTTP::Date; # $Date: 2001/01/04 20:27:15 $ $VERSION = sprintf("%d.%02d", q$Revision: 1.43 $ =~ /(\d+)\.(\d+)/); require 5.004; require Exporter; @ISA = qw(Exporter); @EXPORT = qw(time2str str2time); @EXPORT_OK = qw(parse_date time2iso time2isoz); use strict; require Time::Local; use vars qw(@DoW @MoY %MoY); @DoW = qw(Sun Mon Tue Wed Thu Fri Sat); @MoY = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); @MoY{@MoY} = (1..12); my %GMT_ZONE = (GMT => 1, UTC => 1, UT => 1, Z => 1); sub time2str (;$) { my $time = shift; $time = time unless defined $time; my ($sec, $min, $hour, $mday, $mon, $year, $wday) = gmtime($time); sprintf("%s, %02d %s %04d %02d:%02d:%02d GMT", $DoW[$wday], $mday, $MoY[$mon], $year+1900, $hour, $min, $sec); } sub str2time ($;$) { my $str = shift; return undef unless defined $str; # fast exit for strictly conforming string if ($str =~ /^[SMTWF][a-z][a-z], (\d\d) ([JFMAJSOND][a-z][a-z]) (\d\d\d\d) (\d\d):(\d\d):(\d\d) GMT$/) { return eval { my $t = Time::Local::timegm($6, $5, $4, $1, $MoY{$2}-1, $3-1900); $t < 0 ? undef : $t; }; } my @d = parse_date($str); return undef unless @d; $d[0] -= 1900; # year $d[1]--; # month my $tz = pop(@d); unless (defined $tz) { unless (defined($tz = shift)) { return eval { my $t = Time::Local::timelocal(reverse @d); $t < 0 ? undef : $t; }; } } my $offset = 0; if ($GMT_ZONE{uc $tz}) { # offset already zero } elsif ($tz =~ /^([-+])?(\d\d?):?(\d\d)?$/) { $offset = 3600 * $2; $offset += 60 * $3 if $3; $offset *= -1 if $1 && $1 eq '-'; } else { eval { require Time::Zone } || return undef; $offset = Time::Zone::tz_offset($tz); return undef unless defined $offset; } return eval { my $t = Time::Local::timegm(reverse @d); $t < 0 ? undef : $t - $offset; }; } sub parse_date ($) { local($_) = shift; return unless defined; # More lax parsing below s/^\s+//; # kill leading space s/^(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat)[a-z]*,?\s*//i; # Useless weekday my($day, $mon, $yr, $hr, $min, $sec, $tz, $ampm); # Then we are able to check for most of the formats with this regexp (($day,$mon,$yr,$hr,$min,$sec,$tz) = /^ (\d\d?) # day (?:\s+|[-\/]) (\w+) # month (?:\s+|[-\/]) (\d+) # year (?: (?:\s+|:) # separator before clock (\d\d?):(\d\d) # hour:min (?::(\d\d))? # optional seconds )? # optional clock \s* ([-+]?\d{2,4}|(?![APap][Mm]\b)[A-Za-z]+)? # timezone \s* (?:\(\w+\))? # ASCII representation of timezone in parens. \s*$ /x) || # Try the ctime and asctime format (($mon, $day, $hr, $min, $sec, $tz, $yr) = /^ (\w{1,3}) # month \s+ (\d\d?) # day \s+ (\d\d?):(\d\d) # hour:min (?::(\d\d))? # optional seconds \s+ (?:([A-Za-z]+)\s+)? # optional timezone (\d+) # year \s*$ # allow trailing whitespace /x) || # Then the Unix 'ls -l' date format (($mon, $day, $yr, $hr, $min, $sec) = /^ (\w{3}) # month \s+ (\d\d?) # day \s+ (?: (\d\d\d\d) | # year (\d{1,2}):(\d{2}) # hour:min (?::(\d\d))? # optional seconds ) \s*$ /x) || # ISO 8601 format '1996-02-29 12:00:00 -0100' and variants (($yr, $mon, $day, $hr, $min, $sec, $tz) = /^ (\d{4}) # year [-\/]? (\d\d?) # numerical month [-\/]? (\d\d?) # day (?: (?:\s+|[-:Tt]) # separator before clock (\d\d?):?(\d\d) # hour:min (?::?(\d\d(?:\.\d*)?))? # optional seconds (and fractional) )? # optional clock \s* ([-+]?\d\d?:?(:?\d\d)? |Z|z)? # timezone (Z is "zero meridian", i.e. GMT) \s*$ /x) || # Windows 'dir' 11-12-96 03:52PM (($mon, $day, $yr, $hr, $min, $ampm) = /^ (\d{2}) # numerical month - (\d{2}) # day - (\d{2}) # year \s+ (\d\d?):(\d\d)([APap][Mm]) # hour:min AM or PM \s*$ /x) || return; # unrecognized format # Translate month name to number $mon = $MoY{$mon} || $MoY{"\u\L$mon"} || ($mon >= 1 && $mon <= 12 && int($mon)) || return; # If the year is missing, we assume first date before the current, # because of the formats we support such dates are mostly present # on "ls -l" listings. unless (defined $yr) { my $cur_mon; ($cur_mon, $yr) = (localtime)[4, 5]; $yr += 1900; $cur_mon++; $yr-- if $mon > $cur_mon; } elsif (length($yr) < 3) { # Find "obvious" year my $cur_yr = (localtime)[5] + 1900; my $m = $cur_yr % 100; my $tmp = $yr; $yr += $cur_yr - $m; $m -= $tmp; $yr += ($m > 0) ? 100 : -100 if abs($m) > 50; } # Make sure clock elements are defined $hr = 0 unless defined($hr); $min = 0 unless defined($min); $sec = 0 unless defined($sec); # Compensate for AM/PM if ($ampm) { $ampm = uc $ampm; $hr = 0 if $hr == 12 && $ampm eq 'AM'; $hr += 12 if $ampm eq 'PM' && $hr != 12; } return($yr, $mon, $day, $hr, $min, $sec, $tz) if wantarray; if (defined $tz) { $tz = "Z" if $tz =~ /^(GMT|UTC?|[-+]?0+)$/; } else { $tz = ""; } return sprintf("%04d-%02d-%02d %02d:%02d:%02d%s", $yr, $mon, $day, $hr, $min, $sec, $tz); } sub time2iso (;$) { my $time = shift; $time = time unless defined $time; my($sec,$min,$hour,$mday,$mon,$year) = localtime($time); sprintf("%04d-%02d-%02d %02d:%02d:%02d", $year+1900, $mon+1, $mday, $hour, $min, $sec); } sub time2isoz (;$) { my $time = shift; $time = time unless defined $time; my($sec,$min,$hour,$mday,$mon,$year) = gmtime($time); sprintf("%04d-%02d-%02d %02d:%02d:%02dZ", $year+1900, $mon+1, $mday, $hour, $min, $sec); } 1; __END__ =head1 NAME HTTP::Date - date conversion routines =head1 SYNOPSIS use HTTP::Date; $string = time2str($time); # Format as GMT ASCII time $time = str2time($string); # convert ASCII date to machine time =head1 DESCRIPTION This module provides functions that deal the date formats used by the HTTP protocol (and then some more). Only the first two functions, time2str() and str2time(), are exported by default. =over 4 =item time2str( [$time] ) The time2str() function converts a machine time (seconds since epoch) to a string. If the function is called without an argument, it will use the current time. The string returned is in the format preferred for the HTTP protocol. This is a fixed length subset of the format defined by RFC 1123, represented in Universal Time (GMT). An example of a time stamp in this format is: Sun, 06 Nov 1994 08:49:37 GMT =item str2time( $str [, $zone] ) The str2time() function converts a string to machine time. It returns C if the format of $str is unrecognized, or the time is outside the representable range. The time formats recognized are the same as for parse_date(). The function also takes an optional second argument that specifies the default time zone to use when converting the date. This parameter is ignored if the zone is found in the date string itself. If this parameter is missing, and the date string format does not contain any zone specification, then the local time zone is assumed. If the zone is not "C" or numerical (like "C<-0800>" or "C<+0100>"), then the C module must be installed in order to get the date recognized. =item parse_date( $str ) This function will try to parse a date string, and then return it as a list of numerical values followed by a (possible undefined) time zone specifier; ($year, $month, $day, $hour, $min, $sec, $tz). The $year returned will B have the number 1900 subtracted from it and the $month numbers start with 1. In scalar context the numbers are interpolated in a string of the "YYYY-MM-DD hh:mm:ss TZ"-format and returned. If the date is unrecognized, then the empty list is returned. The function is able to parse the following formats: "Wed, 09 Feb 1994 22:23:32 GMT" -- HTTP format "Thu Feb 3 17:03:55 GMT 1994" -- ctime(3) format "Thu Feb 3 00:00:00 1994", -- ANSI C asctime() format "Tuesday, 08-Feb-94 14:15:29 GMT" -- old rfc850 HTTP format "Tuesday, 08-Feb-1994 14:15:29 GMT" -- broken rfc850 HTTP format "03/Feb/1994:17:03:55 -0700" -- common logfile format "09 Feb 1994 22:23:32 GMT" -- HTTP format (no weekday) "08-Feb-94 14:15:29 GMT" -- rfc850 format (no weekday) "08-Feb-1994 14:15:29 GMT" -- broken rfc850 format (no weekday) "1994-02-03 14:15:29 -0100" -- ISO 8601 format "1994-02-03 14:15:29" -- zone is optional "1994-02-03" -- only date "1994-02-03T14:15:29" -- Use T as separator "19940203T141529Z" -- ISO 8601 compact format "19940203" -- only date "08-Feb-94" -- old rfc850 HTTP format (no weekday, no time) "08-Feb-1994" -- broken rfc850 HTTP format (no weekday, no time) "09 Feb 1994" -- proposed new HTTP format (no weekday, no time) "03/Feb/1994" -- common logfile format (no time, no offset) "Feb 3 1994" -- Unix 'ls -l' format "Feb 3 17:03" -- Unix 'ls -l' format "11-15-96 03:52PM" -- Windows 'dir' format The parser ignores leading and trailing whitespace. It also allow the seconds to be missing and the month to be numerical in most formats. If the year is missing, then we assume that the date is the first matching date I current month. If the year is given with only 2 digits, then parse_date() will select the century that makes the year closest to the current date. =item time2iso( [$time] ) Same as time2str(), but returns a "YYYY-MM-DD hh:mm:ss"-formatted string representing time in the local time zone. =item time2isoz( [$time] ) Same as time2str(), but returns a "YYYY-MM-DD hh:mm:ssZ"-formatted string representing Universal Time. =back =head1 SEE ALSO L, L =head1 COPYRIGHT Copyright 1995-1999, Gisle Aas This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut cgi-bin/extlib/HTTP/Headers.pm0000644002157400001440000004307107771550070022765 0ustar minnesotaviolasociety.orgusers00000000000000package HTTP::Headers; # $Id: Headers.pm,v 1.43 2001/11/15 06:19:22 gisle Exp $ =head1 NAME HTTP::Headers - Class encapsulating HTTP Message headers =head1 SYNOPSIS require HTTP::Headers; $h = HTTP::Headers->new; $h->header('Content-Type' => 'text/plain'); # set $ct = $h->header('Content-Type'); # get $h->remove_header('Content-Type'); # delete =head1 DESCRIPTION The C class encapsulates HTTP-style message headers. The headers consist of attribute-value pairs also called fields, which may be repeated, and which are printed in a particular order. Instances of this class are usually created as member variables of the C and C classes, internal to the library. The following methods are available: =over 4 =cut use strict; use Carp (); use vars qw($VERSION $TRANSLATE_UNDERSCORE); $VERSION = sprintf("%d.%02d", q$Revision: 1.43 $ =~ /(\d+)\.(\d+)/); # The $TRANSLATE_UNDERSCORE variable controls whether '_' can be used # as a replacement for '-' in header field names. $TRANSLATE_UNDERSCORE = 1 unless defined $TRANSLATE_UNDERSCORE; # "Good Practice" order of HTTP message headers: # - General-Headers # - Request-Headers # - Response-Headers # - Entity-Headers my @header_order = qw( Cache-Control Connection Date Pragma Trailer Transfer-Encoding Upgrade Via Warning Accept Accept-Charset Accept-Encoding Accept-Language Authorization Expect From Host If-Match If-Modified-Since If-None-Match If-Range If-Unmodified-Since Max-Forwards Proxy-Authorization Range Referer TE User-Agent Accept-Ranges Age ETag Location Proxy-Authenticate Retry-After Server Vary WWW-Authenticate Allow Content-Encoding Content-Language Content-Length Content-Location Content-MD5 Content-Range Content-Type Expires Last-Modified ); # Make alternative representations of @header_order. This is used # for sorting and case matching. my %header_order; my %standard_case; { my $i = 0; for (@header_order) { my $lc = lc $_; $header_order{$lc} = ++$i; $standard_case{$lc} = $_; } } =item $h = HTTP::Headers->new Constructs a new C object. You might pass some initial attribute-value pairs as parameters to the constructor. I: $h = HTTP::Headers->new( Date => 'Thu, 03 Feb 1994 00:00:00 GMT', Content_Type => 'text/html; version=3.2', Content_Base => 'http://www.perl.org/'); The constructor arguments are passed to the C
method which is described below. =cut sub new { my($class) = shift; my $self = bless {}, $class; $self->header(@_); # set up initial headers $self; } =item $h->header($field [=> $value],...) Get or set the value of one or more header fields. The header field name ($field) is not case sensitive. To make the life easier for perl users who wants to avoid quoting before the => operator, you can use '_' as a replacement for '-' in header names (this behaviour can be suppressed by setting the $HTTP::Headers::TRANSLATE_UNDERSCORE variable to a FALSE value). The header() method accepts multiple ($field => $value) pairs, which means that you can update several fields with a single invocation. The $value argument may be a plain string or a reference to an array of strings for a multi-valued field. If the $value is undefined or not given, then that header field will remain unchanged. The old value (or values) of the last of the header fields is returned. If no such field exists C will be returned. A multi-valued field will be retuned as separate values in list context and will be concatenated with ", " as separator in scalar context. The HTTP spec (RFC 2616) promise that joining multiple values in this way will not change the semantic of a header field, but in practice there are cases like old-style Netscape cookies (see L) where "," is used as part of the syntax of a single field value. Examples: $header->header(MIME_Version => '1.0', User_Agent => 'My-Web-Client/0.01'); $header->header(Accept => "text/html, text/plain, image/*"); $header->header(Accept => [qw(text/html text/plain image/*)]); @accepts = $header->header('Accept'); # get multiple values $accepts = $header->header('Accept'); # get values as a single string =cut sub header { my $self = shift; my(@old); while (my($field, $val) = splice(@_, 0, 2)) { @old = $self->_header($field, $val); } return @old if wantarray; return $old[0] if @old <= 1; join(", ", @old); } =item $h->push_header($field, $value) Add a new field value for the specified header field. Previous values for the same field are retained. As for the header() method, the field name ($field) is not case sensitive and '_' can be used as a replacement for '-'. The $value argument may be a scalar or a reference to a list of scalars. $header->push_header(Accept => 'image/jpeg'); $header->push_header(Accept => [map "image/$_", qw(gif png tiff)]); =cut sub push_header { Carp::croak('Usage: $h->push_header($field, $val)') if @_ != 3; shift->_header(@_, 'PUSH'); } =item $h->init_header($field, $value) Set the specified header to the given value, but only if no previous value for that field is set. The header field name ($field) is not case sensitive and '_' can be used as a replacement for '-'. The $value argument may be a scalar or a reference to a list of scalars. =cut sub init_header { Carp::croak('Usage: $h->init_header($field, $val)') if @_ != 3; shift->_header(@_, 'INIT'); } =item $h->remove_header($field,...) This function removes the headers fields with the specified names. The header field names ($field) are not case sensitive and '_' can be used as a replacement for '-'. The return value is the values of the fields removed. In scalar context the number of fields removed is returned. Note that if you pass in multiple field names then it is generally not possible to tell which of the returned values belonged to which field. =cut sub remove_header { my($self, @fields) = @_; my $field; my @values; foreach $field (@fields) { $field =~ tr/_/-/ if $TRANSLATE_UNDERSCORE; my $v = delete $self->{lc $field}; push(@values, ref($v) ? @$v : $v) if defined $v; } return @values; } sub _header { my($self, $field, $val, $op) = @_; $field =~ tr/_/-/ if $TRANSLATE_UNDERSCORE; # $push is only used interally sub push_header Carp::croak('Need a field name') unless length($field); my $lc_field = lc $field; unless(defined $standard_case{$lc_field}) { # generate a %standard_case entry for this field $field =~ s/\b(\w)/\u$1/g; $standard_case{$lc_field} = $field; } my $h = $self->{$lc_field}; my @old = ref($h) ? @$h : (defined($h) ? ($h) : ()); $op ||= ""; $val = undef if $op eq 'INIT' && @old; if (defined($val)) { my @new = ($op eq 'PUSH') ? @old : (); if (!ref($val)) { push(@new, $val); } elsif (ref($val) eq 'ARRAY') { push(@new, @$val); } else { Carp::croak("Unexpected field value $val"); } $self->{$lc_field} = @new > 1 ? \@new : $new[0]; } @old; } # Compare function which makes it easy to sort headers in the # recommended "Good Practice" order. sub _header_cmp { ($header_order{$a} || 999) <=> ($header_order{$b} || 999) || $a cmp $b; } =item $h->scan(\&doit) Apply a subroutine to each header field in turn. The callback routine is called with two parameters; the name of the field and a single value (a string). If a header field is multi-valued, then the routine is called once for each value. The field name passed to the callback routine has case as suggested by HTTP spec, and the headers will be visited in the recommended "Good Practice" order. Any return values of the callback routine are ignored. The loop can be broken by raising an exception (C). =cut sub scan { my($self, $sub) = @_; my $key; foreach $key (sort _header_cmp keys %$self) { next if $key =~ /^_/; my $vals = $self->{$key}; if (ref($vals)) { my $val; for $val (@$vals) { &$sub($standard_case{$key} || $key, $val); } } else { &$sub($standard_case{$key} || $key, $vals); } } } =item $h->as_string([$endl]) Return the header fields as a formatted MIME header. Since it internally uses the C method to build the string, the result will use case as suggested by HTTP spec, and it will follow recommended "Good Practice" of ordering the header fieds. Long header values are not folded. The optional $endl parameter specifies the line ending sequence to use. The default is "\n". Embedded "\n" characters in header field values will be substitued with this line ending sequence. =cut sub as_string { my($self, $endl) = @_; $endl = "\n" unless defined $endl; my @result = (); $self->scan(sub { my($field, $val) = @_; if ($val =~ /\n/) { # must handle header values with embedded newlines with care $val =~ s/\s+$//; # trailing newlines and space must go $val =~ s/\n\n+/\n/g; # no empty lines $val =~ s/\n([^\040\t])/\n $1/g; # intial space for continuation $val =~ s/\n/$endl/g; # substitute with requested line ending } push(@result, "$field: $val"); }); join($endl, @result, ''); } =item $h->clone Returns a copy of this C object. =back =cut sub clone { my $self = shift; my $clone = new HTTP::Headers; $self->scan(sub { $clone->push_header(@_);} ); $clone; } =head1 CONVENIENCE METHODS The most frequently used headers can also be accessed through the following convenience methods. These methods can both be used to read and to set the value of a header. The header value is set if you pass an argument to the method. The old header value is always returned. If the given header did not exists then C is returned. Methods that deal with dates/times always convert their value to system time (seconds since Jan 1, 1970) and they also expect this kind of value when the header value is set. =over 4 =item $h->date This header represents the date and time at which the message was originated. I: $h->date(time); # set current date =item $h->expires This header gives the date and time after which the entity should be considered stale. =item $h->if_modified_since =item $h->if_unmodified_since These header fields are used to make a request conditional. If the requested resource has (or has not) been modified since the time specified in this field, then the server will return a C<304 Not Modified> response instead of the document itself. =item $h->last_modified This header indicates the date and time at which the resource was last modified. I: # check if document is more than 1 hour old if (my $last_mod = $h->last_modified) { if ($last_mod < time - 60*60) { ... } } =item $h->content_type The Content-Type header field indicates the media type of the message content. I: $h->content_type('text/html'); The value returned will be converted to lower case, and potential parameters will be chopped off and returned as a separate value if in an array context. This makes it safe to do the following: if ($h->content_type eq 'text/html') { # we enter this place even if the real header value happens to # be 'TEXT/HTML; version=3.0' ... } =item $h->content_encoding The Content-Encoding header field is used as a modifier to the media type. When present, its value indicates what additional encoding mechanism has been applied to the resource. =item $h->content_length A decimal number indicating the size in bytes of the message content. =item $h->content_language The natural language(s) of the intended audience for the message content. The value is one or more language tags as defined by RFC 1766. Eg. "no" for some kind of Norwegian and "en-US" for English the way it is written in the US. =item $h->title The title of the document. In libwww-perl this header will be initialized automatically from the ETITLE>...E/TITLE> element of HTML documents. I =item $h->user_agent This header field is used in request messages and contains information about the user agent originating the request. I: $h->user_agent('Mozilla/1.2'); =item $h->server The server header field contains information about the software being used by the originating server program handling the request. =item $h->from This header should contain an Internet e-mail address for the human user who controls the requesting user agent. The address should be machine-usable, as defined by RFC822. E.g.: $h->from('King Kong '); I =item $h->referer Used to specify the address (URI) of the document from which the requested resouce address was obtained. The "Free On-line Dictionary of Computing" as this to say about the word I: A misspelling of "referrer" which somehow made it into the {HTTP} standard. A given {web page}'s referer (sic) is the {URL} of whatever web page contains the link that the user followed to the current page. Most browsers pass this information as part of a request. (1998-10-19) By popular demand C exists as an alias for this method so you can avoid this misspelling in your programs and still send the right thing on the wire. =item $h->www_authenticate This header must be included as part of a C<401 Unauthorized> response. The field value consist of a challenge that indicates the authentication scheme and parameters applicable to the requested URI. =item $h->proxy_authenticate This header must be included in a C<407 Proxy Authentication Required> response. =item $h->authorization =item $h->proxy_authorization A user agent that wishes to authenticate itself with a server or a proxy, may do so by including these headers. =item $h->authorization_basic This method is used to get or set an authorization header that use the "Basic Authentication Scheme". In array context it will return two values; the user name and the password. In scalar context it will return I<"uname:password"> as a single string value. When used to set the header value, it expects two arguments. I: $h->authorization_basic($uname, $password); The method will croak if the $uname contains a colon ':'. =item $h->proxy_authorization_basic Same as authorization_basic() but will set the "Proxy-Authorization" header instead. =back =cut sub _date_header { require HTTP::Date; my($self, $header, $time) = @_; my($old) = $self->_header($header); if (defined $time) { $self->_header($header, HTTP::Date::time2str($time)); } HTTP::Date::str2time($old); } sub date { shift->_date_header('Date', @_); } sub expires { shift->_date_header('Expires', @_); } sub if_modified_since { shift->_date_header('If-Modified-Since', @_); } sub if_unmodified_since { shift->_date_header('If-Unmodified-Since', @_); } sub last_modified { shift->_date_header('Last-Modified', @_); } # This is used as a private LWP extention. The Client-Date header is # added as a timestamp to a response when it has been received. sub client_date { shift->_date_header('Client-Date', @_); } # The retry_after field is dual format (can also be a expressed as # number of seconds from now), so we don't provide an easy way to # access it until we have know how both these interfaces can be # addressed. One possibility is to return a negative value for # relative seconds and a positive value for epoch based time values. #sub retry_after { shift->_date_header('Retry-After', @_); } sub content_type { my $ct = (shift->_header('Content-Type', @_))[0]; return '' unless defined($ct) && length($ct); my @ct = split(/\s*;\s*/, lc($ct)); wantarray ? @ct : $ct[0]; } sub title { (shift->_header('Title', @_))[0] } sub content_encoding { (shift->_header('Content-Encoding', @_))[0] } sub content_language { (shift->_header('Content-Language', @_))[0] } sub content_length { (shift->_header('Content-Length', @_))[0] } sub user_agent { (shift->_header('User-Agent', @_))[0] } sub server { (shift->_header('Server', @_))[0] } sub from { (shift->_header('From', @_))[0] } sub referer { (shift->_header('Referer', @_))[0] } *referrer = \&referer; # on tchrist's request sub warning { (shift->_header('Warning', @_))[0] } sub www_authenticate { (shift->_header('WWW-Authenticate', @_))[0] } sub authorization { (shift->_header('Authorization', @_))[0] } sub proxy_authenticate { (shift->_header('Proxy-Authenticate', @_))[0] } sub proxy_authorization { (shift->_header('Proxy-Authorization', @_))[0] } sub authorization_basic { shift->_basic_auth("Authorization", @_) } sub proxy_authorization_basic { shift->_basic_auth("Proxy-Authorization", @_) } sub _basic_auth { require MIME::Base64; my($self, $h, $user, $passwd) = @_; my($old) = $self->_header($h); if (defined $user) { Carp::croak("Basic authorization user name can't contain ':'") if $user =~ /:/; $passwd = '' unless defined $passwd; $self->_header($h => 'Basic ' . MIME::Base64::encode("$user:$passwd", '')); } if (defined $old && $old =~ s/^\s*Basic\s+//) { my $val = MIME::Base64::decode($old); return $val unless wantarray; return split(/:/, $val, 2); } return; } =head1 COPYRIGHT Copyright 1995-2001 Gisle Aas. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut 1; cgi-bin/extlib/HTTP/Message.pm0000644002157400001440000001127207771550070022774 0ustar minnesotaviolasociety.orgusers00000000000000# # $Id: Message.pm,v 1.25 2001/11/15 06:42:23 gisle Exp $ package HTTP::Message; =head1 NAME HTTP::Message - Class encapsulating HTTP messages =head1 SYNOPSIS package HTTP::Request; # or HTTP::Response require HTTP::Message; @ISA=qw(HTTP::Message); =head1 DESCRIPTION An C object contains some headers and a content (body). The class is abstract, i.e. it only used as a base class for C and C and should never instantiated as itself. The following methods are available: =over 4 =cut ##################################################################### require HTTP::Headers; require Carp; use strict; use vars qw($VERSION $AUTOLOAD); $VERSION = sprintf("%d.%02d", q$Revision: 1.25 $ =~ /(\d+)\.(\d+)/); $HTTP::URI_CLASS ||= $ENV{PERL_HTTP_URI_CLASS} || "URI"; eval "require $HTTP::URI_CLASS"; die $@ if $@; =item $mess = HTTP::Message->new This is the object constructor. It should only be called internally by this library. External code should construct C or C objects. =cut sub new { my($class, $header, $content) = @_; if (defined $header) { Carp::croak("Bad header argument") unless ref $header; $header = $header->clone; } else { $header = HTTP::Headers->new; } $content = '' unless defined $content; bless { '_headers' => $header, '_content' => $content, }, $class; } =item $mess->clone() Returns a copy of the object. =cut sub clone { my $self = shift; my $clone = HTTP::Message->new($self->{'_headers'}, $self->{'_content'}); $clone; } =item $mess->protocol([$proto]) Sets the HTTP protocol used for the message. The protocol() is a string like C or C. =cut sub protocol { shift->_elem('_protocol', @_); } =item $mess->content([$content]) The content() method sets the content if an argument is given. If no argument is given the content is not touched. In either case the previous content is returned. =item $mess->add_content($data) The add_content() methods appends more data to the end of the current content buffer. =cut sub content { shift->_elem('_content', @_); } sub add_content { my $self = shift; if (ref($_[0])) { $self->{'_content'} .= ${$_[0]}; # for backwards compatability } else { $self->{'_content'} .= $_[0]; } } =item $mess->content_ref The content_ref() method will return a reference to content buffer string. It can be more efficient to access the content this way if the content is huge, and it can even be used for direct manipulation of the content, for instance: ${$res->content_ref} =~ s/\bfoo\b/bar/g; =cut sub content_ref { my $self = shift; \$self->{'_content'}; } sub as_string { ""; # To be overridden in subclasses } =item $mess->headers; Return the embedded HTTP::Headers object. =item $mess->headers_as_string([$endl]) Call the as_string() method for the headers in the message. This will be the same as: $mess->headers->as_string but it will make your program a whole character shorter :-) =cut sub headers { shift->{'_headers'}; } sub headers_as_string { shift->{'_headers'}->as_string(@_); } =back All unknown C methods are delegated to the C object that is part of every message. This allows convenient access to these methods. Refer to L for details of these methods: $mess->header($field => $val); $mess->push_header($field => $val); $mess->init_header($field => $val); $mess->remove_header($field); $mess->scan(\&doit); $mess->date; $mess->expires; $mess->if_modified_since; $mess->if_unmodified_since; $mess->last_modified; $mess->content_type; $mess->content_encoding; $mess->content_length; $mess->content_language $mess->title; $mess->user_agent; $mess->server; $mess->from; $mess->referer; $mess->www_authenticate; $mess->authorization; $mess->proxy_authorization; $mess->authorization_basic; $mess->proxy_authorization_basic; =cut # delegate all other method calls the the _headers object. sub AUTOLOAD { my $method = substr($AUTOLOAD, rindex($AUTOLOAD, '::')+2); return if $method eq "DESTROY"; # We create the function here so that it will not need to be # autoloaded the next time. no strict 'refs'; *$method = eval "sub { shift->{'_headers'}->$method(\@_) }"; goto &$method; } # Private method to access members in %$self sub _elem { my $self = shift; my $elem = shift; my $old = $self->{$elem}; $self->{$elem} = $_[0] if @_; return $old; } 1; =head1 COPYRIGHT Copyright 1995-2001 Gisle Aas. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut cgi-bin/extlib/HTTP/Negotiate.pm0000644002157400001440000003753107771550070023335 0ustar minnesotaviolasociety.orgusers00000000000000# $Id: Negotiate.pm,v 1.11 2001/11/27 22:41:33 gisle Exp $ # package HTTP::Negotiate; $VERSION = sprintf("%d.%02d", q$Revision: 1.11 $ =~ /(\d+)\.(\d+)/); sub Version { $VERSION; } require 5.002; require Exporter; @ISA = qw(Exporter); @EXPORT = qw(choose); require HTTP::Headers; $DEBUG = 0; sub choose ($;$) { my($variants, $request) = @_; my(%accept); unless (defined $request) { # Create a request object from the CGI envirionment variables $request = new HTTP::Headers; $request->header('Accept', $ENV{HTTP_ACCEPT}) if $ENV{HTTP_ACCEPT}; $request->header('Accept-Charset', $ENV{HTTP_ACCEPT_CHARSET}) if $ENV{HTTP_ACCEPT_CHARSET}; $request->header('Accept-Encoding', $ENV{HTTP_ACCEPT_ENCODING}) if $ENV{HTTP_ACCEPT_ENCODING}; $request->header('Accept-Language', $ENV{HTTP_ACCEPT_LANGUAGE}) if $ENV{HTTP_ACCEPT_LANGUAGE}; } # Get all Accept values from the request. Build a hash initialized # like this: # # %accept = ( type => { 'audio/*' => { q => 0.2, mbx => 20000 }, # 'audio/basic' => { q => 1 }, # }, # language => { 'no' => { q => 1 }, # } # ); $request->scan(sub { my($key, $val) = @_; my $type; if ($key =~ s/^Accept-//) { $type = lc($key); } elsif ($key eq "Accept") { $type = "type"; } else { return; } $val =~ s/\s+//g; my $default_q = 1; for my $name (split(/,/, $val)) { my(%param, $param); if ($name =~ s/;(.*)//) { for $param (split(/;/, $1)) { my ($pk, $pv) = split(/=/, $param, 2); $param{lc $pk} = $pv; } } $name = lc $name; if (defined $param{'q'}) { $param{'q'} = 1 if $param{'q'} > 1; $param{'q'} = 0 if $param{'q'} < 0; } else { $param{'q'} = $default_q; # This makes sure that the first ones are slightly better off # and therefore more likely to be chosen. $default_q -= 0.0001; } $accept{$type}{$name} = \%param; } }); # Check if any of the variants specify a language. We do this # because it influences how we treat those without (they default to # 0.5 instead of 1). my $any_lang = 0; for $var (@$variants) { if ($var->[5]) { $any_lang = 1; last; } } if ($DEBUG) { print "Negotiation parameters in the request\n"; for $type (keys %accept) { print " $type:\n"; for $name (keys %{$accept{$type}}) { print " $name\n"; for $pv (keys %{$accept{$type}{$name}}) { print " $pv = $accept{$type}{$name}{$pv}\n"; } } } } my @Q = (); # This is where we collect the results of the # quality calcualtions # Calculate quality for all the variants that are available. for (@$variants) { my($id, $qs, $ct, $enc, $cs, $lang, $bs) = @$_; $qs = 1 unless defined $qs; $ct = '' unless defined $ct; $bs = 0 unless defined $bs; $lang = lc($lang) if $lang; # lg tags are always case-insensitive if ($DEBUG) { print "\nEvaluating $id (ct='$ct')\n"; printf " qs = %.3f\n", $qs; print " enc = $enc\n" if $enc && !ref($enc); print " enc = @$enc\n" if $enc && ref($enc); print " cs = $cs\n" if $cs; print " lang = $lang\n" if $lang; print " bs = $bs\n" if $bs; } # Calculate encoding quality my $qe = 1; # If the variant has no assignes Content-Encoding, or if no # Accept-Encoding field is present, then the value assigned # is "qe=1". If *all* of the variant's content encoddings # are listed in the Accept-Encoding field, then the value # assigned is "qw=1". If *any* of the variant's content # encodings are not listed in the provided Accept-Encoding # field, then the value assigned is "qe=0" if (exists $accept{'encoding'} && $enc) { my @enc = ref($enc) ? @$enc : ($enc); for (@enc) { print "Is encoding $_ accepted? " if $DEBUG; unless(exists $accept{'encoding'}{$_}) { print "no\n" if $DEBUG; $qe = 0; last; } else { print "yes\n" if $DEBUG; } } } # Calculate charset quality my $qc = 1; # If the variant's media-type has not charset parameter, # or the variant's charset is US-ASCII, or if no Accept-Charset # field is present, then the value assigned is "qc=1". If the # variant's charset is listed in the Accept-Charset field, # then the value assigned is "qc=1. Otherwise, if the variant's # charset is not listed in the provided Accept-Encoding field, # then the value assigned is "qc=0". if (exists $accept{'charset'} && $cs && $cs ne 'us-ascii' ) { $qc = 0 unless $accept{'charset'}{$cs}; } # Calculate language quality my $ql = 1; if ($lang && exists $accept{'language'}) { my @lang = ref($lang) ? @$lang : ($lang); # If any of the variant's content languages are listed # in the Accept-Language field, the the value assigned is # the maximus of the "q" paramet values for thos language # tags. my $q = undef; for (@lang) { next unless exists $accept{'language'}{$_}; my $this_q = $accept{'language'}{$_}{'q'}; $q = $this_q unless defined $q; $q = $this_q if $this_q > $q; } if(defined $q) { $DEBUG and print " -- Exact language match at q=$q\n"; } else { # If there was no exact match and at least one of # the Accept-Language field values is a complete # subtag prefix of the content language tag(s), then # the "q" parameter value of the largest matching # prefix is used. $DEBUG and print " -- No exact language match\n"; my $selected = undef; for $al (keys %{ $accept{'language'} }) { if (substr($lang, 0, 1 + length($al)) eq "$al-") { # $lang starting with $al isn't enough, or else # Accept-Language: hu (Hungarian) would seem # to accept a document in hup (Hupa) $DEBUG and print " -- $lang ISA $al\n"; $selected = $al unless defined $selected; $selected = $al if length($al) > length($selected); } else { $DEBUG and print " -- $lang isn't a $al\n"; } } $q = $accept{'language'}{$selected}{'q'} if $selected; # If none of the variant's content language tags or # tag prefixes are listed in the provided # Accept-Language field, then the value assigned # is "ql=0.001" $q = 0.001 unless defined $q; } $ql = $q; } else { $ql = 0.5 if $any_lang && exists $accept{'language'}; } my $q = 1; my $mbx = undef; # If no Accept field is given, then the value assigned is "q=1". # If at least one listed media range matches the variant's media # type, then the "q" parameter value assigned to the most specific # of those matched is used (e.g. "text/html;version=3.0" is more # specific than "text/html", which is more specific than "text/*", # which in turn is more specific than "*/*"). If not media range # in the provided Accept field matches the variant's media type, # then the value assigned is "q=0". if (exists $accept{'type'} && $ct) { # First we clean up our content-type $ct =~ s/\s+//g; my $params = ""; $params = $1 if $ct =~ s/;(.*)//; my($type, $subtype) = split("/", $ct, 2); my %param = (); for $param (split(/;/, $params)) { my($pk,$pv) = split(/=/, $param, 2); $param{$pk} = $pv; } my $sel_q = undef; my $sel_mbx = undef; my $sel_specificness = 0; ACCEPT_TYPE: for $at (keys %{ $accept{'type'} }) { print "Consider $at...\n" if $DEBUG; my($at_type, $at_subtype) = split("/", $at, 2); # Is it a match on the type next if $at_type ne '*' && $at_type ne $type; next if $at_subtype ne '*' && $at_subtype ne $subtype; my $specificness = 0; $specificness++ if $at_type ne '*'; $specificness++ if $at_subtype ne '*'; # Let's see if content-type parameters also match while (($pk, $pv) = each %param) { print "Check if $pk = $pv is true\n" if $DEBUG; next unless exists $accept{'type'}{$at}{$pk}; next ACCEPT_TYPE unless $accept{'type'}{$at}{$pk} eq $pv; print "yes it is!!\n" if $DEBUG; $specificness++; } print "Hurray, type match with specificness = $specificness\n" if $DEBUG; if (!defined($sel_q) || $sel_specificness < $specificness) { $sel_q = $accept{'type'}{$at}{'q'}; $sel_mbx = $accept{'type'}{$at}{'mbx'}; $sel_specificness = $specificness; } } $q = $sel_q || 0; $mbx = $sel_mbx; } my $Q; if (!defined($mbx) || $mbx >= $bs) { $Q = $qs * $qe * $qc * $ql * $q; } else { $Q = 0; print "Variant's size is too large ==> Q=0\n" if $DEBUG; } if ($DEBUG) { $mbx = "undef" unless defined $mbx; printf "Q=%.4f", $Q; print " (q=$q, mbx=$mbx, qe=$qe, qc=$qc, ql=$ql, qs=$qs)\n"; } push(@Q, [$id, $Q, $bs]); } @Q = sort { $b->[1] <=> $a->[1] || $a->[2] <=> $b->[2] } @Q; return @Q if wantarray; return undef unless @Q; return undef if $Q[0][1] == 0; $Q[0][0]; } 1; __END__ =head1 NAME choose - choose a variant of a document to serve (HTTP content negotiation) =head1 SYNOPSIS use HTTP::Negotiate; # ID QS Content-Type Encoding Char-Set Lang Size $variants = [['var1', 1.000, 'text/html', undef, 'iso-8859-1', 'en', 3000], ['var2', 0.950, 'text/plain', 'gzip', 'us-ascii', 'no', 400], ['var3', 0.3, 'image/gif', undef, undef, undef, 43555], ]; @prefered = choose($variants, $request_headers); $the_one = choose($variants); =head1 DESCRIPTION This module provides a complete implementation of the HTTP content negotiation algorithm specified in F chapter 12. Content negotiation allows for the selection of a preferred content representation based upon attributes of the negotiable variants and the value of the various Accept* header fields in the request. The variants are ordered by preference by calling the function choose(). The first parameter is reference to an array of the variants to choose among. Each element in this array is an array with the values [$id, $qs, $content_type, $content_encoding, $charset, $content_language, $content_length] whose meanings are described below. The $content_encoding and $content_language can be either a single scalar value or an array reference if there are several values. The second optional parameter is either a HTTP::Headers or a HTTP::Request object which is searched for "Accept*" headers. If this parameter is missing, then the accept specification is initialized from the CGI environment variables HTTP_ACCEPT, HTTP_ACCEPT_CHARSET, HTTP_ACCEPT_ENCODING and HTTP_ACCEPT_LANGUAGE. In an array context, choose() returns a list of variant identifier/calculated quality pairs. The values are sorted by quality, highest quality first. If the calculated quality is the same for two variants, then they are sorted by size (smallest first). I: (['var1' => 1], ['var2', 0.3], ['var3' => 0]); Note that also zero quality variants are included in the return list even if these should never be served to the client. In a scalar context, it returns the identifier of the variant with the highest score or C if none have non-zero quality. If the $HTTP::Negotiate::DEBUG variable is set to TRUE, then a lot of noise is generated on STDOUT during evaluation of choose(). =head1 VARIANTS A variant is described by a list of the following values. If the attribute does not make sense or is unknown for a variant, then use C instead. =over 3 =item identifier This is a string that you use as the name for the variant. This identifier for the preferred variants returned by choose(). =item qs This is a number between 0.000 and 1.000 that describes the "source quality". This is what F says about this value: Source quality is measured by the content provider as representing the amount of degradation from the original source. For example, a picture in JPEG form would have a lower qs when translated to the XBM format, and much lower qs when translated to an ASCII-art representation. Note, however, that this is a function of the source - an original piece of ASCII-art may degrade in quality if it is captured in JPEG form. The qs values should be assigned to each variant by the content provider; if no qs value has been assigned, the default is generally "qs=1". =item content-type This is the media type of the variant. The media type does not include a charset attribute, but might contain other parameters. Examples are: text/html text/html;version=2.0 text/plain image/gif image/jpg =item content-encoding This is one or more content encodings that has been applied to the variant. The content encoding is generally used as a modifier to the content media type. The most common content encodings are: gzip compress =item content-charset This is the character set used when the variant contains text. The charset value should generally be C or one of these: us-ascii iso-8859-1 ... iso-8859-9 iso-2022-jp iso-2022-jp-2 iso-2022-kr unicode-1-1 unicode-1-1-utf-7 unicode-1-1-utf-8 =item content-language This describes one or more languages that are used in the variant. Language is described like this in F: A language is in this context a natural language spoken, written, or otherwise conveyed by human beings for communication of information to other human beings. Computer languages are explicitly excluded. The language tags are defined by RFC 3066. Examples are: no Norwegian en International English en-US US English en-cockney =item content-length This is the number of bytes used to represent the content. =back =head1 ACCEPT HEADERS The following Accept* headers can be used for describing content preferences in a request (This description is an edited extract from F): =over 3 =item Accept This header can be used to indicate a list of media ranges which are acceptable as a reponse to the request. The "*" character is used to group media types into ranges, with "*/*" indicating all media types and "type/*" indicating all subtypes of that type. The parameter q is used to indicate the quality factor, which represents the user's preference for that range of media types. The parameter mbx gives the maximum acceptable size of the response content. The default values are: q=1 and mbx=infinity. If no Accept header is present, then the client accepts all media types with q=1. For example: Accept: audio/*;q=0.2;mbx=200000, audio/basic would mean: "I prefer audio/basic (of any size), but send me any audio type if it is the best available after an 80% mark-down in quality and its size is less than 200000 bytes" =item Accept-Charset Used to indicate what character sets are acceptable for the response. The "us-ascii" character set is assumed to be acceptable for all user agents. If no Accept-Charset field is given, the default is that any charset is acceptable. Example: Accept-Charset: iso-8859-1, unicode-1-1 =item Accept-Encoding Restricts the Content-Encoding values which are acceptable in the response. If no Accept-Encoding field is present, the server may assume that the client will accept any content encoding. An empty Accept-Encoding means that no content encoding is acceptable. Example: Accept-Encoding: compress, gzip =item Accept-Language This field is similar to Accept, but restricts the set of natural languages that are preferred in a response. Each language may be given an associated quality value which represents an estimate of the user's comprehension of that language. For example: Accept-Language: no, en-gb;q=0.8, de;q=0.55 would mean: "I prefer Norwegian, but will accept British English (with 80% comprehension) or German (with 55% comprehension). =back =head1 COPYRIGHT Copyright 1996,2001 Gisle Aas. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 AUTHOR Gisle Aas =cut cgi-bin/extlib/HTTP/Request.pm0000644002157400001440000001032507771550070023036 0ustar minnesotaviolasociety.orgusers00000000000000# # $Id: Request.pm,v 1.30 2001/11/15 06:42:40 gisle Exp $ package HTTP::Request; =head1 NAME HTTP::Request - Class encapsulating HTTP Requests =head1 SYNOPSIS require HTTP::Request; $request = HTTP::Request->new(GET => 'http://www.oslo.net/'); =head1 DESCRIPTION C is a class encapsulating HTTP style requests, consisting of a request line, some headers, and some (potentially empty) content. Note that the LWP library also uses this HTTP style requests for non-HTTP protocols. Instances of this class are usually passed to the C method of an C object: $ua = LWP::UserAgent->new; $request = HTTP::Request->new(GET => 'http://www.oslo.net/'); $response = $ua->request($request); C is a subclass of C and therefore inherits its methods. The inherited methods most often used are header(), push_header(), remove_header(), and content(). See L for details. The following additional methods are available: =over 4 =cut require HTTP::Message; @ISA = qw(HTTP::Message); $VERSION = sprintf("%d.%02d", q$Revision: 1.30 $ =~ /(\d+)\.(\d+)/); use strict; =item $r = HTTP::Request->new($method, $uri) =item $r = HTTP::Request->new($method, $uri, $header) =item $r = HTTP::Request->new($method, $uri, $header, $content) Constructs a new C object describing a request on the object C<$uri> using method C<$method>. The C<$uri> argument can be either a string, or a reference to a C object. The optional $header argument should be a reference to an C object. The optional $content argument should be a string. =cut sub new { my($class, $method, $uri, $header, $content) = @_; my $self = $class->SUPER::new($header, $content); $self->method($method); $self->uri($uri); $self; } sub clone { my $self = shift; my $clone = bless $self->SUPER::clone, ref($self); $clone->method($self->method); $clone->uri($self->uri); $clone; } =item $r->method([$val]) =item $r->uri([$val]) These methods provide public access to the attributes containing respectively the method of the request and the URI object of the request. If an argument is given the attribute is given that as its new value. If no argument is given the value is not touched. In either case the previous value is returned. The method() method argument should be a string. The uri() method accept both a reference to a URI object and a string as its argument. If a string is given, then it should be parseable as an absolute URI. =cut sub method { shift->_elem('_method', @_); } sub uri { my $self = shift; my $old = $self->{'_uri'}; if (@_) { my $uri = shift; if (!defined $uri) { # that's ok } elsif (ref $uri) { Carp::croak("A URI can't be a " . ref($uri) . " reference") if ref($uri) eq 'HASH' or ref($uri) eq 'ARRAY'; Carp::croak("Can't use a " . ref($uri) . " object as a URI") unless $uri->can('scheme'); $uri = $uri->clone; unless ($HTTP::URI_CLASS eq "URI") { # Argh!! Hate this... old LWP legacy! eval { local $SIG{__DIE__}; $uri = $uri->abs; }; die $@ if $@ && $@ !~ /Missing base argument/; } } else { $uri = $HTTP::URI_CLASS->new($uri); } $self->{'_uri'} = $uri; } $old; } *url = \&uri; # this is the same for now =item $r->as_string() Method returning a textual representation of the request. Mainly useful for debugging purposes. It takes no arguments. =cut sub as_string { my $self = shift; my @result; #push(@result, "---- $self -----"); my $req_line = $self->method || "[NO METHOD]"; my $uri = $self->uri; $uri = (defined $uri) ? $uri->as_string : "[NO URI]"; $req_line .= " $uri"; my $proto = $self->protocol; $req_line .= " $proto" if $proto; push(@result, $req_line); push(@result, $self->headers_as_string); my $content = $self->content; if (defined $content) { push(@result, $content); } #push(@result, ("-" x 40)); join("\n", @result, ""); } 1; =back =head1 SEE ALSO L, L, L =head1 COPYRIGHT Copyright 1995-2001 Gisle Aas. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut cgi-bin/extlib/HTTP/Response.pm0000644002157400001440000002275207771550070023213 0ustar minnesotaviolasociety.orgusers00000000000000# # $Id: Response.pm,v 1.36 2001/11/15 06:42:40 gisle Exp $ package HTTP::Response; =head1 NAME HTTP::Response - Class encapsulating HTTP Responses =head1 SYNOPSIS require HTTP::Response; =head1 DESCRIPTION The C class encapsulates HTTP style responses. A response consists of a response line, some headers, and (potentially empty) content. Note that the LWP library also uses HTTP style responses for non-HTTP protocol schemes. Instances of this class are usually created and returned by the C method of an C object: #... $response = $ua->request($request) if ($response->is_success) { print $response->content; } else { print $response->error_as_HTML; } C is a subclass of C and therefore inherits its methods. The inherited methods most often used are header(), push_header(), remove_header(), and content(). The header convenience methods are also available. See L for details. The following additional methods are available: =over 4 =cut require HTTP::Message; @ISA = qw(HTTP::Message); $VERSION = sprintf("%d.%02d", q$Revision: 1.36 $ =~ /(\d+)\.(\d+)/); use HTTP::Status (); use strict; =item $r = HTTP::Response->new($rc, [$msg, [$header, [$content]]]) Constructs a new C object describing a response with response code C<$rc> and optional message C<$msg>. The message is a short human readable single line string that explains the response code. =cut sub new { my($class, $rc, $msg, $header, $content) = @_; my $self = $class->SUPER::new($header, $content); $self->code($rc); $self->message($msg); $self; } sub clone { my $self = shift; my $clone = bless $self->SUPER::clone, ref($self); $clone->code($self->code); $clone->message($self->message); $clone->request($self->request->clone) if $self->request; # we don't clone previous $clone; } =item $r->code([$code]) =item $r->message([$message]) =item $r->request([$request]) =item $r->previous([$previousResponse]) These methods provide public access to the object attributes. The first two contain respectively the response code and the message of the response. The request attribute is a reference the request that caused this response. It does not have to be the same request as passed to the $ua->request() method, because there might have been redirects and authorization retries in between. The previous attribute is used to link together chains of responses. You get chains of responses if the first response is redirect or unauthorized. =cut sub code { shift->_elem('_rc', @_); } sub message { shift->_elem('_msg', @_); } sub previous { shift->_elem('_previous',@_); } sub request { shift->_elem('_request', @_); } =item $r->status_line Returns the string "Ecode> Emessage>". If the message attribute is not set then the official name of Ecode> (see L) is substituted. =cut sub status_line { my $self = shift; my $code = $self->{'_rc'} || "000"; my $mess = $self->{'_msg'} || HTTP::Status::status_message($code) || "?"; return "$code $mess"; } =item $r->base Returns the base URI for this response. The return value will be a reference to a URI object. The base URI is obtained from one the following sources (in priority order): =over 4 =item 1. Embedded in the document content, for instance in HTML documents. =item 2. A "Content-Base:" or a "Content-Location:" header in the response. For backwards compatability with older HTTP implementations we will also look for the "Base:" header. =item 3. The URI used to request this response. This might not be the original URI that was passed to $ua->request() method, because we might have received some redirect responses first. =back When the LWP protocol modules produce the HTTP::Response object, then any base URI embedded in the document (step 1) will already have initialized the "Content-Base:" header. This means that this method only performs the last 2 steps (the content is not always available either). =cut sub base { my $self = shift; my $base = $self->header('Content-Base') || # used to be HTTP/1.1 $self->header('Content-Location') || # HTTP/1.1 $self->header('Base'); # HTTP/1.0 return $HTTP::URI_CLASS->new_abs($base, $self->request->uri); # So yes, if $base is undef, the return value is effectively # just a copy of $self->request->uri. } =item $r->as_string Returns a textual representation of the response. Mainly useful for debugging purposes. It takes no arguments. =cut sub as_string { require HTTP::Status; my $self = shift; my @result; #push(@result, "---- $self ----"); my $code = $self->code; my $status_message = HTTP::Status::status_message($code) || "Unknown code"; my $message = $self->message || ""; my $status_line = "$code"; my $proto = $self->protocol; $status_line = "$proto $status_line" if $proto; $status_line .= " ($status_message)" if $status_message ne $message; $status_line .= " $message"; push(@result, $status_line); push(@result, $self->headers_as_string); my $content = $self->content; if (defined $content) { push(@result, $content); } #push(@result, ("-" x 40)); join("\n", @result, ""); } =item $r->is_info =item $r->is_success =item $r->is_redirect =item $r->is_error These methods indicate if the response was informational, sucessful, a redirection, or an error. =cut sub is_info { HTTP::Status::is_info (shift->{'_rc'}); } sub is_success { HTTP::Status::is_success (shift->{'_rc'}); } sub is_redirect { HTTP::Status::is_redirect (shift->{'_rc'}); } sub is_error { HTTP::Status::is_error (shift->{'_rc'}); } =item $r->error_as_HTML() Returns a string containing a complete HTML document indicating what error occurred. This method should only be called when $r->is_error is TRUE. =cut sub error_as_HTML { my $self = shift; my $title = 'An Error Occurred'; my $body = $self->status_line; return < $title

$title

$body EOM } =item $r->current_age Calculates the "current age" of the response as specified by Edraft-ietf-http-v11-spec-07> section 13.2.3. The age of a response is the time since it was sent by the origin server. The returned value is a number representing the age in seconds. =cut sub current_age { my $self = shift; # Implementation of section 13.2.3 # (age calculations) my $response_time = $self->client_date; my $date = $self->date; my $age = 0; if ($response_time && $date) { $age = $response_time - $date; # apparent_age $age = 0 if $age < 0; } my $age_v = $self->header('Age'); if ($age_v && $age_v > $age) { $age = $age_v; # corrected_received_age } my $request = $self->request; if ($request) { my $request_time = $request->date; if ($request_time) { # Add response_delay to age to get 'corrected_initial_age' $age += $response_time - $request_time; } } if ($response_time) { $age += time - $response_time; } return $age; } =item $r->freshness_lifetime Calculates the "freshness lifetime" of the response as specified by Edraft-ietf-http-v11-spec-07> section 13.2.4. The "freshness lifetime" is the length of time between the generation of a response and its expiration time. The returned value is a number representing the freshness lifetime in seconds. If the response does not contain an "Expires" or a "Cache-Control" header, then this function will apply some simple heuristic based on 'Last-Modified' to determine a suitable lifetime. =cut sub freshness_lifetime { my $self = shift; # First look for the Cache-Control: max-age=n header my @cc = $self->header('Cache-Control'); if (@cc) { my $cc; for $cc (@cc) { my $cc_dir; for $cc_dir (split(/\s*,\s*/, $cc)) { if ($cc_dir =~ /max-age\s*=\s*(\d+)/i) { return $1; } } } } # Next possibility is to look at the "Expires" header my $date = $self->date || $self->client_date || time; my $expires = $self->expires; unless ($expires) { # Must apply heuristic expiration my $last_modified = $self->last_modified; if ($last_modified) { my $h_exp = ($date - $last_modified) * 0.10; # 10% since last-mod if ($h_exp < 60) { return 60; # minimum } elsif ($h_exp > 24 * 3600) { # Should give a warning if more than 24 hours according to # section 13.2.4, but I don't # know how to do it from this function interface, so I just # make this the maximum value. return 24 * 3600; } return $h_exp; } else { return 3600; # 1 hour is fallback when all else fails } } return $expires - $date; } =item $r->is_fresh Returns TRUE if the response is fresh, based on the values of freshness_lifetime() and current_age(). If the response is no longer fresh, then it has to be refetched or revalidated by the origin server. =cut sub is_fresh { my $self = shift; $self->freshness_lifetime > $self->current_age; } =item $r->fresh_until Returns the time when this entiy is no longer fresh. =cut sub fresh_until { my $self = shift; return $self->freshness_lifetime - $self->current_age + time; } 1; =back =head1 COPYRIGHT Copyright 1995-2001 Gisle Aas. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut cgi-bin/extlib/HTTP/Status.pm0000644002157400001440000001510507771550070022672 0ustar minnesotaviolasociety.orgusers00000000000000# # $Id: Status.pm,v 1.26 1999/11/22 10:43:24 gisle Exp $ package HTTP::Status; use strict; require 5.002; # becase we use prototypes =head1 NAME HTTP::Status - HTTP Status code processing =head1 SYNOPSIS use HTTP::Status; if ($rc != RC_OK) { print status_message($rc), "\n"; } if (is_success($rc)) { ... } if (is_error($rc)) { ... } if (is_redirect($rc)) { ... } =head1 DESCRIPTION I is a library of routines for defining and classifying HTTP status codes for libwww-perl. Status codes are used to encode the overall outcome of a HTTP response message. Codes correspond to those defined in RFC 2616 and RFC 2518. =head1 CONSTANTS The following constant functions can be used as mnemonic status code names: RC_CONTINUE (100) RC_SWITCHING_PROTOCOLS (101) RC_PROCESSING (102) RC_OK (200) RC_CREATED (201) RC_ACCEPTED (202) RC_NON_AUTHORITATIVE_INFORMATION (203) RC_NO_CONTENT (204) RC_RESET_CONTENT (205) RC_PARTIAL_CONTENT (206) RC_MULTI_STATUS (207) RC_MULTIPLE_CHOICES (300) RC_MOVED_PERMANENTLY (301) RC_FOUND (302) RC_SEE_OTHER (303) RC_NOT_MODIFIED (304) RC_USE_PROXY (305) RC_TEMPORARY_REDIRECT (307) RC_BAD_REQUEST (400) RC_UNAUTHORIZED (401) RC_PAYMENT_REQUIRED (402) RC_FORBIDDEN (403) RC_NOT_FOUND (404) RC_METHOD_NOT_ALLOWED (405) RC_NOT_ACCEPTABLE (406) RC_PROXY_AUTHENTICATION_REQUIRED (407) RC_REQUEST_TIMEOUT (408) RC_CONFLICT (409) RC_GONE (410) RC_LENGTH_REQUIRED (411) RC_PRECONDITION_FAILED (412) RC_REQUEST_ENTITY_TOO_LARGE (413) RC_REQUEST_URI_TOO_LARGE (414) RC_UNSUPPORTED_MEDIA_TYPE (415) RC_REQUEST_RANGE_NOT_SATISFIABLE (416) RC_EXPECTATION_FAILED (417) RC_UNPROCESSABLE_ENTITY (422) RC_LOCKED (423) RC_FAILED_DEPENDENCY (424) RC_INTERNAL_SERVER_ERROR (500) RC_NOT_IMPLEMENTED (501) RC_BAD_GATEWAY (502) RC_SERVICE_UNAVAILABLE (503) RC_GATEWAY_TIMEOUT (504) RC_HTTP_VERSION_NOT_SUPPORTED (505) RC_INSUFFICIENT_STORAGE (507) =cut ##################################################################### use vars qw(@ISA @EXPORT @EXPORT_OK $VERSION); require Exporter; @ISA = qw(Exporter); @EXPORT = qw(is_info is_success is_redirect is_error status_message); @EXPORT_OK = qw(is_client_error is_server_error); $VERSION = sprintf("%d.%02d", q$Revision: 1.26 $ =~ /(\d+)\.(\d+)/); # Note also addition of mnemonics to @EXPORT below my %StatusCode = ( 100 => 'Continue', 101 => 'Switching Protocols', 102 => 'Processing', # WebDAV 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 207 => 'Multi-Status', # WebDAV 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 307 => 'Temporary Redirect', 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Timeout', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Large', 415 => 'Unsupported Media Type', 416 => 'Request Range Not Satisfiable', 417 => 'Expectation Failed', 422 => 'Unprocessable Entity', # WebDAV 423 => 'Locked', # WebDAV 424 => 'Failed Dependency', # WebDAV 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Timeout', 505 => 'HTTP Version Not Supported', 507 => 'Insufficient Storage', # WebDAV ); my $mnemonicCode = ''; my ($code, $message); while (($code, $message) = each %StatusCode) { # create mnemonic subroutines $message =~ tr/a-z \-/A-Z__/; $mnemonicCode .= "sub RC_$message () { $code }\t"; # make them exportable $mnemonicCode .= "push(\@EXPORT, 'RC_$message');\n"; } # warn $mnemonicCode; # for development eval $mnemonicCode; # only one eval for speed die if $@; # backwards compatibility *RC_MOVED_TEMPORARILY = \&RC_FOUND; # 302 was renamed in the standard push(@EXPORT, "RC_MOVED_TEMPORARILY"); =head1 FUNCTIONS The following additional functions are provided. Most of them are exported by default. =over 4 =item status_message($code) The status_message() function will translate status codes to human readable strings. The string is the same as found in the constant names above. If the $code is unknown, then C is returned. =cut sub status_message ($) { $StatusCode{$_[0]}; } =item is_info($code) Return TRUE if C<$code> is an I status code. This class of status code indicates a provisional response which can't have any content. =item is_success($code) Return TRUE if C<$code> is a I status code. =item is_redirect($code) Return TRUE if C<$code> is a I status code. This class of status code indicates that further action needs to be taken by the user agent in order to fulfill the request. =item is_error($code) Return TRUE if C<$code> is an I status code. The function return TRUE for both client error or a server error status codes. =item is_client_error($code) Return TRUE if C<$code> is an I status code. This class of status code is intended for cases in which the client seems to have erred. This function is B exported by default. =item is_server_error($code) Return TRUE if C<$code> is an I status code. This class of status codes is intended for cases in which the server is aware that it has erred or is incapable of performing the request. This function is B exported by default. =back =cut sub is_info ($) { $_[0] >= 100 && $_[0] < 200; } sub is_success ($) { $_[0] >= 200 && $_[0] < 300; } sub is_redirect ($) { $_[0] >= 300 && $_[0] < 400; } sub is_error ($) { $_[0] >= 400 && $_[0] < 600; } sub is_client_error ($) { $_[0] >= 400 && $_[0] < 500; } sub is_server_error ($) { $_[0] >= 500 && $_[0] < 600; } 1; =head1 BUGS Wished @EXPORT_OK had been used instead of @EXPORT in the beginning. Now too much is exported by default. =cut cgi-bin/extlib/I18N/0000755002157400001440000000000007776605372020762 5ustar minnesotaviolasociety.orgusers00000000000000cgi-bin/extlib/I18N/LangTags/0000755002157400001440000000000007776605372022462 5ustar minnesotaviolasociety.orgusers00000000000000cgi-bin/extlib/I18N/LangTags/_vti_cnf/0000755002157400001440000000000007776605372024251 5ustar minnesotaviolasociety.orgusers00000000000000cgi-bin/extlib/I18N/LangTags/_vti_cnf/List.pm0000644002157400001440000000030407626347051025506 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|24 Feb 2003 07:47:53 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|24 Feb 2003 07:47:53 -0000 vti_filesize:IR|25736 vti_backlinkinfo:VX| cgi-bin/extlib/I18N/LangTags/List.pm0000644002157400001440000006221007626347051023723 0ustar minnesotaviolasociety.orgusers00000000000000 require 5; package I18N::LangTags::List; # Time-stamp: "2002-02-02 20:13:58 MST" use strict; use vars qw(%Name $Debug $VERSION); $VERSION = '0.25'; # POD at the end. #---------------------------------------------------------------------- { # read the table out of our own POD! my $seeking = 1; my $count = 0; my($tag,$name); while() { if($seeking) { $seeking = 0 if m/=for woohah/; } else { next unless ($tag, $name) = m/\{([-0-9a-zA-Z]+)\}(?:\s*:)?\s*([^\[\]]+)/; $name =~ s/\s*[;\.]*\s*$//g; next unless $name; ++$count; print "<$tag> <$name>\n" if $Debug; $Name{$tag} = $name; } } die "No tags read??" unless $count; } #---------------------------------------------------------------------- sub name { my $tag = lc($_[0] || return); $tag =~ s/^\s+//s; $tag =~ s/\s+$//s; my $alt; if($tag =~ m/^x-(.+)/) { $alt = "i-$1"; } elsif($tag =~ m/^i-(.+)/) { $alt = "x-$1"; } else { $alt = ''; } my $subform = ''; my $name = ''; print "Input: {$tag}\n" if $Debug; while(length $tag) { last if $name = $Name{$tag}; last if $name = $Name{$alt}; if($tag =~ s/(-[a-z0-9]+)$//s) { print "Shaving off: $1 leaving $tag\n" if $Debug; $subform = "$1$subform"; # and loop around again $alt =~ s/(-[a-z0-9]+)$//s && $Debug && print " alt -> $alt\n"; } else { # we're trying to pull a subform off a primary tag. TILT! print "Aborting on: {$name}{$subform}\n" if $Debug; last; } } print "Output: {$name}{$subform}\n" if $Debug; return unless $name; # Failure return $name unless $subform; # Exact match $subform =~ s/^-//s; $subform =~ s/-$//s; return "$name (Subform \"$subform\")"; } 1; __DATA__ =head1 NAME I18N::LangTags::List -- tags and names for human languages =head1 SYNOPSIS use I18N::LangTags::List; print "Parlez-vous... ", join(', ', I18N::LangTags::List::name('elx') || 'unknown_language', I18N::LangTags::List::name('ar-Kw') || 'unknown_language', I18N::LangTags::List::name('en') || 'unknown_language', I18N::LangTags::List::name('en-CA') || 'unknown_language', ), "?\n"; prints: Parlez-vous... Elamite, Kuwait Arabic, English, Canadian English? =head1 DESCRIPTION This module provides a function C ) > that takes a language tag (see L) and returns the best attempt at an English name for it, or undef if it can't make sense of the tag. The function I18N::LangTags::List::name(...) is not exported. The map of tags-to-names that it uses is accessable as %I18N::LangTags::List::Name, and it's the same as the list that follows in this documentation, which should be useful to you even if you don't use this module. =head1 ABOUT LANGUAGE TAGS Internet language tags, as defined in RFC 3066, are a formalism for denoting human languages. The two-letter ISO 639-1 language codes are well known (as "en" for English), as are their forms when qualified by a country code ("en-US"). Less well-known are the arbitrary-length non-ISO codes (like "i-mingo"), and the recently (in 2001) introduced three-letter ISO-639-2 codes. Remember these important facts: =over =item * Language tags are not locale IDs. A locale ID is written with a "_" instead of a "-", (almost?) always matches C, and I something different than a language tag. A language tag denotes a language. A locale ID denotes a language I a particular place, in combination with non-linguistic location-specific information such as what currency is used there. Locales I often denote character set information, as in "en_US.ISO8859-1". =item * Language tags are not for computer languages. =item * "Dialect" is not a useful term, since there is no objective criterion for establishing when two language-forms are dialects of eachother, or are separate languages. =item * Language tags are not case-sensitive. en-US, en-us, En-Us, etc., are all the same tag, and denote the same language. =item * Not every language tag really refers to a single language. Some language tags refer to conditions: i-default (system-message text in English plus maybe other languages), und (undetermined language). Others (notably lots of the three-letter codes) are bibliographic tags that classify whole groups of languages, as with cus "Cushitic (Other)" (i.e., a language that has been classed as Cushtic, but which has no more specific code) or the even less linguistically coherent sai for "South American Indian (Other)". Though useful in bibliography, B. For further guidance, email me. =item * Language tags are not country codes. In fact, they are often distinct codes, as with language tag ja for Japanese, and ISO 3166 country code C<.jp> for Japan. =back =head1 LIST OF LANGUAGES The first part of each item is the language tag, between {...}. It is followed by an English name for the language or language-group. Language tags that I judge to be not for general use, are bracketed. This list is in alphabetical order by English name of the language. =for reminder The name in the =item line MUST NOT have E<...>'s in it!! =for woohah START =over =item {ab} : Abkhazian eq Abkhaz =item {ace} : Achinese =item {ach} : Acoli =item {ada} : Adangme =item {aa} : Afar =item {afh} : Afrihili (Artificial) =item {af} : Afrikaans =item [{afa} : Afro-Asiatic (Other)] =item {aka} : Akan =item {akk} : Akkadian (Historical) =item {sq} : Albanian =item {ale} : Aleut =item [{alg} : Algonquian languages] NOT Algonquin! =item [{tut} : Altaic (Other)] =item {am} : Amharic NOT Aramaic! =item {i-ami} : Ami eq Amis. eq 'Amis. eq Pangca. =item [{apa} : Apache languages] =item {ar} : Arabic Many forms are mutually un-intelligible in spoken media. Notable forms: {ar-ae} UAE Arabic; {ar-bh} Bahrain Arabic; {ar-dz} Algerian Arabic; {ar-eg} Egyptian Arabic; {ar-iq} Iraqi Arabic; {ar-jo} Jordanian Arabic; {ar-kw} Kuwait Arabic; {ar-lb} Lebanese Arabic; {ar-ly} Libyan Arabic; {ar-ma} Moroccan Arabic; {ar-om} Omani Arabic; {ar-qa} Qatari Arabic; {ar-sa} Sauda Arabic; {ar-sy} Syrian Arabic; {ar-tn} Tunisian Arabic; {ar-ye} Yemen Arabic. =item {arc} : Aramaic NOT Amharic! NOT Samaritan Aramaic! =item {arp} : Arapaho =item {arn} : Araucanian =item {arw} : Arawak =item {hy} : Armenian =item [{art} : Artificial (Other)] =item {as} : Assamese =item [{ath} : Athapascan languages] eq Athabaskan. eq Athapaskan. eq Athabascan. =item [{aus} : Australian languages] =item [{map} : Austronesian (Other)] =item {ava} : Avaric =item {ae} : Avestan eq Zend =item {awa} : Awadhi =item {ay} : Aymara =item {az} : Azerbaijani eq Azeri =item {ban} : Balinese =item [{bat} : Baltic (Other)] =item {bal} : Baluchi =item {bam} : Bambara =item [{bai} : Bamileke languages] =item {bad} : Banda =item [{bnt} : Bantu (Other)] =item {bas} : Basa =item {ba} : Bashkir =item {eu} : Basque =item {btk} : Batak (Indonesia) =item {bej} : Beja =item {be} : Belarusian eq Belarussian. eq Byelarussian. eq Belorussian. eq Byelorussian. eq White Russian. eq White Ruthenian. NOT Ruthenian! =item {bem} : Bemba =item {bn} : Bengali eq Bangla. =item [{ber} : Berber (Other)] =item {bho} : Bhojpuri =item {bh} : Bihari =item {bik} : Bikol =item {bin} : Bini =item {bi} : Bislama eq Bichelamar. =item {bs} : Bosnian =item {bra} : Braj =item {br} : Breton =item {bug} : Buginese =item {bg} : Bulgarian =item {i-bnn} : Bunun =item {bua} : Buriat =item {my} : Burmese =item {cad} : Caddo =item {car} : Carib =item {ca} : Catalan eq CatalEn. eq Catalonian. =item [{cau} : Caucasian (Other)] =item {ceb} : Cebuano =item [{cel} : Celtic (Other)] Notable forms: {cel-gaulish} Gaulish (Historical) =item [{cai} : Central American Indian (Other)] =item {chg} : Chagatai (Historical?) =item [{cmc} : Chamic languages] =item {ch} : Chamorro =item {ce} : Chechen =item {chr} : Cherokee eq Tsalagi =item {chy} : Cheyenne =item {chb} : Chibcha (Historical) NOT Chibchan (which is a language family). =item {ny} : Chichewa eq Nyanja. eq Chinyanja. =item {zh} : Chinese Many forms are mutually un-intelligible in spoken media. Notable subforms: {zh-cn} PRC Chinese; {zh-hk} Hong Kong Chinese; {zh-mo} Macau Chinese; {zh-sg} Singapore Chinese; {zh-tw} Taiwan Chinese; {zh-guoyu} Mandarin [Putonghua/Guoyu]; {zh-hakka} Hakka [formerly i-hakka]; {zh-min} Hokkien; {zh-min-nan} Southern Hokkien; {zh-wuu} Shanghaiese; {zh-xiang} Hunanese; {zh-gan} Gan; {zh-yue} Cantonese. =for etc {i-hakka} Hakka (old tag) =item {chn} : Chinook Jargon eq Chinook Wawa. =item {chp} : Chipewyan =item {cho} : Choctaw =item {cu} : Church Slavic eq Old Church Slavonic. =item {chk} : Chuukese eq Trukese. eq Chuuk. eq Truk. eq Ruk. =item {cv} : Chuvash =item {cop} : Coptic =item {kw} : Cornish =item {co} : Corsican eq Corse. =item {cre} : Cree NOT Creek! =item {mus} : Creek NOT Cree! =item [{cpe} : English-based Creoles and pidgins (Other)] =item [{cpf} : French-based Creoles and pidgins (Other)] =item [{cpp} : Portuguese-based Creoles and pidgins (Other)] =item [{crp} : Creoles and pidgins (Other)] =item {hr} : Croatian eq Croat. =item [{cus} : Cushitic (Other)] =item {cs} : Czech =item {dak} : Dakota eq Nakota. eq Latoka. =item {da} : Danish =item {day} : Dayak =item {i-default} : Default (Fallthru) Language Defined in RFC 2277, this is for tagging text (which must include English text, and might/should include text in other appropriate languages) that is emitted in a context where language-negotiation wasn't possible -- in SMTP mail failure messages, for example. =item {del} : Delaware =item {din} : Dinka =item {div} : Divehi =item {doi} : Dogri NOT Dogrib! =item {dgr} : Dogrib NOT Dogri! =item [{dra} : Dravidian (Other)] =item {dua} : Duala =item {nl} : Dutch eq Netherlander. Notable forms: {nl-nl} Netherlands Dutch; {nl-be} Belgian Dutch. =item {dum} : Middle Dutch (ca.1050-1350) (Historical) =item {dyu} : Dyula =item {dz} : Dzongkha =item {efi} : Efik =item {egy} : Ancient Egyptian (Historical) =item {eka} : Ekajuk =item {elx} : Elamite (Historical) =item {en} : English Notable forms: {en-au} Australian English; {en-bz} Belize English; {en-ca} Canadian English; {en-gb} UK English; {en-ie} Irish English; {en-jm} Jamaican English; {en-nz} New Zealand English; {en-ph} Philippine English; {en-tt} Trinidad English; {en-us} US English; {en-za} South African English; {en-zw} Zimbabwe English. =item {enm} : Old English (1100-1500) (Historical) =item {ang} : Old English (ca.450-1100) eq Anglo-Saxon. (Historical) =item {eo} : Esperanto (Artificial) =item {et} : Estonian =item {ewe} : Ewe =item {ewo} : Ewondo =item {fan} : Fang =item {fat} : Fanti =item {fo} : Faroese =item {fj} : Fijian =item {fi} : Finnish =item [{fiu} : Finno-Ugrian (Other)] eq Finno-Ugric. NOT Ugaritic! =item {fon} : Fon =item {fr} : French Notable forms: {fr-fr} France French; {fr-be} Belgian French; {fr-ca} Canadian French; {fr-ch} Swiss French; {fr-lu} Luxembourg French; {fr-mc} Monaco French. =item {frm} : Middle French (ca.1400-1600) (Historical) =item {fro} : Old French (842-ca.1400) (Historical) =item {fy} : Frisian =item {fur} : Friulian =item {ful} : Fulah =item {gaa} : Ga =item {gd} : Scots Gaelic NOT Scots! =item {gl} : Gallegan eq Galician =item {lug} : Ganda =item {gay} : Gayo =item {gba} : Gbaya =item {gez} : Geez eq Ge'ez =item {ka} : Georgian =item {de} : German Notable forms: {de-at} Austrian German; {de-be} Belgian German; {de-ch} Swiss German; {de-de} Germany German; {de-li} Liechtenstein German; {de-lu} Luxembourg German. =item {gmh} : Middle High German (ca.1050-1500) (Historical) =item {goh} : Old High German (ca.750-1050) (Historical) =item [{gem} : Germanic (Other)] =item {gil} : Gilbertese =item {gon} : Gondi =item {gor} : Gorontalo =item {got} : Gothic (Historical) =item {grb} : Grebo =item {grc} : Ancient Greek (Historical) (Until 15th century or so.) =item {el} : Modern Greek (Since 15th century or so.) =item {gn} : Guarani GuaranE =item {gu} : Gujarati =item {gwi} : Gwich'in eq Gwichin =item {hai} : Haida =item {ha} : Hausa =item {haw} : Hawaiian Hawai'ian =item {he} : Hebrew (Formerly "iw".) =for etc {iw} Hebrew (old tag) =item {hz} : Herero =item {hil} : Hiligaynon =item {him} : Himachali =item {hi} : Hindi =item {ho} : Hiri Motu =item {hit} : Hittite (Historical) =item {hmn} : Hmong =item {hu} : Hungarian =item {hup} : Hupa =item {iba} : Iban =item {is} : Icelandic =item {ibo} : Igbo =item {ijo} : Ijo =item {ilo} : Iloko =item [{inc} : Indic (Other)] =item [{ine} : Indo-European (Other)] =item {id} : Indonesian (Formerly "in".) =for etc {in} Indonesian (old tag) =item {ia} : Interlingua (International Auxiliary Language Association) (Artificial) NOT Interlingue! =item {ie} : Interlingue (Artificial) NOT Interlingua! =item {iu} : Inuktitut A subform of "Eskimo". =item {ik} : Inupiaq A subform of "Eskimo". =item [{ira} : Iranian (Other)] =item {ga} : Irish =item {mga} : Middle Irish (900-1200) (Historical) =item {sga} : Old Irish (to 900) (Historical) =item [{iro} : Iroquoian languages] =item {it} : Italian Notable forms: {it-it} Italy Italian; {it-ch} Swiss Italian. =item {ja} : Japanese (NOT "jp"!) =item {jw} : Javanese =item {jrb} : Judeo-Arabic =item {jpr} : Judeo-Persian =item {kab} : Kabyle =item {kac} : Kachin =item {kl} : Kalaallisut eq Greenlandic "Eskimo" =item {kam} : Kamba =item {kn} : Kannada eq Kanarese. NOT Canadian! =item {kau} : Kanuri =item {kaa} : Kara-Kalpak =item {kar} : Karen =item {ks} : Kashmiri =item {kaw} : Kawi =item {kk} : Kazakh =item {kha} : Khasi =item {km} : Khmer eq Cambodian. eq Kampuchean. =item [{khi} : Khoisan (Other)] =item {kho} : Khotanese =item {ki} : Kikuyu eq Gikuyu. =item {kmb} : Kimbundu =item {rw} : Kinyarwanda =item {ky} : Kirghiz =item {i-klingon} : Klingon =item {kv} : Komi =item {kon} : Kongo =item {kok} : Konkani =item {ko} : Korean =item {kos} : Kosraean =item {kpe} : Kpelle =item {kro} : Kru =item {kj} : Kuanyama =item {kum} : Kumyk =item {ku} : Kurdish =item {kru} : Kurukh =item {kut} : Kutenai =item {lad} : Ladino eq Judeo-Spanish. NOT Ladin (a minority language in Italy). =item {lah} : Lahnda NOT Lamba! =item {lam} : Lamba NOT Lahnda! =item {lo} : Lao eq Laotian. =item {la} : Latin (Historical) NOT Ladin! NOT Ladino! =item {lv} : Latvian eq Lettish. =item {lb} : Letzeburgesch eq Luxemburgian, eq Luxemburger. (Formerly i-lux.) =for etc {i-lux} Letzeburgesch (old tag) =item {lez} : Lezghian =item {ln} : Lingala =item {lt} : Lithuanian =item {nds} : Low German eq Low Saxon. eq Low German. eq Low Saxon. =item {loz} : Lozi =item {lub} : Luba-Katanga =item {lua} : Luba-Lulua =item {lui} : Luiseno eq LuiseEo. =item {lun} : Lunda =item {luo} : Luo (Kenya and Tanzania) =item {lus} : Lushai =item {mk} : Macedonian eq the modern Slavic language spoken in what was Yugoslavia. NOT the form of Greek spoken in Greek Macedonia! =item {mad} : Madurese =item {mag} : Magahi =item {mai} : Maithili =item {mak} : Makasar =item {mg} : Malagasy =item {ms} : Malay NOT Malayalam! =item {ml} : Malayalam NOT Malay! =item {mt} : Maltese =item {mnc} : Manchu =item {mdr} : Mandar NOT Mandarin! =item {man} : Mandingo =item {mni} : Manipuri eq Meithei. =item [{mno} : Manobo languages] =item {gv} : Manx =item {mi} : Maori NOT Mari! =item {mr} : Marathi =item {chm} : Mari NOT Maori! =item {mh} : Marshall eq Marshallese. =item {mwr} : Marwari =item {mas} : Masai =item [{myn} : Mayan languages] =item {men} : Mende =item {mic} : Micmac =item {min} : Minangkabau =item {i-mingo} : Mingo eq the Irquoian language West Virginia Seneca. NOT New York Seneca! =item [{mis} : Miscellaneous languages] Don't use this. =item {moh} : Mohawk =item {mo} : Moldavian eq Moldovan. =item [{mkh} : Mon-Khmer (Other)] =item {lol} : Mongo =item {mn} : Mongolian eq Mongol. =item {mos} : Mossi =item [{mul} : Multiple languages] Not for normal use. =item [{mun} : Munda languages] =item {nah} : Nahuatl =item {na} : Nauru =item {nv} : Navajo eq Navaho. (Formerly i-navajo.) =for etc {i-navajo} Navajo (old tag) =item {nd} : North Ndebele =item {nr} : South Ndebele =item {ng} : Ndonga =item {ne} : Nepali eq Nepalese. Notable forms: {ne-np} Nepal Nepali; {ne-in} India Nepali. =item {new} : Newari =item {nia} : Nias =item [{nic} : Niger-Kordofanian (Other)] =item [{ssa} : Nilo-Saharan (Other)] =item {niu} : Niuean =item {non} : Old Norse (Historical) =item [{nai} : North American Indian] Do not use this. =item {se} : Northern Sami eq Lappish. eq Lapp. eq (Northern) Saami. =item {no} : Norwegian Note the two following forms: =item {nb} : Norwegian Bokmal eq BokmEl, (A form of Norwegian.) (Formerly no-bok.) =for etc {no-bok} Norwegian Bokmal (old tag) =item {nn} : Norwegian Nynorsk (A form of Norwegian.) (Formerly no-nyn.) =for etc {no-nyn} Norwegian Nynorsk (old tag) =item [{nub} : Nubian languages] =item {nym} : Nyamwezi =item {nyn} : Nyankole =item {nyo} : Nyoro =item {nzi} : Nzima =item {oc} : Occitan (post 1500) eq ProvenEal, eq Provencal =item {oji} : Ojibwa eq Ojibwe. =item {or} : Oriya =item {om} : Oromo =item {osa} : Osage =item {os} : Ossetian; Ossetic =item [{oto} : Otomian languages] Group of languages collectively called "OtomE". =item {pal} : Pahlavi eq Pahlevi =item {i-pwn} : Paiwan eq Pariwan =item {pau} : Palauan =item {pi} : Pali (Historical?) =item {pam} : Pampanga =item {pag} : Pangasinan =item {pa} : Panjabi eq Punjabi =item {pap} : Papiamento eq Papiamentu. =item [{paa} : Papuan (Other)] =item {fa} : Persian eq Farsi. eq Iranian. =item {peo} : Old Persian (ca.600-400 B.C.) =item [{phi} : Philippine (Other)] =item {phn} : Phoenician (Historical) =item {pon} : Pohnpeian NOT Pompeiian! =item {pl} : Polish =item {pt} : Portuguese eq Portugese. Notable forms: {pt-pt} Portugal Portuguese; {pt-br} Brazilian Portuguese. =item [{pra} : Prakrit languages] =item {pro} : Old Provencal (to 1500) eq Old ProvenEal. (Historical.) =item {ps} : Pushto eq Pashto. eq Pushtu. =item {qu} : Quechua eq Quecha. =item {rm} : Raeto-Romance eq Romansh. =item {raj} : Rajasthani =item {rap} : Rapanui =item {rar} : Rarotongan =item [{qaa - qtz} : Reserved for local use.] =item [{roa} : Romance (Other)] NOT Romanian! NOT Romany! NOT Romansh! =item {ro} : Romanian eq Rumanian. NOT Romany! =item {rom} : Romany eq Rom. NOT Romanian! =item {rn} : Rundi =item {ru} : Russian NOT White Russian! NOT Rusyn! =item [{sal} : Salishan languages] Large language group. =item {sam} : Samaritan Aramaic NOT Aramaic! =item [{smi} : Sami languages (Other)] =item {sm} : Samoan =item {sad} : Sandawe =item {sg} : Sango =item {sa} : Sanskrit (Historical) =item {sat} : Santali =item {sc} : Sardinian eq Sard. =item {sas} : Sasak =item {sco} : Scots NOT Scots Gaelic! =item {sel} : Selkup =item [{sem} : Semitic (Other)] =item {sr} : Serbian eq Serb. NOT Sorbian. =item {srr} : Serer =item {shn} : Shan =item {sn} : Shona =item {sid} : Sidamo =item {sgn-...} : Sign Languages Always use with a subtag. Notable forms: {sgn-gb} British Sign Language (BSL); {sgn-ie} Irish Sign Language (ESL); {sgn-ni} Nicaraguan Sign Language (ISN); {sgn-us} American Sign Language (ASL). =item {bla} : Siksika eq Blackfoot. eq Pikanii. =item {sd} : Sindhi =item {si} : Sinhalese eq Sinhala. =item [{sit} : Sino-Tibetan (Other)] =item [{sio} : Siouan languages] =item {den} : Slave (Athapascan) ("Slavey" is a subform.) =item [{sla} : Slavic (Other)] =item {sk} : Slovak eq Slovakian. =item {sl} : Slovenian eq Slovene. =item {sog} : Sogdian =item {so} : Somali =item {son} : Songhai =item {snk} : Soninke =item {wen} : Sorbian languages eq Wendish. eq Sorb. eq Lusatian. eq Wend. NOT Venda! NOT Serbian! =item {nso} : Northern Sotho =item {st} : Southern Sotho eq Sutu. eq Sesotho. =item [{sai} : South American Indian (Other)] =item {es} : Spanish Notable forms: {es-ar} Argentine Spanish; {es-bo} Bolivian Spanish; {es-cl} Chilean Spanish; {es-co} Colombian Spanish; {es-do} Dominican Spanish; {es-ec} Ecuadorian Spanish; {es-es} Spain Spanish; {es-gt} Guatemalan Spanish; {es-hn} Honduran Spanish; {es-mx} Mexican Spanish; {es-pa} Panamanian Spanish; {es-pe} Peruvian Spanish; {es-pr} Puerto Rican Spanish; {es-py} Paraguay Spanish; {es-sv} Salvadoran Spanish; {es-us} US Spanish; {es-uy} Uruguayan Spanish; {es-ve} Venezuelan Spanish. =item {suk} : Sukuma =item {sux} : Sumerian (Historical) =item {su} : Sundanese =item {sus} : Susu =item {sw} : Swahili eq Kiswahili =item {ss} : Swati =item {sv} : Swedish Notable forms: {sv-se} Sweden Swedish; {sv-fi} Finland Swedish. =item {syr} : Syriac =item {tl} : Tagalog =item {ty} : Tahitian =item [{tai} : Tai (Other)] NOT Thai! =item {tg} : Tajik =item {tmh} : Tamashek =item {ta} : Tamil =item {i-tao} : Tao eq Yami. =item {tt} : Tatar =item {i-tay} : Tayal eq Atayal. eq Atayan. =item {te} : Telugu =item {ter} : Tereno =item {tet} : Tetum =item {th} : Thai NOT Tai! =item {bo} : Tibetan =item {tig} : Tigre =item {ti} : Tigrinya =item {tem} : Timne eq Themne. eq Timene. =item {tiv} : Tiv =item {tli} : Tlingit =item {tpi} : Tok Pisin =item {tkl} : Tokelau =item {tog} : Tonga (Nyasa) NOT Tsonga! =item {to} : Tonga (Tonga Islands) (Pronounced "Tong-a", not "Tong-ga") NOT Tsonga! =item {tsi} : Tsimshian eq Sm'algyax =item {ts} : Tsonga NOT Tonga! =item {i-tsu} : Tsou =item {tn} : Tswana Same as Setswana. =item {tum} : Tumbuka =item {tr} : Turkish (Typically in Roman script) =item {ota} : Ottoman Turkish (1500-1928) (Typically in Arabic script) (Historical) =item {tk} : Turkmen eq Turkmeni. =item {tvl} : Tuvalu =item {tyv} : Tuvinian eq Tuvan. eq Tuvin. =item {tw} : Twi =item {uga} : Ugaritic NOT Ugric! =item {ug} : Uighur =item {uk} : Ukrainian =item {umb} : Umbundu =item {und} : Undetermined Not a tag for normal use. =item {ur} : Urdu =item {uz} : Uzbek eq Ezbek =item {vai} : Vai =item {ven} : Venda NOT Wendish! NOT Wend! NOT Avestan! =item {vi} : Vietnamese eq Viet. =item {vo} : Volapuk eq VolapEk. (Artificial) =item {vot} : Votic eq Votian. eq Vod. =item [{wak} : Wakashan languages] =item {wal} : Walamo eq Wolaytta. =item {war} : Waray Presumably the Philippine language Waray-Waray (SamareEo), not the smaller Philippine language Waray Sorsogon, nor the extinct Australian language Waray. =item {was} : Washo eq Washoe =item {cy} : Welsh =item {wo} : Wolof =item {x-...} : Unregistered (Semi-Private Use) "x-" is a prefix for language tags that are not registered with ISO or IANA. Example, x-double-dutch =item {xh} : Xhosa =item {sah} : Yakut =item {yao} : Yao (The Yao in Malawi?) =item {yap} : Yapese eq Yap =item {yi} : Yiddish Formerly "ji". Sometimes in Roman script, sometimes in Hebrew script. =for etc {ji} Yiddish (old tag) =item {yo} : Yoruba =item [{ypk} : Yupik languages] Several "Eskimo" languages. =item {znd} : Zande =item [{zap} : Zapotec] (A group of languages.) =item {zen} : Zenaga NOT Zend. =item {za} : Zhuang =item {zu} : Zulu =item {zun} : Zuni eq ZuEi =back =for woohah END =head1 SEE ALSO L and its "See Also" section. =head1 COPYRIGHT AND DISCLAIMER Copyright (c) 2001,2002 Sean M. Burke. All rights reserved. You can redistribute and/or modify this document under the same terms as Perl itself. This document is provided in the hope that it will be useful, but without any warranty; without even the implied warranty of accuracy, authoritativeness, completeness, merchantability, or fitness for a particular purpose. Email any corrections or questions to me. =head1 AUTHOR Sean M. Burke, sburkeE<64>cpan.org =cut # To generate a list of just the two and three-letter codes: #!/usr/local/bin/perl -w require 5; # Time-stamp: "2001-03-13 21:53:39 MST" # Sean M. Burke, sburke@cpan.org # This program is for generating the language_codes.txt file use strict; use LWP::Simple; use HTML::TreeBuilder 3.10; my $root = HTML::TreeBuilder->new(); my $url = 'http://lcweb.loc.gov/standards/iso639-2/bibcodes.html'; $root->parse(get($url) || die "Can't get $url"); $root->eof(); my @codes; foreach my $tr ($root->find_by_tag_name('tr')) { my @f = map $_->as_text(), $tr->content_list(); #print map("<$_> ", @f), "\n"; next unless @f == 5; pop @f; # nix the French name next if $f[-1] eq 'Language Name (English)'; # it's a header line my $xx = splice(@f, 2,1); # pull out the two-letter code $f[-1] =~ s/^\s+//; $f[-1] =~ s/\s+$//; if($xx =~ m/[a-zA-Z]/) { # there's a two-letter code for it push @codes, [ lc($f[-1]), "$xx\t$f[-1]\n" ]; } else { # print the three-letter codes. if($f[0] eq $f[1]) { push @codes, [ lc($f[-1]), "$f[1]\t$f[2]\n" ]; } else { # shouldn't happen push @codes, [ lc($f[-1]), "@f !!!!!!!!!!\n" ]; } } } print map $_->[1], sort {; $a->[0] cmp $b->[0] } @codes; print "[ based on $url\n at ", scalar(localtime), "]\n", "[Note: doesn't include IANA-registered codes.]\n"; exit; __END__ cgi-bin/extlib/I18N/_vti_cnf/0000755002157400001440000000000007776605372022551 5ustar minnesotaviolasociety.orgusers00000000000000cgi-bin/extlib/I18N/_vti_cnf/LangTags.pm0000644002157400001440000000030407626347050024572 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|24 Feb 2003 07:47:52 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|24 Feb 2003 07:47:52 -0000 vti_filesize:IR|25603 vti_backlinkinfo:VX| cgi-bin/extlib/I18N/LangTags.pm0000644002157400001440000006200307626347050023007 0ustar minnesotaviolasociety.orgusers00000000000000 # Time-stamp: "2002-02-02 20:43:03 MST" # Sean M. Burke require 5.000; package I18N::LangTags; use strict; use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION %Panic); require Exporter; @ISA = qw(Exporter); @EXPORT = qw(); @EXPORT_OK = qw(is_language_tag same_language_tag extract_language_tags super_languages similarity_language_tag is_dialect_of locale2language_tag alternate_language_tags encode_language_tag panic_languages ); %EXPORT_TAGS = ('ALL' => \@EXPORT_OK); $VERSION = "0.27"; =head1 NAME I18N::LangTags - functions for dealing with RFC3066-style language tags =head1 SYNOPSIS use I18N::LangTags qw(is_language_tag same_language_tag extract_language_tags super_languages similarity_language_tag is_dialect_of locale2language_tag alternate_language_tags encode_language_tag panic_languages ); ...or whatever of those functions you want to import. Those are all the exportable functions -- you're free to import only some, or none at all. By default, none are imported. If you say: use I18N::LangTags qw(:ALL) ...then all are exported. (This saves you from having to use something less obvious like C.) If you don't import any of these functions, assume a C<&I18N::LangTags::> in front of all the function names in the following examples. =head1 DESCRIPTION Language tags are a formalism, described in RFC 3066 (obsoleting 1766), for declaring what language form (language and possibly dialect) a given chunk of information is in. This library provides functions for common tasks involving language tags as they are needed in a variety of protocols and applications. Please see the "See Also" references for a thorough explanation of how to correctly use language tags. =over =cut ########################################################################### =item * the function is_language_tag($lang1) Returns true iff $lang1 is a formally valid language tag. is_language_tag("fr") is TRUE is_language_tag("x-jicarilla") is FALSE (Subtags can be 8 chars long at most -- 'jicarilla' is 9) is_language_tag("sgn-US") is TRUE (That's American Sign Language) is_language_tag("i-Klikitat") is TRUE (True without regard to the fact noone has actually registered Klikitat -- it's a formally valid tag) is_language_tag("fr-patois") is TRUE (Formally valid -- altho descriptively weak!) is_language_tag("Spanish") is FALSE is_language_tag("french-patois") is FALSE (No good -- first subtag has to match /^([xXiI]|[a-zA-Z]{2,3})$/ -- see RFC3066) is_language_tag("x-borg-prot2532") is TRUE (Yes, subtags can contain digits, as of RFC3066) =cut sub is_language_tag { ## Changes in the language tagging standards may have to be reflected here. my($tag) = lc($_[0]); return 0 if $tag eq "i" or $tag eq "x"; # Bad degenerate cases that the following # regexp would erroneously let pass return $tag =~ /^(?: # First subtag [xi] | [a-z]{2,3} ) (?: # Subtags thereafter - # separator [a-z0-9]{1,8} # subtag )* $/xs ? 1 : 0; } ########################################################################### =item * the function extract_language_tags($whatever) Returns a list of whatever looks like formally valid language tags in $whatever. Not very smart, so don't get too creative with what you want to feed it. extract_language_tags("fr, fr-ca, i-mingo") returns: ('fr', 'fr-ca', 'i-mingo') extract_language_tags("It's like this: I'm in fr -- French!") returns: ('It', 'in', 'fr') (So don't just feed it any old thing.) The output is untainted. If you don't know what tainting is, don't worry about it. =cut sub extract_language_tags { ## Changes in the language tagging standards may have to be reflected here. my($text) = $_[0] =~ m/(.+)/ # to make for an untainted result ? $1 : '' ; return grep(!m/^[ixIX]$/s, # 'i' and 'x' aren't good tags $text =~ m/ \b (?: # First subtag [iIxX] | [a-zA-Z]{2,3} ) (?: # Subtags thereafter - # separator [a-zA-Z0-9]{1,8} # subtag )* \b /xsg ); } ########################################################################### =item * the function same_language_tag($lang1, $lang2) Returns true iff $lang1 and $lang2 are acceptable variant tags representing the same language-form. same_language_tag('x-kadara', 'i-kadara') is TRUE (The x/i- alternation doesn't matter) same_language_tag('X-KADARA', 'i-kadara') is TRUE (...and neither does case) same_language_tag('en', 'en-US') is FALSE (all-English is not the SAME as US English) same_language_tag('x-kadara', 'x-kadar') is FALSE (these are totally unrelated tags) same_language_tag('no-bok', 'nb') is TRUE (no-bok is a legacy tag for nb (Norwegian Bokmal)) C works by just seeing whether C is the same as C. (Yes, I know this function is named a bit oddly. Call it historic reasons.) =cut sub same_language_tag { my $el1 = &encode_language_tag($_[0]); return 0 unless defined $el1; # this avoids the problem of # encode_language_tag($lang1) eq and encode_language_tag($lang2) # being true if $lang1 and $lang2 are both undef return $el1 eq &encode_language_tag($_[1]) ? 1 : 0; } ########################################################################### =item * the function similarity_language_tag($lang1, $lang2) Returns an integer representing the degree of similarity between tags $lang1 and $lang2 (the order of which does not matter), where similarity is the number of common elements on the left, without regard to case and to x/i- alternation. similarity_language_tag('fr', 'fr-ca') is 1 (one element in common) similarity_language_tag('fr-ca', 'fr-FR') is 1 (one element in common) similarity_language_tag('fr-CA-joual', 'fr-CA-PEI') is 2 similarity_language_tag('fr-CA-joual', 'fr-CA') is 2 (two elements in common) similarity_language_tag('x-kadara', 'i-kadara') is 1 (x/i- doesn't matter) similarity_language_tag('en', 'x-kadar') is 0 similarity_language_tag('x-kadara', 'x-kadar') is 0 (unrelated tags -- no similarity) similarity_language_tag('i-cree-syllabic', 'i-cherokee-syllabic') is 0 (no B elements in common!) =cut sub similarity_language_tag { my $lang1 = &encode_language_tag($_[0]); my $lang2 = &encode_language_tag($_[1]); # And encode_language_tag takes care of the whole # no-nyn==nn, i-hakka==zh-hakka, etc, things # NB: (i-sil-...)? (i-sgn-...)? return undef if !defined($lang1) and !defined($lang2); return 0 if !defined($lang1) or !defined($lang2); my @l1_subtags = split('-', $lang1); my @l2_subtags = split('-', $lang2); my $similarity = 0; while(@l1_subtags and @l2_subtags) { if(shift(@l1_subtags) eq shift(@l2_subtags)) { ++$similarity; } else { last; } } return $similarity; } ########################################################################### =item * the function is_dialect_of($lang1, $lang2) Returns true iff language tag $lang1 represents a subform of language tag $lang2. B is_dialect_of('en-US', 'en') is TRUE (American English IS a dialect of all-English) is_dialect_of('fr-CA-joual', 'fr-CA') is TRUE is_dialect_of('fr-CA-joual', 'fr') is TRUE (Joual is a dialect of (a dialect of) French) is_dialect_of('en', 'en-US') is FALSE (all-English is a NOT dialect of American English) is_dialect_of('fr', 'en-CA') is FALSE is_dialect_of('en', 'en' ) is TRUE is_dialect_of('en-US', 'en-US') is TRUE (B these are degenerate cases) is_dialect_of('i-mingo-tom', 'x-Mingo') is TRUE (the x/i thing doesn't matter, nor does case) is_dialect_of('nn', 'no') is TRUE (because 'nn' (New Norse) is aliased to 'no-nyn', as a special legacy case, and 'no-nyn' is a subform of 'no' (Norwegian)) =cut sub is_dialect_of { my $lang1 = &encode_language_tag($_[0]); my $lang2 = &encode_language_tag($_[1]); return undef if !defined($lang1) and !defined($lang2); return 0 if !defined($lang1) or !defined($lang2); return 1 if $lang1 eq $lang2; return 0 if length($lang1) < length($lang2); $lang1 .= '-'; $lang2 .= '-'; return (substr($lang1, 0, length($lang2)) eq $lang2) ? 1 : 0; } ########################################################################### =item * the function super_languages($lang1) Returns a list of language tags that are superordinate tags to $lang1 -- it gets this by removing subtags from the end of $lang1 until nothing (or just "i" or "x") is left. super_languages("fr-CA-joual") is ("fr-CA", "fr") super_languages("en-AU") is ("en") super_languages("en") is empty-list, () super_languages("i-cherokee") is empty-list, () ...not ("i"), which would be illegal as well as pointless. If $lang1 is not a valid language tag, returns empty-list in a list context, undef in a scalar context. A notable and rather unavoidable problem with this method: "x-mingo-tom" has an "x" because the whole tag isn't an IANA-registered tag -- but super_languages('x-mingo-tom') is ('x-mingo') -- which isn't really right, since 'i-mingo' is registered. But this module has no way of knowing that. (But note that same_language_tag('x-mingo', 'i-mingo') is TRUE.) More importantly, you assume I that superordinates of $lang1 are mutually intelligible with $lang1. Consider this carefully. =cut sub super_languages { my $lang1 = $_[0]; return() unless defined($lang1) && &is_language_tag($lang1); # a hack for those annoying new (2001) tags: $lang1 =~ s/^nb\b/no-bok/i; # yes, backwards $lang1 =~ s/^nn\b/no-nyn/i; # yes, backwards $lang1 =~ s/^[ix](-hakka\b)/zh$1/i; # goes the right way # i-hakka-bork-bjork-bjark => zh-hakka-bork-bjork-bjark my @l1_subtags = split('-', $lang1); ## Changes in the language tagging standards may have to be reflected here. # NB: (i-sil-...)? my @supers = (); foreach my $bit (@l1_subtags) { push @supers, scalar(@supers) ? ($supers[-1] . '-' . $bit) : $bit; } pop @supers if @supers; shift @supers if @supers && $supers[0] =~ m<^[iIxX]$>s; return reverse @supers; } ########################################################################### =item * the function locale2language_tag($locale_identifier) This takes a locale name (like "en", "en_US", or "en_US.ISO8859-1") and maps it to a language tag. If it's not mappable (as with, notably, "C" and "POSIX"), this returns empty-list in a list context, or undef in a scalar context. locale2language_tag("en") is "en" locale2language_tag("en_US") is "en-US" locale2language_tag("en_US.ISO8859-1") is "en-US" locale2language_tag("C") is undef or () locale2language_tag("POSIX") is undef or () locale2language_tag("POSIX") is undef or () I'm not totally sure that locale names map satisfactorily to language tags. Think REAL hard about how you use this. YOU HAVE BEEN WARNED. The output is untainted. If you don't know what tainting is, don't worry about it. =cut sub locale2language_tag { my $lang = $_[0] =~ m/(.+)/ # to make for an untainted result ? $1 : '' ; return $lang if &is_language_tag($lang); # like "en" $lang =~ tr<_><->; # "en_US" -> en-US $lang =~ s<\.[-_a-zA-Z0-9\.]*><>s; # "en_US.ISO8859-1" -> en-US return $lang if &is_language_tag($lang); return; } ########################################################################### =item * the function encode_language_tag($lang1) This function, if given a language tag, returns an encoding of it such that: * tags representing different languages never get the same encoding. * tags representing the same language always get the same encoding. * an encoding of a formally valid language tag always is a string value that is defined, has length, and is true if considered as a boolean. Note that the encoding itself is B a formally valid language tag. Note also that you cannot, currently, go from an encoding back to a language tag that it's an encoding of. Note also that you B consider the encoded value as atomic; i.e., you should not consider it as anything but an opaque, unanalysable string value. (The internals of the encoding method may change in future versions, as the language tagging standard changes over time.) C returns undef if given anything other than a formally valid language tag. The reason C exists is because different language tags may represent the same language; this is normally treatable with C, but consider this situation: You have a data file that expresses greetings in different languages. Its format is "[language tag]=[how to say 'Hello']", like: en-US=Hiho fr=Bonjour i-mingo=Hau' And suppose you write a program that reads that file and then runs as a daemon, answering client requests that specify a language tag and then expect the string that says how to greet in that language. So an interaction looks like: greeting-client asks: fr greeting-server answers: Bonjour So far so good. But suppose the way you're implementing this is: my %greetings; die unless open(IN, ") { chomp; next unless /^([^=]+)=(.+)/s; my($lang, $expr) = ($1, $2); $greetings{$lang} = $expr; } close(IN); at which point %greetings has the contents: "en-US" => "Hiho" "fr" => "Bonjour" "i-mingo" => "Hau'" And suppose then that you answer client requests for language $wanted by just looking up $greetings{$wanted}. If the client asks for "fr", that will look up successfully in %greetings, to the value "Bonjour". And if the client asks for "i-mingo", that will look up successfully in %greetings, to the value "Hau'". But if the client asks for "i-Mingo" or "x-mingo", or "Fr", then the lookup in %greetings fails. That's the Wrong Thing. You could instead do lookups on $wanted with: use I18N::LangTags qw(same_language_tag); my $repsonse = ''; foreach my $l2 (keys %greetings) { if(same_language_tag($wanted, $l2)) { $response = $greetings{$l2}; last; } } But that's rather inefficient. A better way to do it is to start your program with: use I18N::LangTags qw(encode_language_tag); my %greetings; die unless open(IN, ") { chomp; next unless /^([^=]+)=(.+)/s; my($lang, $expr) = ($1, $2); $greetings{ encode_language_tag($lang) } = $expr; } close(IN); and then just answer client requests for language $wanted by just looking up $greetings{encode_language_tag($wanted)} And that does the Right Thing. =cut sub encode_language_tag { # Only similarity_language_tag() is allowed to analyse encodings! ## Changes in the language tagging standards may have to be reflected here. my($tag) = $_[0] || return undef; return undef unless &is_language_tag($tag); # For the moment, these legacy variances are few enough that # we can just handle them here with regexps. $tag =~ s/^iw\b/he/i; # Hebrew $tag =~ s/^in\b/id/i; # Indonesian $tag =~ s/^[ix]-lux\b/lb/i; # Luxemburger $tag =~ s/^[ix]-navajo\b/nv/i; # Navajo $tag =~ s/^ji\b/yi/i; # Yiddish # # These go FROM the simplex to complex form, to get # similarity-comparison right. And that's okay, since # similarity_language_tag is the only thing that # analyzes our output. $tag =~ s/^[ix]-hakka\b/zh-hakka/i; # Hakka $tag =~ s/^nb\b/no-bok/i; # BACKWARDS for Bokmal $tag =~ s/^nn\b/no-nyn/i; # BACKWARDS for Nynorsk $tag =~ s/^[xiXI]-//s; # Just lop off any leading "x/i-" return "~" . uc($tag); } #-------------------------------------------------------------------------- =item * the function alternate_language_tags($lang1) This function, if given a language tag, returns all language tags that are alternate forms of this language tag. (I.e., tags which refer to the same language.) This is meant to handle legacy tags caused by the minor changes in language tag standards over the years; and the x-/i- alternation is also dealt with. Note that this function does I try to equate new (and never-used, and unusable) ISO639-2 three-letter tags to old (and still in use) ISO639-1 two-letter equivalents -- like "ara" -> "ar" -- because "ara" has I been in use as an Internet language tag, and RFC 3066 stipulates that it never should be, since a shorter tag ("ar") exists. Examples: alternate_language_tags('no-bok') is ('nb') alternate_language_tags('nb') is ('no-bok') alternate_language_tags('he') is ('iw') alternate_language_tags('iw') is ('he') alternate_language_tags('i-hakka') is ('zh-hakka', 'x-hakka') alternate_language_tags('zh-hakka') is ('i-hakka', 'x-hakka') alternate_language_tags('en') is () alternate_language_tags('x-mingo-tom') is ('i-mingo-tom') alternate_language_tags('x-klikitat') is ('i-klikitat') alternate_language_tags('i-klikitat') is ('x-klikitat') This function returns empty-list if given anything other than a formally valid language tag. =cut my %alt = qw( i x x i I X X I ); sub alternate_language_tags { my $tag = $_[0]; return() unless &is_language_tag($tag); my @em; # push 'em real goood! # For the moment, these legacy variances are few enough that # we can just handle them here with regexps. if( $tag =~ m/^[ix]-hakka\b(.*)/i) {push @em, "zh-hakka$1"; } elsif($tag =~ m/^zh-hakka\b(.*)/i) { push @em, "x-hakka$1", "i-hakka$1"; } elsif($tag =~ m/^he\b(.*)/i) { push @em, "iw$1"; } elsif($tag =~ m/^iw\b(.*)/i) { push @em, "he$1"; } elsif($tag =~ m/^in\b(.*)/i) { push @em, "id$1"; } elsif($tag =~ m/^id\b(.*)/i) { push @em, "in$1"; } elsif($tag =~ m/^[ix]-lux\b(.*)/i) { push @em, "lb$1"; } elsif($tag =~ m/^lb\b(.*)/i) { push @em, "i-lux$1", "x-lux$1"; } elsif($tag =~ m/^[ix]-navajo\b(.*)/i) { push @em, "nv$1"; } elsif($tag =~ m/^nv\b(.*)/i) { push @em, "i-navajo$1", "x-navajo$1"; } elsif($tag =~ m/^yi\b(.*)/i) { push @em, "ji$1"; } elsif($tag =~ m/^ji\b(.*)/i) { push @em, "yi$1"; } elsif($tag =~ m/^nb\b(.*)/i) { push @em, "no-bok$1"; } elsif($tag =~ m/^no-bok\b(.*)/i) { push @em, "nb$1"; } elsif($tag =~ m/^nn\b(.*)/i) { push @em, "no-nyn$1"; } elsif($tag =~ m/^no-nyn\b(.*)/i) { push @em, "nn$1"; } push @em, $alt{$1} . $2 if $tag =~ /^([XIxi])(-.+)/; return @em; } ########################################################################### { # Init %Panic... my @panic = ( # MUST all be lowercase! # Only large ("national") languages make it in this list. # If you, as a user, are so bizarre that the /only/ language # you claim to accept is Galician, then no, we won't do you # the favor of providing Catalan as a panic-fallback for # you. Because if I start trying to add "little languages" in # here, I'll just go crazy. # Scandinavian lgs. All based on opinion and hearsay. 'sv' => [qw(nb no da nn)], 'da' => [qw(nb no sv nn)], # I guess [qw(no nn nb)], [qw(no nn nb sv da)], 'is' => [qw(da sv no nb nn)], 'fo' => [qw(da is no nb nn sv)], # I guess # I think this is about the extent of tolerable intelligibility # among large modern Romance languages. 'pt' => [qw(es ca it fr)], # Portuguese, Spanish, Catalan, Italian, French 'ca' => [qw(es pt it fr)], 'es' => [qw(ca it fr pt)], 'it' => [qw(es fr ca pt)], 'fr' => [qw(es it ca pt)], # Also assume that speakers of the main Indian languages prefer # to read/hear Hindi over English [qw( as bn gu kn ks kok ml mni mr ne or pa sa sd te ta ur )] => 'hi', # Assamese, Bengali, Gujarati, [Hindi,] Kannada (Kanarese), Kashmiri, # Konkani, Malayalam, Meithei (Manipuri), Marathi, Nepali, Oriya, # Punjabi, Sanskrit, Sindhi, Telugu, Tamil, and Urdu. 'hi' => [qw(bn pa as or)], # I welcome finer data for the other Indian languages. # E.g., what should Oriya's list be, besides just Hindi? # And the panic languages for English is, of course, nil! # My guesses at Slavic intelligibility: ([qw(ru be uk)]) x 2, # Russian, Belarusian, Ukranian 'sr' => 'hr', 'hr' => 'sr', # Serb + Croat 'cs' => 'sk', 'sk' => 'cs', # Czech + Slovak 'ms' => 'id', 'id' => 'ms', # Malay + Indonesian 'et' => 'fi', 'fi' => 'et', # Estonian + Finnish #?? 'lo' => 'th', 'th' => 'lo', # Lao + Thai ); my($k,$v); while(@panic) { ($k,$v) = splice(@panic,0,2); foreach my $k (ref($k) ? @$k : $k) { foreach my $v (ref($v) ? @$v : $v) { push @{$Panic{$k} ||= []}, $v unless $k eq $v; } } } } =item * the function @langs = panic_languages(@accept_languages) This function takes a list of 0 or more language tags that constitute a given user's Accept-Language list, and returns a list of tags for I (non-super) languages that are probably acceptable to the user, to be used I. For example, if a user accepts only 'ca' (Catalan) and 'es' (Spanish), and the documents/interfaces you have available are just in German, Italian, and Chinese, then the user will most likely want the Italian one (and not the Chinese or German one!), instead of getting nothing. So C returns a list containing 'it' (Italian). English ('en') is I in the return list, but whether it's at the very end or not depends on the input languages. This function works by consulting an internal table that stipulates what common languages are "close" to each other. A useful construct you might consider using is: @fallbacks = super_languages(@accept_languages); push @fallbacks, panic_languages( @accept_languages, @fallbacks, ); =cut sub panic_languages { # When in panic or in doubt, run in circles, scream, and shout! my(@out, %seen); foreach my $t (@_) { next unless $t; next if $seen{$t}++; # so we don't return it or hit it again # push @out, super_languages($t); # nah, keep that separate push @out, @{ $Panic{lc $t} || next }; } return grep !$seen{$_}++, @out, 'en'; } ########################################################################### 1; __END__ =back =head1 ABOUT LOWERCASING I've considered making all the above functions that output language tags return all those tags strictly in lowercase. Having all your language tags in lowercase does make some things easier. But you might as well just lowercase as you like, or call C where appropriate. =head1 ABOUT UNICODE PLAINTEXT LANGUAGE TAGS In some future version of I18N::LangTags, I plan to include support for RFC2482-style language tags -- which are basically just normal language tags with their ASCII characters shifted into Plane 14. =head1 SEE ALSO * L * RFC 3066, C, "Tags for the Identification of Languages". (Obsoletes RFC 1766) * RFC 2277, C, "IETF Policy on Character Sets and Languages". * RFC 2231, C, "MIME Parameter Value and Encoded Word Extensions: Character Sets, Languages, and Continuations". * RFC 2482, C, "Language Tagging in Unicode Plain Text". * Locale::Codes, in C * ISO 639, "Code for the representation of names of languages", C * ISO 639-2, "Codes for the representation of names of languages", including three-letter codes, C * The IANA list of registered languages (hopefully up-to-date), C =head1 COPYRIGHT Copyright (c) 1998-2001 Sean M. Burke. All rights reserved. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. The programs and documentation in this dist are distributed in the hope that they will be useful, but without any warranty; without even the implied warranty of merchantability or fitness for a particular purpose. =head1 AUTHOR Sean M. Burke C =cut cgi-bin/extlib/IO/0000755002157400001440000000000007776605372020612 5ustar minnesotaviolasociety.orgusers00000000000000cgi-bin/extlib/IO/_vti_cnf/0000755002157400001440000000000007776605372022401 5ustar minnesotaviolasociety.orgusers00000000000000cgi-bin/extlib/IO/_vti_cnf/SessionData.pm0000644002157400001440000000030307771550070025135 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|22 Dec 2003 11:06:32 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|22 Dec 2003 11:06:32 -0000 vti_filesize:IR|5989 vti_backlinkinfo:VX| cgi-bin/extlib/IO/_vti_cnf/SessionSet.pm0000644002157400001440000000030307771550070025017 0ustar minnesotaviolasociety.orgusers00000000000000vti_encoding:SR|utf8-nl vti_timelastmodified:TR|22 Dec 2003 11:06:32 -0000 vti_extenderversion:SR|5.0.2.2623 vti_cacheddtm:TX|22 Dec 2003 11:06:32 -0000 vti_filesize:IR|4993 vti_backlinkinfo:VX| cgi-bin/extlib/IO/SessionData.pm0000644002157400001440000001354507771550070023362 0ustar minnesotaviolasociety.orgusers00000000000000# ====================================================================== # # Copyright (C) 2000 Lincoln D. Stein # Slightly modified by Paul Kulchenko to work on multiple platforms # # ====================================================================== package IO::SessionData; use strict; use Carp; use IO::SessionSet; use vars '$VERSION'; $VERSION = 1.01; use constant BUFSIZE => 3000; BEGIN { my @names = qw(EWOULDBLOCK EAGAIN EINPROGRESS); my %WOULDBLOCK = (eval {require Errno} ? map {Errno->can($_)->() => 1} grep {Errno->can($_)} @names : ()), (eval {require POSIX} ? map {POSIX->can($_)->() => 1} grep {POSIX->can($_)} @names : ()); sub WOULDBLOCK { $WOULDBLOCK{$_[0]+0} } } # Class method: new() # Create a new IO::SessionData object. Intended to be called from within # IO::SessionSet, not directly. sub new { my $pack = shift; my ($sset,$handle,