Dancer-Plugin-CORS

 view release on metacpan or  search on metacpan

lib/Dancer/Plugin/CORS.pm  view on Meta::CPAN

	my @requested_headers = map { s{\s+}{}g; $_ } split /,+/, (scalar($request->header('Access-Control-Request-Headers')) || '');
	
	my ($ok, $i) = (0, 0);
	my ($headers, $xoptions);
	
	if (exists $routes->{$route}) {
		$path = "$route";
		debug "[CORS] dynamic route: $path" if DEBUG;
	} else {
		debug "[CORS] static route: $path" if DEBUG;
	}
	
	my $n = scalar @{$routes->{$path}};
	
	RULE: foreach my $options (@{$routes->{$path}}) {
		debug "[CORS] testing rule ".++$i." of $n" if DEBUG;
		if (DEBUG) {
			use Data::Dumper;
			debug Dumper($options);
		}
		$headers = {};
		if (exists $options->{origin}) {
			my $reftype = ref $options->{origin};
			if ($reftype eq 'CODE') {
				if (!$options->{origin}->(URI->new($origin))) {
					debug "[CORS] origin $origin did not matched against coderef" if DEBUG;
					next RULE;
				}
			} elsif ($reftype eq 'ARRAY') {
				unless (_isin $origin => @{ $options->{origin} }) {
					debug "[CORS] origin $origin is not in array" if DEBUG;
					next RULE;
				}
			} elsif ($reftype eq 'Regexp') {
				unless ($origin =~ $options->{origin}) {
					debug "[CORS] origin $origin did not matched against regexp" if DEBUG;
					next RULE;
				}
			} elsif ($reftype eq '') {
				unless ($options->{origin} eq $origin) {
					debug "[CORS] origin $origin did not matched against static string" if DEBUG;
					next RULE;
				}
			} else {
				confess("unknown origin type: $reftype");
			}
		} else {
			$origin = '*';
		}
		$headers->{'Access-Control-Allow-Origin'} = $origin;
		$headers->{'Vary'} = 'Origin' if $origin ne '*';
		
		if (exists $options->{timing}) {
			if (defined $options->{timing} and $options->{timing} eq '1') {
				$headers->{'Timing-Allow-Origin'} = $headers->{'Access-Control-Allow-Origin'};
			} else {
				$headers->{'Timing-Allow-Origin'} = 'null';
			}
		}
		
		if (exists $options->{credentials}) {
			if (!!$options->{credentials}) {
				if ($origin eq '*') {
					warning('For a resource that supports credentials a origin matcher must be specified.');
					next RULE;
				}
				$headers->{'Access-Control-Allow-Credentials'} = 'true' ;
			}
		}
		
		if (exists $options->{expose}) {
			$headers->{'Access-Control-Expose-Headers'} = $options->{expose};
		}
		
		if (exists $options->{methods}) {
			unless (_isin lc $requested_method => map lc, @{ $options->{methods} }) {
				debug "[CORS] request method not allowed" if DEBUG;
				next RULE;
			}
			$headers->{'Access-Control-Allow-Methods'} = join ', ' => map uc, @{ $options->{methods} };
		} elsif (exists $options->{method}) {
			unless ($options->{method} eq $requested_method) {
				debug "[CORS] request method '$requested_method' not allowed: ".$options->{method} if DEBUG;
				next RULE;
			}
			$headers->{'Access-Control-Allow-Methods'} = $options->{method};
		}
		
		if (exists $options->{headers}) {
			foreach my $requested_header (@requested_headers) {
				unless (_isin lc $requested_header => map lc, @{ $options->{headers} }) {
					debug "[CORS] requested headers did not match allowed in rule" if DEBUG;
					next RULE;
				}
			}
			$headers->{'Access-Control-Allow-Headers'} = join ', ' => @{ $options->{headers} };
		} elsif (@requested_headers) {
			$headers->{'Access-Control-Allow-Headers'} = join ', ' => @requested_headers;
		}

		if ($preflight and exists $options->{maxage}) {
			$headers->{'Access-Control-Max-Age'} = $options->{maxage};
		}
		
		$ok = 1;
		var CORS => {%$options};
		Dancer::SharedData->response->headers(%$headers);
		if (DEBUG) {
			use Data::Dumper;
			debug Dumper({headers => $headers});
		}
		last RULE;
	}

	if ($ok) {
		debug "[CORS] matched!" if DEBUG;
	} else {
		debug "[CORS] no rule matched" if DEBUG;
	}
	
	return $ok;
}


