| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853 |
- ########################################################################################
- #
- # Common.pm
- #
- # $Id: Common.pm 10759 2016-02-07 20:00:12Z rleins $
- #
- # Now (in this version) part of Fhem.
- #
- # Fhem is free software: you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation, either version 2 of the License, or
- # (at your option) any later version.
- #
- # Fhem is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with fhem. If not, see <http://www.gnu.org/licenses/>.
- #
- ########################################################################################
- package UPnP::Common;
- use 5.006;
- use strict;
- use warnings;
- use HTTP::Headers;
- use IO::Socket;
- use vars qw(@EXPORT $VERSION @ISA $AUTOLOAD);
- require Exporter;
- our @ISA = qw(Exporter);
- our $VERSION = '0.03';
- my %XP_CONSTANTS = (
- SSDP_IP => "239.255.255.250",
- SSDP_PORT => 1900,
- CRLF => "\015\012",
- IP_LEVEL => getprotobyname('ip') || 0,
- );
- #ALW - Changed from 'MSWin32' => [3,5],
- my @MD_CONSTANTS = qw(IP_MULTICAST_TTL IP_ADD_MEMBERSHIP);
- my %MD_CONSTANT_VALUES = (
- 'MSWin32' => [10,12],
- 'cygwin' => [3,5],
- 'darwin' => [10,12],
- 'linux' => [33,35],
- 'default' => [33,35],
- );
- @EXPORT = qw();
- use constant PROBE_IP => "239.255.255.251";
- use constant PROBE_PORT => 8950;
- my $ref = $MD_CONSTANT_VALUES{$^O};
- if (!defined($ref)) {
- $ref = $MD_CONSTANT_VALUES{default};
- }
- my $consts;
- for my $name (keys %XP_CONSTANTS) {
- $consts .= "use constant $name => \'" . $XP_CONSTANTS{$name} . "\';\n";
- }
- for my $index (0..$#MD_CONSTANTS) {
- my $name = $MD_CONSTANTS[$index];
- $consts .= "use constant $name => \'" . $ref->[$index] . "\';\n";
- }
- #warn $consts; # for development
- eval $consts;
- push @EXPORT, (keys %XP_CONSTANTS, @MD_CONSTANTS);
- #findLocalIP();
- my %typeMap = (
- 'ui1' => 'int',
- 'ui2' => 'int',
- 'ui4' => 'int',
- 'i1' => 'int',
- 'i2' => 'int',
- 'i4' => 'int',
- 'int' => 'int',
- 'r4' => 'float',
- 'r8' => 'float',
- 'number' => 'float',
- 'fixed' => 'float',
- 'float' => 'float',
- 'char' => 'string',
- 'string' => 'string',
- 'date' => 'timeInstant',
- 'dateTime.tz' => 'timeInstant',
- 'time' => 'timeInstant',
- 'time.tz' => 'timeInstant',
- 'boolean' => 'boolean',
- 'bin.base64' => 'base64Binary',
- 'bin.hex' => 'hexBinary',
- 'uri' => 'uriReference',
- 'uuid' => 'string',
- );
- BEGIN {
- use SOAP::Lite;
- $SOAP::Constants::DO_NOT_USE_XML_PARSER = 1;
- }
- sub getLocalIP {
- if (defined $UPnP::Common::LocalIP) {
- return $UPnP::Common::LocalIP;
- }
- my $probeSocket = IO::Socket::INET->new(Proto => 'udp',
- Reuse => 1);
- my $listenSocket = IO::Socket::INET->new(Proto => 'udp',
- Reuse => 1,
- LocalPort => PROBE_PORT);
- my $ip_mreq = inet_aton(PROBE_IP) . INADDR_ANY;
- setsockopt($listenSocket,
- getprotobyname('ip'),
- $ref->[1],
- $ip_mreq);
- my $destaddr = sockaddr_in(PROBE_PORT, inet_aton(PROBE_IP));
- send($probeSocket, "Test", 0, $destaddr);
- my $buf = '';
- my $peer = recv($listenSocket, $buf, 2048, 0);
- my ($port, $addr) = sockaddr_in($peer);
-
- $probeSocket->close;
- $listenSocket->close;
- setLocalIP($addr);
- return $UPnP::Common::LocalIP;
- }
- sub setLocalIP {
- my ($addr) = @_;
- $UPnP::Common::LocalIP = inet_ntoa($addr);
- }
- sub parseHTTPHeaders {
- my $buf = shift;
- my $headers = HTTP::Headers->new;
-
- # Header parsing code borrowed from HTTP::Daemon
- my($key, $val);
- HEADER:
- while ($buf =~ s/^([^\012]*)\012//) {
- $_ = $1;
- s/\015$//;
- if (/^([^:\s]+)\s*:\s*(.*)/) {
- $headers->push_header($key => $val) if $key;
- ($key, $val) = ($1, $2);
- }
- elsif (/^\s+(.*)/) {
- $val .= " $1";
- }
- else {
- last HEADER;
- }
- }
- $headers->push_header($key => $val) if $key;
- return $headers;
- }
- sub UPnPToSOAPType {
- my $upnpType = shift;
- return $typeMap{$upnpType};
- }
- # ----------------------------------------------------------------------
- package UPnP::Common::DeviceLoader;
- use strict;
- sub new {
- my $self = shift;
- my $class = ref($self) || $self;
- return bless {
- _parser => UPnP::Common::Parser->new,
- }, $class;
- }
- sub parser {
- my $self = shift;
- return $self->{_parser};
- }
- sub parseServiceElement {
- my $self = shift;
- my $element = shift;
- my($name, $attrs, $children) = @$element;
- my $service = $self->newService(%{$_[1]});
- for my $childElement (@$children) {
- my $childName = $childElement->[0];
- if (UPnP::Common::Service::isProperty($childName)) {
- my $value = $childElement->[2];
- $service->$childName($value);
- }
- }
- return $service;
- }
- sub parseDeviceElement {
- my $self = shift;
- my $element = shift;
- my $parent = shift;
- my($name, $attrs, $children) = @$element;
- my $device = $self->newDevice(%{$_[0]});
- $device->parent($parent);
- for my $childElement (@$children) {
- my $childName = $childElement->[0];
- if ($childName eq 'deviceList') {
- my $childDevices = $childElement->[2];
- next if (ref $childDevices ne "ARRAY");
- for my $deviceElement (@$childDevices) {
- my $childDevice = $self->parseDeviceElement($deviceElement,
- $device,
- @_);
- if ($childDevice) {
- $device->addChild($childDevice);
- }
- }
- }
- elsif ($childName eq 'serviceList') {
- my $services = $childElement->[2];
- next if (ref $services ne "ARRAY");
- for my $serviceElement (@$services) {
- my $service = $self->parseServiceElement($serviceElement,
- @_);
- if ($service) {
- $device->addService($service);
- }
- }
- }
- elsif (UPnP::Common::Device::isProperty($childName)) {
- my $value = $childElement->[2];
- $device->$childName($value);
- }
- }
- return $device;
- }
- sub parseDeviceDescription {
- my $self = shift;
- my $description = shift;
- my ($base, $device);
- my $parser = $self->parser;
- my $element = $parser->parse($description);
- if (defined($element) && ref $element eq 'ARRAY') {
- my($name, $attrs, $children) = @$element;
- for my $child (@$children) {
- my ($childName) = @$child;
- if ($childName eq 'URLBase') {
- $base = $child->[2];
- }
- elsif ($childName eq 'device') {
- $device = $self->parseDeviceElement($child,
- undef,
- @_);
- }
- }
- }
- return ($device, $base);
- }
- # ----------------------------------------------------------------------
- package UPnP::Common::Device;
- use strict;
- use Carp;
- use Scalar::Util qw(weaken);
- use vars qw($AUTOLOAD %deviceProperties);
- for my $prop (qw(deviceType friendlyName manufacturer
- manufacturerURL modelDescription modelName
- modelNumber modelURL serialNumber UDN
- presentationURL UPC location)) {
- $deviceProperties{$prop}++;
- }
- sub new {
- my $self = shift;
- my $class = ref($self) || $self;
- my %args = @_;
- $self = bless {}, $class;
- if ($args{Location}) {
- $self->location($args{Location});
- }
- return $self;
- }
- sub addChild {
- my $self = shift;
- my $child = shift;
- push @{$self->{_children}}, $child;
- }
- sub addService {
- my $self = shift;
- my $service = shift;
- push @{$self->{_services}}, $service;
- }
- sub parent {
- my $self = shift;
- if (@_) {
- $self->{_parent} = shift;
- weaken($self->{_parent});
- }
- return $self->{_parent};
- }
- sub children {
- my $self = shift;
-
- if (ref $self->{_children}) {
- return @{$self->{_children}};
- }
- return ();
- }
- sub services {
- my $self = shift;
-
- if (ref $self->{_services}) {
- return @{$self->{_services}};
- }
- return ();
- }
- sub getService {
- my $self = shift;
- my $id = shift;
- for my $service ($self->services) {
- if ($id &&
- ($id eq $service->serviceId) ||
- ($id eq $service->serviceType)) {
- return $service;
- }
- }
- return undef;
- }
- sub isProperty {
- my $prop = shift;
- return $deviceProperties{$prop};
- }
- sub AUTOLOAD {
- my $self = shift;
- my $attr = $AUTOLOAD;
- $attr =~ s/.*:://;
- return if $attr eq 'DESTROY';
- croak "invalid attribute method: ->$attr()" unless $deviceProperties{$attr};
- $self->{uc $attr} = shift if @_;
- return $self->{uc $attr};
- }
- # ----------------------------------------------------------------------
- package UPnP::Common::Service;
- use strict;
- use SOAP::Lite;
- use Carp;
- use vars qw($AUTOLOAD %serviceProperties);
- for my $prop (qw(serviceType serviceId SCPDURL controlURL
- eventSubURL base)) {
- $serviceProperties{$prop}++;
- }
- sub new {
- my $self = shift;
- my $class = ref($self) || $self;
- return bless {}, $class;
- }
- sub AUTOLOAD {
- my $self = shift;
- my $attr = $AUTOLOAD;
- $attr =~ s/.*:://;
- return if $attr eq 'DESTROY';
- croak "invalid attribute method: ->$attr()" unless $serviceProperties{$attr};
- $self->{uc $attr} = shift if @_;
- return $self->{uc $attr};
- }
- sub isProperty {
- my $prop = shift;
- return $serviceProperties{$prop};
- }
- sub addAction {
- my $self = shift;
- my $action = shift;
- $self->{_actions}->{$action->name} = $action;
- }
- sub addStateVariable {
- my $self = shift;
- my $var = shift;
- $self->{_stateVariables}->{$var->name} = $var;
- }
- sub actions {
- my $self = shift;
- $self->_loadDescription;
-
- if (defined($self->{_actions})) {
- return values %{$self->{_actions}};
- }
- return ();
- }
- sub getAction {
- my $self = shift;
- my $name = shift;
- $self->_loadDescription;
- if (defined($self->{_actions})) {
- return $self->{_actions}->{$name};
- }
- return undef;
- }
- sub stateVariables {
- my $self = shift;
- $self->_loadDescription;
- if (defined($self->{_stateVariables})) {
- return values %{$self->{_stateVariables}};
- }
- return ();
- }
- sub getStateVariable {
- my $self = shift;
- my $name = shift;
- $self->_loadDescription;
- if (defined($self->{_stateVariables})) {
- return $self->{_stateVariables}->{$name};
- }
- return undef;
- }
- sub getArgumentType {
- my $self = shift;
- my $arg = shift;
- $self->_loadDescription;
- my $var = $self->getStateVariable($arg->relatedStateVariable);
- if ($var) {
- return $var->SOAPType;
- }
- return undef;
- }
- sub _parseArgumentList {
- my $self = shift;
- my $list = shift;
- my $action = shift;
- return if (! ref $list);
- for my $argumentElement (@$list) {
- my($name, $attrs, $children) = @$argumentElement;
- if ($name eq 'argument') {
- my $argument = UPnP::Common::Argument->new;
- for my $argumentChild (@$children) {
- my ($childName) = @$argumentChild;
- if ($childName eq 'name') {
- $argument->name($argumentChild->[2]);
- }
- elsif ($childName eq 'direction') {
- my $direction = $argumentChild->[2];
- if ($direction eq 'in') {
- $action->addInArgument($argument);
- }
- elsif ($direction eq 'out') {
- $action->addOutArgument($argument);
- }
- }
- elsif ($childName eq 'relatedStateVariable') {
- $argument->relatedStateVariable($argumentChild->[2]);
- }
- elsif ($childName eq 'retval') {
- $action->retval($argument);
- }
- }
- }
- }
- }
- sub _parseActionList {
- my $self = shift;
- my $list = shift;
- for my $actionElement (@$list) {
- my($name, $attrs, $children) = @$actionElement;
- if ($name eq 'action') {
- my $action = UPnP::Common::Action->new;
- for my $actionChild (@$children) {
- my ($childName) = @$actionChild;
- if ($childName eq 'name') {
- $action->name($actionChild->[2]);
- }
- elsif ($childName eq 'argumentList') {
- $self->_parseArgumentList($actionChild->[2],
- $action);
- }
- }
- $self->addAction($action);
- }
- }
- }
- sub _parseStateTable {
- my $self = shift;
- my $list = shift;
- for my $varElement (@$list) {
- my($name, $attrs, $children) = @$varElement;
- if ($name eq 'stateVariable') {
- my $var = UPnP::Common::StateVariable->new(exists $attrs->{sendEvents} && ($attrs->{sendEvents} eq 'yes'));
- for my $varChild (@$children) {
- my ($childName) = @$varChild;
- if ($childName eq 'name') {
- $var->name($varChild->[2]);
- }
- elsif ($childName eq 'dataType') {
- $var->type($varChild->[2]);
- }
- }
- $self->addStateVariable($var);
- }
- }
- }
- sub parseServiceDescription {
- my $self = shift;
- my $parser = shift;
- my $description = shift;
- my $element = $parser->parse($description);
- if (defined($element) && ref $element eq 'ARRAY') {
- my($name, $attrs, $children) = @$element;
- for my $child (@$children) {
- my ($childName) = @$child;
- if ($childName eq 'actionList') {
- $self->_parseActionList($child->[2]);
- }
- elsif ($childName eq 'serviceStateTable') {
- $self->_parseStateTable($child->[2]);
- }
- }
- }
- else {
- carp("Malformed SCPD document");
- }
- }
- # ----------------------------------------------------------------------
- package UPnP::Common::Action;
- use strict;
- use Carp;
- use vars qw($AUTOLOAD %actionProperties);
- for my $prop (qw(name retval)) {
- $actionProperties{$prop}++;
- }
- sub new {
- return bless {}, shift;
- }
- sub AUTOLOAD {
- my $self = shift;
- my $attr = $AUTOLOAD;
- $attr =~ s/.*:://;
- return if $attr eq 'DESTROY';
- croak "invalid attribute method: ->$attr()" unless $actionProperties{$attr};
- $self->{uc $attr} = shift if @_;
- return $self->{uc $attr};
- }
- sub addInArgument {
- my $self = shift;
- my $argument = shift;
- push @{$self->{_inArguments}}, $argument;
- }
- sub addOutArgument {
- my $self = shift;
- my $argument = shift;
- push @{$self->{_outArguments}}, $argument;
- }
- sub inArguments {
- my $self = shift;
- if (defined $self->{_inArguments}) {
- return @{$self->{_inArguments}};
- }
- return ();
- }
- sub outArguments {
- my $self = shift;
- if (defined $self->{_outArguments}) {
- return @{$self->{_outArguments}};
- }
- return ();
- }
- sub arguments {
- my $self = shift;
- return ($self->inArguments, $self->outArguments);
- }
- # ----------------------------------------------------------------------
- package UPnP::Common::Argument;
- use strict;
- use Carp;
- use vars qw($AUTOLOAD %argumentProperties);
- for my $prop (qw(name relatedStateVariable)) {
- $argumentProperties{$prop}++;
- }
- sub new {
- return bless {}, shift;
- }
- sub AUTOLOAD {
- my $self = shift;
- my $attr = $AUTOLOAD;
- $attr =~ s/.*:://;
- return if $attr eq 'DESTROY';
- croak "invalid attribute method: ->$attr()" unless $argumentProperties{$attr};
- $self->{uc $attr} = shift if @_;
- return $self->{uc $attr};
- }
- # ----------------------------------------------------------------------
- package UPnP::Common::StateVariable;
- use strict;
- use Carp;
- use vars qw($AUTOLOAD %varProperties);
- for my $prop (qw(name type evented)) {
- $varProperties{$prop}++;
- }
- sub new {
- my $self = bless {}, shift;
- $self->evented(shift);
- return $self;
- }
- sub SOAPType {
- my $self = shift;
- return UPnP::Common::UPnPToSOAPType($self->type);
- }
- sub AUTOLOAD {
- my $self = shift;
- my $attr = $AUTOLOAD;
- $attr =~ s/.*:://;
- return if $attr eq 'DESTROY';
- croak "invalid attribute method: ->$attr()" unless $varProperties{$attr};
- $self->{uc $attr} = shift if @_;
- return $self->{uc $attr};
- }
- # ----------------------------------------------------------------------
- package UPnP::Common::Parser;
- use XML::Parser::Lite;
- # Parser code borrowed from SOAP::Lite. This package uses the
- # event-driven XML::Parser::Lite parser to construct a nested data
- # structure - a poor man's DOM. Each XML element in the data structure
- # is represented by an array ref, with the values (listed by subscript
- # below) corresponding with:
- # 0 - The element name.
- # 1 - A hash ref representing the element attributes.
- # 2 - An array ref holding either child elements or concatenated
- # character data.
- sub new {
- my $class = shift;
- return bless { _parser => XML::Parser::Lite->new }, $class;
- }
- sub parse {
- my $self = shift;
- my $parser = $self->{_parser};
- $parser->setHandlers(Final => sub { shift; $self->final(@_) },
- Start => sub { shift; $self->start(@_) },
- End => sub { shift; $self->end(@_) },
- Char => sub { shift; $self->char(@_) },);
- $parser->parse(shift);
- }
- sub final {
- my $self = shift;
- my $parser = $self->{_parser};
- # clean handlers, otherwise ControlPoint::Parser won't be deleted:
- # it refers to XML::Parser which refers to subs from ControlPoint::Parser
- undef $self->{_values};
- $parser->setHandlers(Final => undef,
- Start => undef,
- End => undef,
- Char => undef,);
- $self->{_done};
- }
- sub start { push @{shift->{_values}}, [shift, {@_}] }
- sub char { push @{shift->{_values}->[-1]->[3]}, shift }
- sub end {
- my $self = shift;
- my $done = pop @{$self->{_values}};
- $done->[2] = defined $done->[3] ? join('',@{$done->[3]}) : '' unless ref $done->[2];
- undef $done->[3];
- @{$self->{_values}} ? (push @{$self->{_values}->[-1]->[2]}, $done)
- : ($self->{_done} = $done);
- }
- 1;
- __END__
- =head1 NAME
- UPnP::Common - Internal modules and methods for the UPnP
- implementation. The C<UPnP::ControlPoint> and C<UPnP::DeviceManager>
- modules should be used.
- =head1 DESCRIPTION
- Part of the Perl UPnP implementation suite.
- =head1 SEE ALSO
- UPnP documentation and resources can be found at L<http://www.upnp.org>.
- The C<SOAP::Lite> module can be found at L<http://www.soaplite.com>.
- UPnP implementations in other languages include the UPnP SDK for Linux
- (L<http://upnp.sourceforge.net/>), Cyberlink for Java
- (L<http://www.cybergarage.org/net/upnp/java/index.html>) and C++
- (L<http://sourceforge.net/projects/clinkcc/>), and the Microsoft UPnP
- SDK
- (L<http://msdn.microsoft.com/library/default.asp?url=/library/en-us/upnp/upnp/universal_plug_and_play_start_page.asp>).
- =head1 AUTHOR
- Vidur Apparao (vidurapparao@users.sourceforge.net)
- =head1 COPYRIGHT AND LICENSE
- Copyright (C) 2004 by Vidur Apparao
- This library is free software; you can redistribute it and/or modify
- it under the same terms as Perl itself, either Perl version 5.8 or,
- at your option, any later version of Perl 5 you may have available.
- =cut
- # Local Variables:
- # tab-width:4
- # indent-tabs-mode:t
- # End:
|