File Coverage

blib/lib/Keystone/Resolver/Utils.pm
Criterion Covered Total %
statement 22 65 33.8
branch 0 30 0.0
condition 0 3 0.0
subroutine 7 11 63.6
pod 5 6 83.3
total 34 115 29.5


line stmt bran cond sub pod time code
1             # $Id: Utils.pm,v 1.6 2008-02-15 09:49:17 mike Exp $
2              
3             package Keystone::Resolver::Utils;
4              
5 5     5   25578 use strict;
  5         10  
  5         150  
6 5     5   26 use warnings;
  5         16  
  5         190  
7 5     5   4131 use URI::Escape qw(uri_unescape uri_escape_utf8);
  5         7054  
  5         352  
8 5     5   6944 use Encode;
  5         69840  
  5         507  
9              
10 5     5   44 use Exporter 'import';
  5         9  
  5         3984  
11             our @EXPORT_OK = qw(encode_hash decode_hash utf8param
12             apache_request mod_perl_version
13             apache_non_moronic_logging);
14              
15             =head1 NAME
16              
17             Keystone::Resolver::Utils - Simple utility functions for Keystone Resolver
18              
19             =head1 SYNOPSIS
20              
21             use Keystone::Resolver::Utils qw(encode_hash decode_hash);
22             $string = encode_hash(%foo);
23             %bar = decode_hash($string);
24              
25             =head1 DESCRIPTION
26              
27             This module consists of standalone functions -- yes, that's right,
28             functions: not classes, not methods, functions. These are provided
29             for the use of Keystone Resolver.
30              
31             =head1 FUNCTIONS
32              
33             =head2 encode_hash(), decode_hash()
34              
35             $string = encode_hash(%foo);
36             %bar = decode_hash($string);
37              
38             C encodes a hash into a single scalar string, which may
39             then be stored in a database, specified as a URL parameters, etc.
40             C decodes a string created by C back
41             into a hash identical to the original.
42              
43             These two functions constitute a tiny subset of the functionality of
44             the C module, but have the pleasant property that the
45             encoded form is human-readable and therefore useful in logging. In
46             theory, the encoding is secret, but I may as well admit that the hash
47             is encoded as a URL query.
48              
49             =cut
50              
51             sub encode_hash {
52 26     26 1 7783 my(%hash) = @_;
53              
54 80         1220 return join("&", map {
55 26         96 uri_escape_utf8($_) . "=" . uri_escape_utf8($hash{$_})
56             } sort keys %hash);
57             }
58              
59             sub decode_hash {
60 13     13 1 495 my($string) = @_;
61              
62 80         1373 return (map { decode_utf8(uri_unescape($_)) }
  40         76  
63 13         40 map { (split /=/, $_, -1) } split(/&/, $string, -1));
64             }
65              
66              
67             =head2 utf8param()
68              
69             $unicodeString = utf8param($r, $key);
70             @unicodeKeys = utf8param($r);
71              
72             Returns the value associated with the parameter named C<$key> in the
73             Apache Request (or similar object) C<$r>, on the assumption that the
74             encoded value was a sequence of UTF-8 octets. These octets are
75             decoded into Unicode characters, and it is a string of these that is
76             returned.
77              
78             If called with no C<$key> parameter, returns a list of the names of
79             all parameters available in C<$r>, each such key returned as a string
80             of Unicode characters.
81              
82             =cut
83              
84             # Under Apache 2/mod_perl 2, the ubiquitous $r is no longer and
85             # Apache::Request object, nor even an Apache2::Request, but an
86             # Apache2::RequestReq ... which, astonishingly, doesn't have the
87             # param() method. So if we're given one of these things, we need to
88             # make an Apache::Request out of, which at least isn't too hard.
89             # However *sigh* this may not be a cheap operation, so we keep a cache
90             # of already-made Request objects.
91             #
92             my %_apache2request;
93             my %_paramsbyrequest; # Used for Apache2 only
94             sub utf8param {
95 0     0 1   my($r, $key, $value) = @_;
96              
97 0 0         if ($r->isa('Apache2::RequestRec')) {
98             # Running under Apache2
99 0 0         if (defined $_apache2request{$r}) {
100             #warn "using existing Apache2::RequestReq for '$r'";
101 0           $r = $_apache2request{$r};
102             } else {
103 0           require Apache2::Request;
104             #warn "making new Apache2::RequestReq for '$r'";
105 0           $r = $_apache2request{$r} = new Apache2::Request($r);
106             }
107             }
108              
109 0 0         if (!defined $key) {
110 0           return map { decode_utf8($_) } $r->param();
  0            
111             }
112              
113 0           my $raw = undef;
114 0 0         $raw = $_paramsbyrequest{$r}->{$key} if $r->isa('Apache2::Request');
115 0 0         $raw = $r->param($key) if !defined $raw;
116              
117 0 0         if (defined $value) {
118             # Argh! Simply writing through to the underlying method
119             # param() won't work in Apache2, where param() is readonly.
120             # So we have to keep a hash of additional values, which we
121             # consult (above) before the actual parameters. Ouch ouch.
122 0 0         if ($r->isa('Apache2::Request')) {
123 0           $_paramsbyrequest{$r}->{$key} = encode_utf8($value);
124             } else {
125 0           $r->param($key, encode_utf8($value));
126             }
127             }
128              
129 0 0         return undef if !defined $raw;
130 0           my $cooked = decode_utf8($raw);
131 0 0         warn "converted '$raw' to '", $cooked, "'\n" if $cooked ne $raw;
132 0           return $cooked;
133             }
134              
135              
136             =head2 apache_request()
137              
138             my $r = apache_request($cgi);
139              
140             Because the Apache/Perl project people saw fit to totally change the
141             API between C versions 1 and 2, and because the environment
142             variables that might tell you what version is in use are undocumented
143             and obscure, it is pretty painful getting hold of the Apache request
144             object in a portable way -- which you need for things like setting the
145             content-type. C does this, returning the Apache 1
146             or 2 request object if running under Apache, and otherwise returning
147             the fallback object which is passed in, if any.
148              
149             =cut
150              
151             sub apache_request {
152 0     0 1   my($fallback) = @_;
153              
154 0           my $ver = mod_perl_version();
155             #warn "ver=", (defined $ver ? "'$ver'" : "UNDEFINED"), "\n";
156 0 0         if (!defined $ver) {
157             #warn "Fallback: r='$fallback'\n";
158 0           return $fallback;
159             }
160              
161 0 0         if ($ver == 2) {
162 0           require Apache2::RequestUtil;
163 0           my $r = Apache2::RequestUtil->request();
164             #warn "Apache2: r='$r'\n";
165 0           return $r;
166             }
167              
168 0 0         if ($ver == 1) {
169 0           require Apache;
170 0           my $r = Apache->request();
171             #warn "Apache: r='$r'\n";
172 0           return $r;
173             }
174              
175 0           die "unknown mod_perl version '$ver'";
176             }
177              
178              
179             =head2 mod_perl_version()
180              
181             $ver = mod_perl_version();
182              
183             Returns the major API version number of the version C in
184             effect, or an undefined value if not running under mod_perl (e.g. as
185             an external CGI script or from the command-line).
186              
187             =cut
188              
189             # By inspection, it seems that mod_perl version 2 sets the
190             # MOD_PERL_API_VERSION environment variable, but mod_perl version 1
191             # does not; but that both set MOD_PERL.
192             #
193             sub mod_perl_version {
194 0     0 1   my $api = $ENV{MOD_PERL_API_VERSION};
195 0 0         return $api if defined $api;
196 0           my $mp = $ENV{MOD_PERL};
197 0 0         return undef if !defined $mp;
198             # $mp is of the form "mod_perl/1.29"
199 0           $mp =~ s/mod_perl\/([0-9]+)\..*/$1/;
200 0           return $mp;
201             }
202              
203              
204             =head2
205              
206             apache_non_moronic_logging()
207              
208             I hate the world.
209              
210             For reasons which no rational being could ever fathom, one of the
211             differences between Apache 1.x/mod_perl and Apache 2.x/mod_perl2 is
212             that in the latter, calls to C result in the output going to
213             the I error-log of the server rather than the the error-log of
214             the virtual site. I know, I know, it is truly astonishing. I will
215             not meditate on this further. See the section entitled C
216             Hosts> in the C manual for details, or see the online
217             version at
218             http://perl.apache.org/docs/2.0/api/Apache2/Log.html#Virtual_Hosts
219              
220             Anyway, call C to globally fix this by
221             aliasing C to the non-braindead Apache2 logging function
222             of the same name. Calling under mod_perl 1, or not under mod_perl at
223             all, will no-op.
224              
225             I<### except -- it turns out -- this doesn't actually work, even
226             though it is the very code from the Apache2::Log manual. Or rather,
227             it works intermittently. So I think you will just have to read the
228             global log as well as the resolver log. Nice.>
229              
230             =cut
231              
232             sub apache_non_moronic_logging {
233 0     0 0   my $ver = mod_perl_version();
234 0 0 0       if (defined $ver && $ver == 2) {
235 0           require "Apache2/Log.pm";
236 0           *CORE::GLOBAL::warn = \&Apache2::ServerRec::warn;
237             #warn "calling CORE::warn() as warn()";
238             #CORE::warn "calling CORE::warn() as CORE::warn()";
239             #Apache2::ServerRec::warn "calling Apache2::ServerRec::warn()";
240             }
241             }
242              
243              
244             1;