register(share => \&_add_rule);

hook(before => sub {
	$current_route = shift || return;
	my $preflight = uc Dancer::SharedData->request->method eq 'OPTIONS';
	if ($preflight) {
		debug "[CORS] pre-check: preflight request, handle within main subroutine" if DEBUG;
	} else {
		debug "[CORS] pre-check: no preflight, handle actual request now" if DEBUG;
		_handle($current_route);
	}
});

my $current_sharing;


register sharing => sub {
	my $class = __PACKAGE__.'::Sharing';
	$current_sharing ||= $class->new(@_,_add_rule=>\&_add_rule);
	return $current_sharing;
};

register_plugin;
1;

__END__

=pod

=head1 NAME

Dancer::Plugin::CORS - A plugin for using cross origin resource sharing

=head1 VERSION

version 0.13

=head1 DESCRIPTION

Cross origin resource sharing is a feature used by modern web browser to bypass cross site scripting restrictions. A webservice can provide those rules from which origin a client is allowed to make cross-site requests. This module helps you to setup ...

=head1 SYNOPSIS

    use Dancer::Plugin::CORS;

    get '/foo' => sub { ... };
	share '/foo' =>
		origin => 'http://localhost/',
		credentials => 1,
		expose => [qw[ Content-Type ]],
		method => 'GET',
		headers => [qw[ X-Requested-With ]],
		maxage => 7200,
		timing => 1,
	;

=head1 METHODS

=head2 share(C<$route>, C<%options>)

The parameter C<$route> may be any valid path like used I<get>, I<post>, I<put>, I<delete> or I<patch> but not I<option>.

Alternatively a L<Dancer::Route> object may be used instead:

	$route = post '/' => sub { ... };
	share $route => ... ;

Or a arrayref to one or more Routes:

	@head_and_get = get '/' => sub { ... };
	share \@head_and_get => ...;

This syntax works too:

	share [ get ('/' => sub { ... }) ] => ...;

For any route more than one rule may be defined. The order is relevant: the first matching rule wins.

Following keywords recognized by C<%options>:

=over 4

=item I<origin>

This key defines a static origin (scalar), a list (arrayref), a regex or a subroutine.

If not specified, any origin is allowed.

If a subroutine is used, the first passed parameter is a L<URI> object. It should return a true value if this origin is allowed to access the route in question; otherwise false.

	origin => sub {
		my $host = shift->host;
		# allow only from localhost
		grep { $host eq $_ } qw(localhost 127.0.0.1 ::1)
	}

Hint: a origin consists of protocol, hostname and maybe a port. Examples: C<http://www.example.com>, C<https://securesite.com>, C<http://localhost:3000>, C<http://127.0.0.1>, C<http://[::1]>

=item I<credentials>

This indicates whether cookies, HTTP authentication and/or client-side SSL certificates may sent by a client. Allowed values are C<0> or C<1>.

This option must be used together with I<origin>.

=item I<expose>

A comma-seperated list of headers, that a client may extract from response for use in a client application.

=item I<methods>

A arrayref of allowed methods. If no methods are specified, all methods are allowed.

=item I<method>

A string containing a single supported method. This parameter is autofilled when I<share()> is used together with a L<Dancer::Route> object. If no method is specified, any method is allowed.

=item I<headers>

A arrayref of allowed request headers. In most cases that should be C<[ 'X-Requested-With' ]> when ajax requests are made. If no headers are specified, all requested headers are allowed.

=item I<maxage>

A maximum time (in seconds) a client may cache a preflight request. This can decrease the amount of requests made to the webservice.

=item I<timing>

Allow access to the resource timing information. If set to 1, the header C<Timing-Allow-Origin> is set to the same value as I<Access-Control-Allow-Origin>. Otherwise, its set to the value I<null>. If the keyword is not present, no I<Timing-Allow-Orig...

=back

=head2 sharing

This keyword is a helper for re-using rules for many routes.

See L<Dancer::Plugin::CORS::Sharing> for more information about this feature.

=head1 BUGS

Please report any bugs or feature requests on the bugtracker website
https://github.com/zurborg/libdancer-plugin-cors-perl/issues

When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.

=head1 AUTHOR

David Zurborg <zurborg@cpan.org>

=head1 COPYRIGHT AND LICENSE

This software is Copyright (c) 2014 by David Zurborg.

This is free software, licensed under:

  The ISC License

=cut



( run in 1.708 second using v1.01-cache-2.11-cpan-39bf76dae61 )