File Coverage

lib/App/UserAgent.pm
Criterion Covered Total %
statement 49 100 49.0
branch 16 54 29.6
condition 3 9 33.3
subroutine 5 6 83.3
pod 4 4 100.0
total 77 173 44.5


line stmt bran cond sub pod time code
1              
2             #############################################################################
3             ## $Id: UserAgent.pm 12476 2009-02-06 03:35:02Z spadkins $
4             #############################################################################
5              
6             package App::UserAgent;
7             $VERSION = (q$Revision: 12476 $ =~ /(\d[\d\.]*)/)[0]; # VERSION numbers generated by svn
8              
9 6     6   37 use strict;
  6         16  
  6         307  
10              
11 6     6   39 use App;
  6         22  
  6         7813  
12              
13             =head1 NAME
14              
15             App::UserAgent - the browser this session is connected to
16              
17             =head1 SYNOPSIS
18              
19             # ... official way to get a UserAgent object ...
20             use App;
21             $context = App->context();
22             $user_agent = $context->user_agent(); # get the user_agent
23              
24             if ($user_agent->supports("html.input.style")) {
25             # do something
26             }
27              
28             =cut
29              
30             #############################################################################
31             # CONSTANTS
32             #############################################################################
33              
34             =head1 DESCRIPTION
35              
36             A UserAgent class models the browser connected to this session.
37             It is used to determine what capabilities are supported by the user agent.
38              
39             =cut
40              
41             #############################################################################
42             # CONSTRUCTOR METHODS
43             #############################################################################
44              
45             =head1 Constructor Methods:
46              
47             =cut
48              
49             #############################################################################
50             # new()
51             #############################################################################
52              
53             =head2 new()
54              
55             The App::UserAgent->new() method is rarely called directly.
56             That is because a $user_agent should always be instantiated by getting
57             it from the $context [ $context->user_agent() ].
58              
59             * Signature: $user_agent = App::UserAgent->new($context);
60             * Signature: $user_agent = App::UserAgent->new();
61             * Param: $context App::Context
62             * Return: $user_agent App::UserAgent
63             * Throws:
64             * Since: 0.01
65              
66             Sample Usage:
67              
68             [Common Use]
69             $context = App->context();
70             $user_agent = $context->user_agent();
71              
72             [Internal Use Only]
73             $user_agent = App::UserAgent->new();
74              
75             =cut
76              
77             sub new {
78 1     1 1 2 my $this = shift;
79 1   33     8 my $class = ref($this) || $this;
80 1         2 my $self = {};
81 1         3 bless $self, $class;
82 1         2 my ($context) = @_;
83              
84 1         7 $self->{context} = $context;
85              
86 1 50       6 $self->{http_user_agent} = (defined $ENV{HTTP_USER_AGENT}) ? $ENV{HTTP_USER_AGENT} : "unknown";
87              
88 1         3 my ($lang);
89 1 50       4 if (defined $ENV{HTTP_ACCEPT_LANGUAGE}) {
90 0         0 $lang = lc($ENV{HTTP_ACCEPT_LANGUAGE});
91 0         0 $lang =~ s/[ ,].*//; # trim off everything after the first comma or space
92             }
93              
94 1         6 my ($uatype, $uaver, $ostype, $osver, $arch, $ualang) =
95             $self->parse($self->{http_user_agent});
96 1 50 33     14 $lang = $ualang if (!$lang && $ualang);
97              
98 1         3 $self->{uatype} = $uatype;
99 1         2 $self->{uaver} = $uaver;
100 1         3 $self->{ostype} = $ostype;
101 1         2 $self->{osver} = $osver;
102 1         2 $self->{arch} = $arch;
103 1         8 $self->{lang} = $lang;
104              
105 1         5 $self->{supports} = $self->get_support_matrix($uatype, $uaver,
106             $ostype, $osver, $arch, $lang);
107              
108 1         4 return $self;
109             }
110              
111             #############################################################################
112             # PUBLIC METHODS
113             #############################################################################
114              
115             =head1 Public Methods
116              
117             =cut
118              
119             #############################################################################
120             # supports()
121             #############################################################################
122              
123             =head2 supports()
124              
125             The supports() method returns whether or not a "feature" or "capability" is
126             supported by a user agent (browser).
127              
128             * Signature: $bool = $self->supports($capability);
129             * Param: $capability string
130             * Return: $bool boolean
131             * Throws:
132             * Since: 0.01
133              
134             Sample Usage:
135              
136             if ($ua->supports("html.input.style")) {
137             # do something
138             }
139              
140             The following are some of the types of capabilities that the
141             browser may or may not support.
142             The capability categorization scheme is derived from the O'Reilly book,
143             "Dynamic HTML: The Definitive Reference", which has sections on HTML,
144             DOM, CSS, and JavaScript. Java and HTTP capabilities are also
145             defined.
146             Finally, hints are defined which simply tell the session objects
147             what to use on certain browsers.
148              
149             html.
150             html..
151             html.input.style
152             html.input.style.border-width
153              
154             dom
155             dom.
156             dom..
157              
158             style
159             style.css1
160             style.css2
161             style.
162              
163             js
164             js.1.0
165             js.1.1
166             js.1.2
167             js..
168             js..
169              
170             java.1.0.0
171             java.1.2.2
172             java.1.3.0
173              
174             http.header.accept-encoding.x-gzip
175             http.header.accept-encoding.x-compress
176              
177             session_object.Stylizable.style
178              
179             =cut
180              
181             sub supports {
182 0     0 1 0 my ($self, $capability) = @_;
183              
184             # return immediately if support for the capability is already determined
185 0 0       0 if (defined $self->{supports}{$capability}) {
186 0         0 return ($self->{supports}{$capability});
187             }
188              
189 0 0       0 if ($capability eq "http.header.accept-encoding.x-gzip") {
190 0         0 my ($request, $accept_header, $support_status);
191 0         0 $request = $self->{context}->request();
192 0         0 $accept_header = $request->header("Accept-Encoding");
193 0 0       0 $support_status = ($accept_header =~ /gzip/) ? 1 : 0;
194 0         0 $self->{supports}{$capability} = $support_status;
195 0         0 return $support_status;
196             }
197              
198             # see if this capability has a "parent" capability
199 0 0       0 if ($capability =~ /^(.*)\.([^\.]+)$/) {
200             # we support it if we support its parent capability
201 0         0 $self->{supports}{$capability} = $self->supports($1);
202             }
203             else {
204             # assume we support everything unless otherwise informed
205 0         0 $self->{supports}{$capability} = 1;
206             }
207 0         0 return $self->{supports}{$capability};
208             }
209              
210             #############################################################################
211             # get()
212             #############################################################################
213              
214             =head2 get()
215              
216             The get() method retrieves attributes of the user agent.
217              
218             * Signature: $bool = $self->parse($http_user_agent);
219             * Param: $http_user_agent string
220             * Return: $bool boolean
221             * Throws:
222             * Since: 0.01
223              
224             Sample Usage:
225              
226             $http_user_agent = "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)";
227             @ua = $user_agent->parse($http_user_agent);
228             @ua = $App::UserAgent->parse($ENV{HTTP_USER_AGENT});
229             ($uatype, $uaver, $ostype, $osver, $arch, $lang) = @ua;
230              
231             The following attributes of the $user_agent are also defined.
232             The bracketed values ([value]) are the defaults if no other value can
233             be determined by the HTTP_USER_AGENT string and the other HTTP headers.
234              
235             uatype - User Agent type (i.e. [unknown], NS, IE, Opera, Konqueror, Mozilla)
236             uaver - User Agent version (i.e. [1.0], 4.0, 4.7, 5.01) (always numeric)
237             ostype - Oper System type (i.e. [unknown], Windows, Macintosh, Linux, FreeBSD, HP-UX, SunOS, AIX, IRIX, OSF1)
238             osver - Oper System version (i.e. [unknown], 16, 3.1, 95, 98, 2000, ME, NT 5.1)
239             arch - Hardware Architecture (i.e. [unknown], i386, i586, i686, ppc, sun4u, 9000/835)
240             lang - Preferred Language (i.e. [en], en-us, fr-ca, ja, de)
241              
242             There is very little reason for any SessionObject code to call get() directly.
243             SessionObjects should rather use the supports() method to determine whether a
244             capability is supported by the browser. The supports method will
245             consult these attributes and its capability matrix to determine whether
246             the capability is supported or not.
247              
248             sub get {
249             my ($self, $attribute) = @_;
250             $self->{$attribute};
251             }
252              
253             #############################################################################
254             # parse()
255             #############################################################################
256              
257             =head2 parse()
258              
259             The parse() method parses an HTTP_USER_AGENT string and returns the
260             resulting attributes of the browser.
261              
262             * Signature: $bool = $self->parse($http_user_agent);
263             * Param: $http_user_agent string
264             * Return: $bool boolean
265             * Throws:
266             * Since: 0.01
267              
268             Sample Usage:
269              
270             $http_user_agent = "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)";
271             @ua = $user_agent->parse($http_user_agent);
272             @ua = $App::UserAgent->parse($ENV{HTTP_USER_AGENT});
273             ($uatype, $uaver, $ostype, $osver, $arch, $lang) = @ua;
274              
275             Note: Two additional attributes, $mozver and $iever are probably going to
276             be needed. They represent the Netscape/Mozilla version that the software
277             claims to operate like (IE has always included this) and the IE version
278             that the software claims to operate like (Opera includes this).
279             This will allow for a cascading of one type of compatibility matrix into
280             another.
281              
282             =cut
283              
284             sub parse {
285 1     1 1 3 my ($self, $http_user_agent) = @_;
286 1         1 my ($uatype, $uaver, $ostype, $osver, $arch, $lang);
287 0         0 my ($ua);
288              
289 1         3 $uatype = "unknown"; # NS, IE, Opera, Konqueror, Mozilla, unknown
290 1         2 $uaver = 1.0; # 4.0, 4.7, 5.01
291 1 50       13 if ($http_user_agent =~ /MSIE[ \+\/]*([0-9][\.0-9]*)/) {
    50          
    50          
    50          
    50          
292 0         0 $uatype = "IE"; # MS Internet Explorer
293 0         0 $uaver = $1;
294             }
295             elsif ($http_user_agent =~ /Gecko[ \+\/]*([0-9][\.0-9]*)/) {
296 0         0 $uatype = "Mozilla"; # from www.mozilla.org
297 0         0 $uaver = $1;
298             }
299             # Opera should be first (unless we are OK to believe it is really MSIE)
300             elsif ($http_user_agent =~ /Opera[ \+\/]*([0-9][\.0-9]*)/) {
301 0         0 $uatype = "Opera";
302 0         0 $uaver = $1;
303             }
304             elsif ($http_user_agent =~ /Konqueror[ \+\/]*([0-9][\.0-9]*)/) {
305 0         0 $uatype = "Konqueror";
306 0         0 $uaver = $1;
307             }
308             elsif ($http_user_agent =~ /Mozilla[ \+\/]*([0-9][\.0-9]*)/) {
309 0         0 $uatype = "NS"; # the original Mozilla browser
310 0         0 $uaver = $1;
311             }
312              
313             # ostype/osver
314 1         2 $ostype = "unknown"; # Windows, Macintosh, Linux, FreeBSD, HP-UX, SunOS
315 1         3 $osver = "unknown"; # 16, 3.1, 95, 98, 2000, ME, CE, NT 5.1
316 1         2 $arch = "unknown"; # i386, i586, i686, PPC
317 1         2 $lang = ""; # en, en-US, ja, de
318              
319 1         2 $ua = $http_user_agent;
320 1         2 $ua =~ s/\+/ /g;
321 1         3 $ua =~ s/Service Pack /SP/g;
322 1 50       13 if ($ua =~ /Win/) {
323 0 0       0 if ($ua =~ /Win16/) {
    0          
    0          
    0          
    0          
324 0         0 $ostype = "Windows";
325 0         0 $osver = "16";
326             }
327             elsif ($ua =~ /Win32/) {
328 0         0 $ostype = "Windows";
329 0         0 $osver = "32";
330             }
331             elsif ($ua =~ /Win(9[58x])/) {
332 0         0 $ostype = "Windows";
333 0         0 $osver = $1;
334             }
335             elsif ($ua =~ /Win(NT *[SP0-9. ]*)/) {
336 0         0 $ostype = "Windows";
337 0         0 $osver = $1;
338 0         0 $osver =~ s/ +$//;
339             }
340             elsif ($ua =~ /Windows *([239MCX][A-Z0-9. \/]*)/) {
341 0         0 $ostype = "Windows";
342 0         0 $osver = $1;
343 0         0 $osver =~ s/ +$//;
344             }
345             }
346 1 50       5 if ($ostype eq "unknown") { # haven't found it yet
347 1 50       8 if ($ua =~ /Linux/) {
    50          
348 0         0 $ostype = "Linux";
349 0 0       0 if ($ua =~ /Linux +([0-9][0-9\.a-z-]*) +([a-zA-Z0-9-]+)/) {
    0          
350 0         0 $osver = $1;
351 0         0 $arch = $2;
352             }
353             elsif ($ua =~ /Linux +([0-9][0-9\.a-z-]*)/) {
354 0         0 $osver = $1;
355             }
356             }
357             elsif ($ua =~ /X11/) {
358 0         0 $ostype = "X11";
359             }
360             }
361              
362             # arch
363 1 50       6 if ($http_user_agent =~ /MSIE[ \+]?([0-9][\.0-9]*)/) {
364 0         0 $uatype = "IE";
365 0         0 $uaver = $1;
366             }
367              
368             # lang
369             # NOTE: This is woefully inadequate. Thankfully, the ACCEPT-LANGUAGE header exists
370             # so this is really not needed.
371 1 50       7 if ($http_user_agent =~ /\[([a-zA-Z]{2})\]/) {
    50          
372 0         0 $lang = $1;
373             }
374             elsif ($http_user_agent =~ /\[([a-zA-Z]{2}[-_][a-zA-Z]{2})\]/) {
375 0         0 $lang = $1;
376             }
377              
378 1         6 return ($uatype, $uaver, $ostype, $osver, $arch, $lang);
379             }
380              
381             #############################################################################
382             # PROTECTED METHODS
383             #############################################################################
384              
385             =head1 Protected Methods
386              
387             =cut
388              
389             #############################################################################
390             # get_support_matrix()
391             #############################################################################
392              
393             =head2 get_support_matrix()
394              
395             The get_support_matrix() method returns whether or not a "feature" or "capability" is
396             supported by a user agent (browser).
397              
398             * Signature: $support_matrix = $ua->get_support_matrix($uatype, $uaver, $ostype, $osver, $arch, $lang);
399             * Param: $uatype string
400             * Param: $uaver float
401             * Param: $ostype string
402             * Param: $osver string
403             * Param: $arch string
404             * Param: $lang string
405             * Return: $support_matrix {}
406             * Throws:
407             * Since: 0.01
408              
409             Sample Usage:
410              
411             $support_matrix = $self->get_support_matrix($uatype, $uaver, $ostype, $osver, $arch, $lang);
412              
413             The following are some of the types of capabilities that the
414             browser may or may not support.
415              
416             =cut
417              
418             sub get_support_matrix {
419 1     1 1 3 my ($self, $uatype, $uaver, $ostype, $osver, $arch, $lang) = @_;
420 1         2 my ($support_matrix);
421              
422             # eventually, this will probably attach to an external DBM-style
423             # capabilities database. But for now, we just need a few features.
424 1         2 $support_matrix = {};
425              
426 1 50 33     7 if ($uatype eq "NS" && $uaver <= 4.7) {
427 0         0 $support_matrix->{"session_object.Stylizable.style"} = 0;
428             }
429             else {
430 1         4 $support_matrix->{"session_object.Stylizable.style"} = 1;
431             }
432              
433 1         4 return $support_matrix;
434             }
435              
436             1;
437