Initial Commit

This commit is contained in:
Riley Schneider
2025-12-03 16:38:10 +01:00
parent c5e26bf594
commit b732d8d4b5
17680 changed files with 5977495 additions and 2 deletions

142
database/perl/vendor/lib/Mojo/Asset.pm vendored Normal file
View File

@@ -0,0 +1,142 @@
package Mojo::Asset;
use Mojo::Base 'Mojo::EventEmitter';
use Carp qw(croak);
has 'end_range';
has start_range => 0;
sub add_chunk { croak 'Method "add_chunk" not implemented by subclass' }
sub contains { croak 'Method "contains" not implemented by subclass' }
sub get_chunk { croak 'Method "get_chunk" not implemented by subclass' }
sub is_file {undef}
sub is_range { !!($_[0]->end_range || $_[0]->start_range) }
sub move_to { croak 'Method "move_to" not implemented by subclass' }
sub mtime { croak 'Method "mtime" not implemented by subclass' }
sub size { croak 'Method "size" not implemented by subclass' }
sub slurp { croak 'Method "slurp" not implemented by subclass' }
sub to_file { croak 'Method "to_file" not implemented by subclass' }
1;
=encoding utf8
=head1 NAME
Mojo::Asset - HTTP content storage base class
=head1 SYNOPSIS
package Mojo::Asset::MyAsset;
use Mojo::Base 'Mojo::Asset';
sub add_chunk {...}
sub contains {...}
sub get_chunk {...}
sub move_to {...}
sub mtime {...}
sub size {...}
sub slurp {...}
sub to_file {...}
=head1 DESCRIPTION
L<Mojo::Asset> is an abstract base class for HTTP content storage backends, like L<Mojo::Asset::File> and
L<Mojo::Asset::Memory>.
=head1 EVENTS
L<Mojo::Asset> inherits all events from L<Mojo::EventEmitter>.
=head1 ATTRIBUTES
L<Mojo::Asset> implements the following attributes.
=head2 end_range
my $end = $asset->end_range;
$asset = $asset->end_range(8);
Pretend file ends earlier.
=head2 start_range
my $start = $asset->start_range;
$asset = $asset->start_range(3);
Pretend file starts later.
=head1 METHODS
L<Mojo::Asset> inherits all methods from L<Mojo::EventEmitter> and implements the following new ones.
=head2 add_chunk
$asset = $asset->add_chunk('foo bar baz');
Add chunk of data to asset. Meant to be overloaded in a subclass.
=head2 contains
my $position = $asset->contains('bar');
Check if asset contains a specific string. Meant to be overloaded in a subclass.
=head2 get_chunk
my $bytes = $asset->get_chunk($offset);
my $bytes = $asset->get_chunk($offset, $max);
Get chunk of data starting from a specific position, defaults to a maximum chunk size of C<131072> bytes (128KiB).
Meant to be overloaded in a subclass.
=head2 is_file
my $bool = $asset->is_file;
False, this is not a L<Mojo::Asset::File> object.
=head2 is_range
my $bool = $asset->is_range;
Check if asset has a L</"start_range"> or L</"end_range">.
=head2 move_to
$asset = $asset->move_to('/home/sri/foo.txt');
Move asset data into a specific file. Meant to be overloaded in a subclass.
=head2 mtime
my $mtime = $asset->mtime;
Modification time of asset. Meant to be overloaded in a subclass.
=head2 size
my $size = $asset->size;
Size of asset data in bytes. Meant to be overloaded in a subclass.
=head2 slurp
my $bytes = $asset->slurp;
Read all asset data at once. Meant to be overloaded in a subclass.
=head2 to_file
my $file = $asset->to_file;
Convert asset to L<Mojo::Asset::File> object. Meant to be overloaded in a subclass.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,266 @@
package Mojo::Asset::File;
use Mojo::Base 'Mojo::Asset';
use Carp qw(croak);
use Fcntl qw(SEEK_SET);
use File::Spec::Functions ();
use Mojo::File qw(tempfile);
has [qw(cleanup path)];
has handle => sub {
my $self = shift;
# Open existing file
my $path = $self->path;
return Mojo::File->new($path)->open('<') if defined $path && -e $path;
$self->cleanup(1) unless defined $self->cleanup;
# Create a specific file
return Mojo::File->new($path)->open('+>>') if defined $path;
# Create a temporary file
my $file = tempfile DIR => $self->tmpdir, TEMPLATE => 'mojo.tmp.XXXXXXXXXXXXXXXX', UNLINK => 0;
$self->path($file->to_string);
return $file->open('+>>');
};
has tmpdir => sub { $ENV{MOJO_TMPDIR} || File::Spec::Functions::tmpdir };
sub DESTROY {
my $self = shift;
return unless $self->cleanup && defined(my $path = $self->path);
if (my $handle = $self->handle) { close $handle }
# Only the process that created the file is allowed to remove it
Mojo::File->new($path)->remove if -w $path && ($self->{pid} // $$) == $$;
}
sub add_chunk {
my ($self, $chunk) = @_;
($self->handle->syswrite($chunk) // -1) == length $chunk or croak "Can't write to asset: $!";
return $self;
}
sub contains {
my ($self, $str) = @_;
my $handle = $self->handle;
$handle->sysseek($self->start_range, SEEK_SET);
# Calculate window size
my $end = $self->end_range // $self->size;
my $len = length $str;
my $size = $len > 131072 ? $len : 131072;
$size = $end - $self->start_range if $size > $end - $self->start_range;
# Sliding window search
my $offset = 0;
my $start = $handle->sysread(my $window, $len);
while ($offset < $end) {
# Read as much as possible
my $diff = $end - ($start + $offset);
my $read = $handle->sysread(my $buffer, $diff < $size ? $diff : $size);
$window .= $buffer;
# Search window
my $pos = index $window, $str;
return $offset + $pos if $pos >= 0;
return -1 if $read == 0 || ($offset += $read) == $end;
# Resize window
substr $window, 0, $read, '';
}
return -1;
}
sub get_chunk {
my ($self, $offset, $max) = @_;
$max //= 131072;
$offset += $self->start_range;
my $handle = $self->handle;
$handle->sysseek($offset, SEEK_SET);
my $buffer;
if (defined(my $end = $self->end_range)) {
return '' if (my $chunk = $end + 1 - $offset) <= 0;
$handle->sysread($buffer, $chunk > $max ? $max : $chunk);
}
else { $handle->sysread($buffer, $max) }
return $buffer;
}
sub is_file {1}
sub move_to {
my ($self, $to) = @_;
# Windows requires that the handle is closed
close $self->handle;
delete $self->{handle};
# Move file and prevent clean up
Mojo::File->new($self->path)->move_to($to);
return $self->path($to)->cleanup(0);
}
sub mtime { (stat shift->handle)[9] }
sub new {
my $file = shift->SUPER::new(@_);
$file->{pid} = $$;
return $file;
}
sub size { -s shift->handle }
sub slurp {
my $handle = shift->handle;
$handle->sysseek(0, SEEK_SET);
my $ret = my $content = '';
while ($ret = $handle->sysread(my $buffer, 131072, 0)) { $content .= $buffer }
return defined $ret ? $content : croak "Can't read from asset: $!";
}
sub to_file {shift}
1;
=encoding utf8
=head1 NAME
Mojo::Asset::File - File storage for HTTP content
=head1 SYNOPSIS
use Mojo::Asset::File;
# Temporary file
my $file = Mojo::Asset::File->new;
$file->add_chunk('foo bar baz');
say 'File contains "bar"' if $file->contains('bar') >= 0;
say $file->slurp;
# Existing file
my $file = Mojo::Asset::File->new(path => '/home/sri/foo.txt');
$file->move_to('/yada.txt');
say $file->slurp;
=head1 DESCRIPTION
L<Mojo::Asset::File> is a file storage backend for HTTP content.
=head1 EVENTS
L<Mojo::Asset::File> inherits all events from L<Mojo::Asset>.
=head1 ATTRIBUTES
L<Mojo::Asset::File> inherits all attributes from L<Mojo::Asset> and implements the following new ones.
=head2 cleanup
my $bool = $file->cleanup;
$file = $file->cleanup($bool);
Delete L</"path"> automatically once the file is not used anymore.
=head2 handle
my $handle = $file->handle;
$file = $file->handle(IO::File->new);
Filehandle, created on demand for L</"path">, which can be generated automatically and safely based on L</"tmpdir">.
=head2 path
my $path = $file->path;
$file = $file->path('/home/sri/foo.txt');
File path used to create L</"handle">.
=head2 tmpdir
my $tmpdir = $file->tmpdir;
$file = $file->tmpdir('/tmp');
Temporary directory used to generate L</"path">, defaults to the value of the C<MOJO_TMPDIR> environment variable or
auto-detection.
=head1 METHODS
L<Mojo::Asset::File> inherits all methods from L<Mojo::Asset> and implements the following new ones.
=head2 add_chunk
$file = $file->add_chunk('foo bar baz');
Add chunk of data.
=head2 contains
my $position = $file->contains('bar');
Check if asset contains a specific string.
=head2 get_chunk
my $bytes = $file->get_chunk($offset);
my $bytes = $file->get_chunk($offset, $max);
Get chunk of data starting from a specific position, defaults to a maximum chunk size of C<131072> bytes (128KiB).
=head2 is_file
my $bool = $file->is_file;
True, this is a L<Mojo::Asset::File> object.
=head2 move_to
$file = $file->move_to('/home/sri/bar.txt');
Move asset data into a specific file and disable L</"cleanup">.
=head2 mtime
my $mtime = $file->mtime;
Modification time of asset.
=head2 new
my $file = Mojo::Asset::File->new;
my $file = Mojo::Asset::File->new(path => '/home/sri/test.txt');
my $file = Mojo::Asset::File->new({path => '/home/sri/test.txt'});
Construct a new L<Mojo::Asset::File> object.
=head2 size
my $size = $file->size;
Size of asset data in bytes.
=head2 slurp
my $bytes = $file->slurp;
Read all asset data at once.
=head2 to_file
$file = $file->to_file;
Does nothing but return the invocant, since we already have a L<Mojo::Asset::File> object.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,161 @@
package Mojo::Asset::Memory;
use Mojo::Base 'Mojo::Asset';
use Mojo::Asset::File;
use Mojo::File qw(path);
has 'auto_upgrade';
has max_memory_size => sub { $ENV{MOJO_MAX_MEMORY_SIZE} || 262144 };
has mtime => sub {$^T};
sub add_chunk {
my ($self, $chunk) = @_;
# Upgrade if necessary
$self->{content} .= $chunk;
return $self if !$self->auto_upgrade || $self->size <= $self->max_memory_size;
$self->emit(upgrade => my $file = Mojo::Asset::File->new);
return $file->add_chunk($self->slurp);
}
sub contains {
my ($self, $str) = @_;
my $start = $self->start_range;
my $pos = index $self->{content} // '', $str, $start;
$pos -= $start if $start && $pos >= 0;
my $end = $self->end_range;
return $end && ($pos + length $str) >= $end ? -1 : $pos;
}
sub get_chunk {
my ($self, $offset, $max) = @_;
$max //= 131072;
$offset += $self->start_range;
if (my $end = $self->end_range) { $max = $end + 1 - $offset if ($offset + $max) > $end }
return substr shift->{content} // '', $offset, $max;
}
sub move_to { path($_[1])->spurt($_[0]{content} // '') and return $_[0] }
sub size { length(shift->{content} // '') }
sub slurp { shift->{content} // '' }
sub to_file { Mojo::Asset::File->new->add_chunk(shift->slurp) }
1;
=encoding utf8
=head1 NAME
Mojo::Asset::Memory - In-memory storage for HTTP content
=head1 SYNOPSIS
use Mojo::Asset::Memory;
my $mem = Mojo::Asset::Memory->new;
$mem->add_chunk('foo bar baz');
say $mem->slurp;
=head1 DESCRIPTION
L<Mojo::Asset::Memory> is an in-memory storage backend for HTTP content.
=head1 EVENTS
L<Mojo::Asset::Memory> inherits all events from L<Mojo::Asset> and can emit the following new ones.
=head2 upgrade
$mem->on(upgrade => sub ($mem, $file) {...});
Emitted when asset gets upgraded to a L<Mojo::Asset::File> object.
$mem->on(upgrade => sub ($mem, $file) { $file->tmpdir('/tmp') });
=head1 ATTRIBUTES
L<Mojo::Asset::Memory> inherits all attributes from L<Mojo::Asset> and implements the following new ones.
=head2 auto_upgrade
my $bool = $mem->auto_upgrade;
$mem = $mem->auto_upgrade($bool);
Try to detect if content size exceeds L</"max_memory_size"> limit and automatically upgrade to a L<Mojo::Asset::File>
object.
=head2 max_memory_size
my $size = $mem->max_memory_size;
$mem = $mem->max_memory_size(1024);
Maximum size in bytes of data to keep in memory before automatically upgrading to a L<Mojo::Asset::File> object,
defaults to the value of the C<MOJO_MAX_MEMORY_SIZE> environment variable or C<262144> (256KiB).
=head2 mtime
my $mtime = $mem->mtime;
$mem = $mem->mtime(1408567500);
Modification time of asset, defaults to the value of C<$^T>.
=head1 METHODS
L<Mojo::Asset::Memory> inherits all methods from L<Mojo::Asset> and implements the following new ones.
=head2 add_chunk
$mem = $mem->add_chunk('foo bar baz');
my $file = $mem->add_chunk('abc' x 262144);
Add chunk of data and upgrade to L<Mojo::Asset::File> object if necessary.
=head2 contains
my $position = $mem->contains('bar');
Check if asset contains a specific string.
=head2 get_chunk
my $bytes = $mem->get_chunk($offset);
my $bytes = $mem->get_chunk($offset, $max);
Get chunk of data starting from a specific position, defaults to a maximum chunk size of C<131072> bytes (128KiB).
=head2 move_to
$mem = $mem->move_to('/home/sri/foo.txt');
Move asset data into a specific file.
=head2 size
my $size = $mem->size;
Size of asset data in bytes.
=head2 slurp
my $bytes = mem->slurp;
Read all asset data at once.
=head2 to_file
my $file = $mem->to_file;
Convert asset to L<Mojo::Asset::File> object.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

384
database/perl/vendor/lib/Mojo/Base.pm vendored Normal file
View File

@@ -0,0 +1,384 @@
package Mojo::Base;
use strict;
use warnings;
use utf8;
use feature ':5.16';
use mro;
# No imports because we get subclassed, a lot!
use Carp ();
use Scalar::Util ();
# Defer to runtime so Mojo::Util can use "-strict"
require Mojo::Util;
# Role support requires Role::Tiny 2.000001+
use constant ROLES => !!(eval { require Role::Tiny; Role::Tiny->VERSION('2.000001'); 1 });
# async/await support requires Future::AsyncAwait 0.36+
use constant ASYNC => $ENV{MOJO_NO_ASYNC}
? 0
: !!(eval { require Future::AsyncAwait; Future::AsyncAwait->VERSION('0.36'); 1 });
# Protect subclasses using AUTOLOAD
sub DESTROY { }
sub attr {
my ($self, $attrs, $value, %kv) = @_;
return unless (my $class = ref $self || $self) && $attrs;
Carp::croak 'Default has to be a code reference or constant value' if ref $value && ref $value ne 'CODE';
Carp::croak 'Unsupported attribute option' if grep { $_ ne 'weak' } keys %kv;
# Weaken
if ($kv{weak}) {
state %weak_names;
unless ($weak_names{$class}) {
my $names = $weak_names{$class} = [];
my $sub = sub {
my $self = shift->next::method(@_);
ref $self->{$_} and Scalar::Util::weaken $self->{$_} for @$names;
return $self;
};
Mojo::Util::monkey_patch(my $base = $class . '::_Base', 'new', $sub);
no strict 'refs';
unshift @{"${class}::ISA"}, $base;
}
push @{$weak_names{$class}}, ref $attrs eq 'ARRAY' ? @$attrs : $attrs;
}
for my $attr (@{ref $attrs eq 'ARRAY' ? $attrs : [$attrs]}) {
Carp::croak qq{Attribute "$attr" invalid} unless $attr =~ /^[a-zA-Z_]\w*$/;
# Very performance-sensitive code with lots of micro-optimizations
my $sub;
if ($kv{weak}) {
if (ref $value) {
$sub = sub {
return
exists $_[0]{$attr}
? $_[0]{$attr}
: (ref($_[0]{$attr} = $value->($_[0])) && Scalar::Util::weaken($_[0]{$attr}), $_[0]{$attr})
if @_ == 1;
ref($_[0]{$attr} = $_[1]) and Scalar::Util::weaken($_[0]{$attr});
$_[0];
};
}
else {
$sub = sub {
return $_[0]{$attr} if @_ == 1;
ref($_[0]{$attr} = $_[1]) and Scalar::Util::weaken($_[0]{$attr});
$_[0];
};
}
}
elsif (ref $value) {
$sub = sub {
return exists $_[0]{$attr} ? $_[0]{$attr} : ($_[0]{$attr} = $value->($_[0])) if @_ == 1;
$_[0]{$attr} = $_[1];
$_[0];
};
}
elsif (defined $value) {
$sub = sub {
return exists $_[0]{$attr} ? $_[0]{$attr} : ($_[0]{$attr} = $value) if @_ == 1;
$_[0]{$attr} = $_[1];
$_[0];
};
}
else {
$sub = sub { return $_[0]{$attr} if @_ == 1; $_[0]{$attr} = $_[1]; $_[0] };
}
Mojo::Util::monkey_patch($class, $attr, $sub);
}
}
sub import {
my ($class, $caller) = (shift, caller);
return unless my @flags = @_;
# Mojo modules are strict!
$_->import for qw(strict warnings utf8);
feature->import(':5.16');
while (my $flag = shift @flags) {
# Base
if ($flag eq '-base') { push @flags, $class }
# Role
elsif ($flag eq '-role') {
Carp::croak 'Role::Tiny 2.000001+ is required for roles' unless ROLES;
Mojo::Util::monkey_patch($caller, 'has', sub { attr($caller, @_) });
eval "package $caller; use Role::Tiny; 1" or die $@;
}
# async/await
elsif ($flag eq '-async_await') {
Carp::croak 'Future::AsyncAwait 0.36+ is required for async/await' unless ASYNC;
require Mojo::Promise;
Future::AsyncAwait->import_into($caller, future_class => 'Mojo::Promise');
}
# Signatures (Perl 5.20+)
elsif ($flag eq '-signatures') {
Carp::croak 'Subroutine signatures require Perl 5.20+' if $] < 5.020;
require experimental;
experimental->import('signatures');
}
# Module
elsif ($flag !~ /^-/) {
no strict 'refs';
require(Mojo::Util::class_to_path($flag)) unless $flag->can('new');
push @{"${caller}::ISA"}, $flag;
Mojo::Util::monkey_patch($caller, 'has', sub { attr($caller, @_) });
}
elsif ($flag ne '-strict') { Carp::croak "Unsupported flag: $flag" }
}
}
sub new {
my $class = shift;
bless @_ ? @_ > 1 ? {@_} : {%{$_[0]}} : {}, ref $class || $class;
}
sub tap {
my ($self, $cb) = (shift, shift);
$_->$cb(@_) for $self;
return $self;
}
sub with_roles {
Carp::croak 'Role::Tiny 2.000001+ is required for roles' unless ROLES;
my ($self, @roles) = @_;
return $self unless @roles;
return Role::Tiny->create_class_with_roles($self, map { /^\+(.+)$/ ? "${self}::Role::$1" : $_ } @roles)
unless my $class = Scalar::Util::blessed $self;
return Role::Tiny->apply_roles_to_object($self, map { /^\+(.+)$/ ? "${class}::Role::$1" : $_ } @roles);
}
1;
=encoding utf8
=head1 NAME
Mojo::Base - Minimal base class for Mojo projects
=head1 SYNOPSIS
package Cat;
use Mojo::Base -base;
has name => 'Nyan';
has ['age', 'weight'] => 4;
package Tiger;
use Mojo::Base 'Cat';
has friend => sub { Cat->new };
has stripes => 42;
package main;
use Mojo::Base -strict;
my $mew = Cat->new(name => 'Longcat');
say $mew->age;
say $mew->age(3)->weight(5)->age;
my $rawr = Tiger->new(stripes => 38, weight => 250);
say $rawr->tap(sub { $_->friend->name('Tacgnol') })->weight;
=head1 DESCRIPTION
L<Mojo::Base> is a simple base class for L<Mojo> projects with fluent interfaces.
# Automatically enables "strict", "warnings", "utf8" and Perl 5.16 features
use Mojo::Base -strict;
use Mojo::Base -base;
use Mojo::Base 'SomeBaseClass';
use Mojo::Base -role;
All four forms save a lot of typing. Note that role support depends on L<Role::Tiny> (2.000001+).
# use Mojo::Base -strict;
use strict;
use warnings;
use utf8;
use feature ':5.16';
use mro;
# use Mojo::Base -base;
use strict;
use warnings;
use utf8;
use feature ':5.16';
use mro;
push @ISA, 'Mojo::Base';
sub has { Mojo::Base::attr(__PACKAGE__, @_) }
# use Mojo::Base 'SomeBaseClass';
use strict;
use warnings;
use utf8;
use feature ':5.16';
use mro;
require SomeBaseClass;
push @ISA, 'SomeBaseClass';
sub has { Mojo::Base::attr(__PACKAGE__, @_) }
# use Mojo::Base -role;
use strict;
use warnings;
use utf8;
use feature ':5.16';
use mro;
use Role::Tiny;
sub has { Mojo::Base::attr(__PACKAGE__, @_) }
On Perl 5.20+ you can also use the C<-signatures> flag with all four forms and enable support for L<subroutine
signatures|perlsub/"Signatures">.
# Also enable signatures
use Mojo::Base -strict, -signatures;
use Mojo::Base -base, -signatures;
use Mojo::Base 'SomeBaseClass', -signatures;
use Mojo::Base -role, -signatures;
If you have L<Future::AsyncAwait> 0.36+ installed you can also use the C<-async_await> flag to activate the C<async>
and C<await> keywords to deal much more efficiently with promises. Note that this feature is B<EXPERIMENTAL> and might
change without warning!
# Also enable async/await
use Mojo::Base -strict, -async_await;
use Mojo::Base -base, -signatures, -async_await;
This will also disable experimental warnings on versions of Perl where this feature was still experimental.
=head1 FLUENT INTERFACES
Fluent interfaces are a way to design object-oriented APIs around method chaining to create domain-specific languages,
with the goal of making the readability of the source code close to written prose.
package Duck;
use Mojo::Base -base, -signatures;
has 'name';
sub quack ($self) {
my $name = $self->name;
say "$name: Quack!"
}
L<Mojo::Base> will help you with this by having all attribute accessors created with L</"has"> (or L</"attr">) return
their invocant (C<$self>) whenever they are used to assign a new attribute value.
Duck->new->name('Donald')->quack;
In this case the C<name> attribute accessor is called on the object created by C<Duck-E<gt>new>. It assigns a new
attribute value and then returns the C<Duck> object, so the C<quack> method can be called on it afterwards. These
method chains can continue until one of the methods called does not return the C<Duck> object.
=head1 FUNCTIONS
L<Mojo::Base> implements the following functions, which can be imported with the C<-base> flag or by setting a base
class.
=head2 has
has 'name';
has ['name1', 'name2', 'name3'];
has name => 'foo';
has name => sub {...};
has ['name1', 'name2', 'name3'] => 'foo';
has ['name1', 'name2', 'name3'] => sub {...};
has name => sub {...}, weak => 1;
has name => undef, weak => 1;
has ['name1', 'name2', 'name3'] => sub {...}, weak => 1;
Create attributes for hash-based objects, just like the L</"attr"> method.
=head1 METHODS
L<Mojo::Base> implements the following methods.
=head2 attr
$object->attr('name');
SubClass->attr('name');
SubClass->attr(['name1', 'name2', 'name3']);
SubClass->attr(name => 'foo');
SubClass->attr(name => sub {...});
SubClass->attr(['name1', 'name2', 'name3'] => 'foo');
SubClass->attr(['name1', 'name2', 'name3'] => sub {...});
SubClass->attr(name => sub {...}, weak => 1);
SubClass->attr(name => undef, weak => 1);
SubClass->attr(['name1', 'name2', 'name3'] => sub {...}, weak => 1);
Create attribute accessors for hash-based objects, an array reference can be used to create more than one at a time.
Pass an optional second argument to set a default value, it should be a constant or a callback. The callback will be
executed at accessor read time if there's no set value, and gets passed the current instance of the object as first
argument. Accessors can be chained, that means they return their invocant when they are called with an argument.
These options are currently available:
=over 2
=item weak
weak => $bool
Weaken attribute reference to avoid L<circular references|perlref/"Circular-References"> and memory leaks.
=back
=head2 new
my $object = SubClass->new;
my $object = SubClass->new(name => 'value');
my $object = SubClass->new({name => 'value'});
This base class provides a basic constructor for hash-based objects. You can pass it either a hash or a hash reference
with attribute values.
=head2 tap
$object = $object->tap(sub {...});
$object = $object->tap('some_method');
$object = $object->tap('some_method', @args);
Tap into a method chain to perform operations on an object within the chain (also known as a K combinator or Kestrel).
The object will be the first argument passed to the callback, and is also available as C<$_>. The callback's return
value will be ignored; instead, the object (the callback's first argument) will be the return value. In this way,
arbitrary code can be used within (i.e., spliced or tapped into) a chained set of object method calls.
# Longer version
$object = $object->tap(sub { $_->some_method(@args) });
# Inject side effects into a method chain
$object->foo('A')->tap(sub { say $_->foo })->foo('B');
=head2 with_roles
my $new_class = SubClass->with_roles('SubClass::Role::One');
my $new_class = SubClass->with_roles('+One', '+Two');
$object = $object->with_roles('+One', '+Two');
Create a new class with one or more L<Role::Tiny> roles. If called on a class returns the new class, or if called on an
object reblesses the object into the new class. For roles following the naming scheme C<MyClass::Role::RoleName> you
can use the shorthand C<+RoleName>. Note that role support depends on L<Role::Tiny> (2.000001+).
# Create a new class with the role "SubClass::Role::Foo" and instantiate it
my $new_class = SubClass->with_roles('+Foo');
my $object = $new_class->new;
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,392 @@
package Mojo::ByteStream;
use Mojo::Base -strict;
use overload bool => sub {1}, '""' => sub { ${$_[0]} }, fallback => 1;
use Exporter qw(import);
use Mojo::Collection;
use Mojo::Util;
our @EXPORT_OK = ('b');
# Turn most functions from Mojo::Util into methods
my @UTILS = (
qw(b64_decode b64_encode camelize decamelize gunzip gzip hmac_sha1_sum html_unescape humanize_bytes md5_bytes),
qw(md5_sum punycode_decode punycode_encode quote sha1_bytes sha1_sum slugify term_escape trim unindent unquote),
qw(url_escape url_unescape xml_escape xor_encode)
);
for my $name (@UTILS) {
my $sub = Mojo::Util->can($name);
Mojo::Util::monkey_patch __PACKAGE__, $name, sub {
my $self = shift;
$$self = $sub->($$self, @_);
return $self;
};
}
sub b { __PACKAGE__->new(@_) }
sub clone { $_[0]->new(${$_[0]}) }
sub decode { shift->_delegate(\&Mojo::Util::decode, @_) }
sub encode { shift->_delegate(\&Mojo::Util::encode, @_) }
sub new {
my $class = shift;
return bless \(my $dummy = join '', @_), ref $class || $class;
}
sub say {
my ($self, $handle) = @_;
$handle ||= \*STDOUT;
say $handle $$self;
return $self;
}
sub secure_compare { Mojo::Util::secure_compare ${shift()}, shift }
sub size { length ${$_[0]} }
sub split {
my ($self, $pat, $lim) = (shift, shift, shift // 0);
return Mojo::Collection->new(map { $self->new($_) } split $pat, $$self, $lim);
}
sub tap { shift->Mojo::Base::tap(@_) }
sub to_string { ${$_[0]} }
sub with_roles { shift->Mojo::Base::with_roles(@_) }
sub _delegate {
my ($self, $sub) = (shift, shift);
$$self = $sub->(shift || 'UTF-8', $$self);
return $self;
}
1;
=encoding utf8
=head1 NAME
Mojo::ByteStream - ByteStream
=head1 SYNOPSIS
use Mojo::ByteStream;
# Manipulate bytestream
my $stream = Mojo::ByteStream->new('foo_bar_baz');
say $stream->camelize;
# Chain methods
my $stream = Mojo::ByteStream->new('foo bar baz')->quote;
$stream = $stream->unquote->encode('UTF-8')->b64_encode('');
say "$stream";
# Use the alternative constructor
use Mojo::ByteStream qw(b);
my $stream = b('foobarbaz')->b64_encode('')->say;
=head1 DESCRIPTION
L<Mojo::ByteStream> is a scalar-based container for bytestreams that provides a more friendly API for many of the
functions in L<Mojo::Util>.
# Access scalar directly to manipulate bytestream
my $stream = Mojo::ByteStream->new('foo');
$$stream .= 'bar';
=head1 FUNCTIONS
L<Mojo::ByteStream> implements the following functions, which can be imported individually.
=head2 b
my $stream = b('test123');
Construct a new scalar-based L<Mojo::ByteStream> object.
=head1 METHODS
L<Mojo::ByteStream> implements the following methods.
=head2 b64_decode
$stream = $stream->b64_decode;
Base64 decode bytestream with L<Mojo::Util/"b64_decode">.
=head2 b64_encode
$stream = $stream->b64_encode;
$stream = $stream->b64_encode("\n");
Base64 encode bytestream with L<Mojo::Util/"b64_encode">.
# "Zm9vIGJhciBiYXo="
b('foo bar baz')->b64_encode('');
=head2 camelize
$stream = $stream->camelize;
Camelize bytestream with L<Mojo::Util/"camelize">.
=head2 clone
my $stream2 = $stream->clone;
Return a new L<Mojo::ByteStream> object cloned from this bytestream.
=head2 decamelize
$stream = $stream->decamelize;
Decamelize bytestream with L<Mojo::Util/"decamelize">.
=head2 decode
$stream = $stream->decode;
$stream = $stream->decode('iso-8859-1');
Decode bytestream with L<Mojo::Util/"decode">, defaults to using C<UTF-8>.
# "♥"
b('%E2%99%A5')->url_unescape->decode;
=head2 encode
$stream = $stream->encode;
$stream = $stream->encode('iso-8859-1');
Encode bytestream with L<Mojo::Util/"encode">, defaults to using C<UTF-8>.
# "%E2%99%A5"
b('♥')->encode->url_escape;
=head2 gunzip
$stream = $stream->gunzip;
Uncompress bytestream with L<Mojo::Util/"gunzip">.
=head2 gzip
stream = $stream->gzip;
Compress bytestream with L<Mojo::Util/"gzip">.
=head2 hmac_sha1_sum
$stream = $stream->hmac_sha1_sum('passw0rd');
Generate HMAC-SHA1 checksum for bytestream with L<Mojo::Util/"hmac_sha1_sum">.
# "7fbdc89263974a89210ea71f171c77d3f8c21471"
b('foo bar baz')->hmac_sha1_sum('secr3t');
=head2 html_unescape
$stream = $stream->html_unescape;
Unescape all HTML entities in bytestream with L<Mojo::Util/"html_unescape">.
# "%3Chtml%3E"
b('&lt;html&gt;')->html_unescape->url_escape;
=head2 humanize_bytes
$stream = $stream->humanize_bytes;
Turn number of bytes into a simplified human readable format for bytestream with L<Mojo::Util/"humanize_bytes">.
=head2 md5_bytes
$stream = $stream->md5_bytes;
Generate binary MD5 checksum for bytestream with L<Mojo::Util/"md5_bytes">.
=head2 md5_sum
$stream = $stream->md5_sum;
Generate MD5 checksum for bytestream with L<Mojo::Util/"md5_sum">.
=head2 new
my $stream = Mojo::ByteStream->new('test123');
Construct a new scalar-based L<Mojo::ByteStream> object.
=head2 punycode_decode
$stream = $stream->punycode_decode;
Punycode decode bytestream with L<Mojo::Util/"punycode_decode">.
=head2 punycode_encode
$stream = $stream->punycode_encode;
Punycode encode bytestream with L<Mojo::Util/"punycode_encode">.
=head2 quote
$stream = $stream->quote;
Quote bytestream with L<Mojo::Util/"quote">.
=head2 say
$stream = $stream->say;
$stream = $stream->say(*STDERR);
Print bytestream to handle and append a newline, defaults to using C<STDOUT>.
=head2 secure_compare
my $bool = $stream->secure_compare($str);
Compare bytestream with L<Mojo::Util/"secure_compare">.
=head2 sha1_bytes
$stream = $stream->sha1_bytes;
Generate binary SHA1 checksum for bytestream with L<Mojo::Util/"sha1_bytes">.
=head2 sha1_sum
$stream = $stream->sha1_sum;
Generate SHA1 checksum for bytestream with L<Mojo::Util/"sha1_sum">.
=head2 size
my $size = $stream->size;
Size of bytestream.
=head2 slugify
$stream = $stream->slugify;
$stream = $stream->slugify($bool);
Generate URL slug for bytestream with L<Mojo::Util/"slugify">.
=head2 split
my $collection = $stream->split(',');
my $collection = $stream->split(',', -1);
Turn bytestream into L<Mojo::Collection> object containing L<Mojo::ByteStream> objects.
# "One,Two,Three"
b("one,two,three")->split(',')->map('camelize')->join(',');
# "One,Two,Three,,,"
b("one,two,three,,,")->split(',', -1)->map('camelize')->join(',');
=head2 tap
$stream = $stream->tap(sub {...});
Alias for L<Mojo::Base/"tap">.
=head2 term_escape
$stream = $stream->term_escape;
Escape POSIX control characters in bytestream with L<Mojo::Util/"term_escape">.
# Print binary checksum to terminal
b('foo')->sha1_bytes->term_escape->say;
=head2 to_string
my $str = $stream->to_string;
Stringify bytestream.
=head2 trim
$stream = $stream->trim;
Trim whitespace characters from both ends of bytestream with L<Mojo::Util/"trim">.
=head2 unindent
$stream = $stream->unindent;
Unindent bytestream with L<Mojo::Util/"unindent">.
=head2 unquote
$stream = $stream->unquote;
Unquote bytestream with L<Mojo::Util/"unquote">.
=head2 url_escape
$stream = $stream->url_escape;
$stream = $stream->url_escape('^A-Za-z0-9\-._~');
Percent encode all unsafe characters in bytestream with L<Mojo::Util/"url_escape">.
# "%E2%98%83"
b('☃')->encode->url_escape;
=head2 url_unescape
$stream = $stream->url_unescape;
Decode percent encoded characters in bytestream with L<Mojo::Util/"url_unescape">.
# "&lt;html&gt;"
b('%3Chtml%3E')->url_unescape->xml_escape;
=head2 with_roles
my $new_class = Mojo::ByteStream->with_roles('Mojo::ByteStream::Role::One');
my $new_class = Mojo::ByteStream->with_roles('+One', '+Two');
$stream = $stream->with_roles('+One', '+Two');
Alias for L<Mojo::Base/"with_roles">.
=head2 xml_escape
$stream = $stream->xml_escape;
Escape only the characters C<&>, C<E<lt>>, C<E<gt>>, C<"> and C<'> in bytestream with L<Mojo::Util/"xml_escape">.
=head2 xor_encode
$stream = $stream->xor_encode($key);
XOR encode bytestream with L<Mojo::Util/"xor_encode">.
# "%04%0E%15B%03%1B%10"
b('foo bar')->xor_encode('baz')->url_escape;
=head1 OPERATORS
L<Mojo::ByteStream> overloads the following operators.
=head2 bool
my $bool = !!$bytestream;
Always true.
=head2 stringify
my $str = "$bytestream";
Alias for L</"to_string">.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

73
database/perl/vendor/lib/Mojo/Cache.pm vendored Normal file
View File

@@ -0,0 +1,73 @@
package Mojo::Cache;
use Mojo::Base -base;
has 'max_keys' => 100;
sub get { (shift->{cache} // {})->{shift()} }
sub set {
my ($self, $key, $value) = @_;
return $self unless (my $max = $self->max_keys) > 0;
my $cache = $self->{cache} //= {};
my $queue = $self->{queue} //= [];
delete $cache->{shift @$queue} while @$queue >= $max;
push @$queue, $key unless exists $cache->{$key};
$cache->{$key} = $value;
return $self;
}
1;
=encoding utf8
=head1 NAME
Mojo::Cache - Naive in-memory cache
=head1 SYNOPSIS
use Mojo::Cache;
my $cache = Mojo::Cache->new(max_keys => 50);
$cache->set(foo => 'bar');
my $foo = $cache->get('foo');
=head1 DESCRIPTION
L<Mojo::Cache> is a naive in-memory cache with size limits.
=head1 ATTRIBUTES
L<Mojo::Cache> implements the following attributes.
=head2 max_keys
my $max = $cache->max_keys;
$cache = $cache->max_keys(50);
Maximum number of cache keys, defaults to C<100>. Setting the value to C<0> will disable caching.
=head1 METHODS
L<Mojo::Cache> inherits all methods from L<Mojo::Base> and implements the following new ones.
=head2 get
my $value = $cache->get('foo');
Get cached value.
=head2 set
$cache = $cache->set(foo => 'bar');
Set cached value.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,400 @@
package Mojo::Collection;
use Mojo::Base -strict;
use re qw(is_regexp);
use Carp qw(croak);
use Exporter qw(import);
use List::Util;
use Mojo::ByteStream;
use Scalar::Util qw(blessed);
our @EXPORT_OK = ('c');
sub TO_JSON { [@{shift()}] }
sub c { __PACKAGE__->new(@_) }
sub compact {
my $self = shift;
return $self->new(grep { defined && (ref || length) } @$self);
}
sub each {
my ($self, $cb) = @_;
return @$self unless $cb;
my $i = 1;
$_->$cb($i++) for @$self;
return $self;
}
sub first {
my ($self, $cb) = (shift, shift);
return $self->[0] unless $cb;
return List::Util::first { $_ =~ $cb } @$self if is_regexp $cb;
return List::Util::first { $_->$cb(@_) } @$self;
}
sub flatten { $_[0]->new(_flatten(@{$_[0]})) }
sub grep {
my ($self, $cb) = (shift, shift);
return $self->new(grep { $_ =~ $cb } @$self) if is_regexp $cb;
return $self->new(grep { $_->$cb(@_) } @$self);
}
sub head {
my ($self, $size) = @_;
return $self->new(@$self) if $size > @$self;
return $self->new(@$self[0 .. ($size - 1)]) if $size >= 0;
return $self->new(@$self[0 .. ($#$self + $size)]);
}
sub join {
Mojo::ByteStream->new(join $_[1] // '', map {"$_"} @{$_[0]});
}
sub last { shift->[-1] }
sub map {
my ($self, $cb) = (shift, shift);
return $self->new(map { $_->$cb(@_) } @$self);
}
sub new {
my $class = shift;
return bless [@_], ref $class || $class;
}
sub reduce {
my $self = shift;
@_ = (@_, @$self);
goto &List::Util::reduce;
}
sub reverse { $_[0]->new(reverse @{$_[0]}) }
sub shuffle { $_[0]->new(List::Util::shuffle @{$_[0]}) }
sub size { scalar @{$_[0]} }
sub sort {
my ($self, $cb) = @_;
return $self->new(sort @$self) unless $cb;
my $caller = caller;
no strict 'refs';
my @sorted = sort {
local (*{"${caller}::a"}, *{"${caller}::b"}) = (\$a, \$b);
$a->$cb($b);
} @$self;
return $self->new(@sorted);
}
sub tail {
my ($self, $size) = @_;
return $self->new(@$self) if $size > @$self;
return $self->new(@$self[($#$self - ($size - 1)) .. $#$self]) if $size >= 0;
return $self->new(@$self[(0 - $size) .. $#$self]);
}
sub tap { shift->Mojo::Base::tap(@_) }
sub to_array { [@{shift()}] }
sub uniq {
my ($self, $cb) = (shift, shift);
my %seen;
return $self->new(grep { !$seen{$_->$cb(@_) // ''}++ } @$self) if $cb;
return $self->new(grep { !$seen{$_ // ''}++ } @$self);
}
sub with_roles { shift->Mojo::Base::with_roles(@_) }
sub _flatten {
map { _ref($_) ? _flatten(@$_) : $_ } @_;
}
sub _ref { ref $_[0] eq 'ARRAY' || blessed $_[0] && $_[0]->isa(__PACKAGE__) }
1;
=encoding utf8
=head1 NAME
Mojo::Collection - Collection
=head1 SYNOPSIS
use Mojo::Collection;
# Manipulate collection
my $collection = Mojo::Collection->new(qw(just works));
unshift @$collection, 'it';
say $collection->join("\n");
# Chain methods
$collection->map(sub { ucfirst })->shuffle->each(sub ($word, $num) {
say "$num: $word";
});
# Use the alternative constructor
use Mojo::Collection qw(c);
c(qw(a b c))->join('/')->url_escape->say;
=head1 DESCRIPTION
L<Mojo::Collection> is an array-based container for collections.
# Access array directly to manipulate collection
my $collection = Mojo::Collection->new(1 .. 25);
$collection->[23] += 100;
say for @$collection;
=head1 FUNCTIONS
L<Mojo::Collection> implements the following functions, which can be imported individually.
=head2 c
my $collection = c(1, 2, 3);
Construct a new array-based L<Mojo::Collection> object.
=head1 METHODS
L<Mojo::Collection> implements the following methods.
=head2 TO_JSON
my $array = $collection->TO_JSON;
Alias for L</"to_array">.
=head2 compact
my $new = $collection->compact;
Create a new collection with all elements that are defined and not an empty string.
# "0, 1, 2, 3"
c(0, 1, undef, 2, '', 3)->compact->join(', ');
=head2 each
my @elements = $collection->each;
$collection = $collection->each(sub {...});
Evaluate callback for each element in collection, or return all elements as a list if none has been provided. The
element will be the first argument passed to the callback, and is also available as C<$_>.
# Make a numbered list
$collection->each(sub ($e, $num) {
say "$num: $e";
});
=head2 first
my $first = $collection->first;
my $first = $collection->first(qr/foo/);
my $first = $collection->first(sub {...});
my $first = $collection->first('some_method');
my $first = $collection->first('some_method', @args);
Evaluate regular expression/callback for, or call method on, each element in collection and return the first one that
matched the regular expression, or for which the callback/method returned true. The element will be the first argument
passed to the callback, and is also available as C<$_>.
# Longer version
my $first = $collection->first(sub { $_->some_method(@args) });
# Find first value that contains the word "mojo"
my $interesting = $collection->first(qr/mojo/i);
# Find first value that is greater than 5
my $greater = $collection->first(sub { $_ > 5 });
=head2 flatten
my $new = $collection->flatten;
Flatten nested collections/arrays recursively and create a new collection with all elements.
# "1, 2, 3, 4, 5, 6, 7"
c(1, [2, [3, 4], 5, [6]], 7)->flatten->join(', ');
=head2 grep
my $new = $collection->grep(qr/foo/);
my $new = $collection->grep(sub {...});
my $new = $collection->grep('some_method');
my $new = $collection->grep('some_method', @args);
Evaluate regular expression/callback for, or call method on, each element in collection and create a new collection
with all elements that matched the regular expression, or for which the callback/method returned true. The element will
be the first argument passed to the callback, and is also available as C<$_>.
# Longer version
my $new = $collection->grep(sub { $_->some_method(@args) });
# Find all values that contain the word "mojo"
my $interesting = $collection->grep(qr/mojo/i);
# Find all values that are greater than 5
my $greater = $collection->grep(sub { $_ > 5 });
=head2 head
my $new = $collection->head(4);
my $new = $collection->head(-2);
Create a new collection with up to the specified number of elements from the beginning of the collection. A negative
number will count from the end.
# "A B C"
c('A', 'B', 'C', 'D', 'E')->head(3)->join(' ');
# "A B"
c('A', 'B', 'C', 'D', 'E')->head(-3)->join(' ');
=head2 join
my $stream = $collection->join;
my $stream = $collection->join("\n");
Turn collection into L<Mojo::ByteStream>.
# Join all values with commas
$collection->join(', ')->say;
=head2 last
my $last = $collection->last;
Return the last element in collection.
=head2 map
my $new = $collection->map(sub {...});
my $new = $collection->map('some_method');
my $new = $collection->map('some_method', @args);
Evaluate callback for, or call method on, each element in collection and create a new collection from the results. The
element will be the first argument passed to the callback, and is also available as C<$_>.
# Longer version
my $new = $collection->map(sub { $_->some_method(@args) });
# Append the word "mojo" to all values
my $mojoified = $collection->map(sub { $_ . 'mojo' });
=head2 new
my $collection = Mojo::Collection->new(1, 2, 3);
Construct a new array-based L<Mojo::Collection> object.
=head2 reduce
my $result = $collection->reduce(sub {...});
my $result = $collection->reduce(sub {...}, $initial);
Reduce elements in collection with a callback and return its final result, setting C<$a> and C<$b> each time the
callback is executed. The first time C<$a> will be set to an optional initial value or the first element in the
collection. And from then on C<$a> will be set to the return value of the callback, while C<$b> will always be set to
the next element in the collection.
# Calculate the sum of all values
my $sum = $collection->reduce(sub { $a + $b });
# Count how often each value occurs in collection
my $hash = $collection->reduce(sub { $a->{$b}++; $a }, {});
=head2 reverse
my $new = $collection->reverse;
Create a new collection with all elements in reverse order.
=head2 shuffle
my $new = $collection->shuffle;
Create a new collection with all elements in random order.
=head2 size
my $size = $collection->size;
Number of elements in collection.
=head2 sort
my $new = $collection->sort;
my $new = $collection->sort(sub {...});
Sort elements based on return value of a callback and create a new collection from the results, setting C<$a> and C<$b>
to the elements being compared, each time the callback is executed.
# Sort values case-insensitive
my $case_insensitive = $collection->sort(sub { uc($a) cmp uc($b) });
=head2 tail
my $new = $collection->tail(4);
my $new = $collection->tail(-2);
Create a new collection with up to the specified number of elements from the end of the collection. A negative number
will count from the beginning.
# "C D E"
c('A', 'B', 'C', 'D', 'E')->tail(3)->join(' ');
# "D E"
c('A', 'B', 'C', 'D', 'E')->tail(-3)->join(' ');
=head2 tap
$collection = $collection->tap(sub {...});
Alias for L<Mojo::Base/"tap">.
=head2 to_array
my $array = $collection->to_array;
Turn collection into array reference.
=head2 uniq
my $new = $collection->uniq;
my $new = $collection->uniq(sub {...});
my $new = $collection->uniq('some_method');
my $new = $collection->uniq('some_method', @args);
Create a new collection without duplicate elements, using the string representation of either the elements or the
return value of the callback/method to decide uniqueness. Note that C<undef> and empty string are treated the same.
# Longer version
my $new = $collection->uniq(sub { $_->some_method(@args) });
# "foo bar baz"
c('foo', 'bar', 'bar', 'baz')->uniq->join(' ');
# "[[1, 2], [2, 1]]"
c([1, 2], [2, 1], [3, 2])->uniq(sub{ $_->[1] })->to_array;
=head2 with_roles
my $new_class = Mojo::Collection->with_roles('Mojo::Collection::Role::One');
my $new_class = Mojo::Collection->with_roles('+One', '+Two');
$collection = $collection->with_roles('+One', '+Two');
Alias for L<Mojo::Base/"with_roles">.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

548
database/perl/vendor/lib/Mojo/Content.pm vendored Normal file
View File

@@ -0,0 +1,548 @@
package Mojo::Content;
use Mojo::Base 'Mojo::EventEmitter';
use Carp qw(croak);
use Compress::Raw::Zlib qw(WANT_GZIP Z_STREAM_END);
use Mojo::Headers;
use Scalar::Util qw(looks_like_number);
has [qw(auto_decompress auto_relax relaxed skip_body)];
has headers => sub { Mojo::Headers->new };
has max_buffer_size => sub { $ENV{MOJO_MAX_BUFFER_SIZE} || 262144 };
has max_leftover_size => sub { $ENV{MOJO_MAX_LEFTOVER_SIZE} || 262144 };
my $BOUNDARY_RE = qr!multipart.*boundary\s*=\s*(?:"([^"]+)"|([\w'(),.:?\-+/]+))!i;
sub body_contains { croak 'Method "body_contains" not implemented by subclass' }
sub body_size { croak 'Method "body_size" not implemented by subclass' }
sub boundary { (shift->headers->content_type // '') =~ $BOUNDARY_RE ? $1 // $2 : undef }
sub charset {
my $type = shift->headers->content_type // '';
return $type =~ /charset\s*=\s*"?([^"\s;]+)"?/i ? $1 : undef;
}
sub clone {
my $self = shift;
return undef if $self->is_dynamic;
return $self->new(headers => $self->headers->clone);
}
sub generate_body_chunk {
my ($self, $offset) = @_;
$self->emit(drain => $offset) unless length($self->{body_buffer} //= '');
return delete $self->{body_buffer} if length $self->{body_buffer};
return '' if $self->{eof};
my $len = $self->headers->content_length;
return looks_like_number $len && $len == $offset ? '' : undef;
}
sub get_body_chunk { croak 'Method "get_body_chunk" not implemented by subclass' }
sub get_header_chunk { substr shift->_headers->{header_buffer}, shift, 131072 }
sub header_size { length shift->_headers->{header_buffer} }
sub headers_contain { index(shift->_headers->{header_buffer}, shift) >= 0 }
sub is_chunked { !!shift->headers->transfer_encoding }
sub is_compressed { lc(shift->headers->content_encoding // '') eq 'gzip' }
sub is_dynamic { !!$_[0]{dynamic} }
sub is_finished { (shift->{state} // '') eq 'finished' }
sub is_limit_exceeded { !!shift->{limit} }
sub is_multipart {undef}
sub is_parsing_body { (shift->{state} // '') eq 'body' }
sub leftovers { shift->{buffer} }
sub parse {
my $self = shift;
# Headers
$self->_parse_until_body(@_);
return $self if $self->{state} eq 'headers';
# Chunked content
$self->{real_size} //= 0;
if ($self->is_chunked && $self->{state} ne 'headers') {
$self->_parse_chunked;
$self->{state} = 'finished' if ($self->{chunk_state} // '') eq 'finished';
}
# Not chunked, pass through to second buffer
else {
$self->{real_size} += length $self->{pre_buffer};
my $limit = $self->is_finished && length($self->{buffer}) > $self->max_leftover_size;
$self->{buffer} .= $self->{pre_buffer} unless $limit;
$self->{pre_buffer} = '';
}
# No content
if ($self->skip_body) {
$self->{state} = 'finished';
return $self;
}
# Relaxed parsing
my $headers = $self->headers;
my $len = $headers->content_length // '';
if ($self->auto_relax && !length $len) {
my $connection = lc($headers->connection // '');
$self->relaxed(1) if $connection eq 'close' || !$connection;
}
# Chunked or relaxed content
if ($self->is_chunked || $self->relaxed) {
$self->_decompress($self->{buffer} //= '');
$self->{size} += length $self->{buffer};
$self->{buffer} = '';
return $self;
}
# Normal content
$len = 0 unless looks_like_number $len;
if ((my $need = $len - ($self->{size} ||= 0)) > 0) {
my $len = length $self->{buffer};
my $chunk = substr $self->{buffer}, 0, $need > $len ? $len : $need, '';
$self->_decompress($chunk);
$self->{size} += length $chunk;
}
$self->{state} = 'finished' if $len <= $self->progress;
return $self;
}
sub parse_body {
my $self = shift;
$self->{state} = 'body';
return $self->parse(@_);
}
sub progress {
my $self = shift;
return 0 unless my $state = $self->{state};
return 0 unless $state eq 'body' || $state eq 'finished';
return $self->{raw_size} - ($self->{header_size} || 0);
}
sub write {
my ($self, $chunk, $cb) = @_;
$self->{dynamic} = 1;
$self->{body_buffer} .= $chunk if defined $chunk;
$self->once(drain => $cb) if $cb;
$self->{eof} = 1 if defined $chunk && !length $chunk;
return $self;
}
sub write_chunk {
my ($self, $chunk, $cb) = @_;
$self->headers->transfer_encoding('chunked') unless $self->{chunked};
@{$self}{qw(chunked dynamic)} = (1, 1);
$self->{body_buffer} .= $self->_build_chunk($chunk) if defined $chunk;
$self->once(drain => $cb) if $cb;
$self->{eof} = 1 if defined $chunk && !length $chunk;
return $self;
}
sub _build_chunk {
my ($self, $chunk) = @_;
# End
return "\x0d\x0a0\x0d\x0a\x0d\x0a" unless length $chunk;
# First chunk has no leading CRLF
my $crlf = $self->{chunks}++ ? "\x0d\x0a" : '';
return $crlf . sprintf('%x', length $chunk) . "\x0d\x0a$chunk";
}
sub _decompress {
my ($self, $chunk) = @_;
# No compression
return $self->emit(read => $chunk) unless $self->auto_decompress && $self->is_compressed;
# Decompress
$self->{post_buffer} .= $chunk;
my $gz = $self->{gz} //= Compress::Raw::Zlib::Inflate->new(WindowBits => WANT_GZIP);
my $status = $gz->inflate(\$self->{post_buffer}, my $out);
$self->emit(read => $out) if defined $out;
# Replace Content-Encoding with Content-Length
$self->headers->content_length($gz->total_out)->remove('Content-Encoding') if $status == Z_STREAM_END;
# Check buffer size
@$self{qw(state limit)} = ('finished', 1) if length($self->{post_buffer} // '') > $self->max_buffer_size;
}
sub _headers {
my $self = shift;
return $self if defined $self->{header_buffer};
my $headers = $self->headers->to_string;
$self->{header_buffer} = $headers ? "$headers\x0d\x0a\x0d\x0a" : "\x0d\x0a";
return $self;
}
sub _parse_chunked {
my $self = shift;
# Trailing headers
return $self->_parse_chunked_trailing_headers if ($self->{chunk_state} // '') eq 'trailing_headers';
while (my $len = length $self->{pre_buffer}) {
# Start new chunk (ignore the chunk extension)
unless ($self->{chunk_len}) {
last unless $self->{pre_buffer} =~ s/^(?:\x0d?\x0a)?([0-9a-fA-F]+).*\x0a//;
next if $self->{chunk_len} = hex $1;
# Last chunk
$self->{chunk_state} = 'trailing_headers';
last;
}
# Remove as much as possible from payload
$len = $self->{chunk_len} if $self->{chunk_len} < $len;
$self->{buffer} .= substr $self->{pre_buffer}, 0, $len, '';
$self->{real_size} += $len;
$self->{chunk_len} -= $len;
}
# Trailing headers
$self->_parse_chunked_trailing_headers if ($self->{chunk_state} // '') eq 'trailing_headers';
# Check buffer size
@$self{qw(state limit)} = ('finished', 1) if length($self->{pre_buffer} // '') > $self->max_buffer_size;
}
sub _parse_chunked_trailing_headers {
my $self = shift;
my $headers = $self->headers->parse(delete $self->{pre_buffer});
return unless $headers->is_finished;
$self->{chunk_state} = 'finished';
# Take care of leftover and replace Transfer-Encoding with Content-Length
$self->{buffer} .= $headers->leftovers;
$headers->remove('Transfer-Encoding');
$headers->content_length($self->{real_size}) unless $headers->content_length;
}
sub _parse_headers {
my $self = shift;
my $headers = $self->headers->parse(delete $self->{pre_buffer});
return unless $headers->is_finished;
$self->{state} = 'body';
# Take care of leftovers
my $leftovers = $self->{pre_buffer} = $headers->leftovers;
$self->{header_size} = $self->{raw_size} - length $leftovers;
}
sub _parse_until_body {
my ($self, $chunk) = @_;
$self->{raw_size} += length($chunk //= '');
$self->{pre_buffer} .= $chunk;
$self->_parse_headers if ($self->{state} ||= 'headers') eq 'headers';
$self->emit('body') if $self->{state} ne 'headers' && !$self->{body}++;
}
1;
=encoding utf8
=head1 NAME
Mojo::Content - HTTP content base class
=head1 SYNOPSIS
package Mojo::Content::MyContent;
use Mojo::Base 'Mojo::Content';
sub body_contains {...}
sub body_size {...}
sub get_body_chunk {...}
=head1 DESCRIPTION
L<Mojo::Content> is an abstract base class for HTTP content containers, based on L<RFC
7230|https://tools.ietf.org/html/rfc7230> and L<RFC 7231|https://tools.ietf.org/html/rfc7231>, like
L<Mojo::Content::MultiPart> and L<Mojo::Content::Single>.
=head1 EVENTS
L<Mojo::Content> inherits all events from L<Mojo::EventEmitter> and can emit the following new ones.
=head2 body
$content->on(body => sub ($content) {...});
Emitted once all headers have been parsed and the body starts.
$content->on(body => sub ($content) {
$content->auto_upgrade(0) if $content->headers->header('X-No-MultiPart');
});
=head2 drain
$content->on(drain => sub ($content, $offset) {...});
Emitted once all data has been written.
$content->on(drain => sub ($content) {
$content->write_chunk(time);
});
=head2 read
$content->on(read => sub ($content, $bytes) {...});
Emitted when a new chunk of content arrives.
$content->on(read => sub ($content, $bytes) {
say "Streaming: $bytes";
});
=head1 ATTRIBUTES
L<Mojo::Content> implements the following attributes.
=head2 auto_decompress
my $bool = $content->auto_decompress;
$content = $content->auto_decompress($bool);
Decompress content automatically if L</"is_compressed"> is true.
=head2 auto_relax
my $bool = $content->auto_relax;
$content = $content->auto_relax($bool);
Try to detect when relaxed parsing is necessary.
=head2 headers
my $headers = $content->headers;
$content = $content->headers(Mojo::Headers->new);
Content headers, defaults to a L<Mojo::Headers> object.
=head2 max_buffer_size
my $size = $content->max_buffer_size;
$content = $content->max_buffer_size(1024);
Maximum size in bytes of buffer for content parser, defaults to the value of the C<MOJO_MAX_BUFFER_SIZE> environment
variable or C<262144> (256KiB).
=head2 max_leftover_size
my $size = $content->max_leftover_size;
$content = $content->max_leftover_size(1024);
Maximum size in bytes of buffer for pipelined HTTP requests, defaults to the value of the C<MOJO_MAX_LEFTOVER_SIZE>
environment variable or C<262144> (256KiB).
=head2 relaxed
my $bool = $content->relaxed;
$content = $content->relaxed($bool);
Activate relaxed parsing for responses that are terminated with a connection close.
=head2 skip_body
my $bool = $content->skip_body;
$content = $content->skip_body($bool);
Skip body parsing and finish after headers.
=head1 METHODS
L<Mojo::Content> inherits all methods from L<Mojo::EventEmitter> and implements the following new ones.
=head2 body_contains
my $bool = $content->body_contains('foo bar baz');
Check if content contains a specific string. Meant to be overloaded in a subclass.
=head2 body_size
my $size = $content->body_size;
Content size in bytes. Meant to be overloaded in a subclass.
=head2 boundary
my $boundary = $content->boundary;
Extract multipart boundary from C<Content-Type> header.
=head2 charset
my $charset = $content->charset;
Extract charset from C<Content-Type> header.
=head2 clone
my $clone = $content->clone;
Return a new L<Mojo::Content> object cloned from this content if possible, otherwise return C<undef>.
=head2 generate_body_chunk
my $bytes = $content->generate_body_chunk(0);
Generate dynamic content.
=head2 get_body_chunk
my $bytes = $content->get_body_chunk(0);
Get a chunk of content starting from a specific position. Meant to be overloaded in a subclass.
=head2 get_header_chunk
my $bytes = $content->get_header_chunk(13);
Get a chunk of the headers starting from a specific position. Note that this method finalizes the content.
=head2 header_size
my $size = $content->header_size;
Size of headers in bytes. Note that this method finalizes the content.
=head2 headers_contain
my $bool = $content->headers_contain('foo bar baz');
Check if headers contain a specific string. Note that this method finalizes the content.
=head2 is_chunked
my $bool = $content->is_chunked;
Check if C<Transfer-Encoding> header indicates chunked transfer encoding.
=head2 is_compressed
my $bool = $content->is_compressed;
Check C<Content-Encoding> header for C<gzip> value.
=head2 is_dynamic
my $bool = $content->is_dynamic;
Check if content will be dynamically generated, which prevents L</"clone"> from working.
=head2 is_finished
my $bool = $content->is_finished;
Check if parser is finished.
=head2 is_limit_exceeded
my $bool = $content->is_limit_exceeded;
Check if buffer has exceeded L</"max_buffer_size">.
=head2 is_multipart
my $bool = $content->is_multipart;
False, this is not a L<Mojo::Content::MultiPart> object.
=head2 is_parsing_body
my $bool = $content->is_parsing_body;
Check if body parsing started yet.
=head2 leftovers
my $bytes = $content->leftovers;
Get leftover data from content parser.
=head2 parse
$content
= $content->parse("Content-Length: 12\x0d\x0a\x0d\x0aHello World!");
Parse content chunk.
=head2 parse_body
$content = $content->parse_body('Hi!');
Parse body chunk and skip headers.
=head2 progress
my $size = $content->progress;
Size of content already received from message in bytes.
=head2 write
$content = $content->write;
$content = $content->write('');
$content = $content->write($bytes);
$content = $content->write($bytes => sub {...});
Write dynamic content non-blocking, the optional drain callback will be executed once all data has been written.
Calling this method without a chunk of data will finalize the L</"headers"> and allow for dynamic content to be written
later. You can write an empty chunk of data at any time to end the stream.
# Make sure previous chunk of data has been written before continuing
$content->write('He' => sub ($content) {
$content->write('llo!' => sub ($content) {
$content->write('');
});
});
=head2 write_chunk
$content = $content->write_chunk;
$content = $content->write_chunk('');
$content = $content->write_chunk($bytes);
$content = $content->write_chunk($bytes => sub {...});
Write dynamic content non-blocking with chunked transfer encoding, the optional drain callback will be executed once
all data has been written. Calling this method without a chunk of data will finalize the L</"headers"> and allow for
dynamic content to be written later. You can write an empty chunk of data at any time to end the stream.
# Make sure previous chunk of data has been written before continuing
$content->write_chunk('He' => sub ($content) {
$content->write_chunk('llo!' => sub ($content) {
$content->write_chunk('');
});
});
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,290 @@
package Mojo::Content::MultiPart;
use Mojo::Base 'Mojo::Content';
use Mojo::Util qw(b64_encode);
has parts => sub { [] };
sub body_contains {
my ($self, $chunk) = @_;
($_->headers_contain($chunk) or $_->body_contains($chunk)) and return 1 for @{$self->parts};
return undef;
}
sub body_size {
my $self = shift;
# Check for existing Content-Length header
if (my $len = $self->headers->content_length) { return $len }
# Calculate length of whole body
my $len = my $boundary_len = length($self->build_boundary) + 6;
$len += $_->header_size + $_->body_size + $boundary_len for @{$self->parts};
return $len;
}
sub build_boundary {
my $self = shift;
# Check for existing boundary
my $boundary;
return $boundary if defined($boundary = $self->boundary);
# Generate and check boundary
my $size = 1;
do {
$boundary = b64_encode join('', map chr(rand 256), 1 .. $size++ * 3);
$boundary =~ s/\W/X/g;
} while $self->body_contains($boundary);
# Add boundary to Content-Type header
my $headers = $self->headers;
my ($before, $after) = ('multipart/mixed', '');
($before, $after) = ($1, $2) if ($headers->content_type // '') =~ m!^(.*multipart/[^;]+)(.*)$!;
$headers->content_type("$before; boundary=$boundary$after");
return $boundary;
}
sub clone {
my $self = shift;
return undef unless my $clone = $self->SUPER::clone();
return $clone->parts($self->parts);
}
sub get_body_chunk {
my ($self, $offset) = @_;
# Body generator
return $self->generate_body_chunk($offset) if $self->is_dynamic;
# First boundary
my $boundary = $self->{boundary} //= $self->build_boundary;
my $boundary_len = length($boundary) + 6;
my $len = $boundary_len - 2;
return substr "--$boundary\x0d\x0a", $offset if $len > $offset;
# Skip parts that have already been processed
my $start = 0;
($len, $start) = ($self->{last_len}, $self->{last_part} + 1) if $self->{offset} && $offset > $self->{offset};
# Prepare content part by part
my $parts = $self->parts;
for (my $i = $start; $i < @$parts; $i++) {
my $part = $parts->[$i];
# Headers
my $header_len = $part->header_size;
return $part->get_header_chunk($offset - $len) if ($len + $header_len) > $offset;
$len += $header_len;
# Content
my $content_len = $part->body_size;
return $part->get_body_chunk($offset - $len) if ($len + $content_len) > $offset;
$len += $content_len;
# Boundary
if ($#$parts == $i) {
$boundary .= '--';
$boundary_len += 2;
}
return substr "\x0d\x0a--$boundary\x0d\x0a", $offset - $len if ($len + $boundary_len) > $offset;
$len += $boundary_len;
@{$self}{qw(last_len last_part offset)} = ($len, $i, $offset);
}
}
sub is_multipart {1}
sub new {
my $self = shift->SUPER::new(@_);
$self->on(read => \&_read);
return $self;
}
sub _parse_multipart_body {
my ($self, $boundary) = @_;
# Whole part in buffer
my $pos = index $self->{multipart}, "\x0d\x0a--$boundary";
if ($pos < 0) {
my $len = length($self->{multipart}) - (length($boundary) + 8);
return undef unless $len > 0;
# Store chunk
my $chunk = substr $self->{multipart}, 0, $len, '';
$self->parts->[-1] = $self->parts->[-1]->parse($chunk);
return undef;
}
# Store chunk
my $chunk = substr $self->{multipart}, 0, $pos, '';
$self->parts->[-1] = $self->parts->[-1]->parse($chunk);
return !!($self->{multi_state} = 'multipart_boundary');
}
sub _parse_multipart_boundary {
my ($self, $boundary) = @_;
# Boundary begins
if ((index $self->{multipart}, "\x0d\x0a--$boundary\x0d\x0a") == 0) {
substr $self->{multipart}, 0, length($boundary) + 6, '';
# New part
my $part = Mojo::Content::Single->new(relaxed => 1);
$self->emit(part => $part);
push @{$self->parts}, $part;
return !!($self->{multi_state} = 'multipart_body');
}
# Boundary ends
my $end = "\x0d\x0a--$boundary--";
if ((index $self->{multipart}, $end) == 0) {
substr $self->{multipart}, 0, length $end, '';
$self->{multi_state} = 'finished';
}
return undef;
}
sub _parse_multipart_preamble {
my ($self, $boundary) = @_;
# No boundary yet
return undef if (my $pos = index $self->{multipart}, "--$boundary") < 0;
# Replace preamble with carriage return and line feed
substr $self->{multipart}, 0, $pos, "\x0d\x0a";
# Parse boundary
return !!($self->{multi_state} = 'multipart_boundary');
}
sub _read {
my ($self, $chunk) = @_;
$self->{multipart} .= $chunk;
my $boundary = $self->boundary;
until (($self->{multi_state} //= 'multipart_preamble') eq 'finished') {
# Preamble
if ($self->{multi_state} eq 'multipart_preamble') { last unless $self->_parse_multipart_preamble($boundary) }
# Boundary
elsif ($self->{multi_state} eq 'multipart_boundary') { last unless $self->_parse_multipart_boundary($boundary) }
# Body
elsif ($self->{multi_state} eq 'multipart_body') { last unless $self->_parse_multipart_body($boundary) }
}
# Check buffer size
@$self{qw(state limit)} = ('finished', 1) if length($self->{multipart} // '') > $self->max_buffer_size;
}
1;
=encoding utf8
=head1 NAME
Mojo::Content::MultiPart - HTTP multipart content
=head1 SYNOPSIS
use Mojo::Content::MultiPart;
my $multi = Mojo::Content::MultiPart->new;
$multi->parse('Content-Type: multipart/mixed; boundary=---foobar');
my $single = $multi->parts->[4];
=head1 DESCRIPTION
L<Mojo::Content::MultiPart> is a container for HTTP multipart content, based on L<RFC
7230|https://tools.ietf.org/html/rfc7230>, L<RFC 7231|https://tools.ietf.org/html/rfc7231> and L<RFC
2388|https://tools.ietf.org/html/rfc2388>.
=head1 EVENTS
L<Mojo::Content::Multipart> inherits all events from L<Mojo::Content> and can emit the following new ones.
=head2 part
$multi->on(part => sub ($multi, $single) {...});
Emitted when a new L<Mojo::Content::Single> part starts.
$multi->on(part => sub ($multi, $single) {
return unless $single->headers->content_disposition =~ /name="([^"]+)"/;
say "Field: $1";
});
=head1 ATTRIBUTES
L<Mojo::Content::MultiPart> inherits all attributes from L<Mojo::Content> and implements the following new ones.
=head2 parts
my $parts = $multi->parts;
$multi = $multi->parts([Mojo::Content::Single->new]);
Content parts embedded in this multipart content, usually L<Mojo::Content::Single> objects.
=head1 METHODS
L<Mojo::Content::MultiPart> inherits all methods from L<Mojo::Content> and implements the following new ones.
=head2 body_contains
my $bool = $multi->body_contains('foobarbaz');
Check if content parts contain a specific string.
=head2 body_size
my $size = $multi->body_size;
Content size in bytes.
=head2 build_boundary
my $boundary = $multi->build_boundary;
Generate a suitable boundary for content and add it to C<Content-Type> header.
=head2 clone
my $clone = $multi->clone;
Return a new L<Mojo::Content::MultiPart> object cloned from this content if possible, otherwise return C<undef>.
=head2 get_body_chunk
my $bytes = $multi->get_body_chunk(0);
Get a chunk of content starting from a specific position. Note that it might not be possible to get the same chunk
twice if content was generated dynamically.
=head2 is_multipart
my $bool = $multi->is_multipart;
True, this is a L<Mojo::Content::MultiPart> object.
=head2 new
my $multi = Mojo::Content::MultiPart->new;
my $multi
= Mojo::Content::MultiPart->new(parts => [Mojo::Content::Single->new]);
my $multi
= Mojo::Content::MultiPart->new({parts => [Mojo::Content::Single->new]});
Construct a new L<Mojo::Content::MultiPart> object and subscribe to event L<Mojo::Content/"read"> with default content
parser.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,156 @@
package Mojo::Content::Single;
use Mojo::Base 'Mojo::Content';
use Mojo::Asset::Memory;
use Mojo::Content::MultiPart;
has asset => sub { Mojo::Asset::Memory->new(auto_upgrade => 1) };
has auto_upgrade => 1;
sub body_contains { shift->asset->contains(shift) >= 0 }
sub body_size {
my $self = shift;
return ($self->headers->content_length || 0) if $self->is_dynamic;
return $self->{body_size} //= $self->asset->size;
}
sub clone {
my $self = shift;
return undef unless my $clone = $self->SUPER::clone();
return $clone->asset($self->asset);
}
sub get_body_chunk {
my ($self, $offset) = @_;
return $self->generate_body_chunk($offset) if $self->is_dynamic;
return $self->asset->get_chunk($offset);
}
sub new {
my $self = shift->SUPER::new(@_);
$self->{read} = $self->on(read => sub { $_[0]->asset($_[0]->asset->add_chunk($_[1])) });
return $self;
}
sub parse {
my $self = shift;
# Parse headers
$self->_parse_until_body(@_);
# Parse body
return $self->SUPER::parse unless $self->auto_upgrade && defined $self->boundary;
# Content needs to be upgraded to multipart
$self->unsubscribe(read => $self->{read});
my $multi = Mojo::Content::MultiPart->new(%$self);
$self->emit(upgrade => $multi);
return $multi->parse;
}
1;
=encoding utf8
=head1 NAME
Mojo::Content::Single - HTTP content
=head1 SYNOPSIS
use Mojo::Content::Single;
my $single = Mojo::Content::Single->new;
$single->parse("Content-Length: 12\x0d\x0a\x0d\x0aHello World!");
say $single->headers->content_length;
=head1 DESCRIPTION
L<Mojo::Content::Single> is a container for HTTP content, based on L<RFC 7230|https://tools.ietf.org/html/rfc7230> and
L<RFC 7231|https://tools.ietf.org/html/rfc7231>.
=head1 EVENTS
L<Mojo::Content::Single> inherits all events from L<Mojo::Content> and can emit the following new ones.
=head2 upgrade
$single->on(upgrade => sub ($single, $multi) {...});
Emitted when content gets upgraded to a L<Mojo::Content::MultiPart> object.
$single->on(upgrade => sub ($single, $multi) {
return unless $multi->headers->content_type =~ /multipart\/([^;]+)/i;
say "Multipart: $1";
});
=head1 ATTRIBUTES
L<Mojo::Content::Single> inherits all attributes from L<Mojo::Content> and implements the following new ones.
=head2 asset
my $asset = $single->asset;
$single = $single->asset(Mojo::Asset::Memory->new);
The actual content, defaults to a L<Mojo::Asset::Memory> object with L<Mojo::Asset::Memory/"auto_upgrade"> enabled.
=head2 auto_upgrade
my $bool = $single->auto_upgrade;
$single = $single->auto_upgrade($bool);
Try to detect multipart content and automatically upgrade to a L<Mojo::Content::MultiPart> object, defaults to a true
value.
=head1 METHODS
L<Mojo::Content::Single> inherits all methods from L<Mojo::Content> and implements the following new ones.
=head2 body_contains
my $bool = $single->body_contains('1234567');
Check if content contains a specific string.
=head2 body_size
my $size = $single->body_size;
Content size in bytes.
=head2 clone
my $clone = $single->clone;
Return a new L<Mojo::Content::Single> object cloned from this content if possible, otherwise return C<undef>.
=head2 get_body_chunk
my $bytes = $single->get_body_chunk(0);
Get a chunk of content starting from a specific position. Note that it might not be possible to get the same chunk
twice if content was generated dynamically.
=head2 new
my $single = Mojo::Content::Single->new;
my $single = Mojo::Content::Single->new(asset => Mojo::Asset::File->new);
my $single = Mojo::Content::Single->new({asset => Mojo::Asset::File->new});
Construct a new L<Mojo::Content::Single> object and subscribe to event L<Mojo::Content/"read"> with default content
parser.
=head2 parse
$single = $single->parse("Content-Length: 12\x0d\x0a\x0d\x0aHello World!");
my $multi = $single->parse("Content-Type: multipart/form-data\x0d\x0a\x0d\x0a");
Parse content chunk and upgrade to L<Mojo::Content::MultiPart> object if necessary.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

87
database/perl/vendor/lib/Mojo/Cookie.pm vendored Normal file
View File

@@ -0,0 +1,87 @@
package Mojo::Cookie;
use Mojo::Base -base;
use overload bool => sub {1}, '""' => sub { shift->to_string }, fallback => 1;
use Carp qw(croak);
has [qw(name value)];
sub parse { croak 'Method "parse" not implemented by subclass' }
sub to_string { croak 'Method "to_string" not implemented by subclass' }
1;
=encoding utf8
=head1 NAME
Mojo::Cookie - HTTP cookie base class
=head1 SYNOPSIS
package Mojo::Cookie::MyCookie;
use Mojo::Base 'Mojo::Cookie';
sub parse {...}
sub to_string {...}
=head1 DESCRIPTION
L<Mojo::Cookie> is an abstract base class for HTTP cookie containers, based on L<RFC
6265|https://tools.ietf.org/html/rfc6265>, like L<Mojo::Cookie::Request> and L<Mojo::Cookie::Response>.
=head1 ATTRIBUTES
L<Mojo::Cookie> implements the following attributes.
=head2 name
my $name = $cookie->name;
$cookie = $cookie->name('foo');
Cookie name.
=head2 value
my $value = $cookie->value;
$cookie = $cookie->value('/test');
Cookie value.
=head1 METHODS
L<Mojo::Cookie> inherits all methods from L<Mojo::Base> and implements the following new ones.
=head2 parse
my $cookies = $cookie->parse($str);
Parse cookies. Meant to be overloaded in a subclass.
=head2 to_string
my $str = $cookie->to_string;
Render cookie. Meant to be overloaded in a subclass.
=head1 OPERATORS
L<Mojo::Cookie> overloads the following operators.
=head2 bool
my $bool = !!$cookie;
Always true.
=head2 stringify
my $str = "$cookie";
Alias for L</"to_string">.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,72 @@
package Mojo::Cookie::Request;
use Mojo::Base 'Mojo::Cookie';
use Mojo::Util qw(quote split_header);
sub parse {
my ($self, $str) = @_;
my @cookies;
my @pairs = map {@$_} @{split_header $str // ''};
while (my ($name, $value) = splice @pairs, 0, 2) {
next if $name =~ /^\$/;
push @cookies, $self->new(name => $name, value => $value // '');
}
return \@cookies;
}
sub to_string {
my $self = shift;
return '' unless length(my $name = $self->name // '');
my $value = $self->value // '';
return join '=', $name, $value =~ /[,;" ]/ ? quote $value : $value;
}
1;
=encoding utf8
=head1 NAME
Mojo::Cookie::Request - HTTP request cookie
=head1 SYNOPSIS
use Mojo::Cookie::Request;
my $cookie = Mojo::Cookie::Request->new;
$cookie->name('foo');
$cookie->value('bar');
say "$cookie";
=head1 DESCRIPTION
L<Mojo::Cookie::Request> is a container for HTTP request cookies, based on L<RFC
6265|https://tools.ietf.org/html/rfc6265>.
=head1 ATTRIBUTES
L<Mojo::Cookie::Request> inherits all attributes from L<Mojo::Cookie>.
=head1 METHODS
L<Mojo::Cookie::Request> inherits all methods from L<Mojo::Cookie> and implements the following new ones.
=head2 parse
my $cookies = Mojo::Cookie::Request->parse('f=b; g=a');
Parse cookies.
=head2 to_string
my $str = $cookie->to_string;
Render cookie.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,169 @@
package Mojo::Cookie::Response;
use Mojo::Base 'Mojo::Cookie';
use Mojo::Date;
use Mojo::Util qw(quote split_cookie_header);
has [qw(domain expires host_only httponly max_age path samesite secure)];
my %ATTRS = map { $_ => 1 } qw(domain expires httponly max-age path samesite secure);
sub parse {
my ($self, $str) = @_;
my @cookies;
my $tree = split_cookie_header $str // '';
while (my $pairs = shift @$tree) {
my ($name, $value) = splice @$pairs, 0, 2;
push @cookies, $self->new(name => $name, value => $value // '');
while (my ($name, $value) = splice @$pairs, 0, 2) {
next unless $ATTRS{my $attr = lc $name};
$value =~ s/^\.// if $attr eq 'domain' && defined $value;
$value = Mojo::Date->new($value // '')->epoch if $attr eq 'expires';
$value = 1 if $attr eq 'secure' || $attr eq 'httponly';
$cookies[-1]{$attr eq 'max-age' ? 'max_age' : $attr} = $value;
}
}
return \@cookies;
}
sub to_string {
my $self = shift;
# Name and value
return '' unless length(my $name = $self->name // '');
my $value = $self->value // '';
my $cookie = join '=', $name, $value =~ /[,;" ]/ ? quote $value : $value;
# "expires"
my $expires = $self->expires;
$cookie .= '; expires=' . Mojo::Date->new($expires) if defined $expires;
# "domain"
if (my $domain = $self->domain) { $cookie .= "; domain=$domain" }
# "path"
if (my $path = $self->path) { $cookie .= "; path=$path" }
# "secure"
$cookie .= "; secure" if $self->secure;
# "HttpOnly"
$cookie .= "; HttpOnly" if $self->httponly;
# "Same-Site"
if (my $samesite = $self->samesite) { $cookie .= "; SameSite=$samesite" }
# "Max-Age"
if (defined(my $max = $self->max_age)) { $cookie .= "; Max-Age=$max" }
return $cookie;
}
1;
=encoding utf8
=head1 NAME
Mojo::Cookie::Response - HTTP response cookie
=head1 SYNOPSIS
use Mojo::Cookie::Response;
my $cookie = Mojo::Cookie::Response->new;
$cookie->name('foo');
$cookie->value('bar');
say "$cookie";
=head1 DESCRIPTION
L<Mojo::Cookie::Response> is a container for HTTP response cookies, based on L<RFC
6265|https://tools.ietf.org/html/rfc6265>.
=head1 ATTRIBUTES
L<Mojo::Cookie::Response> inherits all attributes from L<Mojo::Cookie> and implements the following new ones.
=head2 domain
my $domain = $cookie->domain;
$cookie = $cookie->domain('localhost');
Cookie domain.
=head2 expires
my $expires = $cookie->expires;
$cookie = $cookie->expires(time + 60);
Expiration for cookie.
=head2 host_only
my $bool = $cookie->host_only;
$cookie = $cookie->host_only($bool);
Host-only flag, indicating that the canonicalized request-host is identical to the cookie's L</"domain">.
=head2 httponly
my $bool = $cookie->httponly;
$cookie = $cookie->httponly($bool);
HttpOnly flag, which can prevent client-side scripts from accessing this cookie.
=head2 max_age
my $max_age = $cookie->max_age;
$cookie = $cookie->max_age(60);
Max age for cookie.
=head2 path
my $path = $cookie->path;
$cookie = $cookie->path('/test');
Cookie path.
=head2 samesite
my $samesite = $cookie->samesite;
$cookie = $cookie->samesite('Lax');
SameSite value. Note that this attribute is B<EXPERIMENTAL> because even though most commonly used browsers support the
feature, there is no specification yet besides L<this
draft|https://tools.ietf.org/html/draft-west-first-party-cookies-07>.
=head2 secure
my $bool = $cookie->secure;
$cookie = $cookie->secure($bool);
Secure flag, which instructs browsers to only send this cookie over HTTPS connections.
=head1 METHODS
L<Mojo::Cookie::Response> inherits all methods from L<Mojo::Cookie> and implements the following new ones.
=head2 parse
my $cookies = Mojo::Cookie::Response->parse('f=b; path=/');
Parse cookies.
=head2 to_string
my $str = $cookie->to_string;
Render cookie.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

1105
database/perl/vendor/lib/Mojo/DOM.pm vendored Normal file

File diff suppressed because it is too large Load Diff

736
database/perl/vendor/lib/Mojo/DOM/CSS.pm vendored Normal file
View File

@@ -0,0 +1,736 @@
package Mojo::DOM::CSS;
use Mojo::Base -base;
use Carp qw(croak);
use Mojo::Util qw(dumper trim);
use constant DEBUG => $ENV{MOJO_DOM_CSS_DEBUG} || 0;
has 'tree';
my $ESCAPE_RE = qr/\\[^0-9a-fA-F]|\\[0-9a-fA-F]{1,6}/;
my $ATTR_RE = qr/
\[
((?:$ESCAPE_RE|[\w\-])+) # Key
(?:
(\W)?= # Operator
(?:"((?:\\"|[^"])*)"|'((?:\\'|[^'])*)'|([^\]]+?)) # Value
(?:\s+(?:(i|I)|s|S))? # Case-sensitivity
)?
\]
/x;
sub matches {
my $tree = shift->tree;
return $tree->[0] ne 'tag' ? undef : _match(_compile(@_), $tree, $tree, _root($tree));
}
sub select { _select(0, shift->tree, _compile(@_)) }
sub select_one { _select(1, shift->tree, _compile(@_)) }
sub _absolutize { [map { _is_scoped($_) ? $_ : [[['pc', 'scope']], ' ', @$_] } @{shift()}] }
sub _ancestor {
my ($selectors, $current, $tree, $scope, $one, $pos) = @_;
while ($current ne $scope && $current->[0] ne 'root' && ($current = $current->[3])) {
return 1 if _combinator($selectors, $current, $tree, $scope, $pos);
return undef if $current eq $scope;
last if $one;
}
return undef;
}
sub _attr {
my ($name_re, $value_re, $current) = @_;
my $attrs = $current->[2];
for my $name (keys %$attrs) {
my $value = $attrs->{$name};
next if $name !~ $name_re || (!defined $value && defined $value_re);
return 1 if !(defined $value && defined $value_re) || $value =~ $value_re;
}
return undef;
}
sub _combinator {
my ($selectors, $current, $tree, $scope, $pos) = @_;
# Selector
return undef unless my $c = $selectors->[$pos];
if (ref $c) {
return undef unless _selector($c, $current, $tree, $scope);
return 1 unless $c = $selectors->[++$pos];
}
# ">" (parent only)
return _ancestor($selectors, $current, $tree, $scope, 1, ++$pos) if $c eq '>';
# "~" (preceding siblings)
return _sibling($selectors, $current, $tree, $scope, 0, ++$pos) if $c eq '~';
# "+" (immediately preceding siblings)
return _sibling($selectors, $current, $tree, $scope, 1, ++$pos) if $c eq '+';
# " " (ancestor)
return _ancestor($selectors, $current, $tree, $scope, 0, ++$pos);
}
sub _compile {
my ($css, %ns) = (trim('' . shift), @_);
my $group = [[]];
while (my $selectors = $group->[-1]) {
push @$selectors, [] unless @$selectors && ref $selectors->[-1];
my $last = $selectors->[-1];
# Separator
if ($css =~ /\G\s*,\s*/gc) { push @$group, [] }
# Combinator
elsif ($css =~ /\G\s*([ >+~])\s*/gc) {
push @$last, ['pc', 'scope'] unless @$last;
push @$selectors, $1;
}
# Class or ID
elsif ($css =~ /\G([.#])((?:$ESCAPE_RE\s|\\.|[^,.#:[ >~+])+)/gco) {
my ($name, $op) = $1 eq '.' ? ('class', '~') : ('id', '');
push @$last, ['attr', _name($name), _value($op, $2)];
}
# Attributes
elsif ($css =~ /\G$ATTR_RE/gco) { push @$last, ['attr', _name($1), _value($2 // '', $3 // $4 // $5, $6)] }
# Pseudo-class
elsif ($css =~ /\G:([\w\-]+)(?:\(((?:\([^)]+\)|[^)])+)\))?/gcs) {
my ($name, $args) = (lc $1, $2);
# ":is" and ":not" (contains more selectors)
$args = _compile($args, %ns) if $name eq 'has' || $name eq 'is' || $name eq 'not';
# ":nth-*" (with An+B notation)
$args = _equation($args) if $name =~ /^nth-/;
# ":first-*" (rewrite to ":nth-*")
($name, $args) = ("nth-$1", [0, 1]) if $name =~ /^first-(.+)$/;
# ":last-*" (rewrite to ":nth-*")
($name, $args) = ("nth-$name", [-1, 1]) if $name =~ /^last-/;
push @$last, ['pc', $name, $args];
}
# Tag
elsif ($css =~ /\G((?:$ESCAPE_RE\s|\\.|[^,.#:[ >~+])+)/gco) {
my $alias = (my $name = $1) =~ s/^([^|]*)\|// && $1 ne '*' ? $1 : undef;
my $ns = length $alias ? $ns{$alias} // return [['invalid']] : $alias;
push @$last, ['tag', $name eq '*' ? undef : _name($name), _unescape($ns)];
}
else { pos $css < length $css ? croak "Unknown CSS selector: $css" : last }
}
warn qq{-- CSS Selector ($css)\n@{[dumper $group]}} if DEBUG;
return $group;
}
sub _empty { $_[0][0] eq 'comment' || $_[0][0] eq 'pi' }
sub _equation {
return [0, 0] unless my $equation = shift;
# "even"
return [2, 2] if $equation =~ /^\s*even\s*$/i;
# "odd"
return [2, 1] if $equation =~ /^\s*odd\s*$/i;
# "4", "+4" or "-4"
return [0, $1] if $equation =~ /^\s*((?:\+|-)?\d+)\s*$/;
# "n", "4n", "+4n", "-4n", "n+1", "4n-1", "+4n-1" (and other variations)
return [0, 0] unless $equation =~ /^\s*((?:\+|-)?(?:\d+)?)?n\s*((?:\+|-)\s*\d+)?\s*$/i;
return [$1 eq '-' ? -1 : !length $1 ? 1 : $1, join('', split(' ', $2 // 0))];
}
sub _is_scoped {
my $selector = shift;
for my $pc (grep { $_->[0] eq 'pc' } map { ref $_ ? @$_ : () } @$selector) {
# Selector with ":scope"
return 1 if $pc->[1] eq 'scope';
# Argument of functional pseudo-class with ":scope"
return 1 if ($pc->[1] eq 'has' || $pc->[1] eq 'is' || $pc->[1] eq 'not') && grep { _is_scoped($_) } @{$pc->[2]};
}
return undef;
}
sub _match {
my ($group, $current, $tree, $scope) = @_;
_combinator([reverse @$_], $current, $tree, $scope, 0) and return 1 for @$group;
return undef;
}
sub _name {qr/(?:^|:)\Q@{[_unescape(shift)]}\E$/}
sub _namespace {
my ($ns, $current) = @_;
my $attr = $current->[1] =~ /^([^:]+):/ ? "xmlns:$1" : 'xmlns';
while ($current) {
last if $current->[0] eq 'root';
return $current->[2]{$attr} eq $ns if exists $current->[2]{$attr};
$current = $current->[3];
}
# Failing to match yields true if searching for no namespace, false otherwise
return !length $ns;
}
sub _pc {
my ($class, $args, $current, $tree, $scope) = @_;
# ":scope" (root can only be a :scope)
return $current eq $scope if $class eq 'scope';
return undef if $current->[0] eq 'root';
# ":checked"
return exists $current->[2]{checked} || exists $current->[2]{selected} if $class eq 'checked';
# ":not"
return !_match($args, $current, $current, $scope) if $class eq 'not';
# ":is"
return !!_match($args, $current, $current, $scope) if $class eq 'is';
# ":has"
return !!_select(1, $current, $args) if $class eq 'has';
# ":empty"
return !grep { !_empty($_) } @$current[4 .. $#$current] if $class eq 'empty';
# ":root"
return $current->[3] && $current->[3][0] eq 'root' if $class eq 'root';
# ":any-link", ":link" and ":visited"
if ($class eq 'any-link' || $class eq 'link' || $class eq 'visited') {
return undef unless $current->[0] eq 'tag' && exists $current->[2]{href};
return !!grep { $current->[1] eq $_ } qw(a area link);
}
# ":only-child" or ":only-of-type"
if ($class eq 'only-child' || $class eq 'only-of-type') {
my $type = $class eq 'only-of-type' ? $current->[1] : undef;
$_ ne $current and return undef for @{_siblings($current, $type)};
return 1;
}
# ":nth-child", ":nth-last-child", ":nth-of-type" or ":nth-last-of-type"
if (ref $args) {
my $type = $class eq 'nth-of-type' || $class eq 'nth-last-of-type' ? $current->[1] : undef;
my @siblings = @{_siblings($current, $type)};
@siblings = reverse @siblings if $class eq 'nth-last-child' || $class eq 'nth-last-of-type';
for my $i (0 .. $#siblings) {
next if (my $result = $args->[0] * $i + $args->[1]) < 1;
return undef unless my $sibling = $siblings[$result - 1];
return 1 if $sibling eq $current;
}
}
# Everything else
return undef;
}
sub _root {
my $tree = shift;
$tree = $tree->[3] while $tree->[0] ne 'root';
return $tree;
}
sub _select {
my ($one, $scope, $group) = @_;
# Scoped selectors require the whole tree to be searched
my $tree = $scope;
($group, $tree) = (_absolutize($group), _root($scope)) if grep { _is_scoped($_) } @$group;
my @results;
my @queue = @$tree[($tree->[0] eq 'root' ? 1 : 4) .. $#$tree];
while (my $current = shift @queue) {
next unless $current->[0] eq 'tag';
unshift @queue, @$current[4 .. $#$current];
next unless _match($group, $current, $tree, $scope);
$one ? return $current : push @results, $current;
}
return $one ? undef : \@results;
}
sub _selector {
my ($selector, $current, $tree, $scope) = @_;
# The root might be the scope
my $is_tag = $current->[0] eq 'tag';
for my $s (@$selector) {
my $type = $s->[0];
# Tag
if ($is_tag && $type eq 'tag') {
return undef if defined $s->[1] && $current->[1] !~ $s->[1];
return undef if defined $s->[2] && !_namespace($s->[2], $current);
}
# Attribute
elsif ($is_tag && $type eq 'attr') { return undef unless _attr(@$s[1, 2], $current) }
# Pseudo-class
elsif ($type eq 'pc') { return undef unless _pc(@$s[1, 2], $current, $tree, $scope) }
# No match
else { return undef }
}
return 1;
}
sub _sibling {
my ($selectors, $current, $tree, $scope, $immediate, $pos) = @_;
my $found;
for my $sibling (@{_siblings($current)}) {
return $found if $sibling eq $current;
# "+" (immediately preceding sibling)
if ($immediate) { $found = _combinator($selectors, $sibling, $tree, $scope, $pos) }
# "~" (preceding sibling)
else { return 1 if _combinator($selectors, $sibling, $tree, $scope, $pos) }
}
return undef;
}
sub _siblings {
my ($current, $type) = @_;
my $parent = $current->[3];
my @siblings = grep { $_->[0] eq 'tag' } @$parent[($parent->[0] eq 'root' ? 1 : 4) .. $#$parent];
@siblings = grep { $type eq $_->[1] } @siblings if defined $type;
return \@siblings;
}
sub _unescape {
return undef unless defined(my $value = shift);
# Remove escaped newlines
$value =~ s/\\\n//g;
# Unescape Unicode characters
$value =~ s/\\([0-9a-fA-F]{1,6})\s?/pack 'U', hex $1/ge;
# Remove backslash
$value =~ s/\\//g;
return $value;
}
sub _value {
my ($op, $value, $insensitive) = @_;
return undef unless defined $value;
$value = ($insensitive ? '(?i)' : '') . quotemeta _unescape($value);
# "~=" (word)
return qr/(?:^|\s+)$value(?:\s+|$)/ if $op eq '~';
# "|=" (hyphen-separated)
return qr/^$value(?:-|$)/ if $op eq '|';
# "*=" (contains)
return qr/$value/ if $op eq '*';
# "^=" (begins with)
return qr/^$value/ if $op eq '^';
# "$=" (ends with)
return qr/$value$/ if $op eq '$';
# Everything else
return qr/^$value$/;
}
1;
=encoding utf8
=head1 NAME
Mojo::DOM::CSS - CSS selector engine
=head1 SYNOPSIS
use Mojo::DOM::CSS;
# Select elements from DOM tree
my $css = Mojo::DOM::CSS->new(tree => $tree);
my $elements = $css->select('h1, h2, h3');
=head1 DESCRIPTION
L<Mojo::DOM::CSS> is the CSS selector engine used by L<Mojo::DOM>, based on the L<HTML Living
Standard|https://html.spec.whatwg.org> and L<Selectors Level 3|https://www.w3.org/TR/css3-selectors/>.
=head1 SELECTORS
All CSS selectors that make sense for a standalone parser are supported.
=head2 *
Any element.
my $all = $css->select('*');
=head2 E
An element of type C<E>.
my $title = $css->select('title');
=head2 E[foo]
An C<E> element with a C<foo> attribute.
my $links = $css->select('a[href]');
=head2 E[foo="bar"]
An C<E> element whose C<foo> attribute value is exactly equal to C<bar>.
my $case_sensitive = $css->select('input[type="hidden"]');
my $case_sensitive = $css->select('input[type=hidden]');
=head2 E[foo="bar" i]
An C<E> element whose C<foo> attribute value is exactly equal to any (ASCII-range) case-permutation of C<bar>. Note
that this selector is B<EXPERIMENTAL> and might change without warning!
my $case_insensitive = $css->select('input[type="hidden" i]');
my $case_insensitive = $css->select('input[type=hidden i]');
my $case_insensitive = $css->select('input[class~="foo" i]');
This selector is part of L<Selectors Level 4|https://dev.w3.org/csswg/selectors-4>, which is still a work in progress.
=head2 E[foo="bar" s]
An C<E> element whose C<foo> attribute value is exactly and case-sensitively equal to C<bar>. Note that this selector
is B<EXPERIMENTAL> and might change without warning!
my $case_sensitive = $css->select('input[type="hidden" s]');
This selector is part of L<Selectors Level 4|https://dev.w3.org/csswg/selectors-4>, which is still a work in progress.
=head2 E[foo~="bar"]
An C<E> element whose C<foo> attribute value is a list of whitespace-separated values, one of which is exactly equal to
C<bar>.
my $foo = $css->select('input[class~="foo"]');
my $foo = $css->select('input[class~=foo]');
=head2 E[foo^="bar"]
An C<E> element whose C<foo> attribute value begins exactly with the string C<bar>.
my $begins_with = $css->select('input[name^="f"]');
my $begins_with = $css->select('input[name^=f]');
=head2 E[foo$="bar"]
An C<E> element whose C<foo> attribute value ends exactly with the string C<bar>.
my $ends_with = $css->select('input[name$="o"]');
my $ends_with = $css->select('input[name$=o]');
=head2 E[foo*="bar"]
An C<E> element whose C<foo> attribute value contains the substring C<bar>.
my $contains = $css->select('input[name*="fo"]');
my $contains = $css->select('input[name*=fo]');
=head2 E[foo|="en"]
An C<E> element whose C<foo> attribute has a hyphen-separated list of values beginning (from the left) with C<en>.
my $english = $css->select('link[hreflang|=en]');
=head2 E:root
An C<E> element, root of the document.
my $root = $css->select(':root');
=head2 E:nth-child(n)
An C<E> element, the C<n-th> child of its parent.
my $third = $css->select('div:nth-child(3)');
my $odd = $css->select('div:nth-child(odd)');
my $even = $css->select('div:nth-child(even)');
my $top3 = $css->select('div:nth-child(-n+3)');
=head2 E:nth-last-child(n)
An C<E> element, the C<n-th> child of its parent, counting from the last one.
my $third = $css->select('div:nth-last-child(3)');
my $odd = $css->select('div:nth-last-child(odd)');
my $even = $css->select('div:nth-last-child(even)');
my $bottom3 = $css->select('div:nth-last-child(-n+3)');
=head2 E:nth-of-type(n)
An C<E> element, the C<n-th> sibling of its type.
my $third = $css->select('div:nth-of-type(3)');
my $odd = $css->select('div:nth-of-type(odd)');
my $even = $css->select('div:nth-of-type(even)');
my $top3 = $css->select('div:nth-of-type(-n+3)');
=head2 E:nth-last-of-type(n)
An C<E> element, the C<n-th> sibling of its type, counting from the last one.
my $third = $css->select('div:nth-last-of-type(3)');
my $odd = $css->select('div:nth-last-of-type(odd)');
my $even = $css->select('div:nth-last-of-type(even)');
my $bottom3 = $css->select('div:nth-last-of-type(-n+3)');
=head2 E:first-child
An C<E> element, first child of its parent.
my $first = $css->select('div p:first-child');
=head2 E:last-child
An C<E> element, last child of its parent.
my $last = $css->select('div p:last-child');
=head2 E:first-of-type
An C<E> element, first sibling of its type.
my $first = $css->select('div p:first-of-type');
=head2 E:last-of-type
An C<E> element, last sibling of its type.
my $last = $css->select('div p:last-of-type');
=head2 E:only-child
An C<E> element, only child of its parent.
my $lonely = $css->select('div p:only-child');
=head2 E:only-of-type
An C<E> element, only sibling of its type.
my $lonely = $css->select('div p:only-of-type');
=head2 E:empty
An C<E> element that has no children (including text nodes).
my $empty = $css->select(':empty');
=head2 E:any-link
Alias for L</"E:link">. Note that this selector is B<EXPERIMENTAL> and might change without warning! This selector is
part of L<Selectors Level 4|https://dev.w3.org/csswg/selectors-4>, which is still a work in progress.
=head2 E:link
An C<E> element being the source anchor of a hyperlink of which the target is not yet visited (C<:link>) or already
visited (C<:visited>). Note that L<Mojo::DOM::CSS> is not stateful, therefore C<:any-link>, C<:link> and C<:visited>
yield exactly the same results.
my $links = $css->select(':any-link');
my $links = $css->select(':link');
my $links = $css->select(':visited');
=head2 E:visited
Alias for L</"E:link">.
=head2 E:scope
An C<E> element being a designated reference element. Note that this selector is B<EXPERIMENTAL> and might change
without warning!
my $scoped = $css->select('a:not(:scope > a)');
my $scoped = $css->select('div :scope p');
my $scoped = $css->select('~ p');
This selector is part of L<Selectors Level 4|https://dev.w3.org/csswg/selectors-4>, which is still a work in progress.
=head2 E:checked
A user interface element C<E> which is checked (for instance a radio-button or checkbox).
my $input = $css->select(':checked');
=head2 E.warning
An C<E> element whose class is "warning".
my $warning = $css->select('div.warning');
=head2 E#myid
An C<E> element with C<ID> equal to "myid".
my $foo = $css->select('div#foo');
=head2 E:not(s1, s2)
An C<E> element that does not match either compound selector C<s1> or compound selector C<s2>. Note that support for
compound selectors is B<EXPERIMENTAL> and might change without warning!
my $others = $css->select('div p:not(:first-child, :last-child)');
Support for compound selectors was added as part of L<Selectors Level 4|https://dev.w3.org/csswg/selectors-4>, which is
still a work in progress.
=head2 E:is(s1, s2)
An C<E> element that matches compound selector C<s1> and/or compound selector C<s2>. Note that this selector is
B<EXPERIMENTAL> and might change without warning!
my $headers = $css->select(':is(section, article, aside, nav) h1');
This selector is part of L<Selectors Level 4|https://dev.w3.org/csswg/selectors-4>, which is still a work in progress.
=head2 E:has(rs1, rs2)
An C<E> element, if either of the relative selectors C<rs1> or C<rs2>, when evaluated with C<E> as the :scope elements,
match an element. Note that this selector is B<EXPERIMENTAL> and might change without warning!
my $link = $css->select('a:has(> img)');
This selector is part of L<Selectors Level 4|https://dev.w3.org/csswg/selectors-4>, which is still a work in progress.
Also be aware that this feature is currently marked C<at-risk>, so there is a high chance that it will get removed
completely.
=head2 A|E
An C<E> element that belongs to the namespace alias C<A> from L<CSS Namespaces Module Level
3|https://www.w3.org/TR/css-namespaces-3/>. Key/value pairs passed to selector methods are used to declare namespace
aliases.
my $elem = $css->select('lq|elem', lq => 'http://example.com/q-markup');
Using an empty alias searches for an element that belongs to no namespace.
my $div = $c->select('|div');
=head2 E F
An C<F> element descendant of an C<E> element.
my $headlines = $css->select('div h1');
=head2 E E<gt> F
An C<F> element child of an C<E> element.
my $headlines = $css->select('html > body > div > h1');
=head2 E + F
An C<F> element immediately preceded by an C<E> element.
my $second = $css->select('h1 + h2');
=head2 E ~ F
An C<F> element preceded by an C<E> element.
my $second = $css->select('h1 ~ h2');
=head2 E, F, G
Elements of type C<E>, C<F> and C<G>.
my $headlines = $css->select('h1, h2, h3');
=head2 E[foo=bar][bar=baz]
An C<E> element whose attributes match all following attribute selectors.
my $links = $css->select('a[foo^=b][foo$=ar]');
=head1 ATTRIBUTES
L<Mojo::DOM::CSS> implements the following attributes.
=head2 tree
my $tree = $css->tree;
$css = $css->tree(['root']);
Document Object Model. Note that this structure should only be used very carefully since it is very dynamic.
=head1 METHODS
L<Mojo::DOM::CSS> inherits all methods from L<Mojo::Base> and implements the following new ones.
=head2 matches
my $bool = $css->matches('head > title');
my $bool = $css->matches('svg|line', svg => 'http://www.w3.org/2000/svg');
Check if first node in L</"tree"> matches the CSS selector. Trailing key/value pairs can be used to declare xml
namespace aliases.
=head2 select
my $results = $css->select('head > title');
my $results = $css->select('svg|line', svg => 'http://www.w3.org/2000/svg');
Run CSS selector against L</"tree">. Trailing key/value pairs can be used to declare xml namespace aliases.
=head2 select_one
my $result = $css->select_one('head > title');
my $result =
$css->select_one('svg|line', svg => 'http://www.w3.org/2000/svg');
Run CSS selector against L</"tree"> and stop as soon as the first node matched. Trailing key/value pairs can be used to
declare xml namespace aliases.
=head1 DEBUGGING
You can set the C<MOJO_DOM_CSS_DEBUG> environment variable to get some advanced diagnostics information printed to
C<STDERR>.
MOJO_DOM_CSS_DEBUG=1
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,370 @@
package Mojo::DOM::HTML;
use Mojo::Base -base;
use Exporter qw(import);
use Mojo::Util qw(html_attr_unescape html_unescape xml_escape);
use Scalar::Util qw(weaken);
our @EXPORT_OK = ('tag_to_html');
has tree => sub { ['root'] };
has 'xml';
my $ATTR_RE = qr/
([^<>=\s\/]+|\/) # Key
(?:
\s*=\s*
(?s:(["'])(.*?)\g{-2}|([^>\s]*)) # Value
)?
\s*
/x;
my $TOKEN_RE = qr/
([^<]+)? # Text
(?:
<(?:
!(?:
DOCTYPE(
\s+\w+ # Doctype
(?:(?:\s+\w+)?(?:\s+(?:"[^"]*"|'[^']*'))+)? # External ID
(?:\s+\[.+?\])? # Int Subset
\s*)
|
--(.*?)--\s* # Comment
|
\[CDATA\[(.*?)\]\] # CDATA
)
|
\?(.*?)\? # Processing Instruction
|
\s*([^<>\s]+\s*(?:(?:$ATTR_RE){0,32766})*+) # Tag
)>
|
(<) # Runaway "<"
)??
/xis;
# HTML elements that only contain raw text
my %RAW = map { $_ => 1 } qw(script style);
# HTML elements that only contain raw text and entities
my %RCDATA = map { $_ => 1 } qw(title textarea);
# HTML elements with optional end tags
my %END = (body => 'head', optgroup => 'optgroup', option => 'option');
# HTML elements that break paragraphs
map { $END{$_} = 'p' } (
qw(address article aside blockquote details dialog div dl fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6),
qw(header hgroup hr main menu nav ol p pre section table ul)
);
# HTML table elements with optional end tags
my %TABLE = map { $_ => 1 } qw(colgroup tbody td tfoot th thead tr);
# HTML elements with optional end tags and scoping rules
my %CLOSE = (li => [{li => 1}, {ul => 1, ol => 1}], tr => [{tr => 1}, {table => 1}]);
$CLOSE{$_} = [\%TABLE, {table => 1}] for qw(colgroup tbody tfoot thead);
$CLOSE{$_} = [{dd => 1, dt => 1}, {dl => 1}] for qw(dd dt);
$CLOSE{$_} = [{rp => 1, rt => 1}, {ruby => 1}] for qw(rp rt);
$CLOSE{$_} = [{th => 1, td => 1}, {table => 1}] for qw(td th);
# HTML parent elements that signal no more content when closed, but that are also phrasing content
my %NO_MORE_CONTENT = (ruby => [qw(rt rp)], select => [qw(option optgroup)]);
# HTML elements without end tags
my %EMPTY = map { $_ => 1 } qw(area base br col embed hr img input keygen link menuitem meta param source track wbr);
# HTML elements categorized as phrasing content (and obsolete inline elements)
my @PHRASING = (
qw(a abbr area audio b bdi bdo br button canvas cite code data datalist del dfn em embed i iframe img input ins kbd),
qw(keygen label link map mark math meta meter noscript object output picture progress q ruby s samp script select),
qw(slot small span strong sub sup svg template textarea time u var video wbr)
);
my @OBSOLETE = qw(acronym applet basefont big font strike tt);
my %PHRASING = map { $_ => 1 } @OBSOLETE, @PHRASING;
# HTML elements that don't get their self-closing flag acknowledged
my %BLOCK = map { $_ => 1 } (
qw(a address applet article aside b big blockquote body button caption center code col colgroup dd details dialog),
qw(dir div dl dt em fieldset figcaption figure font footer form frameset h1 h2 h3 h4 h5 h6 head header hgroup html),
qw(i iframe li listing main marquee menu nav nobr noembed noframes noscript object ol optgroup option p plaintext),
qw(pre rp rt s script section select small strike strong style summary table tbody td template textarea tfoot th),
qw(thead title tr tt u ul xmp)
);
sub parse {
my ($self, $html) = (shift, "$_[0]");
my $xml = $self->xml;
my $current = my $tree = ['root'];
while ($html =~ /\G$TOKEN_RE/gcso) {
my ($text, $doctype, $comment, $cdata, $pi, $tag, $runaway) = ($1, $2, $3, $4, $5, $6, $11);
# Text (and runaway "<")
$text .= '<' if defined $runaway;
_node($current, 'text', html_unescape $text) if defined $text;
# Tag
if (defined $tag) {
# End
if ($tag =~ /^\/\s*(\S+)/) {
my $end = $xml ? $1 : lc $1;
# No more content
if (!$xml && (my $tags = $NO_MORE_CONTENT{$end})) { _end($_, $xml, \$current) for @$tags }
_end($xml ? $1 : lc $1, $xml, \$current);
}
# Start
elsif ($tag =~ m!^([^\s/]+)([\s\S]*)!) {
my ($start, $attr) = ($xml ? $1 : lc $1, $2);
# Attributes
my (%attrs, $closing);
while ($attr =~ /$ATTR_RE/go) {
my ($key, $value) = ($xml ? $1 : lc $1, $3 // $4);
# Empty tag
++$closing and next if $key eq '/';
$attrs{$key} = defined $value ? html_attr_unescape $value : $value;
}
# "image" is an alias for "img"
$start = 'img' if !$xml && $start eq 'image';
_start($start, \%attrs, $xml, \$current);
# Element without end tag (self-closing)
_end($start, $xml, \$current) if !$xml && $EMPTY{$start} || ($xml || !$BLOCK{$start}) && $closing;
# Raw text elements
next if $xml || !$RAW{$start} && !$RCDATA{$start};
next unless $html =~ m!\G(.*?)<\s*/\s*\Q$start\E\s*>!gcsi;
_node($current, 'raw', $RCDATA{$start} ? html_unescape $1 : $1);
_end($start, 0, \$current);
}
}
# DOCTYPE
elsif (defined $doctype) { _node($current, 'doctype', $doctype) }
# Comment
elsif (defined $comment) { _node($current, 'comment', $comment) }
# CDATA
elsif (defined $cdata) { _node($current, 'cdata', $cdata) }
# Processing instruction (try to detect XML)
elsif (defined $pi) {
$self->xml($xml = 1) if !exists $self->{xml} && $pi =~ /xml/i;
_node($current, 'pi', $pi);
}
}
return $self->tree($tree);
}
sub render { _render($_[0]->tree, $_[0]->xml) }
sub tag { shift->tree(['root', _tag(@_)]) }
sub tag_to_html { _render(_tag(@_), undef) }
sub _end {
my ($end, $xml, $current) = @_;
# Search stack for start tag
my $next = $$current;
do {
# Ignore useless end tag
return if $next->[0] eq 'root';
# Right tag
return $$current = $next->[3] if $next->[1] eq $end;
# Phrasing content can only cross phrasing content
return if !$xml && $PHRASING{$end} && !$PHRASING{$next->[1]};
} while $next = $next->[3];
}
sub _node {
my ($current, $type, $content) = @_;
push @$current, my $new = [$type, $content, $current];
weaken $new->[2];
}
sub _render {
my ($tree, $xml) = @_;
# Tag
my $type = $tree->[0];
if ($type eq 'tag') {
# Start tag
my $tag = $tree->[1];
my $result = "<$tag";
# Attributes
for my $key (sort keys %{$tree->[2]}) {
my $value = $tree->[2]{$key};
$result .= $xml ? qq{ $key="$key"} : " $key" and next unless defined $value;
$result .= qq{ $key="} . xml_escape($value) . '"';
}
# No children
return $xml ? "$result />" : $EMPTY{$tag} ? "$result>" : "$result></$tag>" unless $tree->[4];
# Children
no warnings 'recursion';
$result .= '>' . join '', map { _render($_, $xml) } @$tree[4 .. $#$tree];
# End tag
return "$result</$tag>";
}
# Text (escaped)
return xml_escape $tree->[1] if $type eq 'text';
# Raw text
return $tree->[1] if $type eq 'raw';
# Root
return join '', map { _render($_, $xml) } @$tree[1 .. $#$tree] if $type eq 'root';
# DOCTYPE
return '<!DOCTYPE' . $tree->[1] . '>' if $type eq 'doctype';
# Comment
return '<!--' . $tree->[1] . '-->' if $type eq 'comment';
# CDATA
return '<![CDATA[' . $tree->[1] . ']]>' if $type eq 'cdata';
# Processing instruction
return '<?' . $tree->[1] . '?>' if $type eq 'pi';
# Everything else
return '';
}
sub _start {
my ($start, $attrs, $xml, $current) = @_;
# Autoclose optional HTML elements
if (!$xml && $$current->[0] ne 'root') {
if (my $end = $END{$start}) { _end($end, 0, $current) }
elsif (my $close = $CLOSE{$start}) {
my ($allowed, $scope) = @$close;
# Close allowed parent elements in scope
my $parent = $$current;
while ($parent->[0] ne 'root' && !$scope->{$parent->[1]}) {
_end($parent->[1], 0, $current) if $allowed->{$parent->[1]};
$parent = $parent->[3];
}
}
}
# New tag
push @$$current, my $new = ['tag', $start, $attrs, $$current];
weaken $new->[3];
$$current = $new;
}
sub _tag {
my $tree = ['tag', shift, undef, undef];
# Content
push @$tree, ref $_[-1] eq 'CODE' ? ['raw', pop->()] : ['text', pop] if @_ % 2;
# Attributes
my $attrs = $tree->[2] = {@_};
return $tree unless exists $attrs->{data} && ref $attrs->{data} eq 'HASH';
my $data = delete $attrs->{data};
@$attrs{map { y/_/-/; lc "data-$_" } keys %$data} = values %$data;
return $tree;
}
1;
=encoding utf8
=head1 NAME
Mojo::DOM::HTML - HTML/XML engine
=head1 SYNOPSIS
use Mojo::DOM::HTML;
# Turn HTML into DOM tree
my $html = Mojo::DOM::HTML->new;
$html->parse('<div><p id="a">Test</p><p id="b">123</p></div>');
my $tree = $html->tree;
=head1 DESCRIPTION
L<Mojo::DOM::HTML> is the HTML/XML engine used by L<Mojo::DOM>, based on the L<HTML Living
Standard|https://html.spec.whatwg.org> and the L<Extensible Markup Language (XML) 1.0|https://www.w3.org/TR/xml/>.
=head1 FUNCTIONS
L<Mojo::DOM::HTML> implements the following functions, which can be imported individually.
=head2 tag_to_html
my $str = tag_to_html 'div', id => 'foo', 'safe content';
Generate HTML/XML tag and render it right away. This is a significantly faster alternative to L</"tag"> for template
systems that have to generate a lot of tags.
=head1 ATTRIBUTES
L<Mojo::DOM::HTML> implements the following attributes.
=head2 tree
my $tree = $html->tree;
$html = $html->tree(['root']);
Document Object Model. Note that this structure should only be used very carefully since it is very dynamic.
=head2 xml
my $bool = $html->xml;
$html = $html->xml($bool);
Disable HTML semantics in parser and activate case-sensitivity, defaults to auto-detection based on XML declarations.
=head1 METHODS
L<Mojo::DOM::HTML> inherits all methods from L<Mojo::Base> and implements the following new ones.
=head2 parse
$html = $html->parse('<foo bar="baz">I ♥ Mojolicious!</foo>');
Parse HTML/XML fragment.
=head2 render
my $str = $html->render;
Render DOM to HTML/XML.
=head2 tag
$html = $html->tag('div', id => 'foo', 'safe content');
Generate HTML/XML tag.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

182
database/perl/vendor/lib/Mojo/Date.pm vendored Normal file
View File

@@ -0,0 +1,182 @@
package Mojo::Date;
use Mojo::Base -base;
use overload bool => sub {1}, '""' => sub { shift->to_string }, fallback => 1;
use Time::Local qw(timegm);
has epoch => sub {time};
my $RFC3339_RE = qr/
^(\d+)-(\d+)-(\d+)\D+(\d+):(\d+):(\d+(?:\.\d+)?) # Date and time
(?:Z|([+-])(\d+):(\d+))?$ # Offset
/xi;
my @DAYS = qw(Sun Mon Tue Wed Thu Fri Sat);
my @MONTHS = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
my %MONTHS;
@MONTHS{@MONTHS} = (0 .. 11);
sub new { @_ > 1 ? shift->SUPER::new->parse(@_) : shift->SUPER::new }
sub parse {
my ($self, $date) = @_;
# epoch (784111777)
return $self->epoch($date) if $date =~ /^\d+$|^\d+\.\d+$/;
# RFC 822/1123 (Sun, 06 Nov 1994 08:49:37 GMT)
# RFC 850/1036 (Sunday, 06-Nov-94 08:49:37 GMT)
my $offset = 0;
my ($day, $month, $year, $h, $m, $s);
if ($date =~ /^\w+\W+(\d+)\W+(\w+)\W+(\d+)\W+(\d+):(\d+):(\d+)\W*\w+$/) {
($day, $month, $year, $h, $m, $s) = ($1, $MONTHS{$2}, $3, $4, $5, $6);
}
# RFC 3339 (1994-11-06T08:49:37Z)
elsif ($date =~ $RFC3339_RE) {
($year, $month, $day, $h, $m, $s) = ($1, $2 - 1, $3, $4, $5, $6);
$offset = (($8 * 3600) + ($9 * 60)) * ($7 eq '+' ? -1 : 1) if $7;
}
# ANSI C asctime() (Sun Nov 6 08:49:37 1994)
elsif ($date =~ /^\w+\s+(\w+)\s+(\d+)\s+(\d+):(\d+):(\d+)\s+(\d+)$/) {
($month, $day, $h, $m, $s, $year) = ($MONTHS{$1}, $2, $3, $4, $5, $6);
}
# Invalid
else { return $self->epoch(undef) }
my $epoch = eval { timegm $s, $m, $h, $day, $month, $year };
return $self->epoch((defined $epoch && ($epoch += $offset) >= 0) ? $epoch : undef);
}
sub to_datetime {
# RFC 3339 (1994-11-06T08:49:37Z)
my ($s, $m, $h, $day, $month, $year) = gmtime(my $epoch = shift->epoch);
my $str = sprintf '%04d-%02d-%02dT%02d:%02d:%02d', $year + 1900, $month + 1, $day, $h, $m, $s;
return $str . ($epoch =~ /(\.\d+)$/ ? $1 : '') . 'Z';
}
sub to_string {
# RFC 7231 (Sun, 06 Nov 1994 08:49:37 GMT)
my ($s, $m, $h, $mday, $month, $year, $wday) = gmtime shift->epoch;
return sprintf '%s, %02d %s %04d %02d:%02d:%02d GMT', $DAYS[$wday], $mday, $MONTHS[$month], $year + 1900, $h, $m, $s;
}
1;
=encoding utf8
=head1 NAME
Mojo::Date - HTTP date
=head1 SYNOPSIS
use Mojo::Date;
# Parse
my $date = Mojo::Date->new('Sun, 06 Nov 1994 08:49:37 GMT');
say $date->epoch;
# Build
my $date = Mojo::Date->new(time + 60);
say "$date";
=head1 DESCRIPTION
L<Mojo::Date> implements HTTP date and time functions, based on L<RFC 7230|https://tools.ietf.org/html/rfc7230>, L<RFC
7231|https://tools.ietf.org/html/rfc7231> and L<RFC 3339|https://tools.ietf.org/html/rfc3339>.
=head1 ATTRIBUTES
L<Mojo::Date> implements the following attributes.
=head2 epoch
my $epoch = $date->epoch;
$date = $date->epoch(784111777);
Epoch seconds, defaults to the current time.
=head1 METHODS
L<Mojo::Date> inherits all methods from L<Mojo::Base> and implements the following new ones.
=head2 new
my $date = Mojo::Date->new;
my $date = Mojo::Date->new('Sun Nov 6 08:49:37 1994');
Construct a new L<Mojo::Date> object and L</"parse"> date if necessary.
=head2 parse
$date = $date->parse('Sun Nov 6 08:49:37 1994');
Parse date.
# Epoch
say Mojo::Date->new('784111777')->epoch;
say Mojo::Date->new('784111777.21')->epoch;
# RFC 822/1123
say Mojo::Date->new('Sun, 06 Nov 1994 08:49:37 GMT')->epoch;
# RFC 850/1036
say Mojo::Date->new('Sunday, 06-Nov-94 08:49:37 GMT')->epoch;
# Ansi C asctime()
say Mojo::Date->new('Sun Nov 6 08:49:37 1994')->epoch;
# RFC 3339
say Mojo::Date->new('1994-11-06T08:49:37Z')->epoch;
say Mojo::Date->new('1994-11-06T08:49:37')->epoch;
say Mojo::Date->new('1994-11-06T08:49:37.21Z')->epoch;
say Mojo::Date->new('1994-11-06T08:49:37+01:00')->epoch;
say Mojo::Date->new('1994-11-06T08:49:37-01:00')->epoch;
=head2 to_datetime
my $str = $date->to_datetime;
Render L<RFC 3339|https://tools.ietf.org/html/rfc3339> date and time.
# "1994-11-06T08:49:37Z"
Mojo::Date->new(784111777)->to_datetime;
# "1994-11-06T08:49:37.21Z"
Mojo::Date->new(784111777.21)->to_datetime;
=head2 to_string
my $str = $date->to_string;
Render date suitable for HTTP messages.
# "Sun, 06 Nov 1994 08:49:37 GMT"
Mojo::Date->new(784111777)->to_string;
=head1 OPERATORS
L<Mojo::Date> overloads the following operators.
=head2 bool
my $bool = !!$date;
Always true.
=head2 stringify
my $str = "$date";
Alias for L</"to_string">.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,110 @@
package Mojo::DynamicMethods;
use Mojo::Base -strict;
use Hash::Util::FieldHash qw(fieldhash);
use Mojo::Util qw(monkey_patch);
sub import {
my ($flag, $caller) = ($_[1] // '', caller);
return unless $flag eq '-dispatch';
my $dyn_pkg = "${caller}::_Dynamic";
my $caller_can = $caller->can('SUPER::can');
monkey_patch $dyn_pkg, 'can', sub {
my ($self, $method, @rest) = @_;
# Delegate to our parent's "can" if there is one, without breaking if not
my $can = $self->$caller_can($method, @rest);
return undef unless $can;
no warnings 'once';
my $h = do { no strict 'refs'; *{"${dyn_pkg}::${method}"}{CODE} };
return $h && $h eq $can ? undef : $can;
};
{
no strict 'refs';
unshift @{"${caller}::ISA"}, $dyn_pkg;
}
}
sub register {
my ($target, $object, $name, $code) = @_;
state %dyn_methods;
state $setup = do { fieldhash %dyn_methods; 1 };
my $dyn_pkg = "${target}::_Dynamic";
monkey_patch($dyn_pkg, $name, $target->BUILD_DYNAMIC($name, \%dyn_methods))
unless do { no strict 'refs'; *{"${dyn_pkg}::${name}"}{CODE} };
$dyn_methods{$object}{$name} = $code;
}
1;
=encoding utf8
=head1 NAME
Mojo::DynamicMethods - Fast dynamic method dispatch
=head1 SYNOPSIS
package MyClass;
use Mojo::Base -base, -signatures;
use Mojo::DynamicMethods -dispatch;
sub BUILD_DYNAMIC ($class, $method, $dyn_methods) {
return sub {...};
}
sub add_helper ($self, $name, $cb) {
Mojo::DynamicMethods::register 'MyClass', $self, $name, $cb;
}
package main;
# Generate methods dynamically (and hide them from "$obj->can(...)")
my $obj = MyClass->new;
$obj->add_helper(foo => sub { warn 'Hello Helper!' });
$obj->foo;
=head1 DESCRIPTION
L<Mojo::DynamicMethods> provides dynamic method dispatch for per-object helper methods without requiring use of
C<AUTOLOAD>.
To opt your class into dynamic dispatch simply pass the C<-dispatch> flag.
use Mojo::DynamicMethods -dispatch;
And then implement a C<BUILD_DYNAMIC> method in your class, making sure that the key you use to lookup methods in
C<$dyn_methods> is the same thing you pass as C<$ref> to L</"register">.
sub BUILD_DYNAMIC ($class, $method, $dyn_methods) {
return sub ($self, @args) {
my $dynamic = $dyn_methods->{$self}{$method};
return $self->$dynamic(@args) if $dynamic;
my $package = ref $self;
croak qq{Can't locate object method "$method" via package "$package"};
};
}
Note that this module is B<EXPERIMENTAL> and might change without warning!
=head1 FUNCTIONS
L<Mojo::DynamicMethods> implements the following functions.
=head2 register
Mojo::DynamicMethods::register $class, $ref, $name, $cb;
Registers the method C<$name> as eligible for dynamic dispatch for C<$class>, and sets C<$cb> to be looked up for
C<$name> by reference C<$ref> in a dynamic method constructed by C<BUILD_DYNAMIC>.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,171 @@
package Mojo::EventEmitter;
use Mojo::Base -base;
use Scalar::Util qw(blessed weaken);
use constant DEBUG => $ENV{MOJO_EVENTEMITTER_DEBUG} || 0;
sub catch { $_[0]->on(error => $_[1]) and return $_[0] }
sub emit {
my ($self, $name) = (shift, shift);
if (my $s = $self->{events}{$name}) {
warn "-- Emit $name in @{[blessed $self]} (@{[scalar @$s]})\n" if DEBUG;
for my $cb (@$s) { $self->$cb(@_) }
}
else {
warn "-- Emit $name in @{[blessed $self]} (0)\n" if DEBUG;
die "@{[blessed $self]}: $_[0]" if $name eq 'error';
}
return $self;
}
sub has_subscribers { !!shift->{events}{shift()} }
sub on { push @{$_[0]{events}{$_[1]}}, $_[2] and return $_[2] }
sub once {
my ($self, $name, $cb) = @_;
weaken $self;
my $wrapper = sub {
$self->unsubscribe($name => __SUB__);
$cb->(@_);
};
$self->on($name => $wrapper);
return $wrapper;
}
sub subscribers { shift->{events}{shift()} //= [] }
sub unsubscribe {
my ($self, $name, $cb) = @_;
# One
if ($cb) {
$self->{events}{$name} = [grep { $cb ne $_ } @{$self->{events}{$name}}];
delete $self->{events}{$name} unless @{$self->{events}{$name}};
}
# All
else { delete $self->{events}{$name} }
return $self;
}
1;
=encoding utf8
=head1 NAME
Mojo::EventEmitter - Event emitter base class
=head1 SYNOPSIS
package Cat;
use Mojo::Base 'Mojo::EventEmitter', -signatures;
# Emit events
sub poke ($self) { $self->emit(roar => 3) }
package main;
# Subscribe to events
my $tiger = Cat->new;
$tiger->on(roar => sub ($tiger, $times) { say 'RAWR!' for 1 .. $times });
$tiger->poke;
=head1 DESCRIPTION
L<Mojo::EventEmitter> is a simple base class for event emitting objects.
=head1 EVENTS
L<Mojo::EventEmitter> can emit the following events.
=head2 error
$e->on(error => sub ($e, $err) {...});
This is a special event for errors, it will not be emitted directly by this class, but is fatal if unhandled.
Subclasses may choose to emit it, but are not required to do so.
$e->on(error => sub ($e, $err) { say "This looks bad: $err" });
=head1 METHODS
L<Mojo::EventEmitter> inherits all methods from L<Mojo::Base> and implements the following new ones.
=head2 catch
$e = $e->catch(sub {...});
Subscribe to L</"error"> event.
# Longer version
$e->on(error => sub {...});
=head2 emit
$e = $e->emit('foo');
$e = $e->emit('foo', 123);
Emit event.
=head2 has_subscribers
my $bool = $e->has_subscribers('foo');
Check if event has subscribers.
=head2 on
my $cb = $e->on(foo => sub {...});
Subscribe to event.
$e->on(foo => sub ($e, @args) {...});
=head2 once
my $cb = $e->once(foo => sub {...});
Subscribe to event and unsubscribe again after it has been emitted once.
$e->once(foo => sub ($e, @args) {...});
=head2 subscribers
my $subscribers = $e->subscribers('foo');
All subscribers for event.
# Unsubscribe last subscriber
$e->unsubscribe(foo => $e->subscribers('foo')->[-1]);
# Change order of subscribers
@{$e->subscribers('foo')} = reverse @{$e->subscribers('foo')};
=head2 unsubscribe
$e = $e->unsubscribe('foo');
$e = $e->unsubscribe(foo => $cb);
Unsubscribe from event.
=head1 DEBUGGING
You can set the C<MOJO_EVENTEMITTER_DEBUG> environment variable to get some advanced diagnostics information printed to
C<STDERR>.
MOJO_EVENTEMITTER_DEBUG=1
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,382 @@
package Mojo::Exception;
use Mojo::Base -base;
use overload bool => sub {1}, '""' => sub { shift->to_string }, fallback => 1;
use Exporter qw(import);
use Mojo::Util qw(decode scope_guard);
use Scalar::Util qw(blessed);
has [qw(frames line lines_after lines_before)] => sub { [] };
has message => 'Exception!';
has verbose => sub { $ENV{MOJO_EXCEPTION_VERBOSE} };
our @EXPORT_OK = qw(check raise);
sub check {
my ($err, @spec) = @_ % 2 ? @_ : ($@, @_);
# Finally (search backwards since it is usually at the end)
my $guard;
for (my $i = $#spec - 1; $i >= 0; $i -= 2) {
($guard = scope_guard($spec[$i + 1])) and last if $spec[$i] eq 'finally';
}
return undef unless $err;
my ($default, $handler);
my ($is_obj, $str) = (!!blessed($err), "$err");
CHECK: for (my $i = 0; $i < @spec; $i += 2) {
my ($checks, $cb) = @spec[$i, $i + 1];
($default = $cb) and next if $checks eq 'default';
for my $c (ref $checks eq 'ARRAY' ? @$checks : $checks) {
my $is_re = !!ref $c;
($handler = $cb) and last CHECK if $is_obj && !$is_re && $err->isa($c);
($handler = $cb) and last CHECK if $is_re && $str =~ $c;
}
}
# Rethrow if no handler could be found
die $err unless $handler ||= $default;
$handler->($_) for $err;
return 1;
}
sub inspect {
my ($self, @sources) = @_;
return $self if @{$self->line};
# Extract file and line from message
my @files;
my $msg = $self->message;
unshift @files, [$1, $2] while $msg =~ /at\s+(.+?)\s+line\s+(\d+)/g;
# Extract file and line from stack trace
if (my $zero = $self->frames->[0]) { push @files, [$zero->[1], $zero->[2]] }
# Search for context in files
for my $file (@files) {
next unless -r $file->[0] && open my $handle, '<', $file->[0];
$self->_context($file->[1], [[<$handle>]]);
return $self;
}
# Search for context in sources
$self->_context($files[-1][1], [map { [split /\n/] } @sources]) if @sources;
return $self;
}
sub new { defined $_[1] ? shift->SUPER::new(message => shift) : shift->SUPER::new }
sub raise {
my ($class, $err) = @_ > 1 ? (@_) : (__PACKAGE__, shift);
if (!$class->can('new')) { die $@ unless eval "package $class; use Mojo::Base 'Mojo::Exception'; 1" }
elsif (!$class->isa(__PACKAGE__)) { die "$class is not a Mojo::Exception subclass" }
CORE::die $class->new($err)->trace;
}
sub to_string {
my $self = shift;
my $str = $self->message;
my $frames = $self->frames;
if ($str !~ /\n$/) {
$str .= @$frames ? " at $frames->[0][1] line $frames->[0][2].\n" : "\n";
}
return $str unless $self->verbose;
my $line = $self->line;
if (@$line) {
$str .= "Context:\n";
$str .= " $_->[0]: $_->[1]\n" for @{$self->lines_before};
$str .= " $line->[0]: $line->[1]\n";
$str .= " $_->[0]: $_->[1]\n" for @{$self->lines_after};
}
if (my $max = @$frames) {
$str .= "Traceback (most recent call first):\n";
$str .= qq{ File "$_->[1]", line $_->[2], in "$_->[0]"\n} for @$frames;
}
return $str;
}
sub throw { CORE::die shift->new(shift)->trace }
sub trace {
my ($self, $start) = (shift, shift // 1);
my @frames;
while (my @trace = caller($start++)) { push @frames, \@trace }
return $self->frames(\@frames);
}
sub _append {
my ($stack, $line) = @_;
$line = decode('UTF-8', $line) // $line;
chomp $line;
push @$stack, $line;
}
sub _context {
my ($self, $num, $sources) = @_;
# Line
return unless defined $sources->[0][$num - 1];
$self->line([$num]);
_append($self->line, $_->[$num - 1]) for @$sources;
# Before
for my $i (2 .. 6) {
last if ((my $previous = $num - $i) < 0);
unshift @{$self->lines_before}, [$previous + 1];
_append($self->lines_before->[0], $_->[$previous]) for @$sources;
}
# After
for my $i (0 .. 4) {
next if ((my $next = $num + $i) < 0);
next unless defined $sources->[0][$next];
push @{$self->lines_after}, [$next + 1];
_append($self->lines_after->[-1], $_->[$next]) for @$sources;
}
}
1;
=encoding utf8
=head1 NAME
Mojo::Exception - Exception base class
=head1 SYNOPSIS
# Create exception classes
package MyApp::X::Foo {
use Mojo::Base 'Mojo::Exception';
}
package MyApp::X::Bar {
use Mojo::Base 'Mojo::Exception';
}
# Throw exceptions and handle them gracefully
use Mojo::Exception qw(check);
eval {
MyApp::X::Foo->throw('Something went wrong!');
};
check(
'MyApp::X::Foo' => sub { say "Foo: $_" },
'MyApp::X::Bar' => sub { say "Bar: $_" }
);
# Generate exception classes on demand
use Mojo::Exception qw(check raise);
eval {
raise 'MyApp::X::Name', 'The name Minion is already taken';
};
check(
'MyApp::X::Name' => sub { say "Name error: $_" },
default => sub { say "Error: $_" }
);
=head1 DESCRIPTION
L<Mojo::Exception> is a container for exceptions with context information.
=head1 FUNCTIONS
L<Mojo::Exception> implements the following functions, which can be imported individually.
=head2 check
my $bool = check 'MyApp::X::Foo' => sub {...};
my $bool = check $err, 'MyApp::X::Foo' => sub {...};
Process exceptions by dispatching them to handlers with one or more matching conditions. Exceptions that could not be
handled will be rethrown automatically. By default C<$@> will be used as exception source, so C<check> needs to be
called right after C<eval>. Note that this function is B<EXPERIMENTAL> and might change without warning!
# Handle various types of exceptions
eval {
dangerous_code();
};
check(
'MyApp::X::Foo' => sub { say "Foo: $_" },
qr/^Could not open/ => sub { say "Open error: $_" },
default => sub { say "Something went wrong: $_" },
finally => sub { say 'Dangerous code is done' }
);
Matching conditions can be class names for ISA checks on exception objects, or regular expressions to match string
exceptions and stringified exception objects. The matching exception will be the first argument passed to the callback,
and is also available as C<$_>.
# Catch MyApp::X::Foo object or a specific string exception
eval {
dangerous_code();
};
check(
'MyApp::X::Foo' => sub { say "Foo: $_" },
qr/^Could not open/ => sub { say "Open error: $_" }
);
An array reference can be used to share the same handler with multiple conditions, of which only one needs to match.
And since exception handlers are just callbacks, they can also throw their own exceptions.
# Handle MyApp::X::Foo and MyApp::X::Bar the same
eval {
dangerous_code();
};
check(
['MyApp::X::Foo', 'MyApp::X::Bar'] => sub { die "Foo/Bar: $_" }
);
There are currently two keywords you can use to set special handlers. The C<default> handler is used when no other
handler matched. And the C<finally> handler runs always, it does not affect normal handlers and even runs if the
exception was rethrown or if there was no exception to be handled at all.
# Use "default" to catch everything
eval {
dangerous_code();
};
check(
default => sub { say "Error: $_" },
finally => sub { say 'Dangerous code is done' }
);
=head2 raise
raise 'Something went wrong!';
raise 'MyApp::X::Foo', 'Something went wrong!';
Raise a L<Mojo::Exception>, if the class does not exist yet (classes are checked for a C<new> method), one is created
as a L<Mojo::Exception> subclass on demand. Note that this function is B<EXPERIMENTAL> and might change without
warning!
=head1 ATTRIBUTES
L<Mojo::Exception> implements the following attributes.
=head2 frames
my $frames = $e->frames;
$e = $e->frames([$frame1, $frame2]);
Stack trace if available.
# Extract information from the last frame
my ($package, $filename, $line, $subroutine, $hasargs, $wantarray, $evaltext,
$is_require, $hints, $bitmask, $hinthash) = @{$e->frames->[-1]};
=head2 line
my $line = $e->line;
$e = $e->line([3, 'die;']);
The line where the exception occurred if available.
=head2 lines_after
my $lines = $e->lines_after;
$e = $e->lines_after([[4, 'say $foo;'], [5, 'say $bar;']]);
Lines after the line where the exception occurred if available.
=head2 lines_before
my $lines = $e->lines_before;
$e = $e->lines_before([[1, 'my $foo = 23;'], [2, 'my $bar = 24;']]);
Lines before the line where the exception occurred if available.
=head2 message
my $msg = $e->message;
$e = $e->message('Died at test.pl line 3.');
Exception message, defaults to C<Exception!>.
=head2 verbose
my $bool = $e->verbose;
$e = $e->verbose($bool);
Show more information with L</"to_string">, such as L</"frames">, defaults to the value of the
C<MOJO_EXCEPTION_VERBOSE> environment variable.
=head1 METHODS
L<Mojo::Exception> inherits all methods from L<Mojo::Base> and implements the following new ones.
=head2 inspect
$e = $e->inspect;
$e = $e->inspect($source1, $source2);
Inspect L</"message">, L</"frames"> and optional additional sources to fill L</"lines_before">, L</"line"> and
L</"lines_after"> with context information.
=head2 new
my $e = Mojo::Exception->new;
my $e = Mojo::Exception->new('Died at test.pl line 3.');
Construct a new L<Mojo::Exception> object and assign L</"message"> if necessary.
=head2 to_string
my $str = $e->to_string;
Render exception. Note that the output format may change as more features are added, only the error message at the
beginning is guaranteed not to be modified to allow regex matching.
=head2 throw
Mojo::Exception->throw('Something went wrong!');
Throw exception from the current execution context.
# Longer version
die Mojo::Exception->new('Something went wrong!')->trace;
=head2 trace
$e = $e->trace;
$e = $e->trace($skip);
Generate stack trace and store all L</"frames">, defaults to skipping C<1> call frame.
# Skip 3 call frames
$e->trace(3);
# Skip no call frames
$e->trace(0);
=head1 OPERATORS
L<Mojo::Exception> overloads the following operators.
=head2 bool
my $bool = !!$e;
Always true.
=head2 stringify
my $str = "$e";
Alias for L</"to_string">.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

575
database/perl/vendor/lib/Mojo/File.pm vendored Normal file
View File

@@ -0,0 +1,575 @@
package Mojo::File;
use Mojo::Base -strict;
use overload '@{}' => sub { shift->to_array }, bool => sub {1}, '""' => sub { ${$_[0]} }, fallback => 1;
use Carp qw(croak);
use Cwd qw(getcwd);
use Exporter qw(import);
use File::Basename ();
use File::Copy qw(copy move);
use File::Find qw(find);
use File::Path ();
use File::Spec::Functions qw(abs2rel canonpath catfile file_name_is_absolute rel2abs splitdir);
use File::stat ();
use File::Temp ();
use IO::File ();
use Mojo::Collection;
our @EXPORT_OK = ('curfile', 'path', 'tempdir', 'tempfile');
sub basename { File::Basename::basename ${shift()}, @_ }
sub child { $_[0]->new(${shift()}, @_) }
sub chmod {
my ($self, $mode) = @_;
chmod $mode, $$self or croak qq{Can't chmod file "$$self": $!};
return $self;
}
sub copy_to {
my ($self, $to) = @_;
copy($$self, $to) or croak qq{Can't copy file "$$self" to "$to": $!};
return $self->new(-d $to ? ($to, File::Basename::basename $self) : $to);
}
sub curfile { __PACKAGE__->new(Cwd::realpath((caller)[1])) }
sub dirname { $_[0]->new(scalar File::Basename::dirname ${$_[0]}) }
sub extname { shift->basename =~ /.+\.([^.]+)$/ ? $1 : '' }
sub is_abs { file_name_is_absolute ${shift()} }
sub list {
my ($self, $options) = (shift, shift // {});
return Mojo::Collection->new unless -d $$self;
opendir(my $dir, $$self) or croak qq{Can't open directory "$$self": $!};
my @files = grep { $_ ne '.' && $_ ne '..' } readdir $dir;
@files = grep { !/^\./ } @files unless $options->{hidden};
@files = map { catfile $$self, $_ } @files;
@files = grep { !-d } @files unless $options->{dir};
return Mojo::Collection->new(map { $self->new($_) } sort @files);
}
sub list_tree {
my ($self, $options) = (shift, shift // {});
# This may break in the future, but is worth it for performance
local $File::Find::skip_pattern = qr/^\./ unless $options->{hidden};
# The File::Find documentation lies, this is needed for CIFS
local $File::Find::dont_use_nlink = 1 if $options->{dont_use_nlink};
my %all;
my $wanted = sub {
if ($options->{max_depth}) {
(my $rel = $File::Find::name) =~ s!^\Q$$self\E/?!!;
$File::Find::prune = 1 if splitdir($rel) >= $options->{max_depth};
}
$all{$File::Find::name}++ if $options->{dir} || !-d $File::Find::name;
};
find {wanted => $wanted, no_chdir => 1}, $$self if -d $$self;
delete $all{$$self};
return Mojo::Collection->new(map { $self->new(canonpath $_) } sort keys %all);
}
sub lstat { File::stat::lstat(${shift()}) }
sub make_path {
my $self = shift;
File::Path::make_path $$self, @_;
return $self;
}
sub move_to {
my ($self, $to) = @_;
move($$self, $to) or croak qq{Can't move file "$$self" to "$to": $!};
return $self->new(-d $to ? ($to, File::Basename::basename $self) : $to);
}
sub new {
my $class = shift;
croak 'Invalid path' if grep { !defined } @_;
my $value = @_ == 1 ? $_[0] : @_ > 1 ? catfile @_ : canonpath getcwd;
return bless \$value, ref $class || $class;
}
sub open {
my $self = shift;
my $handle = IO::File->new;
$handle->open($$self, @_) or croak qq{Can't open file "$$self": $!};
return $handle;
}
sub path { __PACKAGE__->new(@_) }
sub realpath { $_[0]->new(Cwd::realpath ${$_[0]}) }
sub remove {
my ($self, $mode) = @_;
unlink $$self or croak qq{Can't remove file "$$self": $!} if -e $$self;
return $self;
}
sub remove_tree {
my $self = shift;
File::Path::remove_tree $$self, @_;
return $self;
}
sub sibling {
my $self = shift;
return $self->new(scalar File::Basename::dirname($self), @_);
}
sub slurp {
my $self = shift;
CORE::open my $file, '<', $$self or croak qq{Can't open file "$$self": $!};
my $ret = my $content = '';
while ($ret = $file->sysread(my $buffer, 131072, 0)) { $content .= $buffer }
croak qq{Can't read from file "$$self": $!} unless defined $ret;
return $content;
}
sub spurt {
my ($self, $content) = (shift, join '', @_);
CORE::open my $file, '>', $$self or croak qq{Can't open file "$$self": $!};
($file->syswrite($content) // -1) == length $content or croak qq{Can't write to file "$$self": $!};
return $self;
}
sub stat { File::stat::stat(${shift()}) }
sub tap { shift->Mojo::Base::tap(@_) }
sub tempdir { __PACKAGE__->new(File::Temp->newdir(@_)) }
sub tempfile { __PACKAGE__->new(File::Temp->new(@_)) }
sub to_abs { $_[0]->new(rel2abs ${$_[0]}) }
sub to_array { [splitdir ${shift()}] }
sub to_rel { $_[0]->new(abs2rel(${$_[0]}, $_[1])) }
sub to_string {"${$_[0]}"}
sub touch {
my $self = shift;
$self->open('>') unless -e $$self;
utime undef, undef, $$self or croak qq{Can't touch file "$$self": $!};
return $self;
}
sub with_roles { shift->Mojo::Base::with_roles(@_) }
1;
=encoding utf8
=head1 NAME
Mojo::File - File system paths
=head1 SYNOPSIS
use Mojo::File;
# Portably deal with file system paths
my $path = Mojo::File->new('/home/sri/.vimrc');
say $path->slurp;
say $path->dirname;
say $path->basename;
say $path->extname;
say $path->sibling('.bashrc');
# Use the alternative constructor
use Mojo::File qw(path);
my $path = path('/tmp/foo/bar')->make_path;
$path->child('test.txt')->spurt('Hello Mojo!');
=head1 DESCRIPTION
L<Mojo::File> is a scalar-based container for file system paths that provides a friendly API for dealing with different
operating systems.
# Access scalar directly to manipulate path
my $path = Mojo::File->new('/home/sri/test');
$$path .= '.txt';
=head1 FUNCTIONS
L<Mojo::File> implements the following functions, which can be imported individually.
=head2 curfile
my $path = curfile;
Construct a new scalar-based L<Mojo::File> object for the absolute path to the current source file.
=head2 path
my $path = path;
my $path = path('/home/sri/.vimrc');
my $path = path('/home', 'sri', '.vimrc');
my $path = path(File::Temp->newdir);
Construct a new scalar-based L<Mojo::File> object, defaults to using the current working directory.
# "foo/bar/baz.txt" (on UNIX)
path('foo', 'bar', 'baz.txt');
=head2 tempdir
my $path = tempdir;
my $path = tempdir('tempXXXXX');
Construct a new scalar-based L<Mojo::File> object for a temporary directory with L<File::Temp>.
# Longer version
my $path = path(File::Temp->newdir('tempXXXXX'));
=head2 tempfile
my $path = tempfile;
my $path = tempfile(DIR => '/tmp');
Construct a new scalar-based L<Mojo::File> object for a temporary file with L<File::Temp>.
# Longer version
my $path = path(File::Temp->new(DIR => '/tmp'));
=head1 METHODS
L<Mojo::File> implements the following methods.
=head2 basename
my $name = $path->basename;
my $name = $path->basename('.txt');
Return the last level of the path with L<File::Basename>.
# ".vimrc" (on UNIX)
path('/home/sri/.vimrc')->basename;
# "test" (on UNIX)
path('/home/sri/test.txt')->basename('.txt');
=head2 child
my $child = $path->child('.vimrc');
Return a new L<Mojo::File> object relative to the path.
# "/home/sri/.vimrc" (on UNIX)
path('/home')->child('sri', '.vimrc');
=head2 chmod
$path = $path->chmod(0644);
Change file permissions.
=head2 copy_to
my $destination = $path->copy_to('/home/sri');
my $destination = $path->copy_to('/home/sri/.vimrc.backup');
Copy file with L<File::Copy> and return the destination as a L<Mojo::File> object.
=head2 dirname
my $name = $path->dirname;
Return all but the last level of the path with L<File::Basename> as a L<Mojo::File> object.
# "/home/sri" (on UNIX)
path('/home/sri/.vimrc')->dirname;
=head2 extname
my $ext = $path->extname;
Return file extension of the path. Note that this method is B<EXPERIMENTAL> and might change without warning!
# "js"
path('/home/sri/test.js')->extname;
=head2 is_abs
my $bool = $path->is_abs;
Check if the path is absolute.
# True (on UNIX)
path('/home/sri/.vimrc')->is_abs;
# False (on UNIX)
path('.vimrc')->is_abs;
=head2 list
my $collection = $path->list;
my $collection = $path->list({hidden => 1});
List all files in the directory and return a L<Mojo::Collection> object containing the results as L<Mojo::File>
objects. The list does not include C<.> and C<..>.
# List files
say for path('/home/sri/myapp')->list->each;
These options are currently available:
=over 2
=item dir
dir => 1
Include directories.
=item hidden
hidden => 1
Include hidden files.
=back
=head2 list_tree
my $collection = $path->list_tree;
my $collection = $path->list_tree({hidden => 1});
List all files recursively in the directory and return a L<Mojo::Collection> object containing the results as
L<Mojo::File> objects. The list does not include C<.> and C<..>.
# List all templates
say for path('/home/sri/myapp/templates')->list_tree->each;
These options are currently available:
=over 2
=item dir
dir => 1
Include directories.
=item dont_use_nlink
dont_use_nlink => 1
Force L<File::Find> to always stat directories.
=item hidden
hidden => 1
Include hidden files and directories.
=item max_depth
max_depth => 3
Maximum number of levels to descend when searching for files.
=back
=head2 lstat
my $stat = $path->lstat;
Return a L<File::stat> object for the symlink.
# Get symlink size
say path('/usr/sbin/sendmail')->lstat->size;
# Get symlink modification time
say path('/usr/sbin/sendmail')->lstat->mtime;
=head2 make_path
$path = $path->make_path;
$path = $path->make_path({mode => 0711});
Create the directories if they don't already exist, any additional arguments are passed through to L<File::Path>.
=head2 move_to
my $destination = $path->move_to('/home/sri');
my $destination = $path->move_to('/home/sri/.vimrc.backup');
Move file with L<File::Copy> and return the destination as a L<Mojo::File> object.
=head2 new
my $path = Mojo::File->new;
my $path = Mojo::File->new('/home/sri/.vimrc');
my $path = Mojo::File->new('/home', 'sri', '.vimrc');
my $path = Mojo::File->new(File::Temp->new);
my $path = Mojo::File->new(File::Temp->newdir);
Construct a new L<Mojo::File> object, defaults to using the current working directory.
# "foo/bar/baz.txt" (on UNIX)
Mojo::File->new('foo', 'bar', 'baz.txt');
=head2 open
my $handle = $path->open('+<');
my $handle = $path->open('r+');
my $handle = $path->open(O_RDWR);
my $handle = $path->open('<:encoding(UTF-8)');
Open file with L<IO::File>.
# Combine "fcntl.h" constants
use Fcntl qw(O_CREAT O_EXCL O_RDWR);
my $handle = path('/home/sri/test.pl')->open(O_RDWR | O_CREAT | O_EXCL);
=head2 realpath
my $realpath = $path->realpath;
Resolve the path with L<Cwd> and return the result as a L<Mojo::File> object.
=head2 remove
$path = $path->remove;
Delete file.
=head2 remove_tree
$path = $path->remove_tree;
$path = $path->remove_tree({keep_root => 1});
Delete this directory and any files and subdirectories it may contain, any additional arguments are passed through to
L<File::Path>.
=head2 sibling
my $sibling = $path->sibling('.vimrc');
Return a new L<Mojo::File> object relative to the directory part of the path.
# "/home/sri/.vimrc" (on UNIX)
path('/home/sri/.bashrc')->sibling('.vimrc');
# "/home/sri/.ssh/known_hosts" (on UNIX)
path('/home/sri/.bashrc')->sibling('.ssh', 'known_hosts');
=head2 slurp
my $bytes = $path->slurp;
Read all data at once from the file.
=head2 spurt
$path = $path->spurt($bytes);
$path = $path->spurt(@chunks_of_bytes);
Write all data at once to the file.
=head2 stat
my $stat = $path->stat;
Return a L<File::stat> object for the path.
# Get file size
say path('/home/sri/.bashrc')->stat->size;
# Get file modification time
say path('/home/sri/.bashrc')->stat->mtime;
=head2 tap
$path = $path->tap(sub {...});
Alias for L<Mojo::Base/"tap">.
=head2 to_abs
my $absolute = $path->to_abs;
Return absolute path as a L<Mojo::File> object, the path does not need to exist on the file system.
=head2 to_array
my $parts = $path->to_array;
Split the path on directory separators.
# "home:sri:.vimrc" (on UNIX)
join ':', @{path('/home/sri/.vimrc')->to_array};
=head2 to_rel
my $relative = $path->to_rel('/some/base/path');
Return a relative path from the original path to the destination path as a L<Mojo::File> object.
# "sri/.vimrc" (on UNIX)
path('/home/sri/.vimrc')->to_rel('/home');
=head2 to_string
my $str = $path->to_string;
Stringify the path.
=head2 touch
$path = $path->touch;
Create file if it does not exist or change the modification and access time to the current time.
# Safely read file
say path('.bashrc')->touch->slurp;
=head2 with_roles
my $new_class = Mojo::File->with_roles('Mojo::File::Role::One');
my $new_class = Mojo::File->with_roles('+One', '+Two');
$path = $path->with_roles('+One', '+Two');
Alias for L<Mojo::Base/"with_roles">.
=head1 OPERATORS
L<Mojo::File> overloads the following operators.
=head2 array
my @parts = @$path;
Alias for L</"to_array">.
=head2 bool
my $bool = !!$path;
Always true.
=head2 stringify
my $str = "$path";
Alias for L</"to_string">.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

721
database/perl/vendor/lib/Mojo/Headers.pm vendored Normal file
View File

@@ -0,0 +1,721 @@
package Mojo::Headers;
use Mojo::Base -base;
use Carp qw(croak);
use Mojo::Util qw(monkey_patch);
has max_line_size => sub { $ENV{MOJO_MAX_LINE_SIZE} || 8192 };
has max_lines => sub { $ENV{MOJO_MAX_LINES} || 100 };
# Common headers
my %NAMES = map { lc() => $_ } (
qw(Accept Accept-Charset Accept-Encoding Accept-Language Accept-Ranges Access-Control-Allow-Origin Allow),
qw(Authorization Cache-Control Connection Content-Disposition Content-Encoding Content-Language Content-Length),
qw(Content-Location Content-Range Content-Security-Policy Content-Type Cookie DNT Date ETag Expect Expires Host),
qw(If-Modified-Since If-None-Match Last-Modified Link Location Origin Proxy-Authenticate Proxy-Authorization),
qw(Range Sec-WebSocket-Accept Sec-WebSocket-Extensions Sec-WebSocket-Key Sec-WebSocket-Protocol),
qw(Sec-WebSocket-Version Server Server-Timing Set-Cookie Status Strict-Transport-Security TE Trailer),
qw(Transfer-Encoding Upgrade User-Agent Vary WWW-Authenticate)
);
for my $header (keys %NAMES) {
my $name = $header;
$name =~ y/-/_/;
monkey_patch __PACKAGE__, $name, sub {
my $self = shift;
$self->{headers}{$header} = [@_] and return $self if @_;
return undef unless my $headers = $self->{headers}{$header};
return join ', ', @$headers;
};
}
# Hop-by-hop headers
my @HOP_BY_HOP
= map {lc} qw(Connection Keep-Alive Proxy-Authenticate Proxy-Authorization TE Trailer Transfer-Encoding Upgrade);
sub add {
my ($self, $name) = (shift, shift);
tr/\x0d\x0a// and croak "Invalid characters in $name header" for @_;
# Make sure we have a normal case entry for name
my $key = lc $name;
$self->{names}{$key} //= $name unless $NAMES{$key};
push @{$self->{headers}{$key}}, @_;
return $self;
}
sub append {
my ($self, $name, $value) = @_;
my $old = $self->header($name);
return $self->header($name => defined $old ? "$old, $value" : $value);
}
sub clone {
my $self = shift;
my $clone = $self->new;
%{$clone->{names}} = %{$self->{names} // {}};
@{$clone->{headers}{$_}} = @{$self->{headers}{$_}} for keys %{$self->{headers}};
return $clone;
}
sub dehop {
my $self = shift;
delete @{$self->{headers}}{@HOP_BY_HOP};
return $self;
}
sub every_header { shift->{headers}{lc shift} // [] }
sub from_hash {
my ($self, $hash) = @_;
# Empty hash deletes all headers
delete $self->{headers} if keys %{$hash} == 0;
# Merge
for my $header (keys %$hash) {
my $value = $hash->{$header};
$self->add($header => ref $value eq 'ARRAY' ? @$value : $value);
}
return $self;
}
sub header {
my ($self, $name) = (shift, shift);
# Replace
return $self->remove($name)->add($name, @_) if @_;
return undef unless my $headers = $self->{headers}{lc $name};
return join ', ', @$headers;
}
sub is_finished { (shift->{state} // '') eq 'finished' }
sub is_limit_exceeded { !!shift->{limit} }
sub leftovers { delete shift->{buffer} }
sub names {
my $self = shift;
return [map { $NAMES{$_} || $self->{names}{$_} } keys %{$self->{headers}}];
}
sub parse {
my ($self, $chunk) = @_;
$self->{state} = 'headers';
$self->{buffer} .= $chunk;
my $headers = $self->{cache} //= [];
my $size = $self->max_line_size;
my $lines = $self->max_lines;
while ($self->{buffer} =~ s/^(.*?)\x0d?\x0a//) {
my $line = $1;
# Check line size limit
if ($+[0] > $size || @$headers >= $lines) {
@$self{qw(state limit)} = ('finished', 1);
return $self;
}
# New header
if ($line =~ /^(\S[^:]*)\s*:\s*(.*)$/) { push @$headers, [$1, $2] }
# Multi-line
elsif ($line =~ s/^\s+// && @$headers) { $headers->[-1][1] .= " $line" }
# Empty line
else {
$self->add(@$_) for @$headers;
@$self{qw(state cache)} = ('finished', []);
return $self;
}
}
# Check line size limit
@$self{qw(state limit)} = ('finished', 1) if length $self->{buffer} > $size;
return $self;
}
sub referrer { shift->header(Referer => @_) }
sub remove {
my ($self, $name) = @_;
delete $self->{headers}{lc $name};
return $self;
}
sub to_hash {
my ($self, $multi) = @_;
return {map { $_ => $self->{headers}{lc $_} } @{$self->names}} if $multi;
return {map { $_ => $self->header($_) } @{$self->names}};
}
sub to_string {
my $self = shift;
# Make sure multi-line values are formatted correctly
my @headers;
for my $name (@{$self->names}) { push @headers, "$name: $_" for @{$self->{headers}{lc $name}} }
return join "\x0d\x0a", @headers;
}
1;
=encoding utf8
=head1 NAME
Mojo::Headers - HTTP headers
=head1 SYNOPSIS
use Mojo::Headers;
# Parse
my $headers = Mojo::Headers->new;
$headers->parse("Content-Length: 42\x0d\x0a");
$headers->parse("Content-Type: text/html\x0d\x0a\x0d\x0a");
say $headers->content_length;
say $headers->content_type;
# Build
my $headers = Mojo::Headers->new;
$headers->content_length(42);
$headers->content_type('text/plain');
say $headers->to_string;
=head1 DESCRIPTION
L<Mojo::Headers> is a container for HTTP headers, based on L<RFC 7230|https://tools.ietf.org/html/rfc7230> and L<RFC
7231|https://tools.ietf.org/html/rfc7231>.
=head1 ATTRIBUTES
L<Mojo::Headers> implements the following attributes.
=head2 max_line_size
my $size = $headers->max_line_size;
$headers = $headers->max_line_size(1024);
Maximum header line size in bytes, defaults to the value of the C<MOJO_MAX_LINE_SIZE> environment variable or C<8192>
(8KiB).
=head2 max_lines
my $num = $headers->max_lines;
$headers = $headers->max_lines(200);
Maximum number of header lines, defaults to the value of the C<MOJO_MAX_LINES> environment variable or C<100>.
=head1 METHODS
L<Mojo::Headers> inherits all methods from L<Mojo::Base> and implements the following new ones.
=head2 accept
my $accept = $headers->accept;
$headers = $headers->accept('application/json');
Get or replace current header value, shortcut for the C<Accept> header.
=head2 accept_charset
my $charset = $headers->accept_charset;
$headers = $headers->accept_charset('UTF-8');
Get or replace current header value, shortcut for the C<Accept-Charset> header.
=head2 accept_encoding
my $encoding = $headers->accept_encoding;
$headers = $headers->accept_encoding('gzip');
Get or replace current header value, shortcut for the C<Accept-Encoding> header.
=head2 accept_language
my $language = $headers->accept_language;
$headers = $headers->accept_language('de, en');
Get or replace current header value, shortcut for the C<Accept-Language> header.
=head2 accept_ranges
my $ranges = $headers->accept_ranges;
$headers = $headers->accept_ranges('bytes');
Get or replace current header value, shortcut for the C<Accept-Ranges> header.
=head2 access_control_allow_origin
my $origin = $headers->access_control_allow_origin;
$headers = $headers->access_control_allow_origin('*');
Get or replace current header value, shortcut for the C<Access-Control-Allow-Origin> header from L<Cross-Origin
Resource Sharing|https://www.w3.org/TR/cors/>.
=head2 add
$headers = $headers->add(Foo => 'one value');
$headers = $headers->add(Foo => 'first value', 'second value');
Add header with one or more lines.
# "Vary: Accept
# Vary: Accept-Encoding"
$headers->add(Vary => 'Accept')->add(Vary => 'Accept-Encoding')->to_string;
=head2 allow
my $allow = $headers->allow;
$headers = $headers->allow('GET, POST');
Get or replace current header value, shortcut for the C<Allow> header.
=head2 append
$headers = $headers->append(Vary => 'Accept-Encoding');
Append value to header and flatten it if necessary.
# "Vary: Accept"
$headers->append(Vary => 'Accept')->to_string;
# "Vary: Accept, Accept-Encoding"
$headers->vary('Accept')->append(Vary => 'Accept-Encoding')->to_string;
=head2 authorization
my $authorization = $headers->authorization;
$headers = $headers->authorization('Basic Zm9vOmJhcg==');
Get or replace current header value, shortcut for the C<Authorization> header.
=head2 cache_control
my $cache_control = $headers->cache_control;
$headers = $headers->cache_control('max-age=1, no-cache');
Get or replace current header value, shortcut for the C<Cache-Control> header.
=head2 clone
my $clone = $headers->clone;
Return a new L<Mojo::Headers> object cloned from these headers.
=head2 connection
my $connection = $headers->connection;
$headers = $headers->connection('close');
Get or replace current header value, shortcut for the C<Connection> header.
=head2 content_disposition
my $disposition = $headers->content_disposition;
$headers = $headers->content_disposition('foo');
Get or replace current header value, shortcut for the C<Content-Disposition> header.
=head2 content_encoding
my $encoding = $headers->content_encoding;
$headers = $headers->content_encoding('gzip');
Get or replace current header value, shortcut for the C<Content-Encoding> header.
=head2 content_language
my $language = $headers->content_language;
$headers = $headers->content_language('en');
Get or replace current header value, shortcut for the C<Content-Language> header.
=head2 content_length
my $len = $headers->content_length;
$headers = $headers->content_length(4000);
Get or replace current header value, shortcut for the C<Content-Length> header.
=head2 content_location
my $location = $headers->content_location;
$headers = $headers->content_location('http://127.0.0.1/foo');
Get or replace current header value, shortcut for the C<Content-Location> header.
=head2 content_range
my $range = $headers->content_range;
$headers = $headers->content_range('bytes 2-8/100');
Get or replace current header value, shortcut for the C<Content-Range> header.
=head2 content_security_policy
my $policy = $headers->content_security_policy;
$headers = $headers->content_security_policy('default-src https:');
Get or replace current header value, shortcut for the C<Content-Security-Policy> header from L<Content Security Policy
1.0|https://www.w3.org/TR/CSP/>.
=head2 content_type
my $type = $headers->content_type;
$headers = $headers->content_type('text/plain');
Get or replace current header value, shortcut for the C<Content-Type> header.
=head2 cookie
my $cookie = $headers->cookie;
$headers = $headers->cookie('f=b');
Get or replace current header value, shortcut for the C<Cookie> header from L<RFC
6265|https://tools.ietf.org/html/rfc6265>.
=head2 date
my $date = $headers->date;
$headers = $headers->date('Sun, 17 Aug 2008 16:27:35 GMT');
Get or replace current header value, shortcut for the C<Date> header.
=head2 dehop
$headers = $headers->dehop;
Remove hop-by-hop headers that should not be retransmitted.
=head2 dnt
my $dnt = $headers->dnt;
$headers = $headers->dnt(1);
Get or replace current header value, shortcut for the C<DNT> (Do Not Track) header, which has no specification yet, but
is very commonly used.
=head2 etag
my $etag = $headers->etag;
$headers = $headers->etag('"abc321"');
Get or replace current header value, shortcut for the C<ETag> header.
=head2 every_header
my $all = $headers->every_header('Location');
Similar to L</"header">, but returns all headers sharing the same name as an array reference.
# Get first header value
say $headers->every_header('Location')->[0];
=head2 expect
my $expect = $headers->expect;
$headers = $headers->expect('100-continue');
Get or replace current header value, shortcut for the C<Expect> header.
=head2 expires
my $expires = $headers->expires;
$headers = $headers->expires('Thu, 01 Dec 1994 16:00:00 GMT');
Get or replace current header value, shortcut for the C<Expires> header.
=head2 from_hash
$headers = $headers->from_hash({'Cookie' => 'a=b'});
$headers = $headers->from_hash({'Cookie' => ['a=b', 'c=d']});
$headers = $headers->from_hash({});
Parse headers from a hash reference, an empty hash removes all headers.
=head2 header
my $value = $headers->header('Foo');
$headers = $headers->header(Foo => 'one value');
$headers = $headers->header(Foo => 'first value', 'second value');
Get or replace the current header values.
=head2 host
my $host = $headers->host;
$headers = $headers->host('127.0.0.1');
Get or replace current header value, shortcut for the C<Host> header.
=head2 if_modified_since
my $date = $headers->if_modified_since;
$headers = $headers->if_modified_since('Sun, 17 Aug 2008 16:27:35 GMT');
Get or replace current header value, shortcut for the C<If-Modified-Since> header.
=head2 if_none_match
my $etag = $headers->if_none_match;
$headers = $headers->if_none_match('"abc321"');
Get or replace current header value, shortcut for the C<If-None-Match> header.
=head2 is_finished
my $bool = $headers->is_finished;
Check if header parser is finished.
=head2 is_limit_exceeded
my $bool = $headers->is_limit_exceeded;
Check if headers have exceeded L</"max_line_size"> or L</"max_lines">.
=head2 last_modified
my $date = $headers->last_modified;
$headers = $headers->last_modified('Sun, 17 Aug 2008 16:27:35 GMT');
Get or replace current header value, shortcut for the C<Last-Modified> header.
=head2 leftovers
my $bytes = $headers->leftovers;
Get and remove leftover data from header parser.
=head2 link
my $link = $headers->link;
$headers = $headers->link('<http://127.0.0.1/foo/3>; rel="next"');
Get or replace current header value, shortcut for the C<Link> header from L<RFC
5988|https://tools.ietf.org/html/rfc5988>.
=head2 location
my $location = $headers->location;
$headers = $headers->location('http://127.0.0.1/foo');
Get or replace current header value, shortcut for the C<Location> header.
=head2 names
my $names = $headers->names;
Return an array reference with all currently defined headers.
# Names of all headers
say for @{$headers->names};
=head2 origin
my $origin = $headers->origin;
$headers = $headers->origin('http://example.com');
Get or replace current header value, shortcut for the C<Origin> header from L<RFC
6454|https://tools.ietf.org/html/rfc6454>.
=head2 parse
$headers = $headers->parse("Content-Type: text/plain\x0d\x0a\x0d\x0a");
Parse formatted headers.
=head2 proxy_authenticate
my $authenticate = $headers->proxy_authenticate;
$headers = $headers->proxy_authenticate('Basic "realm"');
Get or replace current header value, shortcut for the C<Proxy-Authenticate> header.
=head2 proxy_authorization
my $authorization = $headers->proxy_authorization;
$headers = $headers->proxy_authorization('Basic Zm9vOmJhcg==');
Get or replace current header value, shortcut for the C<Proxy-Authorization> header.
=head2 range
my $range = $headers->range;
$headers = $headers->range('bytes=2-8');
Get or replace current header value, shortcut for the C<Range> header.
=head2 referrer
my $referrer = $headers->referrer;
$headers = $headers->referrer('http://example.com');
Get or replace current header value, shortcut for the C<Referer> header, there was a typo in L<RFC
2068|https://tools.ietf.org/html/rfc2068> which resulted in C<Referer> becoming an official header.
=head2 remove
$headers = $headers->remove('Foo');
Remove a header.
=head2 sec_websocket_accept
my $accept = $headers->sec_websocket_accept;
$headers = $headers->sec_websocket_accept('s3pPLMBiTxaQ9kYGzzhZRbK+xOo=');
Get or replace current header value, shortcut for the C<Sec-WebSocket-Accept> header from L<RFC
6455|https://tools.ietf.org/html/rfc6455>.
=head2 sec_websocket_extensions
my $extensions = $headers->sec_websocket_extensions;
$headers = $headers->sec_websocket_extensions('foo');
Get or replace current header value, shortcut for the C<Sec-WebSocket-Extensions> header from L<RFC
6455|https://tools.ietf.org/html/rfc6455>.
=head2 sec_websocket_key
my $key = $headers->sec_websocket_key;
$headers = $headers->sec_websocket_key('dGhlIHNhbXBsZSBub25jZQ==');
Get or replace current header value, shortcut for the C<Sec-WebSocket-Key> header from L<RFC
6455|https://tools.ietf.org/html/rfc6455>.
=head2 sec_websocket_protocol
my $proto = $headers->sec_websocket_protocol;
$headers = $headers->sec_websocket_protocol('sample');
Get or replace current header value, shortcut for the C<Sec-WebSocket-Protocol> header from L<RFC
6455|https://tools.ietf.org/html/rfc6455>.
=head2 sec_websocket_version
my $version = $headers->sec_websocket_version;
$headers = $headers->sec_websocket_version(13);
Get or replace current header value, shortcut for the C<Sec-WebSocket-Version> header from L<RFC
6455|https://tools.ietf.org/html/rfc6455>.
=head2 server
my $server = $headers->server;
$headers = $headers->server('Mojo');
Get or replace current header value, shortcut for the C<Server> header.
=head2 server_timing
my $timing = $headers->server_timing;
$headers = $headers->server_timing('app;desc=Mojolicious;dur=0.0001');
Get or replace current header value, shortcut for the C<Server-Timing> header from L<Server
Timing|https://www.w3.org/TR/server-timing/>.
=head2 set_cookie
my $cookie = $headers->set_cookie;
$headers = $headers->set_cookie('f=b; path=/');
Get or replace current header value, shortcut for the C<Set-Cookie> header from L<RFC
6265|https://tools.ietf.org/html/rfc6265>.
=head2 status
my $status = $headers->status;
$headers = $headers->status('200 OK');
Get or replace current header value, shortcut for the C<Status> header from L<RFC
3875|https://tools.ietf.org/html/rfc3875>.
=head2 strict_transport_security
my $policy = $headers->strict_transport_security;
$headers = $headers->strict_transport_security('max-age=31536000');
Get or replace current header value, shortcut for the C<Strict-Transport-Security> header from L<RFC
6797|https://tools.ietf.org/html/rfc6797>.
=head2 te
my $te = $headers->te;
$headers = $headers->te('chunked');
Get or replace current header value, shortcut for the C<TE> header.
=head2 to_hash
my $single = $headers->to_hash;
my $multi = $headers->to_hash(1);
Turn headers into hash reference, array references to represent multiple headers with the same name are disabled by
default.
say $headers->to_hash->{DNT};
=head2 to_string
my $str = $headers->to_string;
Turn headers into a string, suitable for HTTP messages.
=head2 trailer
my $trailer = $headers->trailer;
$headers = $headers->trailer('X-Foo');
Get or replace current header value, shortcut for the C<Trailer> header.
=head2 transfer_encoding
my $encoding = $headers->transfer_encoding;
$headers = $headers->transfer_encoding('chunked');
Get or replace current header value, shortcut for the C<Transfer-Encoding> header.
=head2 upgrade
my $upgrade = $headers->upgrade;
$headers = $headers->upgrade('websocket');
Get or replace current header value, shortcut for the C<Upgrade> header.
=head2 user_agent
my $agent = $headers->user_agent;
$headers = $headers->user_agent('Mojo/1.0');
Get or replace current header value, shortcut for the C<User-Agent> header.
=head2 vary
my $vary = $headers->vary;
$headers = $headers->vary('*');
Get or replace current header value, shortcut for the C<Vary> header.
=head2 www_authenticate
my $authenticate = $headers->www_authenticate;
$headers = $headers->www_authenticate('Basic realm="realm"');
Get or replace current header value, shortcut for the C<WWW-Authenticate> header.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,47 @@
package Mojo::HelloWorld;
use Mojo::Base 'Mojolicious';
sub startup {
my $self = shift;
$self->log->level('error')->path(undef);
$self->routes->any('/*whatever' => {whatever => '', text => 'Your Mojo is working!'});
}
1;
=encoding utf8
=head1 NAME
Mojo::HelloWorld - Hello World!
=head1 SYNOPSIS
use Mojo::HelloWorld;
my $hello = Mojo::HelloWorld->new;
$hello->start;
=head1 DESCRIPTION
L<Mojo::HelloWorld> is the default L<Mojolicious> application, used mostly for testing.
=head1 ATTRIBUTES
L<Mojo::HelloWorld> inherits all attributes from L<Mojolicious>.
=head1 METHODS
L<Mojo::HelloWorld> inherits all methods from L<Mojolicious> and implements the following new ones.
=head2 startup
$hello->startup;
Creates a catch-all route that renders a text message.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

73
database/perl/vendor/lib/Mojo/Home.pm vendored Normal file
View File

@@ -0,0 +1,73 @@
package Mojo::Home;
use Mojo::Base 'Mojo::File';
use Mojo::Util qw(class_to_path);
sub detect {
my ($self, $class) = @_;
# Environment variable
my $home;
if ($ENV{MOJO_HOME}) { $home = Mojo::File->new($ENV{MOJO_HOME})->to_array }
# Location of the application class (Windows mixes backslash and slash)
elsif ($class && (my $path = $INC{my $file = class_to_path $class})) {
$home = Mojo::File->new($path)->to_array;
splice @$home, (my @dummy = split(/\//, $file)) * -1;
@$home && $home->[-1] eq $_ && pop @$home for qw(lib blib);
}
$$self = Mojo::File->new(@$home)->to_abs->to_string if $home;
return $self;
}
sub rel_file { shift->child(split(/\//, shift)) }
1;
=encoding utf8
=head1 NAME
Mojo::Home - Home sweet home
=head1 SYNOPSIS
use Mojo::Home;
# Find and manage the project root directory
my $home = Mojo::Home->new;
$home->detect;
say $home->child('templates', 'layouts', 'default.html.ep');
say "$home";
=head1 DESCRIPTION
L<Mojo::Home> is a container for home directories based on L<Mojo::File>.
=head1 METHODS
L<Mojo::Home> inherits all methods from L<Mojo::File> and implements the following new ones.
=head2 detect
$home = $home->detect;
$home = $home->detect('My::App');
Detect home directory from the value of the C<MOJO_HOME> environment variable or the location of the application class.
=head2 rel_file
my $path = $home->rel_file('foo/bar.html');
Return a new L<Mojo::Home> object relative to the home directory.
=head1 OPERATORS
L<Mojo::Home> inherits all overloaded operators from L<Mojo::File>.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

614
database/perl/vendor/lib/Mojo/IOLoop.pm vendored Normal file
View File

@@ -0,0 +1,614 @@
package Mojo::IOLoop;
use Mojo::Base 'Mojo::EventEmitter';
# "Professor: Amy, technology isn't intrinsically good or evil. It's how it's
# used. Like the death ray."
use Carp qw(croak);
use Mojo::IOLoop::Client;
use Mojo::IOLoop::Delay;
use Mojo::IOLoop::Server;
use Mojo::IOLoop::Stream;
use Mojo::IOLoop::Subprocess;
use Mojo::Reactor::Poll;
use Mojo::Util qw(md5_sum steady_time);
use Scalar::Util qw(blessed weaken);
use constant DEBUG => $ENV{MOJO_IOLOOP_DEBUG} || 0;
has max_accepts => 0;
has max_connections => 1000;
has reactor => sub {
my $class = Mojo::Reactor::Poll->detect;
warn "-- Reactor initialized ($class)\n" if DEBUG;
return $class->new->catch(sub { warn "@{[blessed $_[0]]}: $_[1]" });
};
# Ignore PIPE signal
$SIG{PIPE} = 'IGNORE';
# Initialize singleton reactor early
__PACKAGE__->singleton->reactor;
sub acceptor {
my ($self, $acceptor) = (_instance(shift), @_);
# Find acceptor for id
return $self->{acceptors}{$acceptor} unless ref $acceptor;
# Connect acceptor with reactor
$self->{acceptors}{my $id = $self->_id} = $acceptor->reactor($self->reactor);
# Allow new acceptor to get picked up
$self->_not_accepting->_maybe_accepting;
return $id;
}
sub client {
my ($self, $cb) = (_instance(shift), pop);
my $id = $self->_id;
my $client = $self->{out}{$id}{client} = Mojo::IOLoop::Client->new(reactor => $self->reactor);
weaken $self;
$client->on(
connect => sub {
delete $self->{out}{$id}{client};
my $stream = Mojo::IOLoop::Stream->new(pop);
$self->_stream($stream => $id);
$self->$cb(undef, $stream);
}
);
$client->on(error => sub { $self->_remove($id); $self->$cb(pop, undef) });
$client->connect(@_);
return $id;
}
sub delay {
my $delay = Mojo::IOLoop::Delay->new->ioloop(_instance(shift));
return @_ ? $delay->steps(@_) : $delay;
}
sub is_running { _instance(shift)->reactor->is_running }
sub next_tick {
my ($self, $cb) = (_instance(shift), @_);
weaken $self;
return $self->reactor->next_tick(sub { $self->$cb });
}
sub one_tick {
my $self = _instance(shift);
croak 'Mojo::IOLoop already running' if $self->is_running;
$self->reactor->one_tick;
}
sub recurring { shift->_timer(recurring => @_) }
sub remove {
my ($self, $id) = (_instance(shift), @_);
my $c = $self->{in}{$id} || $self->{out}{$id};
if ($c && (my $stream = $c->{stream})) { return $stream->close_gracefully }
$self->_remove($id);
}
sub reset {
my ($self, $options) = (_instance(shift), shift // {});
$self->emit('reset')->stop;
if ($options->{freeze}) {
state @frozen;
push @frozen, {%$self};
delete $self->{reactor};
}
else { $self->reactor->reset }
delete @$self{qw(accepting acceptors events in out stop)};
}
sub server {
my ($self, $cb) = (_instance(shift), pop);
my $server = Mojo::IOLoop::Server->new;
weaken $self;
$server->on(
accept => sub {
my $stream = Mojo::IOLoop::Stream->new(pop);
$self->$cb($stream, $self->_stream($stream, $self->_id, 1));
# Enforce connection limit (randomize to improve load balancing)
if (my $max = $self->max_accepts) {
$self->{accepts} //= $max - int rand $max / 2;
$self->stop_gracefully if ($self->{accepts} -= 1) <= 0;
}
# Stop accepting if connection limit has been reached
$self->_not_accepting if $self->_limit;
}
);
$server->listen(@_);
return $self->acceptor($server);
}
sub singleton { state $loop = shift->new }
sub start {
my $self = _instance(shift);
croak 'Mojo::IOLoop already running' if $self->is_running;
$self->reactor->start;
}
sub stop { _instance(shift)->reactor->stop }
sub stop_gracefully {
my $self = _instance(shift)->_not_accepting;
++$self->{stop} and !$self->emit('finish')->_in and $self->stop;
}
sub stream {
my ($self, $stream) = (_instance(shift), @_);
return $self->_stream($stream => $self->_id) if ref $stream;
my $c = $self->{in}{$stream} || $self->{out}{$stream} // {};
return $c->{stream};
}
sub subprocess {
my $subprocess = Mojo::IOLoop::Subprocess->new(ioloop => _instance(shift));
return @_ ? $subprocess->run(@_) : $subprocess;
}
sub timer { shift->_timer(timer => @_) }
sub _id {
my $self = shift;
my $id;
do { $id = md5_sum 'c' . steady_time . rand } while $self->{in}{$id} || $self->{out}{$id} || $self->{acceptors}{$id};
return $id;
}
sub _in { scalar keys %{shift->{in} // {}} }
sub _instance { ref $_[0] ? $_[0] : $_[0]->singleton }
sub _limit { $_[0]{stop} ? 1 : $_[0]->_in >= $_[0]->max_connections }
sub _maybe_accepting {
my $self = shift;
return if $self->{accepting} || $self->_limit;
$_->start for values %{$self->{acceptors} // {}};
$self->{accepting} = 1;
}
sub _not_accepting {
my $self = shift;
return $self unless delete $self->{accepting};
$_->stop for values %{$self->{acceptors} // {}};
return $self;
}
sub _out { scalar keys %{shift->{out} // {}} }
sub _remove {
my ($self, $id) = @_;
# Timer
return undef unless my $reactor = $self->reactor;
return undef if $reactor->remove($id);
# Acceptor
return $self->_not_accepting->_maybe_accepting if delete $self->{acceptors}{$id};
# Connection
return undef unless delete $self->{in}{$id} || delete $self->{out}{$id};
return $self->stop if $self->{stop} && !$self->_in;
$self->_maybe_accepting;
warn "-- $id <<< $$ (@{[$self->_in]}:@{[$self->_out]})\n" if DEBUG;
}
sub _stream {
my ($self, $stream, $id, $server) = @_;
# Connect stream with reactor
$self->{$server ? 'in' : 'out'}{$id}{stream} = $stream->reactor($self->reactor);
warn "-- $id >>> $$ (@{[$self->_in]}:@{[$self->_out]})\n" if DEBUG;
weaken $self;
$stream->on(close => sub { $self && $self->_remove($id) });
$stream->start;
return $id;
}
sub _timer {
my ($self, $method, $after, $cb) = (_instance(shift), @_);
weaken $self;
return $self->reactor->$method($after => sub { $self->$cb });
}
1;
=encoding utf8
=head1 NAME
Mojo::IOLoop - Minimalistic event loop
=head1 SYNOPSIS
use Mojo::IOLoop;
# Listen on port 3000
Mojo::IOLoop->server({port => 3000} => sub ($loop, $stream) {
$stream->on(read => sub ($stream, $bytes) {
# Process input chunk
say $bytes;
# Write response
$stream->write('HTTP/1.1 200 OK');
});
});
# Connect to port 3000
my $id = Mojo::IOLoop->client({port => 3000} => sub ($loop, $err, $stream) {
$stream->on(read => sub ($stream, $bytes) {
# Process input
say "Input: $bytes";
});
# Write request
$stream->write("GET / HTTP/1.1\x0d\x0a\x0d\x0a");
});
# Add a timer
Mojo::IOLoop->timer(5 => sub ($loop) { $loop->remove($id) });
# Start event loop if necessary
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
=head1 DESCRIPTION
L<Mojo::IOLoop> is a very minimalistic event loop based on L<Mojo::Reactor>, it has been reduced to the absolute
minimal feature set required to build solid and scalable non-blocking clients and servers.
Depending on operating system, the default per-process and system-wide file descriptor limits are often very low and
need to be tuned for better scalability. The C<LIBEV_FLAGS> environment variable should also be used to select the best
possible L<EV> backend, which usually defaults to the not very scalable C<select>.
LIBEV_FLAGS=1 # select
LIBEV_FLAGS=2 # poll
LIBEV_FLAGS=4 # epoll (Linux)
LIBEV_FLAGS=8 # kqueue (*BSD, OS X)
LIBEV_FLAGS=64 # Linux AIO
The event loop will be resilient to time jumps if a monotonic clock is available through L<Time::HiRes>. A TLS
certificate and key are also built right in, to make writing test servers as easy as possible. Also note that for
convenience the C<PIPE> signal will be set to C<IGNORE> when L<Mojo::IOLoop> is loaded.
For better scalability (epoll, kqueue) and to provide non-blocking name resolution, SOCKS5 as well as TLS support, the
optional modules L<EV> (4.32+), L<Net::DNS::Native> (0.15+), L<IO::Socket::Socks> (0.64+) and L<IO::Socket::SSL>
(2.009+) will be used automatically if possible. Individual features can also be disabled with the C<MOJO_NO_NNR>,
C<MOJO_NO_SOCKS> and C<MOJO_NO_TLS> environment variables.
See L<Mojolicious::Guides::Cookbook/"REAL-TIME WEB"> for more.
=head1 EVENTS
L<Mojo::IOLoop> inherits all events from L<Mojo::EventEmitter> and can emit the following new ones.
=head2 finish
$loop->on(finish => sub ($loop) {...});
Emitted when the event loop wants to shut down gracefully and is just waiting for all existing connections to be
closed.
=head2 reset
$loop->on(reset => sub ($loop) {...});
Emitted when the event loop is reset, this usually happens after the process is forked to clean up resources that
cannot be shared.
=head1 ATTRIBUTES
L<Mojo::IOLoop> implements the following attributes.
=head2 max_accepts
my $max = $loop->max_accepts;
$loop = $loop->max_accepts(1000);
The maximum number of connections this event loop is allowed to accept, before shutting down gracefully without
interrupting existing connections, defaults to C<0>. Setting the value to C<0> will allow this event loop to accept new
connections indefinitely. Note that up to half of this value can be subtracted randomly to improve load balancing
between multiple server processes, and to make sure that not all of them restart at the same time.
=head2 max_connections
my $max = $loop->max_connections;
$loop = $loop->max_connections(100);
The maximum number of accepted connections this event loop is allowed to handle concurrently, before stopping to accept
new incoming connections, defaults to C<1000>.
=head2 reactor
my $reactor = $loop->reactor;
$loop = $loop->reactor(Mojo::Reactor->new);
Low-level event reactor, usually a L<Mojo::Reactor::Poll> or L<Mojo::Reactor::EV> object with a default subscriber to
the event L<Mojo::Reactor/"error">.
# Watch if handle becomes readable or writable
Mojo::IOLoop->singleton->reactor->io($handle => sub ($reactor, $writable) {
say $writable ? 'Handle is writable' : 'Handle is readable';
});
# Change to watching only if handle becomes writable
Mojo::IOLoop->singleton->reactor->watch($handle, 0, 1);
# Remove handle again
Mojo::IOLoop->singleton->reactor->remove($handle);
=head1 METHODS
L<Mojo::IOLoop> inherits all methods from L<Mojo::EventEmitter> and implements the following new ones.
=head2 acceptor
my $server = Mojo::IOLoop->acceptor($id);
my $server = $loop->acceptor($id);
my $id = $loop->acceptor(Mojo::IOLoop::Server->new);
Get L<Mojo::IOLoop::Server> object for id or turn object into an acceptor.
=head2 client
my $id = Mojo::IOLoop->client(address => '127.0.0.1', port => 3000, sub {...});
my $id = $loop->client(address => '127.0.0.1', port => 3000, sub {...});
my $id = $loop->client({address => '127.0.0.1', port => 3000} => sub {...});
Open a TCP/IP or UNIX domain socket connection with L<Mojo::IOLoop::Client> and create a stream object (usually
L<Mojo::IOLoop::Stream>), takes the same arguments as L<Mojo::IOLoop::Client/"connect">.
=head2 delay
my $delay = Mojo::IOLoop->delay;
my $delay = $loop->delay;
my $delay = $loop->delay(sub {...});
my $delay = $loop->delay(sub {...}, sub {...});
Build L<Mojo::IOLoop::Delay> object to use as a promise and/or for flow-control. Callbacks will be passed along to
L<Mojo::IOLoop::Delay/"steps">.
# Wrap continuation-passing style APIs with promises
my $ua = Mojo::UserAgent->new;
sub get {
my $promise = Mojo::IOLoop->delay;
$ua->get(@_ => sub ($ua, $tx) {
my $err = $tx->error;
if (!$err || $err->{code}) { $promise->resolve($tx) }
else { $promise->reject($err->{message}) }
});
return $promise;
}
my $mojo = get('https://mojolicious.org');
my $cpan = get('https://metacpan.org');
Mojo::Promise->race($mojo, $cpan)->then(sub ($tx) { say $tx->req->url })->wait;
# Synchronize multiple non-blocking operations
my $delay = Mojo::IOLoop->delay(sub { say 'BOOM!' });
for my $i (1 .. 10) {
my $end = $delay->begin;
Mojo::IOLoop->timer($i => sub {
say 10 - $i;
$end->();
});
}
$delay->wait;
# Sequentialize multiple non-blocking operations
Mojo::IOLoop->delay(
# First step (simple timer)
sub ($delay) {
Mojo::IOLoop->timer(2 => $delay->begin);
say 'Second step in 2 seconds.';
},
# Second step (concurrent timers)
sub ($delay) {
Mojo::IOLoop->timer(1 => $delay->begin);
Mojo::IOLoop->timer(3 => $delay->begin);
say 'Third step in 3 seconds.';
},
# Third step (the end)
sub { say 'And done after 5 seconds total.' }
)->wait;
=head2 is_running
my $bool = Mojo::IOLoop->is_running;
my $bool = $loop->is_running;
Check if event loop is running.
=head2 next_tick
my $undef = Mojo::IOLoop->next_tick(sub ($loop) {...});
my $undef = $loop->next_tick(sub ($loop) {...});
Execute callback as soon as possible, but not before returning or other callbacks that have been registered with this
method, always returns C<undef>.
# Perform operation on next reactor tick
Mojo::IOLoop->next_tick(sub ($loop) {...});
=head2 one_tick
Mojo::IOLoop->one_tick;
$loop->one_tick;
Run event loop until an event occurs.
# Don't block longer than 0.5 seconds
my $id = Mojo::IOLoop->timer(0.5 => sub ($loop) {});
Mojo::IOLoop->one_tick;
Mojo::IOLoop->remove($id);
=head2 recurring
my $id = Mojo::IOLoop->recurring(3 => sub ($loop) {...});
my $id = $loop->recurring(0 => sub ($loop) {...});
my $id = $loop->recurring(0.25 => sub ($loop) {...});
Create a new recurring timer, invoking the callback repeatedly after a given amount of time in seconds.
# Perform operation every 5 seconds
Mojo::IOLoop->recurring(5 => sub ($loop) {...});
=head2 remove
Mojo::IOLoop->remove($id);
$loop->remove($id);
Remove anything with an id, connections will be dropped gracefully by allowing them to finish writing all data in their
write buffers.
=head2 reset
Mojo::IOLoop->reset;
$loop->reset;
$loop->reset({freeze => 1});
Remove everything and stop the event loop.
These options are currently available:
=over 2
=item freeze
freeze => 1
Freeze the current state of the event loop in time before resetting it. This will prevent active connections from
getting closed immediately, which can help with many unintended side effects when processes are forked. Note that this
option is B<EXPERIMENTAL> and might change without warning!
=back
=head2 server
my $id = Mojo::IOLoop->server(port => 3000, sub {...});
my $id = $loop->server(port => 3000, sub {...});
my $id = $loop->server({port => 3000} => sub {...});
Accept TCP/IP and UNIX domain socket connections with L<Mojo::IOLoop::Server> and create stream objects (usually
L<Mojo::IOLoop::Stream>, takes the same arguments as L<Mojo::IOLoop::Server/"listen">.
# Listen on random port
my $id = Mojo::IOLoop->server({address => '127.0.0.1'} => sub ($loop, $stream, $id) {...});
my $port = Mojo::IOLoop->acceptor($id)->port;
=head2 singleton
my $loop = Mojo::IOLoop->singleton;
The global L<Mojo::IOLoop> singleton, used to access a single shared event loop object from everywhere inside the
process.
# Many methods also allow you to take shortcuts
Mojo::IOLoop->timer(2 => sub { Mojo::IOLoop->stop });
Mojo::IOLoop->start;
# Restart active timer
my $id = Mojo::IOLoop->timer(3 => sub { say 'Timeout!' });
Mojo::IOLoop->singleton->reactor->again($id);
# Turn file descriptor into handle and watch if it becomes readable
my $handle = IO::Handle->new_from_fd($fd, 'r');
Mojo::IOLoop->singleton->reactor->io($handle => sub ($reactor, $writable) {
say $writable ? 'Handle is writable' : 'Handle is readable';
})->watch($handle, 1, 0);
=head2 start
Mojo::IOLoop->start;
$loop->start;
Start the event loop, this will block until L</"stop"> is called. Note that some reactors stop automatically if there
are no events being watched anymore.
# Start event loop only if it is not running already
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
=head2 stop
Mojo::IOLoop->stop;
$loop->stop;
Stop the event loop, this will not interrupt any existing connections and the event loop can be restarted by running
L</"start"> again.
=head2 stop_gracefully
Mojo::IOLoop->stop_gracefully;
$loop->stop_gracefully;
Stop accepting new connections and wait for already accepted connections to be closed, before stopping the event loop.
=head2 stream
my $stream = Mojo::IOLoop->stream($id);
my $stream = $loop->stream($id);
my $id = $loop->stream(Mojo::IOLoop::Stream->new);
Get L<Mojo::IOLoop::Stream> object for id or turn object into a connection.
# Increase inactivity timeout for connection to 300 seconds
Mojo::IOLoop->stream($id)->timeout(300);
=head2 subprocess
my $subprocess = Mojo::IOLoop->subprocess;
my $subprocess = $loop->subprocess;
my $subprocess = $loop->subprocess(sub ($subprocess) {...}, sub ($subprocess, $err, @results) {...});
Build L<Mojo::IOLoop::Subprocess> object to perform computationally expensive operations in subprocesses, without
blocking the event loop. Callbacks will be passed along to L<Mojo::IOLoop::Subprocess/"run">.
# Operation that would block the event loop for 5 seconds
Mojo::IOLoop->subprocess->run_p(sub {
sleep 5;
return '♥', 'Mojolicious';
})->then(sub (@results) {
say "I $results[0] $results[1]!";
})->catch(sub ($err) {
say "Subprocess error: $err";
});
=head2 timer
my $id = Mojo::IOLoop->timer(3 => sub ($loop) {...});
my $id = $loop->timer(0 => sub ($loop) {...});
my $id = $loop->timer(0.25 => sub ($loop) {...});
Create a new timer, invoking the callback after a given amount of time in seconds.
# Perform operation in 5 seconds
Mojo::IOLoop->timer(5 => sub ($loop) {...});
=head1 DEBUGGING
You can set the C<MOJO_IOLOOP_DEBUG> environment variable to get some advanced diagnostics information printed to
C<STDERR>.
MOJO_IOLOOP_DEBUG=1
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,355 @@
package Mojo::IOLoop::Client;
use Mojo::Base 'Mojo::EventEmitter';
use Errno qw(EINPROGRESS);
use IO::Socket::IP;
use IO::Socket::UNIX;
use Mojo::IOLoop;
use Mojo::IOLoop::TLS;
use Scalar::Util qw(weaken);
use Socket qw(IPPROTO_TCP SOCK_STREAM TCP_NODELAY);
# Non-blocking name resolution requires Net::DNS::Native
use constant NNR => $ENV{MOJO_NO_NNR} ? 0 : eval { require Net::DNS::Native; Net::DNS::Native->VERSION('0.15'); 1 };
my $NDN;
# SOCKS support requires IO::Socket::Socks
use constant SOCKS => $ENV{MOJO_NO_SOCKS}
? 0
: eval { require IO::Socket::Socks; IO::Socket::Socks->VERSION('0.64'); 1 };
use constant READ => SOCKS ? IO::Socket::Socks::SOCKS_WANT_READ() : 0;
use constant WRITE => SOCKS ? IO::Socket::Socks::SOCKS_WANT_WRITE() : 0;
has reactor => sub { Mojo::IOLoop->singleton->reactor }, weak => 1;
sub DESTROY { shift->_cleanup }
sub can_nnr {NNR}
sub can_socks {SOCKS}
sub connect {
my ($self, $args) = (shift, ref $_[0] ? $_[0] : {@_});
# Timeout
weaken $self;
my $reactor = $self->reactor;
$self->{timer} = $reactor->timer($args->{timeout} || 10, sub { $self->emit(error => 'Connect timeout') });
# Blocking name resolution
$_ && s/[[\]]//g for @$args{qw(address socks_address)};
my $address = $args->{socks_address} || ($args->{address} ||= '127.0.0.1');
return $reactor->next_tick(sub { $self && $self->_connect($args) }) if !NNR || $args->{handle} || $args->{path};
# Non-blocking name resolution
$NDN //= Net::DNS::Native->new(pool => 5, extra_thread => 1);
my $handle = $self->{dns}
= $NDN->getaddrinfo($address, _port($args), {protocol => IPPROTO_TCP, socktype => SOCK_STREAM});
$reactor->io(
$handle => sub {
my $reactor = shift;
$reactor->remove($self->{dns});
my ($err, @res) = $NDN->get_result(delete $self->{dns});
return $self->emit(error => "Can't resolve: $err") if $err;
$args->{addr_info} = \@res;
$self->_connect($args);
}
)->watch($handle, 1, 0);
}
sub _cleanup {
my $self = shift;
$NDN->timedout($self->{dns}) if $NDN && $self->{dns};
return $self unless my $reactor = $self->reactor;
$self->{$_} && $reactor->remove(delete $self->{$_}) for qw(dns timer handle);
return $self;
}
sub _connect {
my ($self, $args) = @_;
my $path = $args->{path};
my $handle = $self->{handle} = $args->{handle};
unless ($handle) {
my $class = $path ? 'IO::Socket::UNIX' : 'IO::Socket::IP';
my %options = (Blocking => 0);
# UNIX domain socket
if ($path) { $options{Peer} = $path }
# IP socket
else {
if (my $info = $args->{addr_info}) { $options{PeerAddrInfo} = $info }
else {
$options{PeerAddr} = $args->{socks_address} || $args->{address};
$options{PeerPort} = _port($args);
}
$options{LocalAddr} = $args->{local_address} if $args->{local_address};
}
return $self->emit(error => "Can't connect: $@") unless $self->{handle} = $handle = $class->new(%options);
}
$handle->blocking(0);
$path ? $self->_try_socks($args) : $self->_wait('_ready', $handle, $args);
}
sub _port { $_[0]{socks_port} || $_[0]{port} || ($_[0]{tls} ? 443 : 80) }
sub _ready {
my ($self, $args) = @_;
# Socket changes in between attempts and needs to be re-added for epoll/kqueue
my $handle = $self->{handle};
unless ($handle->connect) {
return $self->emit(error => $!) unless $! == EINPROGRESS;
$self->reactor->remove($handle);
return $self->_wait('_ready', $handle, $args);
}
return $self->emit(error => $! || 'Not connected') unless $handle->connected;
# Disable Nagle's algorithm
setsockopt $handle, IPPROTO_TCP, TCP_NODELAY, 1;
$self->_try_socks($args);
}
sub _socks {
my ($self, $args) = @_;
# Connected
my $handle = $self->{handle};
return $self->_try_tls($args) if $handle->ready;
# Switch between reading and writing
my $err = $IO::Socket::Socks::SOCKS_ERROR;
if ($err == READ) { $self->reactor->watch($handle, 1, 0) }
elsif ($err == WRITE) { $self->reactor->watch($handle, 1, 1) }
else { $self->emit(error => $err) }
}
sub _try_socks {
my ($self, $args) = @_;
my $handle = $self->{handle};
return $self->_try_tls($args) unless $args->{socks_address};
return $self->emit(error => 'IO::Socket::Socks 0.64+ required for SOCKS support') unless SOCKS;
my %options = (ConnectAddr => $args->{address}, ConnectPort => $args->{port});
@options{qw(AuthType Username Password)} = ('userpass', @$args{qw(socks_user socks_pass)}) if $args->{socks_user};
my $reactor = $self->reactor;
$reactor->remove($handle);
return $self->emit(error => 'SOCKS upgrade failed') unless IO::Socket::Socks->start_SOCKS($handle, %options);
$self->_wait('_socks', $handle, $args);
}
sub _try_tls {
my ($self, $args) = @_;
my $handle = $self->{handle};
return $self->_cleanup->emit(connect => $handle) unless $args->{tls};
my $reactor = $self->reactor;
$reactor->remove($handle);
# Start TLS handshake
weaken $self;
my $tls = Mojo::IOLoop::TLS->new($handle)->reactor($self->reactor);
$tls->on(upgrade => sub { $self->_cleanup->emit(connect => pop) });
$tls->on(error => sub { $self->emit(error => pop) });
$tls->negotiate(%$args);
}
sub _wait {
my ($self, $next, $handle, $args) = @_;
weaken $self;
$self->reactor->io($handle => sub { $self->$next($args) })->watch($handle, 0, 1);
}
1;
=encoding utf8
=head1 NAME
Mojo::IOLoop::Client - Non-blocking TCP/IP and UNIX domain socket client
=head1 SYNOPSIS
use Mojo::IOLoop::Client;
# Create socket connection
my $client = Mojo::IOLoop::Client->new;
$client->on(connect => sub ($client, $handle) {...});
$client->on(error => sub ($client, $err) {...});
$client->connect(address => 'example.com', port => 80);
# Start reactor if necessary
$client->reactor->start unless $client->reactor->is_running;
=head1 DESCRIPTION
L<Mojo::IOLoop::Client> opens TCP/IP and UNIX domain socket connections for L<Mojo::IOLoop>.
=head1 EVENTS
L<Mojo::IOLoop::Client> inherits all events from L<Mojo::EventEmitter> and can emit the following new ones.
=head2 connect
$client->on(connect => sub ($client, $handle) {...});
Emitted once the connection is established.
=head2 error
$client->on(error => sub ($client, $err) {...});
Emitted if an error occurs on the connection, fatal if unhandled.
=head1 ATTRIBUTES
L<Mojo::IOLoop::Client> implements the following attributes.
=head2 reactor
my $reactor = $client->reactor;
$client = $client->reactor(Mojo::Reactor::Poll->new);
Low-level event reactor, defaults to the C<reactor> attribute value of the global L<Mojo::IOLoop> singleton. Note that
this attribute is weakened.
=head1 METHODS
L<Mojo::IOLoop::Client> inherits all methods from L<Mojo::EventEmitter> and implements the following new ones.
=head2 can_nnr
my $bool = Mojo::IOLoop::Client->can_nnr;
True if L<Net::DNS::Native> 0.15+ is installed and non-blocking name resolution support enabled.
=head2 can_socks
my $bool = Mojo::IOLoop::Client->can_socks;
True if L<IO::Socket::SOCKS> 0.64+ is installed and SOCKS5 support enabled.
=head2 connect
$client->connect(address => '127.0.0.1', port => 3000);
$client->connect({address => '127.0.0.1', port => 3000});
Open a socket connection to a remote host. Note that non-blocking name resolution depends on L<Net::DNS::Native>
(0.15+), SOCKS5 support on L<IO::Socket::Socks> (0.64), and TLS support on L<IO::Socket::SSL> (2.009+).
These options are currently available:
=over 2
=item address
address => 'mojolicious.org'
Address or host name of the peer to connect to, defaults to C<127.0.0.1>.
=item handle
handle => $handle
Use an already prepared L<IO::Socket::IP> object.
=item local_address
local_address => '127.0.0.1'
Local address to bind to.
=item path
path => '/tmp/myapp.sock'
Path of UNIX domain socket to connect to.
=item port
port => 80
Port to connect to, defaults to C<80> or C<443> with C<tls> option.
=item socks_address
socks_address => '127.0.0.1'
Address or host name of SOCKS5 proxy server to use for connection.
=item socks_pass
socks_pass => 'secr3t'
Password to use for SOCKS5 authentication.
=item socks_port
socks_port => 9050
Port of SOCKS5 proxy server to use for connection.
=item socks_user
socks_user => 'sri'
Username to use for SOCKS5 authentication.
=item timeout
timeout => 15
Maximum amount of time in seconds establishing connection may take before getting canceled, defaults to C<10>.
=item tls
tls => 1
Enable TLS.
=item tls_ca
tls_ca => '/etc/tls/ca.crt'
Path to TLS certificate authority file.
=item tls_cert
tls_cert => '/etc/tls/client.crt'
Path to the TLS certificate file.
=item tls_key
tls_key => '/etc/tls/client.key'
Path to the TLS key file.
=item tls_protocols
tls_protocols => ['foo', 'bar']
ALPN protocols to negotiate.
=item tls_verify
tls_verify => 0x00
TLS verification mode.
=back
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,204 @@
package Mojo::IOLoop::Delay;
use Mojo::Base 'Mojo::Promise';
sub begin {
my ($self, $offset, $len) = @_;
$self->{pending}++;
my $id = $self->{counter}++;
return sub { $self->_step($id, $offset // 1, $len, @_) };
}
sub pass { $_[0]->begin->(@_) }
sub steps {
my ($self, @steps) = @_;
$self->{steps} = \@steps;
$self->ioloop->next_tick($self->begin);
return $self;
}
sub _step {
my ($self, $id, $offset, $len) = (shift, shift, shift, shift);
$self->{args}[$id] = [@_ ? defined $len ? splice @_, $offset, $len : splice @_, $offset : ()];
return $self if $self->{fail} || --$self->{pending} || $self->{lock};
local $self->{lock} = 1;
my @args = map {@$_} @{delete $self->{args}};
$self->{counter} = 0;
if (my $cb = shift @{$self->{steps}}) {
unless (eval { $self->$cb(@args); 1 }) {
my $err = $@;
@{$self}{qw(fail steps)} = (1, []);
return $self->reject($err);
}
}
($self->{steps} = []) and return $self->resolve(@args) unless $self->{counter};
$self->ioloop->next_tick($self->begin) unless $self->{pending};
return $self;
}
1;
=encoding utf8
=head1 NAME
Mojo::IOLoop::Delay - Promises/A+ and flow-control helpers
=head1 SYNOPSIS
use Mojo::IOLoop::Delay;
# Synchronize multiple non-blocking operations
my $delay = Mojo::IOLoop::Delay->new;
$delay->steps(sub { say 'BOOM!' });
for my $i (1 .. 10) {
my $end = $delay->begin;
Mojo::IOLoop->timer($i => sub {
say 10 - $i;
$end->();
});
}
$delay->wait;
# Sequentialize multiple non-blocking operations
Mojo::IOLoop::Delay->new->steps(
# First step (simple timer)
sub ($delay) {
Mojo::IOLoop->timer(2 => $delay->begin);
say 'Second step in 2 seconds.';
},
# Second step (concurrent timers)
sub ($delay, @args) {
Mojo::IOLoop->timer(1 => $delay->begin);
Mojo::IOLoop->timer(3 => $delay->begin);
say 'Third step in 3 seconds.';
},
# Third step (the end)
sub ($delay, @args) {
say 'And done after 5 seconds total.';
}
)->wait;
=head1 DESCRIPTION
L<Mojo::IOLoop::Delay> adds flow-control helpers to L<Mojo::Promise>, which can help you avoid deep nested closures
that often result from continuation-passing style.
use Mojo::IOLoop;
# These deep nested closures are often referred to as "Callback Hell"
Mojo::IOLoop->timer(3 => sub ($loop) {
say '3 seconds';
Mojo::IOLoop->timer(3 => sub ($loop) {
say '6 seconds';
Mojo::IOLoop->timer(3 => sub ($loop) {
say '9 seconds';
Mojo::IOLoop->stop;
});
});
});
Mojo::IOLoop->start;
The idea behind L<Mojo::IOLoop::Delay> is to turn the nested closures above into a flat series of closures. In the
example below, the call to L</"begin"> creates a code reference that we can pass to L<Mojo::IOLoop/"timer"> as a
callback, and that leads to the next closure in the series when executed.
use Mojo::IOLoop;
# Instead of nested closures we now have a simple chain of steps
my $delay = Mojo::IOLoop->delay(
sub ($delay) { Mojo::IOLoop->timer(3 => $delay->begin) },
sub ($delay) {
say '3 seconds';
Mojo::IOLoop->timer(3 => $delay->begin);
},
sub ($delay) {
say '6 seconds';
Mojo::IOLoop->timer(3 => $delay->begin);
},
sub ($delay) { say '9 seconds' }
);
$delay->wait;
Another positive side effect of this pattern is that we do not need to call L<Mojo::IOLoop/"start"> and
L<Mojo::IOLoop/"stop"> manually, because we know exactly when our chain of L</"steps"> has reached the end. So
L<Mojo::Promise/"wait"> can stop the event loop automatically if it had to be started at all in the first place.
=head1 ATTRIBUTES
L<Mojo::IOLoop::Delay> inherits all attributes from L<Mojo::Promise>.
=head1 METHODS
L<Mojo::IOLoop::Delay> inherits all methods from L<Mojo::Promise> and implements the following new ones.
=head2 begin
my $cb = $delay->begin;
my $cb = $delay->begin($offset);
my $cb = $delay->begin($offset, $len);
Indicate an active event by incrementing the event counter, the returned code reference can be used as a callback, and
needs to be executed when the event has completed to decrement the event counter again. When all code references
generated by this method have been executed and the event counter has reached zero, L</"steps"> will continue.
# Capture all arguments except for the first one (invocant)
my $delay = Mojo::IOLoop->delay(sub ($delay, $err, $stream) { ... });
Mojo::IOLoop->client({port => 3000} => $delay->begin);
$delay->wait;
Arguments passed to the returned code reference are spliced with the given offset and length, defaulting to an offset
of C<1> with no default length. The arguments are then combined in the same order L</"begin"> was called, and passed
together to the next step.
# Capture all arguments
my $delay = Mojo::IOLoop->delay(sub ($delay, $loop, $err, $stream) { ... });
Mojo::IOLoop->client({port => 3000} => $delay->begin(0));
$delay->wait;
# Capture only the second argument
my $delay = Mojo::IOLoop->delay(sub ($delay, $err) { ... });
Mojo::IOLoop->client({port => 3000} => $delay->begin(1, 1));
$delay->wait;
# Capture and combine arguments
my $delay = Mojo::IOLoop->delay(sub ($delay, $three_err, $three_stream, $four_err, $four_stream) { ... });
Mojo::IOLoop->client({port => 3000} => $delay->begin);
Mojo::IOLoop->client({port => 4000} => $delay->begin);
$delay->wait;
=head2 pass
$delay = $delay->pass;
$delay = $delay->pass(@args);
Shortcut for passing values between L</"steps">.
# Longer version
$delay->begin(0)->(@args);
=head2 steps
$delay = $delay->steps(sub {...}, sub {...});
Sequentialize multiple events, every time the event counter reaches zero a callback will run, the first one
automatically runs during the next reactor tick unless it is delayed by incrementing the event counter. This chain will
continue until there are no remaining callbacks, a callback does not increment the event counter or an exception gets
thrown in a callback. Finishing the chain will also result in the promise being fulfilled, or if an exception got
thrown it will be rejected.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,313 @@
package Mojo::IOLoop::Server;
use Mojo::Base 'Mojo::EventEmitter';
use Carp qw(croak);
use IO::Socket::IP;
use IO::Socket::UNIX;
use Mojo::File qw(path);
use Mojo::IOLoop;
use Mojo::IOLoop::TLS;
use Scalar::Util qw(weaken);
use Socket qw(IPPROTO_TCP TCP_NODELAY);
has reactor => sub { Mojo::IOLoop->singleton->reactor }, weak => 1;
sub DESTROY {
my $self = shift;
$ENV{MOJO_REUSE} =~ s/(?:^|\,)\Q$self->{reuse}\E// if $self->{reuse};
$self->stop if $self->{handle} && $self->reactor;
}
sub generate_port { IO::Socket::IP->new(Listen => 5, LocalAddr => '127.0.0.1')->sockport }
sub handle { shift->{handle} }
sub is_accepting { !!shift->{active} }
sub listen {
my ($self, $args) = (shift, ref $_[0] ? $_[0] : {@_});
# Look for reusable file descriptor
my $path = $args->{path};
my $address = $args->{address} || '0.0.0.0';
my $port = $args->{port};
$ENV{MOJO_REUSE} ||= '';
my $fd = ($path && $ENV{MOJO_REUSE} =~ /(?:^|\,)unix:\Q$path\E:(\d+)/)
|| ($port && $ENV{MOJO_REUSE} =~ /(?:^|\,)\Q$address:$port\E:(\d+)/) ? $1 : undef;
# Allow file descriptor inheritance
local $^F = 1023;
# Reuse file descriptor
my $handle;
my $class = $path ? 'IO::Socket::UNIX' : 'IO::Socket::IP';
if (defined($fd //= $args->{fd})) {
$handle = $class->new_from_fd($fd, 'r') or croak "Can't open file descriptor $fd: $!";
}
else {
my %options = (Listen => $args->{backlog} // SOMAXCONN, Type => SOCK_STREAM);
# UNIX domain socket
my $reuse;
if ($path) {
path($path)->remove if -S $path;
$options{Local} = $path;
$handle = $class->new(%options) or croak "Can't create listen socket: $!";
$reuse = $self->{reuse} = join ':', 'unix', $path, fileno $handle;
}
# IP socket
else {
$options{LocalAddr} = $address;
$options{LocalAddr} =~ y/[]//d;
$options{LocalPort} = $port if $port;
$options{ReuseAddr} = 1;
$options{ReusePort} = $args->{reuse};
$handle = $class->new(%options) or croak "Can't create listen socket: $@";
$fd = fileno $handle;
$reuse = $self->{reuse} = join ':', $address, $handle->sockport, $fd;
}
$ENV{MOJO_REUSE} .= length $ENV{MOJO_REUSE} ? ",$reuse" : "$reuse";
}
$handle->blocking(0);
@$self{qw(args handle)} = ($args, $handle);
croak 'IO::Socket::SSL 2.009+ required for TLS support' if !Mojo::IOLoop::TLS->can_tls && $args->{tls};
}
sub port { shift->{handle}->sockport }
sub start {
my $self = shift;
weaken $self;
++$self->{active} and $self->reactor->io($self->{handle} => sub { $self->_accept })->watch($self->{handle}, 1, 0);
}
sub stop { delete($_[0]{active}) and $_[0]->reactor->remove($_[0]{handle}) }
sub _accept {
my $self = shift;
# Greedy accept
my $args = $self->{args};
my $accepted = 0;
while ($self->{active} && !($args->{single_accept} && $accepted++)) {
return unless my $handle = $self->{handle}->accept;
$handle->blocking(0);
# Disable Nagle's algorithm
setsockopt $handle, IPPROTO_TCP, TCP_NODELAY, 1;
$self->emit(accept => $handle) and next unless $args->{tls};
# Start TLS handshake
my $tls = Mojo::IOLoop::TLS->new($handle)->reactor($self->reactor);
$tls->on(upgrade => sub { $self->emit(accept => pop) });
$tls->on(error => sub { });
$tls->negotiate(%$args, server => 1);
}
}
1;
=encoding utf8
=head1 NAME
Mojo::IOLoop::Server - Non-blocking TCP and UNIX domain socket server
=head1 SYNOPSIS
use Mojo::IOLoop::Server;
# Create listen socket
my $server = Mojo::IOLoop::Server->new;
$server->on(accept => sub ($server, $handle) {...});
$server->listen(port => 3000);
# Start and stop accepting connections
$server->start;
$server->stop;
# Start reactor if necessary
$server->reactor->start unless $server->reactor->is_running;
=head1 DESCRIPTION
L<Mojo::IOLoop::Server> accepts TCP/IP and UNIX domain socket connections for L<Mojo::IOLoop>.
=head1 EVENTS
L<Mojo::IOLoop::Server> inherits all events from L<Mojo::EventEmitter> and can emit the following new ones.
=head2 accept
$server->on(accept => sub ($server, $handle) {...});
Emitted for each accepted connection.
=head1 ATTRIBUTES
L<Mojo::IOLoop::Server> implements the following attributes.
=head2 reactor
my $reactor = $server->reactor;
$server = $server->reactor(Mojo::Reactor::Poll->new);
Low-level event reactor, defaults to the C<reactor> attribute value of the global L<Mojo::IOLoop> singleton. Note that
this attribute is weakened.
=head1 METHODS
L<Mojo::IOLoop::Server> inherits all methods from L<Mojo::EventEmitter> and implements the following new ones.
=head2 generate_port
my $port = Mojo::IOLoop::Server->generate_port;
Find a free TCP port, primarily used for tests.
=head2 handle
my $handle = $server->handle;
Get handle for server, usually an L<IO::Socket::IP> object.
=head2 is_accepting
my $bool = $server->is_accepting;
Check if connections are currently being accepted.
=head2 listen
$server->listen(port => 3000);
$server->listen({port => 3000});
Create a new listen socket. Note that TLS support depends on L<IO::Socket::SSL> (2.009+).
These options are currently available:
=over 2
=item address
address => '127.0.0.1'
Local address to listen on, defaults to C<0.0.0.0>.
=item backlog
backlog => 128
Maximum backlog size, defaults to C<SOMAXCONN>.
=item fd
fd => 3
File descriptor with an already prepared listen socket.
=item path
path => '/tmp/myapp.sock'
Path for UNIX domain socket to listen on.
=item port
port => 80
Port to listen on, defaults to a random port.
=item reuse
reuse => 1
Allow multiple servers to use the same port with the C<SO_REUSEPORT> socket option.
=item single_accept
single_accept => 1
Only accept one connection at a time.
=item tls
tls => 1
Enable TLS.
=item tls_ca
tls_ca => '/etc/tls/ca.crt'
Path to TLS certificate authority file.
=item tls_cert
tls_cert => '/etc/tls/server.crt'
tls_cert => {'mojolicious.org' => '/etc/tls/mojo.crt'}
Path to the TLS cert file, defaults to a built-in test certificate.
=item tls_ciphers
tls_ciphers => 'AES128-GCM-SHA256:RC4:HIGH:!MD5:!aNULL:!EDH'
TLS cipher specification string. For more information about the format see
L<https://www.openssl.org/docs/manmaster/apps/ciphers.html#CIPHER-STRINGS>.
=item tls_key
tls_key => '/etc/tls/server.key'
tls_key => {'mojolicious.org' => '/etc/tls/mojo.key'}
Path to the TLS key file, defaults to a built-in test key.
=item tls_protocols
tls_protocols => ['foo', 'bar']
ALPN protocols to negotiate.
=item tls_verify
tls_verify => 0x00
TLS verification mode.
=item tls_version
tls_version => 'TLSv1_2'
TLS protocol version.
=back
=head2 port
my $port = $server->port;
Get port this server is listening on.
=head2 start
$server->start;
Start or resume accepting connections.
=head2 stop
$server->stop;
Stop accepting connections.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,327 @@
package Mojo::IOLoop::Stream;
use Mojo::Base 'Mojo::EventEmitter';
use Errno qw(EAGAIN ECONNRESET EINTR EWOULDBLOCK);
use Mojo::IOLoop;
use Mojo::Util;
use Scalar::Util qw(weaken);
has high_water_mark => 1048576;
has reactor => sub { Mojo::IOLoop->singleton->reactor }, weak => 1;
sub DESTROY { Mojo::Util::_global_destruction() or shift->close }
sub bytes_read { shift->{read} || 0 }
sub bytes_waiting { length(shift->{buffer} // '') }
sub bytes_written { shift->{written} || 0 }
sub can_write { $_[0]{handle} && $_[0]->bytes_waiting < $_[0]->high_water_mark }
sub close {
my $self = shift;
return unless my $reactor = $self->reactor;
return unless my $handle = delete $self->timeout(0)->{handle};
$reactor->remove($handle);
$self->emit('close');
}
sub close_gracefully { $_[0]->is_writing ? $_[0]{graceful}++ : $_[0]->close }
sub handle { shift->{handle} }
sub is_readable {
my $self = shift;
$self->_again;
return $self->{handle} && Mojo::Util::_readable(0, fileno $self->{handle});
}
sub is_writing {
my $self = shift;
return undef unless $self->{handle};
return !!length($self->{buffer}) || $self->has_subscribers('drain');
}
sub new { shift->SUPER::new(handle => shift, timeout => 15) }
sub start {
my $self = shift;
# Resume
return unless $self->{handle};
my $reactor = $self->reactor;
return $reactor->watch($self->{handle}, 1, $self->is_writing) if delete $self->{paused};
weaken $self;
my $cb = sub { pop() ? $self->_write : $self->_read };
$reactor->io($self->timeout($self->{timeout})->{handle} => $cb);
}
sub steal_handle {
my $self = shift;
$self->reactor->remove($self->{handle});
return delete $self->{handle};
}
sub stop { $_[0]->reactor->watch($_[0]{handle}, 0, $_[0]->is_writing) if $_[0]{handle} && !$_[0]{paused}++ }
sub timeout {
my ($self, $timeout) = @_;
return $self->{timeout} unless defined $timeout;
$self->{timeout} = $timeout;
my $reactor = $self->reactor;
if ($self->{timer}) {
if (!$self->{timeout}) { $reactor->remove(delete $self->{timer}) }
else { $reactor->again($self->{timer}, $self->{timeout}) }
}
elsif ($self->{timeout}) {
weaken $self;
$self->{timer}
= $reactor->timer($timeout => sub { $self and delete($self->{timer}) and $self->emit('timeout')->close });
}
return $self;
}
sub write {
my ($self, $chunk, $cb) = @_;
# IO::Socket::SSL will corrupt data with the wrong internal representation
utf8::downgrade $chunk;
$self->{buffer} .= $chunk;
if ($cb) { $self->once(drain => $cb) }
elsif (!length $self->{buffer}) { return $self }
$self->reactor->watch($self->{handle}, !$self->{paused}, 1) if $self->{handle};
return $self;
}
sub _again { $_[0]->reactor->again($_[0]{timer}) if $_[0]{timer} }
sub _read {
my $self = shift;
if (defined(my $read = $self->{handle}->sysread(my $buffer, 131072, 0))) {
$self->{read} += $read;
return $read == 0 ? $self->close : $self->emit(read => $buffer)->_again;
}
# Retry
return undef if $! == EAGAIN || $! == EINTR || $! == EWOULDBLOCK;
# Closed (maybe real error)
$! == ECONNRESET ? $self->close : $self->emit(error => $!)->close;
}
sub _write {
my $self = shift;
# Handle errors only when reading (to avoid timing problems)
my $handle = $self->{handle};
if (length $self->{buffer}) {
return undef unless defined(my $written = $handle->syswrite($self->{buffer}));
$self->{written} += $written;
$self->emit(write => substr($self->{buffer}, 0, $written, ''))->_again;
}
# Clear the buffer to free the underlying SV* memory
undef $self->{buffer}, $self->emit('drain') unless length $self->{buffer};
return undef if $self->is_writing;
return $self->close if $self->{graceful};
$self->reactor->watch($handle, !$self->{paused}, 0) if $self->{handle};
}
1;
=encoding utf8
=head1 NAME
Mojo::IOLoop::Stream - Non-blocking I/O stream
=head1 SYNOPSIS
use Mojo::IOLoop::Stream;
# Create stream
my $stream = Mojo::IOLoop::Stream->new($handle);
$stream->on(read => sub ($stream, $bytes) {...});
$stream->on(close => sub ($stream) {...});
$stream->on(error => sub ($stream, $err) {...});
# Start and stop watching for new data
$stream->start;
$stream->stop;
# Start reactor if necessary
$stream->reactor->start unless $stream->reactor->is_running;
=head1 DESCRIPTION
L<Mojo::IOLoop::Stream> is a container for I/O streams used by L<Mojo::IOLoop>.
=head1 EVENTS
L<Mojo::IOLoop::Stream> inherits all events from L<Mojo::EventEmitter> and can emit the following new ones.
=head2 close
$stream->on(close => sub ($stream) {...});
Emitted if the stream gets closed.
=head2 drain
$stream->on(drain => sub ($stream) {...});
Emitted once all data has been written.
=head2 error
$stream->on(error => sub ($stream, $err) {...});
Emitted if an error occurs on the stream, fatal if unhandled.
=head2 read
$stream->on(read => sub ($stream, $bytes) {...});
Emitted if new data arrives on the stream.
=head2 timeout
$stream->on(timeout => sub ($stream) {...});
Emitted if the stream has been inactive for too long and will get closed automatically.
=head2 write
$stream->on(write => sub ($stream, $bytes) {...});
Emitted if new data has been written to the stream.
=head1 ATTRIBUTES
L<Mojo::IOLoop::Stream> implements the following attributes.
=head2 high_water_mark
my $size = $msg->high_water_mark;
$msg = $msg->high_water_mark(1024);
Maximum size of L</"write"> buffer in bytes before L</"can_write"> returns false, defaults to C<1048576> (1MiB).
=head2 reactor
my $reactor = $stream->reactor;
$stream = $stream->reactor(Mojo::Reactor::Poll->new);
Low-level event reactor, defaults to the C<reactor> attribute value of the global L<Mojo::IOLoop> singleton. Note that
this attribute is weakened.
=head1 METHODS
L<Mojo::IOLoop::Stream> inherits all methods from L<Mojo::EventEmitter> and implements the following new ones.
=head2 bytes_read
my $num = $stream->bytes_read;
Number of bytes received.
=head2 bytes_waiting
my $num = $stream->bytes_waiting;
Number of bytes that have been enqueued with L</"write"> and are waiting to be written.
=head2 bytes_written
my $num = $stream->bytes_written;
Number of bytes written.
=head2 can_write
my $bool = $stream->can_write;
Returns true if calling L</"write"> is safe.
=head2 close
$stream->close;
Close stream immediately.
=head2 close_gracefully
$stream->close_gracefully;
Close stream gracefully.
=head2 handle
my $handle = $stream->handle;
Get handle for stream, usually an L<IO::Socket::IP> or L<IO::Socket::SSL> object.
=head2 is_readable
my $bool = $stream->is_readable;
Quick non-blocking check if stream is readable, useful for identifying tainted sockets.
=head2 is_writing
my $bool = $stream->is_writing;
Check if stream is writing.
=head2 new
my $stream = Mojo::IOLoop::Stream->new($handle);
Construct a new L<Mojo::IOLoop::Stream> object.
=head2 start
$stream->start;
Start or resume watching for new data on the stream.
=head2 steal_handle
my $handle = $stream->steal_handle;
Steal L</"handle"> and prevent it from getting closed automatically.
=head2 stop
$stream->stop;
Stop watching for new data on the stream.
=head2 timeout
my $timeout = $stream->timeout;
$stream = $stream->timeout(45);
Maximum amount of time in seconds stream can be inactive before getting closed automatically, defaults to C<15>.
Setting the value to C<0> will allow this stream to be inactive indefinitely.
=head2 write
$stream = $stream->write($bytes);
$stream = $stream->write($bytes => sub {...});
Enqueue data to be written to the stream as soon as possible, the optional drain callback will be executed once all
data has been written.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,265 @@
package Mojo::IOLoop::Subprocess;
use Mojo::Base 'Mojo::EventEmitter';
use Config;
use Mojo::IOLoop;
use Mojo::IOLoop::Stream;
use Mojo::JSON;
use Mojo::Promise;
use POSIX ();
has deserialize => sub { \&Mojo::JSON::decode_json };
has ioloop => sub { Mojo::IOLoop->singleton }, weak => 1;
has serialize => sub { \&Mojo::JSON::encode_json };
sub exit_code { shift->{exit_code} }
sub pid { shift->{pid} }
sub run {
my ($self, @args) = @_;
$self->ioloop->next_tick(sub { $self->_start(@args) });
return $self;
}
sub run_p {
my ($self, $child) = @_;
my $p = Mojo::Promise->new;
my $parent = sub {
my ($self, $err) = (shift, shift);
$err ? $p->reject($err) : $p->resolve(@_);
};
$self->ioloop->next_tick(sub { $self->_start($child, $parent) });
return $p;
}
sub _start {
my ($self, $child, $parent) = @_;
# No fork emulation support
return $self->$parent('Subprocesses do not support fork emulation') if $Config{d_pseudofork};
# Pipe for subprocess communication
return $self->$parent("Can't create pipe: $!") unless pipe(my $reader, $self->{writer});
$self->{writer}->autoflush(1);
# Child
return $self->$parent("Can't fork: $!") unless defined(my $pid = $self->{pid} = fork);
unless ($pid) {
eval {
$self->ioloop->reset({freeze => 1});
my $results = eval { [$self->$child] } // [];
print {$self->{writer}} '0-', $self->serialize->([$@, @$results]);
$self->emit('cleanup');
} or warn $@;
POSIX::_exit(0);
}
# Parent
my $me = $$;
close $self->{writer};
my $stream = Mojo::IOLoop::Stream->new($reader)->timeout(0);
$self->emit('spawn')->ioloop->stream($stream);
my $buffer = '';
$stream->on(
read => sub {
$buffer .= pop;
while (1) {
my ($len) = $buffer =~ /^([0-9]+)\-/;
last unless $len and length $buffer >= $len + $+[0];
my $snippet = substr $buffer, 0, $len + $+[0], '';
my $args = $self->deserialize->(substr $snippet, $+[0]);
$self->emit(progress => @$args);
}
}
);
$stream->on(
close => sub {
return unless $$ == $me;
waitpid $pid, 0;
$self->{exit_code} = $? >> 8;
substr $buffer, 0, 2, '';
my $results = eval { $self->deserialize->($buffer) } // [];
$self->$parent(shift(@$results) // $@, @$results);
}
);
}
sub progress {
my ($self, @args) = @_;
my $serialized = $self->serialize->(\@args);
print {$self->{writer}} length($serialized), '-', $serialized;
}
1;
=encoding utf8
=head1 NAME
Mojo::IOLoop::Subprocess - Subprocesses
=head1 SYNOPSIS
use Mojo::IOLoop::Subprocess;
# Operation that would block the event loop for 5 seconds
my $subprocess = Mojo::IOLoop::Subprocess->new;
$subprocess->run(
sub ($subprocess) {
sleep 5;
return '♥', 'Mojolicious';
},
sub ($subprocess, $err, @results) {
say "Subprocess error: $err" and return if $err;
say "I $results[0] $results[1]!";
}
);
# Operation that would block the event loop for 5 seconds (with promise)
$subprocess->run_p(sub {
sleep 5;
return '♥', 'Mojolicious';
})->then(sub (@results) {
say "I $results[0] $results[1]!";
})->catch(sub {
my $err = shift;
say "Subprocess error: $err";
});
# Start event loop if necessary
$subprocess->ioloop->start unless $subprocess->ioloop->is_running;
=head1 DESCRIPTION
L<Mojo::IOLoop::Subprocess> allows L<Mojo::IOLoop> to perform computationally expensive operations in subprocesses,
without blocking the event loop.
=head1 EVENTS
L<Mojo::IOLoop::Subprocess> inherits all events from L<Mojo::EventEmitter> and can emit the following new ones.
=head2 cleanup
$subprocess->on(cleanup => sub ($subprocess) {...});
Emitted in the subprocess right before the process will exit.
$subprocess->on(cleanup => sub ($subprocess) { say "Process $$ is about to exit" });
=head2 progress
$subprocess->on(progress => sub ($subprocess, @data) {...});
Emitted in the parent process when the subprocess calls the L<progress|/"progress1"> method.
=head2 spawn
$subprocess->on(spawn => sub ($subprocess) {...});
Emitted in the parent process when the subprocess has been spawned.
$subprocess->on(spawn => sub ($subprocess) {
my $pid = $subprocess->pid;
say "Performing work in process $pid";
});
=head1 ATTRIBUTES
L<Mojo::IOLoop::Subprocess> implements the following attributes.
=head2 deserialize
my $cb = $subprocess->deserialize;
$subprocess = $subprocess->deserialize(sub {...});
A callback used to deserialize subprocess return values, defaults to using L<Mojo::JSON>.
$subprocess->deserialize(sub ($bytes) { return [] });
=head2 ioloop
my $loop = $subprocess->ioloop;
$subprocess = $subprocess->ioloop(Mojo::IOLoop->new);
Event loop object to control, defaults to the global L<Mojo::IOLoop> singleton. Note that this attribute is weakened.
=head2 serialize
my $cb = $subprocess->serialize;
$subprocess = $subprocess->serialize(sub {...});
A callback used to serialize subprocess return values, defaults to using L<Mojo::JSON>.
$subprocess->serialize(sub ($array) { return '' });
=head1 METHODS
L<Mojo::IOLoop::Subprocess> inherits all methods from L<Mojo::EventEmitter> and implements the following new ones.
=head2 exit_code
my $code = $subprocess->exit_code;
Returns the subprocess exit code, or C<undef> if the subprocess is still running.
=head2 pid
my $pid = $subprocess->pid;
Process id of the spawned subprocess if available.
=head2 progress
$subprocess->progress(@data);
Send data serialized with L<Mojo::JSON> to the parent process at any time during the subprocess's execution. Must be
called by the subprocess and emits the L</"progress"> event in the parent process with the data.
# Send progress information to the parent process
$subprocess->run(
sub ($subprocess) {
$subprocess->progress('0%');
sleep 5;
$subprocess->progress('50%');
sleep 5;
return 'Hello Mojo!';
},
sub ($subprocess, $err, @results) {
say 'Progress is 100%';
say $results[0];
}
);
$subprocess->on(progress => sub ($subprocess, @data) { say "Progress is $data[0]" });
=head2 run
$subprocess = $subprocess->run(sub {...}, sub {...});
Execute the first callback in a child process and wait for it to return one or more values, without blocking
L</"ioloop"> in the parent process. Then execute the second callback in the parent process with the results. The return
values of the first callback and exceptions thrown by it, will be serialized with L<Mojo::JSON>, so they can be shared
between processes.
=head2 run_p
my $promise = $subprocess->run_p(sub {...});
Same as L</"run">, but returns a L<Mojo::Promise> object instead of accepting a second callback.
$subprocess->run_p(sub {
sleep 5;
return '♥', 'Mojolicious';
})->then(sub (@results) {
say "I $results[0] $results[1]!";
})->catch(sub ($err) {
say "Subprocess error: $err";
})->wait;
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,221 @@
package Mojo::IOLoop::TLS;
use Mojo::Base 'Mojo::EventEmitter';
use Mojo::File qw(curfile);
use Mojo::IOLoop;
use Scalar::Util qw(weaken);
# TLS support requires IO::Socket::SSL
use constant TLS => $ENV{MOJO_NO_TLS} ? 0 : eval { require IO::Socket::SSL; IO::Socket::SSL->VERSION('2.009'); 1 };
use constant READ => TLS ? IO::Socket::SSL::SSL_WANT_READ() : 0;
use constant WRITE => TLS ? IO::Socket::SSL::SSL_WANT_WRITE() : 0;
has reactor => sub { Mojo::IOLoop->singleton->reactor }, weak => 1;
# To regenerate the certificate run this command (28.06.2019)
# openssl req -x509 -newkey rsa:4096 -nodes -sha256 -out server.crt \
# -keyout server.key -days 7300 -subj '/CN=localhost'
my $CERT = curfile->sibling('resources', 'server.crt')->to_string;
my $KEY = curfile->sibling('resources', 'server.key')->to_string;
sub DESTROY { shift->_cleanup }
sub can_tls {TLS}
sub negotiate {
my ($self, $args) = (shift, ref $_[0] ? $_[0] : {@_});
return $self->emit(error => 'IO::Socket::SSL 2.009+ required for TLS support') unless TLS;
my $handle = $self->{handle};
return $self->emit(error => $IO::Socket::SSL::SSL_ERROR)
unless IO::Socket::SSL->start_SSL($handle, %{$self->_expand($args)});
$self->reactor->io($handle => sub { $self->_tls($handle, $args->{server}) });
}
sub new { shift->SUPER::new(handle => shift) }
sub _cleanup {
my $self = shift;
return undef unless my $reactor = $self->reactor;
$reactor->remove($self->{handle}) if $self->{handle};
return $self;
}
sub _expand {
my ($self, $args) = @_;
weaken $self;
my $tls = {SSL_error_trap => sub { $self->_cleanup->emit(error => $_[1]) }, SSL_startHandshake => 0};
$tls->{SSL_alpn_protocols} = $args->{tls_protocols} if $args->{tls_protocols};
$tls->{SSL_ca_file} = $args->{tls_ca} if $args->{tls_ca} && -T $args->{tls_ca};
$tls->{SSL_cert_file} = $args->{tls_cert} if $args->{tls_cert};
$tls->{SSL_cipher_list} = $args->{tls_ciphers} if $args->{tls_ciphers};
$tls->{SSL_key_file} = $args->{tls_key} if $args->{tls_key};
$tls->{SSL_server} = $args->{server} if $args->{server};
$tls->{SSL_verify_mode} = $args->{tls_verify} if defined $args->{tls_verify};
$tls->{SSL_version} = $args->{tls_version} if $args->{tls_version};
if ($args->{server}) {
$tls->{SSL_cert_file} ||= $CERT;
$tls->{SSL_key_file} ||= $KEY;
}
else {
$tls->{SSL_hostname} = IO::Socket::SSL->can_client_sni ? $args->{address} : '';
$tls->{SSL_verifycn_name} = $args->{address};
}
return $tls;
}
sub _tls {
my ($self, $handle, $server) = @_;
# Switch between reading and writing
if (!($server ? $handle->accept_SSL : $handle->connect_SSL)) {
my $err = $IO::Socket::SSL::SSL_ERROR;
if ($err == READ) { $self->reactor->watch($handle, 1, 0) }
elsif ($err == WRITE) { $self->reactor->watch($handle, 1, 1) }
}
else { $self->_cleanup->emit(upgrade => delete $self->{handle}) }
}
1;
=encoding utf8
=head1 NAME
Mojo::IOLoop::TLS - Non-blocking TLS handshake
=head1 SYNOPSIS
use Mojo::IOLoop::TLS;
# Negotiate TLS
my $tls = Mojo::IOLoop::TLS->new($old_handle);
$tls->on(upgrade => sub ($tls, $new_handle) {...});
$tls->on(error => sub ($tls, $err) {...});
$tls->negotiate(server => 1, tls_version => 'TLSv1_2');
# Start reactor if necessary
$tls->reactor->start unless $tls->reactor->is_running;
=head1 DESCRIPTION
L<Mojo::IOLoop::TLS> negotiates TLS for L<Mojo::IOLoop>.
=head1 EVENTS
L<Mojo::IOLoop::TLS> inherits all events from L<Mojo::EventEmitter> and can emit the following new ones.
=head2 upgrade
$tls->on(upgrade => sub ($tls, $handle) {...});
Emitted once TLS has been negotiated.
=head2 error
$tls->on(error => sub ($tls, $err) {...});
Emitted if an error occurs during negotiation, fatal if unhandled.
=head1 ATTRIBUTES
L<Mojo::IOLoop::TLS> implements the following attributes.
=head2 reactor
my $reactor = $tls->reactor;
$tls = $tls->reactor(Mojo::Reactor::Poll->new);
Low-level event reactor, defaults to the C<reactor> attribute value of the global L<Mojo::IOLoop> singleton. Note that
this attribute is weakened.
=head1 METHODS
L<Mojo::IOLoop::TLS> inherits all methods from L<Mojo::EventEmitter> and implements the following new ones.
=head2 can_tls
my $bool = Mojo::IOLoop::TLS->can_tls;
True if L<IO::Socket::SSL> 2.009+ is installed and TLS support enabled.
=head2 negotiate
$tls->negotiate(server => 1, tls_version => 'TLSv1_2');
$tls->negotiate({server => 1, tls_version => 'TLSv1_2'});
Negotiate TLS.
These options are currently available:
=over 2
=item server
server => 1
Negotiate TLS from the server-side, defaults to the client-side.
=item tls_ca
tls_ca => '/etc/tls/ca.crt'
Path to TLS certificate authority file.
=item tls_cert
tls_cert => '/etc/tls/server.crt'
tls_cert => {'mojolicious.org' => '/etc/tls/mojo.crt'}
Path to the TLS cert file, defaults to a built-in test certificate on the server-side.
=item tls_ciphers
tls_ciphers => 'AES128-GCM-SHA256:RC4:HIGH:!MD5:!aNULL:!EDH'
TLS cipher specification string. For more information about the format see
L<https://www.openssl.org/docs/manmaster/apps/ciphers.html#CIPHER-STRINGS>.
=item tls_key
tls_key => '/etc/tls/server.key'
tls_key => {'mojolicious.org' => '/etc/tls/mojo.key'}
Path to the TLS key file, defaults to a built-in test key on the server-side.
=item tls_protocols
tls_protocols => ['foo', 'bar']
ALPN protocols to negotiate.
=item tls_verify
tls_verify => 0x00
TLS verification mode.
=item tls_version
tls_version => 'TLSv1_2'
TLS protocol version.
=back
=head2 new
my $tls = Mojo::IOLoop::TLS->new($handle);
Construct a new L<Mojo::IOLoop::Stream> object.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,27 @@
-----BEGIN CERTIFICATE-----
MIIEpDCCAowCCQD2f63fTFHflTANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAls
b2NhbGhvc3QwHhcNMTkwNjI4MjExNDI5WhcNMzkwNjIzMjExNDI5WjAUMRIwEAYD
VQQDDAlsb2NhbGhvc3QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2
lW4DOBswU1YJkekNF6c4b1VVcpOvtqsLHhTxUz538bffcvhI2vv+aCltG6g5mlvJ
wo5NEu9l0ZG5TD9Ca4+WOOisVWrAI/i2YxXFQLOdjhKRBB1BvrOxSaFOuCXz9+cj
VRo0R8Dq3k+1aSy93Yf+fq9pL7LFJaUOlxcU2FOM+HW9FYPeVbqCzYqpPJoaBnwN
tQkQg7i8ufbeMS0bCcFpfTSV4pCgpWg1L9z6cVmBHtxc4MQv7rTTal+BF/iZDfDk
qTNFJpuK7IGtSVB5laTcssYKGuY5QhN5BBPoGEMP3f0KiZmgMOUqwR6fMUiidacG
iSIcgy05uOJyZ4oroqOzesz8nm2jH1eRPys2WLLFd801GKOZZE2LvNhCVzNIE0s1
Rr8yyWBU9jbjQuxlTAtyMUKKOqG9qsfEnKOsl9T9/pFcpJjad3spwhQSWhWEPWca
avw0CGVaGQ3nYmr9aJ9vpGBIiIsLQOPTzpOOPCDauMFpAPOoKnvIu+iz3Z8sUrMu
Ld+aT/3yxpAtNkmVv5A951XyFt9WDXF7jZOMHdOSZPvvI/Yn7joJUzfP9d7TLKjz
Xu+dzQnrAN3xuAXuy+jBpMIl3OPzwER6a8v7gUKRA/achNlIeOOmBdNn1cyHddcn
k6wiaXHJlFsl8X6IjCs9ILwv6H+ZGq/5QNU1Nrv5kQIDAQABMA0GCSqGSIb3DQEB
CwUAA4ICAQCo3xjINrsIQYvpVwVLpcO1p+oE5NV0ipA30JT+89Dn+vCejel9NzxT
msuD9aQSiNaB4znlIDqux4bSKkcRXDGINiaGNIDNXOtO/787LXjUZlljPVVHoPWi
hxgwc0nUHz3l/YvoXMKHI8blPkIhXl7xgKSuKQu05evjd//kpdHs1h+7b2vtCB0/
VoYTX/NrIX5oMYCvHkZEypQbDJ3VeAkOhRJ4efGEuEskPRm0+eDSL7elas/65saZ
l8vgkKDHZ9K0pd8JXc7EKmg3OBS22C5Lfhhy8AgqMa6R9p54oE4rH4yFaTe3BzFL
xSA6HWqC987L2OCFr2LJ8hZpawDF1otukGHDou/5+4Q03EZz10RuZfzlCLO5DXzW
Q28AtcCxz40n9o1giWzEj4LSYW4qsrpr5cNIhCqMzGPwp2OyS/TazPNJGoT8WKFU
Kr+Y/prfkXAwgVkXlUSiu7ukiYslSM4BbYWHDxd75Iv4GzzhUirSuJKN95RglxR8
XsJFlQwZ/tLvpflqb1Z8gPIV/4avtF+ybdx1AvqYViBQDf6GmLkM3p6Nwfj1LnCn
kFhnqY80gyVjbZXvp9ClypExzgz55/o2ZIijznCaDkFSVBdv+aUIzl98IicZxHqP
WREB+GMKmkaYrfKqlliQKdkXd2mXP/N8rv7SJEzHHpGRKBXsIAalrA==
-----END CERTIFICATE-----

View File

@@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQC2lW4DOBswU1YJ
kekNF6c4b1VVcpOvtqsLHhTxUz538bffcvhI2vv+aCltG6g5mlvJwo5NEu9l0ZG5
TD9Ca4+WOOisVWrAI/i2YxXFQLOdjhKRBB1BvrOxSaFOuCXz9+cjVRo0R8Dq3k+1
aSy93Yf+fq9pL7LFJaUOlxcU2FOM+HW9FYPeVbqCzYqpPJoaBnwNtQkQg7i8ufbe
MS0bCcFpfTSV4pCgpWg1L9z6cVmBHtxc4MQv7rTTal+BF/iZDfDkqTNFJpuK7IGt
SVB5laTcssYKGuY5QhN5BBPoGEMP3f0KiZmgMOUqwR6fMUiidacGiSIcgy05uOJy
Z4oroqOzesz8nm2jH1eRPys2WLLFd801GKOZZE2LvNhCVzNIE0s1Rr8yyWBU9jbj
QuxlTAtyMUKKOqG9qsfEnKOsl9T9/pFcpJjad3spwhQSWhWEPWcaavw0CGVaGQ3n
Ymr9aJ9vpGBIiIsLQOPTzpOOPCDauMFpAPOoKnvIu+iz3Z8sUrMuLd+aT/3yxpAt
NkmVv5A951XyFt9WDXF7jZOMHdOSZPvvI/Yn7joJUzfP9d7TLKjzXu+dzQnrAN3x
uAXuy+jBpMIl3OPzwER6a8v7gUKRA/achNlIeOOmBdNn1cyHddcnk6wiaXHJlFsl
8X6IjCs9ILwv6H+ZGq/5QNU1Nrv5kQIDAQABAoICAAINoiQVIHElrsUCyA0mo/HF
hr8kP7btJfVFDFU+a2hr5nZz04j2NXlB8J1Sf0zOiJO3RWRmfxy1A5+C1P9JOF8n
Gq69cyrf/K8IZDlIpfxymZDZ6/5OR7UJr++zsHGS6x2Bmn7WA7xgbaMLoL4t3Jan
FA/pwmfnKXkFh/PrDt15+dD7ifUZH7TS3OlUTiNWyVRaIdT2tkAhEz6ibPBt5qfq
CYpZ9uhnk8ltVV3XonsKPs4olOw5Ef2Cp7pK67fE6V2Y7YOskHk6eabaOTZ00VrO
A94fOVGRhaiJvDOS+kYWZ/8TVw/vHNSjQVXm9vskuZEgP6r0arDIfHtu4KXm+VJJ
f6v8VLHdP7EU9ce2COc77iWMpUZrLBGRo0K1aZAVknzIKrt5aiRcG5e/PzPtxh6h
eTMHlMak9XLnENDRsbJEMedxLb2VOmqiJOikOPy9U33nt403oi2h2eOZ6+wh+IMK
d8EJH7cxbeiq/Aelp3IvwOagCiFpOatYL29zhUC/fufR8/y82Xz1TWlJ/mwZbPqo
6R/LPrEBafAilBApzpRvcxs+zofe2FhnSRbk+Hozu5XfmECdivoavr2SZhtDLfrK
LaHTUPxVbK4BOSTqoXsUtnUSpiP5F1IYzu59cm4S85KBB95KJuAGAaykeuWRjGXX
7kQ4T6vWn9JAdj3QZqVBAoIBAQDt/q3VvuinB2xjJZae2B0XYBXKgGl1svLPjP3w
tfQmi+tefjZ+GY8V4L05GraBMi/qcaQmy4wipVdVu7isXF3GancMsCu549ZZSAJO
DOv+u6oq0kd4mkiQ1/LUUoTNwwjKpcH6fEsXJHXKdnhUGE15hm+YGh3YrDo6xmpC
HoXk9qefDy7xL4mTJAfdr/KGIc1BpXic3VF+S0ewHom1L+dhkdRpew0oeeVTZ10O
9NQP4SqI2jIiNTLDSZ37FFJXD3dIxJ1niX3hRlSAKAIRvhzcs9581ea30F2BenhT
EuSM89kXJPub/dVG/WWuC5VQBCHmvVtGUWv8u0lacc3Ge4PZAoIBAQDEZZX9l2NN
viPwN2joiJa4LLH1+HC7X6MaKXQZ+jPr2ptO5F3ZekJ9W2jJOoXQCso7wnuEGYB5
KnbS/NWF3V9NSAWFb4nukXgIvTNudrgXr4bBkXVa26YwfxcCRv9qWtWp3W76K9F6
/jRe4MYf7NGbP7SndViGO7u2AhwejsxgqET1AM8eHrdtpkvC/aSqpEAOUWbwSXxc
G5dgVzoH0RZV5YVldPbdS7DOUZoh1co92lTB5LfPGOxwsb364nH61+lkhxWAiMe0
Q3hG8WLDF3wTRkpTUKAyjuBEE7Ve+bdFaC9cyhRiwgxPjie4qtt100IEHgpF0mw7
mWBB6x+pDuh5AoIBAQCs/eMzrAoGZxH023ypR2OV+yS7xi1h/UobbVukXU3zut7C
F7HaZQ+pkmtYl78zF9zWZ/YusOPSxyY9Ti9FMfqD4B1a3q9Z9m93BC2QuDnONnDR
oXmMA3Fdv2plxPl9axf33Rar0S7vynPIT+bVEbk27W4uPEWXmlDVKiZQm0kuDc/3
gRzY+Xnht130WRFLSESfQ/zw4Lp8t5GLRhdI2WIxfMPOTEBbPIdh4Y818OY4CK5X
PWsVjF+yrc8kkzfqynYlMa1MdhdG6U1AvlQKu4rVLfU5/m0vDUj6daACmogAoLsa
5KnzUEV3zXbcVNUajXZq9xbifQqmcSg3kuNFM8C5AoIBAHRKirPsLlrcWb9lr/Lw
3f4USRQSlf39NUDKhvrS0me3u/rM8l1SLYi41aVBx/ZWTUVxdV3VE+OrJ0zrdSuc
10+Vc999GjlvXZofHhMsrPkpcCuyC8FPCmrw9hjdHWRGgPniKlJsG9AuMah0hBxn
R/4bjMcTjuV8/TtaqHfXqmEZgito3TtCiO6eZ4IAWr7IHz3bKY7ilIadt9bOD4iN
YCJgk8ptpbeHmBuy6gda5jQV0dY1rjks0uQv+wRRjZgwvPxPmIXReB7fTJsFV6uZ
fliTaHNI7HLDczwcR2sDhmfMty7EYanQqSV6UT7hvK1Z+F8jwoVxgbEQspSVutuJ
/lECggEAVdvU6sPQH2QNnN8mxYF5zqST8Fzsi9+O6iQe/aymDKZoHa8/9O/BOx39
JSasQCnOt1yhRZwo50WhSUquJ1R0KUiybDyv1jvff7R+i3pl98Czjfc3iZuEDHGI
anD3qC9DrbsqotIsnjpxULJ3Hotohhy5NQtoQLsucNzZRWquQGE0sUGes6IIeEJR
1NWA6VnGdVrlltm3fkkJMwdn1pbEWXXI3VutEIn9NJfvuVDzPb1f5ih1ChLm5ijf
nK13sEavqpo7L8cpeaPeNLY2Tt4mVXw6Ujq1fLM/7VOvmNTQMu3lVXQve32w+gm0
0N/URKPaZ8Z9V/c15kNhIZBgJhOoVg==
-----END PRIVATE KEY-----

377
database/perl/vendor/lib/Mojo/JSON.pm vendored Normal file
View File

@@ -0,0 +1,377 @@
package Mojo::JSON;
use Mojo::Base -strict;
use Carp qw(croak);
use Exporter qw(import);
use JSON::PP ();
use Mojo::Util qw(decode encode monkey_patch);
use Scalar::Util qw(blessed);
# For better performance Cpanel::JSON::XS is required
use constant JSON_XS => $ENV{MOJO_NO_JSON_XS}
? 0
: eval { require Cpanel::JSON::XS; Cpanel::JSON::XS->VERSION('4.09'); 1 };
our @EXPORT_OK = qw(decode_json encode_json false from_json j to_json true);
# Escaped special character map
my %ESCAPE
= ('"' => '"', '\\' => '\\', '/' => '/', 'b' => "\x08", 'f' => "\x0c", 'n' => "\x0a", 'r' => "\x0d", 't' => "\x09");
my %REVERSE = map { $ESCAPE{$_} => "\\$_" } keys %ESCAPE;
for (0x00 .. 0x1f) { $REVERSE{pack 'C', $_} //= sprintf '\u%.4X', $_ }
# Replace pure-Perl fallbacks if Cpanel::JSON::XS is available
if (JSON_XS) {
my $BINARY = Cpanel::JSON::XS->new->utf8;
my $TEXT = Cpanel::JSON::XS->new;
$_->canonical->allow_nonref->allow_unknown->allow_blessed->convert_blessed->stringify_infnan->escape_slash
->allow_dupkeys
for $BINARY, $TEXT;
monkey_patch __PACKAGE__, 'encode_json', sub { $BINARY->encode($_[0]) };
monkey_patch __PACKAGE__, 'decode_json', sub { $BINARY->decode($_[0]) };
monkey_patch __PACKAGE__, 'to_json', sub { $TEXT->encode($_[0]) };
monkey_patch __PACKAGE__, 'from_json', sub { $TEXT->decode($_[0]) };
}
sub decode_json {
my $err = _decode(\my $value, shift);
return defined $err ? croak $err : $value;
}
sub encode_json { encode('UTF-8', _encode_value(shift)) }
sub false () {JSON::PP::false}
sub from_json {
my $err = _decode(\my $value, shift, 1);
return defined $err ? croak $err : $value;
}
sub j {
return encode_json($_[0]) if ref $_[0] eq 'ARRAY' || ref $_[0] eq 'HASH';
return eval { decode_json($_[0]) };
}
sub to_json { _encode_value(shift) }
sub true () {JSON::PP::true}
sub _decode {
my $valueref = shift;
eval {
# Missing input
die "Missing or empty input at offset 0\n" unless length(local $_ = shift);
# UTF-8
$_ = decode('UTF-8', $_) unless shift;
die "Input is not UTF-8 encoded\n" unless defined;
# Value
$$valueref = _decode_value();
# Leftover data
/\G[\x20\x09\x0a\x0d]*\z/gc or _throw('Unexpected data');
} ? return undef : chomp $@;
return $@;
}
sub _decode_array {
my @array;
until (m/\G[\x20\x09\x0a\x0d]*\]/gc) {
# Value
push @array, _decode_value();
# Separator
redo if /\G[\x20\x09\x0a\x0d]*,/gc;
# End
last if /\G[\x20\x09\x0a\x0d]*\]/gc;
# Invalid character
_throw('Expected comma or right square bracket while parsing array');
}
return \@array;
}
sub _decode_object {
my %hash;
until (m/\G[\x20\x09\x0a\x0d]*\}/gc) {
# Quote
/\G[\x20\x09\x0a\x0d]*"/gc or _throw('Expected string while parsing object');
# Key
my $key = _decode_string();
# Colon
/\G[\x20\x09\x0a\x0d]*:/gc or _throw('Expected colon while parsing object');
# Value
$hash{$key} = _decode_value();
# Separator
redo if /\G[\x20\x09\x0a\x0d]*,/gc;
# End
last if /\G[\x20\x09\x0a\x0d]*\}/gc;
# Invalid character
_throw('Expected comma or right curly bracket while parsing object');
}
return \%hash;
}
sub _decode_string {
my $pos = pos;
# Extract string with escaped characters
m!\G((?:(?:[^\x00-\x1f\\"]|\\(?:["\\/bfnrt]|u[0-9a-fA-F]{4})){0,32766})*)!gc;
my $str = $1;
# Invalid character
unless (m/\G"/gc) {
_throw('Unexpected character or invalid escape while parsing string') if /\G[\x00-\x1f\\]/;
_throw('Unterminated string');
}
# Unescape popular characters
if (index($str, '\\u') < 0) {
$str =~ s!\\(["\\/bfnrt])!$ESCAPE{$1}!gs;
return $str;
}
# Unescape everything else
my $buffer = '';
while ($str =~ /\G([^\\]*)\\(?:([^u])|u(.{4}))/gc) {
$buffer .= $1;
# Popular character
if ($2) { $buffer .= $ESCAPE{$2} }
# Escaped
else {
my $ord = hex $3;
# Surrogate pair
if (($ord & 0xf800) == 0xd800) {
# High surrogate
($ord & 0xfc00) == 0xd800 or pos = $pos + pos($str), _throw('Missing high-surrogate');
# Low surrogate
$str =~ /\G\\u([Dd][C-Fc-f]..)/gc or pos = $pos + pos($str), _throw('Missing low-surrogate');
$ord = 0x10000 + ($ord - 0xd800) * 0x400 + (hex($1) - 0xdc00);
}
# Character
$buffer .= pack 'U', $ord;
}
}
# The rest
return $buffer . substr $str, pos($str), length($str);
}
sub _decode_value {
# Leading whitespace
/\G[\x20\x09\x0a\x0d]*/gc;
# String
return _decode_string() if /\G"/gc;
# Object
return _decode_object() if /\G\{/gc;
# Array
return _decode_array() if /\G\[/gc;
# Number
return 0 + $1 if /\G([-]?(?:0|[1-9][0-9]*)(?:\.[0-9]*)?(?:[eE][+-]?[0-9]+)?)/gc;
# True
return true() if /\Gtrue/gc;
# False
return false() if /\Gfalse/gc;
# Null
return undef if /\Gnull/gc;
# Invalid character
_throw('Expected string, array, object, number, boolean or null');
}
sub _encode_array {
'[' . join(',', map { _encode_value($_) } @{$_[0]}) . ']';
}
sub _encode_object {
my $object = shift;
my @pairs = map { _encode_string($_) . ':' . _encode_value($object->{$_}) } sort keys %$object;
return '{' . join(',', @pairs) . '}';
}
sub _encode_string {
my $str = shift;
$str =~ s!([\x00-\x1f\\"/])!$REVERSE{$1}!gs;
return "\"$str\"";
}
sub _encode_value {
my $value = shift;
# Reference
if (my $ref = ref $value) {
# Object
return _encode_object($value) if $ref eq 'HASH';
# Array
return _encode_array($value) if $ref eq 'ARRAY';
# True or false
return $$value ? 'true' : 'false' if $ref eq 'SCALAR';
return $value ? 'true' : 'false' if $ref eq 'JSON::PP::Boolean';
# Everything else
return 'null' unless blessed $value;
return _encode_string($value) unless my $sub = $value->can('TO_JSON');
return _encode_value($value->$sub);
}
# Null
return 'null' unless defined $value;
# Number
no warnings 'numeric';
return $value
if !utf8::is_utf8($value) && length((my $dummy = '') & $value) && 0 + $value eq $value && $value * 0 == 0;
# String
return _encode_string($value);
}
sub _throw {
# Leading whitespace
/\G[\x20\x09\x0a\x0d]*/gc;
# Context
my $context = 'Malformed JSON: ' . shift;
if (m/\G\z/gc) { $context .= ' before end of data' }
else {
my @lines = split /\n/, substr($_, 0, pos);
$context .= ' at line ' . @lines . ', offset ' . length(pop @lines || '');
}
die "$context\n";
}
1;
=encoding utf8
=head1 NAME
Mojo::JSON - Minimalistic JSON
=head1 SYNOPSIS
use Mojo::JSON qw(decode_json encode_json);
my $bytes = encode_json {foo => [1, 2], bar => 'hello!', baz => \1};
my $hash = decode_json $bytes;
=head1 DESCRIPTION
L<Mojo::JSON> is a minimalistic and possibly the fastest pure-Perl implementation of L<RFC
8259|https://tools.ietf.org/html/rfc8259>.
It supports normal Perl data types like scalar, array reference, hash reference and will try to call the C<TO_JSON>
method on blessed references, or stringify them if it doesn't exist. Differentiating between strings and numbers in
Perl is hard, depending on how it has been used, a scalar can be both at the same time. The string value has a higher
precedence unless both representations are equivalent.
[1, -2, 3] -> [1, -2, 3]
{"foo": "bar"} -> {foo => 'bar'}
Literal names will be translated to and from L<Mojo::JSON> constants or a similar native Perl value.
true -> Mojo::JSON->true
false -> Mojo::JSON->false
null -> undef
In addition scalar references will be used to generate booleans, based on if their values are true or false.
\1 -> true
\0 -> false
The character C</> will always be escaped to prevent XSS attacks.
"</script>" -> "<\/script>"
For better performance the optional module L<Cpanel::JSON::XS> (4.09+) will be used automatically if possible. This can
also be disabled with the C<MOJO_NO_JSON_XS> environment variable.
=head1 FUNCTIONS
L<Mojo::JSON> implements the following functions, which can be imported individually.
=head2 decode_json
my $value = decode_json $bytes;
Decode JSON to Perl value and die if decoding fails.
=head2 encode_json
my $bytes = encode_json {i => '♥ mojolicious'};
Encode Perl value to JSON.
=head2 false
my $false = false;
False value, used because Perl has no native equivalent.
=head2 from_json
my $value = from_json $chars;
Decode JSON text that is not C<UTF-8> encoded to Perl value and die if decoding fails.
=head2 j
my $bytes = j [1, 2, 3];
my $bytes = j {i => '♥ mojolicious'};
my $value = j $bytes;
Encode Perl data structure (which may only be an array reference or hash reference) or decode JSON, an C<undef> return
value indicates a bare C<null> or that decoding failed.
=head2 to_json
my $chars = to_json {i => '♥ mojolicious'};
Encode Perl value to JSON text without C<UTF-8> encoding it.
=head2 true
my $true = true;
True value, used because Perl has no native equivalent.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,117 @@
package Mojo::JSON::Pointer;
use Mojo::Base -base;
has 'data';
sub contains { shift->_pointer(0, @_) }
sub get { shift->_pointer(1, @_) }
sub new { @_ > 1 ? shift->SUPER::new(data => shift) : shift->SUPER::new }
sub _pointer {
my ($self, $get, $pointer) = @_;
my $data = $self->data;
return length $pointer ? undef : $get ? $data : 1 unless $pointer =~ s!^/!!;
for my $p (length $pointer ? (split /\//, $pointer, -1) : ($pointer)) {
$p =~ s!~1!/!g;
$p =~ s/~0/~/g;
# Hash
if (ref $data eq 'HASH' && exists $data->{$p}) { $data = $data->{$p} }
# Array
elsif (ref $data eq 'ARRAY' && $p =~ /^\d+$/ && @$data > $p) { $data = $data->[$p] }
# Nothing
else { return undef }
}
return $get ? $data : 1;
}
1;
=encoding utf8
=head1 NAME
Mojo::JSON::Pointer - JSON Pointers
=head1 SYNOPSIS
use Mojo::JSON::Pointer;
my $pointer = Mojo::JSON::Pointer->new({foo => [23, 'bar']});
say $pointer->get('/foo/1');
say 'Contains "/foo".' if $pointer->contains('/foo');
=head1 DESCRIPTION
L<Mojo::JSON::Pointer> is an implementation of L<RFC 6901|https://tools.ietf.org/html/rfc6901>.
=head1 ATTRIBUTES
L<Mojo::JSON::Pointer> implements the following attributes.
=head2 data
my $data = $pointer->data;
$pointer = $pointer->data({foo => 'bar'});
Data structure to be processed.
=head1 METHODS
L<Mojo::JSON::Pointer> inherits all methods from L<Mojo::Base> and implements the following new ones.
=head2 contains
my $bool = $pointer->contains('/foo/1');
Check if L</"data"> contains a value that can be identified with the given JSON Pointer.
# True
Mojo::JSON::Pointer->new('just a string')->contains('');
Mojo::JSON::Pointer->new({'♥' => 'mojolicious'})->contains('/♥');
Mojo::JSON::Pointer->new({foo => 'bar', baz => [4, 5]})->contains('/foo');
Mojo::JSON::Pointer->new({foo => 'bar', baz => [4, 5]})->contains('/baz/1');
# False
Mojo::JSON::Pointer->new({'♥' => 'mojolicious'})->contains('/☃');
Mojo::JSON::Pointer->new({foo => 'bar', baz => [4, 5]})->contains('/bar');
Mojo::JSON::Pointer->new({foo => 'bar', baz => [4, 5]})->contains('/baz/9');
=head2 get
my $value = $pointer->get('/foo/bar');
Extract value from L</"data"> identified by the given JSON Pointer.
# "just a string"
Mojo::JSON::Pointer->new('just a string')->get('');
# "mojolicious"
Mojo::JSON::Pointer->new({'♥' => 'mojolicious'})->get('/♥');
# "bar"
Mojo::JSON::Pointer->new({foo => 'bar', baz => [4, 5, 6]})->get('/foo');
# "4"
Mojo::JSON::Pointer->new({foo => 'bar', baz => [4, 5, 6]})->get('/baz/0');
# "6"
Mojo::JSON::Pointer->new({foo => 'bar', baz => [4, 5, 6]})->get('/baz/2');
=head2 new
my $pointer = Mojo::JSON::Pointer->new;
my $pointer = Mojo::JSON::Pointer->new({foo => 'bar'});
Build new L<Mojo::JSON::Pointer> object.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

213
database/perl/vendor/lib/Mojo/Loader.pm vendored Normal file
View File

@@ -0,0 +1,213 @@
package Mojo::Loader;
use Mojo::Base -strict;
use Exporter qw(import);
use Mojo::Exception;
use Mojo::File qw(path);
use Mojo::Util qw(b64_decode class_to_path);
our @EXPORT_OK = qw(data_section file_is_binary find_modules find_packages load_class load_classes);
my (%BIN, %CACHE);
sub data_section { $_[0] ? $_[1] ? _all($_[0])->{$_[1]} : _all($_[0]) : undef }
sub file_is_binary { keys %{_all($_[0])} ? !!$BIN{$_[0]}{$_[1]} : undef }
sub find_modules {
my ($ns, $options) = (shift, shift // {});
my @ns = split /::/, $ns;
my @inc = grep { -d $$_ } map { path($_, @ns) } @INC;
my %modules;
for my $dir (@inc) {
for my $file ($options->{recursive} ? $dir->list_tree->each : $dir->list->each) {
next unless $$file =~ s/\.pm$//;
$modules{join('::', $ns, @{$file->to_rel($$dir)})}++;
}
}
return sort keys %modules;
}
sub find_packages {
my $ns = shift;
no strict 'refs';
return sort map { /^(.+)::$/ ? "${ns}::$1" : () } keys %{"${ns}::"};
}
sub load_class {
my $class = shift;
# Invalid class name
return 1 if ($class || '') !~ /^\w(?:[\w:']*\w)?$/;
# Load if not already loaded
return undef if $class->can('new') || eval "require $class; 1";
# Does not exist
return 1 if $@ =~ /^Can't locate \Q@{[class_to_path $class]}\E in \@INC/;
# Real error
return Mojo::Exception->new($@)->inspect;
}
sub load_classes {
my $ns = shift;
my @classes;
for my $module (find_modules($ns, {recursive => 1})) {
push @classes, $module unless my $e = load_class($module);
die $e if ref $e;
}
return @classes;
}
sub _all {
my $class = shift;
return $CACHE{$class} if $CACHE{$class};
local $.;
my $handle = do { no strict 'refs'; \*{"${class}::DATA"} };
return {} unless fileno $handle;
seek $handle, 0, 0;
my $data = join '', <$handle>;
# Ignore everything before __DATA__ (some versions seek to start of file)
$data =~ s/^.*\n__DATA__\r?\n/\n/s;
# Ignore everything after __END__
$data =~ s/\n__END__\r?\n.*$/\n/s;
# Split files
(undef, my @files) = split /^@@\s*(.+?)\s*\r?\n/m, $data;
# Find data
my $all = $CACHE{$class} = {};
while (@files) {
my ($name, $data) = splice @files, 0, 2;
$all->{$name} = $name =~ s/\s*\(\s*base64\s*\)$// && ++$BIN{$class}{$name} ? b64_decode $data : $data;
}
return $all;
}
1;
=encoding utf8
=head1 NAME
Mojo::Loader - Load all kinds of things
=head1 SYNOPSIS
use Mojo::Loader qw(data_section find_modules load_class);
# Find modules in a namespace
for my $module (find_modules 'Some::Namespace') {
# Load them safely
my $e = load_class $module;
warn qq{Loading "$module" failed: $e} and next if ref $e;
# And extract files from the DATA section
say data_section($module, 'some_file.txt');
}
=head1 DESCRIPTION
L<Mojo::Loader> is a class loader and plugin framework. Aside from finding modules and loading classes, it allows
multiple files to be stored in the C<DATA> section of a class, which can then be accessed individually.
package Foo;
1;
__DATA__
@@ test.txt
This is the first file.
@@ test2.html (base64)
VGhpcyBpcyB0aGUgc2Vjb25kIGZpbGUu
@@ test
This is the
third file.
Each file has a header starting with C<@@>, followed by the file name and optional instructions for decoding its
content. Currently only the Base64 encoding is supported, which can be quite convenient for the storage of binary data.
=head1 FUNCTIONS
L<Mojo::Loader> implements the following functions, which can be imported individually.
=head2 data_section
my $all = data_section 'Foo::Bar';
my $index = data_section 'Foo::Bar', 'index.html';
Extract embedded file from the C<DATA> section of a class, all files will be cached once they have been accessed for
the first time.
# List embedded files
say for keys %{data_section 'Foo::Bar'};
=head2 file_is_binary
my $bool = file_is_binary 'Foo::Bar', 'test.png';
Check if embedded file from the C<DATA> section of a class was Base64 encoded.
=head2 find_packages
my @pkgs = find_packages 'MyApp::Namespace';
Search for packages in a namespace non-recursively.
=head2 find_modules
my @modules = find_modules 'MyApp::Namespace';
my @modules = find_modules 'MyApp::Namespace', {recursive => 1};
Search for modules in a namespace.
These options are currently available:
=over 2
=item recursive
recursive => 1
Search namespace recursively.
=back
=head2 load_class
my $e = load_class 'Foo::Bar';
Load a class and catch exceptions, returns a false value if loading was successful, a true value if the class was not
found, or a L<Mojo::Exception> object if loading failed. Note that classes are checked for a C<new> method to see if
they are already loaded, so trying to load the same class multiple times may yield different results.
# Handle exceptions
if (my $e = load_class 'Foo::Bar') {
die ref $e ? "Exception: $e" : 'Not found!';
}
=head2 load_classes
my @classes = load_classes 'Foo::Bar';
Load all classes in a namespace recursively. Note that this function is B<EXPERIMENTAL> and might change without
warning!
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

276
database/perl/vendor/lib/Mojo/Log.pm vendored Normal file
View File

@@ -0,0 +1,276 @@
package Mojo::Log;
use Mojo::Base 'Mojo::EventEmitter';
use Carp qw(croak);
use Fcntl qw(:flock);
use Mojo::File;
use Mojo::Util qw(encode);
use Time::HiRes qw(time);
has format => sub { shift->short ? \&_short : \&_default };
has handle => sub {
# STDERR
return \*STDERR unless my $path = shift->path;
# File
return Mojo::File->new($path)->open('>>');
};
has history => sub { [] };
has level => 'debug';
has max_history_size => 10;
has 'path';
has short => sub { $ENV{MOJO_LOG_SHORT} };
# Supported log levels
my %LEVEL = (debug => 1, info => 2, warn => 3, error => 4, fatal => 5);
# Systemd magic numbers
my %MAGIC = (debug => 7, info => 6, warn => 4, error => 3, fatal => 2);
sub append {
my ($self, $msg) = @_;
return unless my $handle = $self->handle;
flock $handle, LOCK_EX;
$handle->print(encode('UTF-8', $msg)) or croak "Can't write to log: $!";
flock $handle, LOCK_UN;
}
sub debug { 1 >= $LEVEL{$_[0]->level} ? _log(@_, 'debug') : $_[0] }
sub context { $_[0]->new(parent => $_[0], context => $_[1], level => $_[0]->level) }
sub error { 4 >= $LEVEL{$_[0]->level} ? _log(@_, 'error') : $_[0] }
sub fatal { 5 >= $LEVEL{$_[0]->level} ? _log(@_, 'fatal') : $_[0] }
sub info { 2 >= $LEVEL{$_[0]->level} ? _log(@_, 'info') : $_[0] }
sub is_level { $LEVEL{pop()} >= $LEVEL{shift->level} }
sub new {
my $self = shift->SUPER::new(@_);
$self->on(message => \&_message);
return $self;
}
sub warn { 3 >= $LEVEL{$_[0]->level} ? _log(@_, 'warn') : $_[0] }
sub _default {
my ($time, $level) = (shift, shift);
my ($s, $m, $h, $day, $month, $year) = localtime $time;
$time = sprintf '%04d-%02d-%02d %02d:%02d:%08.5f', $year + 1900, $month + 1, $day, $h, $m,
"$s." . ((split /\./, $time)[1] // 0);
return "[$time] [$$] [$level] " . join "\n", @_, '';
}
sub _log {
my ($self, $level) = (shift, pop);
my @msgs = ref $_[0] eq 'CODE' ? $_[0]() : @_;
$msgs[0] = "$self->{context} $msgs[0]" if $self->{context};
($self->{parent} || $self)->emit('message', $level, @msgs);
}
sub _message {
my ($self, $level) = (shift, shift);
my $max = $self->max_history_size;
my $history = $self->history;
push @$history, my $msg = [time, $level, @_];
shift @$history while @$history > $max;
$self->append($self->format->(@$msg));
}
sub _short {
my ($time, $level) = (shift, shift);
my ($magic, $short) = ("<$MAGIC{$level}>", substr($level, 0, 1));
return "${magic}[$$] [$short] " . join("\n$magic", @_) . "\n";
}
1;
=encoding utf8
=head1 NAME
Mojo::Log - Simple logger
=head1 SYNOPSIS
use Mojo::Log;
# Log to STDERR
my $log = Mojo::Log->new;
# Customize log file location and minimum log level
my $log = Mojo::Log->new(path => '/var/log/mojo.log', level => 'warn');
# Log messages
$log->debug('Not sure what is happening here');
$log->info('FYI: it happened again');
$log->warn('This might be a problem');
$log->error('Garden variety error');
$log->fatal('Boom');
=head1 DESCRIPTION
L<Mojo::Log> is a simple logger for L<Mojo> projects.
=head1 EVENTS
L<Mojo::Log> inherits all events from L<Mojo::EventEmitter> and can emit the following new ones.
=head2 message
$log->on(message => sub ($log, $level, @lines) {...});
Emitted when a new message gets logged.
$log->on(message => sub ($log, $level, @lines) { say "$level: ", @lines });
=head1 ATTRIBUTES
L<Mojo::Log> implements the following attributes.
=head2 format
my $cb = $log->format;
$log = $log->format(sub {...});
A callback for formatting log messages.
$log->format(sub ($time, $level, @lines) { "[2018-11-08 14:20:13.77168] [28320] [info] I ♥ Mojolicious\n" });
=head2 handle
my $handle = $log->handle;
$log = $log->handle(IO::Handle->new);
Log filehandle used by default L</"message"> event, defaults to opening L</"path"> or C<STDERR>.
=head2 history
my $history = $log->history;
$log = $log->history([[time, 'debug', 'That went wrong']]);
The last few logged messages.
=head2 level
my $level = $log->level;
$log = $log->level('debug');
Active log level, defaults to C<debug>. Available log levels are C<debug>, C<info>, C<warn>, C<error> and C<fatal>, in
that order.
=head2 max_history_size
my $size = $log->max_history_size;
$log = $log->max_history_size(5);
Maximum number of logged messages to store in L</"history">, defaults to C<10>.
=head2 path
my $path = $log->path
$log = $log->path('/var/log/mojo.log');
Log file path used by L</"handle">.
=head2 short
my $bool = $log->short;
$log = $log->short($bool);
Generate short log messages without a timestamp, suitable for systemd, defaults to the value of the C<MOJO_LOG_SHORT>
environment variables.
=head1 METHODS
L<Mojo::Log> inherits all methods from L<Mojo::EventEmitter> and implements the following new ones.
=head2 append
$log->append("[2018-11-08 14:20:13.77168] [28320] [info] I ♥ Mojolicious\n");
Append message to L</"handle">.
=head2 context
my $new = $log->context('[extra] [information]');
Construct a new child L<Mojo::Log> object that will include context information with every log message.
# Log with context
my $log = Mojo::Log->new;
my $context = $log->context('[17a60115]');
$context->debug('This is a log message with context information');
$context->info('And another');
=head2 debug
$log = $log->debug('You screwed up, but that is ok');
$log = $log->debug('All', 'cool');
$log = $log->debug(sub {...});
Emit L</"message"> event and log C<debug> message.
=head2 error
$log = $log->error('You really screwed up this time');
$log = $log->error('Wow', 'seriously');
$log = $log->error(sub {...});
Emit L</"message"> event and log C<error> message.
=head2 fatal
$log = $log->fatal('Its over...');
$log = $log->fatal('Bye', 'bye');
$log = $log->fatal(sub {...});
Emit L</"message"> event and log C<fatal> message.
=head2 info
$log = $log->info('You are bad, but you prolly know already');
$log = $log->info('Ok', 'then');
$log = $log->info(sub {...});
Emit L</"message"> event and log C<info> message.
=head2 is_level
my $bool = $log->is_level('debug');
Check active log L</"level">.
# True
$log->level('debug')->is_level('debug');
$log->level('debug')->is_level('info');
# False
$log->level('info')->is_level('debug');
$log->level('fatal')->is_level('warn');
=head2 new
my $log = Mojo::Log->new;
my $log = Mojo::Log->new(level => 'warn');
my $log = Mojo::Log->new({level => 'warn'});
Construct a new L<Mojo::Log> object and subscribe to L</"message"> event with default logger.
=head2 warn
$log = $log->warn('Dont do that Dave...');
$log = $log->warn('No', 'really');
$log = $log->warn(sub {...});
Emit L</"message"> event and log C<warn> message.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

628
database/perl/vendor/lib/Mojo/Message.pm vendored Normal file
View File

@@ -0,0 +1,628 @@
package Mojo::Message;
use Mojo::Base 'Mojo::EventEmitter';
use Carp qw(croak);
use Mojo::Asset::Memory;
use Mojo::Content::Single;
use Mojo::DOM;
use Mojo::JSON qw(j);
use Mojo::JSON::Pointer;
use Mojo::Parameters;
use Mojo::Upload;
use Mojo::Util qw(decode);
has content => sub { Mojo::Content::Single->new };
has default_charset => 'UTF-8';
has max_line_size => sub { $ENV{MOJO_MAX_LINE_SIZE} || 8192 };
has max_message_size => sub { $ENV{MOJO_MAX_MESSAGE_SIZE} // 16777216 };
has version => '1.1';
sub body {
my $self = shift;
# Get
my $content = $self->content;
return $content->is_multipart ? '' : $content->asset->slurp unless @_;
# Set (multipart content needs to be downgraded)
$content = $self->content(Mojo::Content::Single->new)->content if $content->is_multipart;
$content->asset(Mojo::Asset::Memory->new->add_chunk(@_));
return $self;
}
sub body_params {
my $self = shift;
return $self->{body_params} if $self->{body_params};
my $params = $self->{body_params} = Mojo::Parameters->new;
$params->charset($self->content->charset || $self->default_charset);
# "application/x-www-form-urlencoded"
my $type = $self->headers->content_type // '';
if ($type =~ m!application/x-www-form-urlencoded!i) {
$params->parse($self->content->asset->slurp);
}
# "multipart/form-data"
elsif ($type =~ m!multipart/form-data!i) {
$params->append(@$_[0, 1]) for @{$self->_parse_formdata};
}
return $params;
}
sub body_size { shift->content->body_size }
sub build_body { shift->_build('get_body_chunk') }
sub build_headers { shift->_build('get_header_chunk') }
sub build_start_line { shift->_build('get_start_line_chunk') }
sub cookie { shift->_cache('cookies', 0, @_) }
sub cookies { croak 'Method "cookies" not implemented by subclass' }
sub dom {
my $self = shift;
return undef if $self->content->is_multipart;
my $dom = $self->{dom} ||= Mojo::DOM->new($self->text);
return @_ ? $dom->find(@_) : $dom;
}
sub error {
my $self = shift;
return $self->{error} unless @_;
$self->{error} = shift;
return $self->finish;
}
sub every_cookie { shift->_cache('cookies', 1, @_) }
sub every_upload { shift->_cache('uploads', 1, @_) }
sub extract_start_line { croak 'Method "extract_start_line" not implemented by subclass' }
sub finish {
my $self = shift;
$self->{state} = 'finished';
return $self->{finished}++ ? $self : $self->emit('finish');
}
sub fix_headers {
my $self = shift;
return $self if $self->{fix}++;
# Content-Length or Connection (unless chunked transfer encoding is used)
my $content = $self->content;
my $headers = $content->headers;
if ($content->is_multipart) { $headers->remove('Content-Length') }
elsif ($content->is_chunked || $headers->content_length) { return $self }
if ($content->is_dynamic) { $headers->connection('close') }
else { $headers->content_length($self->body_size) }
return $self;
}
sub get_body_chunk {
my ($self, $offset) = @_;
$self->emit('progress', 'body', $offset);
my $chunk = $self->content->get_body_chunk($offset);
return $chunk if !defined $chunk || length $chunk;
$self->finish;
return $chunk;
}
sub get_header_chunk {
my ($self, $offset) = @_;
$self->emit('progress', 'headers', $offset);
return $self->fix_headers->content->get_header_chunk($offset);
}
sub get_start_line_chunk { croak 'Method "get_start_line_chunk" not implemented by subclass' }
sub header_size { shift->fix_headers->content->header_size }
sub headers { shift->content->headers }
sub is_finished { (shift->{state} // '') eq 'finished' }
sub is_limit_exceeded { !!shift->{limit} }
sub json {
my ($self, $pointer) = @_;
return undef if $self->content->is_multipart;
my $data = $self->{json} //= j($self->body);
return $pointer ? Mojo::JSON::Pointer->new($data)->get($pointer) : $data;
}
sub parse {
my ($self, $chunk) = @_;
return $self if $self->{error};
$self->{raw_size} += length $chunk;
$self->{buffer} .= $chunk;
# Start-line
unless ($self->{state}) {
# Check start-line size
my $len = index $self->{buffer}, "\x0a";
$len = length $self->{buffer} if $len < 0;
return $self->_limit('Maximum start-line size exceeded') if $len > $self->max_line_size;
$self->{state} = 'content' if $self->extract_start_line(\$self->{buffer});
}
# Content
my $state = $self->{state} // '';
$self->content($self->content->parse(delete $self->{buffer})) if $state eq 'content' || $state eq 'finished';
# Check message size
my $max = $self->max_message_size;
return $self->_limit('Maximum message size exceeded') if $max && $max < $self->{raw_size};
# Check header size
return $self->_limit('Maximum header size exceeded') if $self->headers->is_limit_exceeded;
# Check buffer size
return $self->_limit('Maximum buffer size exceeded') if $self->content->is_limit_exceeded;
return $self->emit('progress')->content->is_finished ? $self->finish : $self;
}
sub save_to {
my ($self, $path) = @_;
my $content = $self->content;
croak 'Multipart content cannot be saved to files' if $content->is_multipart;
$content->asset->move_to($path);
return $self;
}
sub start_line_size { croak 'Method "start_line_size" not implemented by subclass' }
sub text {
my $self = shift;
my $body = $self->body;
my $charset = $self->content->charset || $self->default_charset;
return $charset ? decode($charset, $body) // $body : $body;
}
sub to_string {
my $self = shift;
return $self->build_start_line . $self->build_headers . $self->build_body;
}
sub upload { shift->_cache('uploads', 0, @_) }
sub uploads {
my $self = shift;
my @uploads;
for my $data (@{$self->_parse_formdata(1)}) {
my $upload = Mojo::Upload->new(
name => $data->[0],
filename => $data->[2],
asset => $data->[1]->asset,
headers => $data->[1]->headers
);
push @uploads, $upload;
}
return \@uploads;
}
sub _build {
my ($self, $method) = @_;
my ($buffer, $offset) = ('', 0);
while (1) {
# No chunk yet, try again
next unless defined(my $chunk = $self->$method($offset));
# End of part
last unless my $len = length $chunk;
$offset += $len;
$buffer .= $chunk;
}
return $buffer;
}
sub _cache {
my ($self, $method, $all, $name) = @_;
# Cache objects by name
unless ($self->{$method}) {
$self->{$method} = {};
push @{$self->{$method}{$_->name}}, $_ for @{$self->$method};
}
my $objects = $self->{$method}{$name} // [];
return $all ? $objects : $objects->[-1];
}
sub _limit { ++$_[0]{limit} and return $_[0]->error({message => $_[1]}) }
sub _parse_formdata {
my ($self, $upload) = @_;
my @formdata;
my $content = $self->content;
return \@formdata unless $content->is_multipart;
my $charset = $content->charset || $self->default_charset;
# Check all parts recursively
my @parts = ($content);
while (my $part = shift @parts) {
if ($part->is_multipart) {
unshift @parts, @{$part->parts};
next;
}
next unless my $disposition = $part->headers->content_disposition;
my ($filename) = $disposition =~ /[; ]filename="((?:\\"|[^"])*)"/;
next if $upload && !defined $filename || !$upload && defined $filename;
my ($name) = $disposition =~ /[; ]name="((?:\\"|[^;"])*)"/;
$part = $part->asset->slurp unless $upload;
if ($charset) {
$name = decode($charset, $name) // $name if $name;
$filename = decode($charset, $filename) // $filename if $filename;
$part = decode($charset, $part) // $part unless $upload;
}
push @formdata, [$name, $part, $filename];
}
return \@formdata;
}
1;
=encoding utf8
=head1 NAME
Mojo::Message - HTTP message base class
=head1 SYNOPSIS
package Mojo::Message::MyMessage;
use Mojo::Base 'Mojo::Message';
sub cookies {...}
sub extract_start_line {...}
sub get_start_line_chunk {...}
sub start_line_size {...}
=head1 DESCRIPTION
L<Mojo::Message> is an abstract base class for HTTP message containers, based on L<RFC
7230|https://tools.ietf.org/html/rfc7230>, L<RFC 7231|https://tools.ietf.org/html/rfc7231> and L<RFC
2388|https://tools.ietf.org/html/rfc2388>, like L<Mojo::Message::Request> and L<Mojo::Message::Response>.
=head1 EVENTS
L<Mojo::Message> inherits all events from L<Mojo::EventEmitter> and can emit the following new ones.
=head2 finish
$msg->on(finish => sub ($msg) {...});
Emitted after message building or parsing is finished.
my $before = time;
$msg->on(finish => sub ($msg) { $msg->headers->header('X-Parser-Time' => time - $before) });
=head2 progress
$msg->on(progress => sub ($msg) {...});
Emitted when message building or parsing makes progress.
# Building
$msg->on(progress => sub ($msg, $state, $offset) { say qq{Building "$state" at offset $offset} });
# Parsing
$msg->on(progress => sub ($msg) {
return unless my $len = $msg->headers->content_length;
my $size = $msg->content->progress;
say 'Progress: ', $size == $len ? 100 : int($size / ($len / 100)), '%';
});
=head1 ATTRIBUTES
L<Mojo::Message> implements the following attributes.
=head2 content
my $msg = $msg->content;
$msg = $msg->content(Mojo::Content::Single->new);
Message content, defaults to a L<Mojo::Content::Single> object.
=head2 default_charset
my $charset = $msg->default_charset;
$msg = $msg->default_charset('UTF-8');
Default charset used by L</"text"> and to extract data from C<application/x-www-form-urlencoded> or
C<multipart/form-data> message body, defaults to C<UTF-8>.
=head2 max_line_size
my $size = $msg->max_line_size;
$msg = $msg->max_line_size(1024);
Maximum start-line size in bytes, defaults to the value of the C<MOJO_MAX_LINE_SIZE> environment variable or C<8192>
(8KiB).
=head2 max_message_size
my $size = $msg->max_message_size;
$msg = $msg->max_message_size(1024);
Maximum message size in bytes, defaults to the value of the C<MOJO_MAX_MESSAGE_SIZE> environment variable or
C<16777216> (16MiB). Setting the value to C<0> will allow messages of indefinite size.
=head2 version
my $version = $msg->version;
$msg = $msg->version('1.1');
HTTP version of message, defaults to C<1.1>.
=head1 METHODS
L<Mojo::Message> inherits all methods from L<Mojo::EventEmitter> and implements the following new ones.
=head2 body
my $bytes = $msg->body;
$msg = $msg->body('Hello!');
Slurp or replace L</"content">.
=head2 body_params
my $params = $msg->body_params;
C<POST> parameters extracted from C<application/x-www-form-urlencoded> or C<multipart/form-data> message body, usually
a L<Mojo::Parameters> object. Note that this method caches all data, so it should not be called before the entire
message body has been received. Parts of the message body need to be loaded into memory to parse C<POST> parameters, so
you have to make sure it is not excessively large. There's a 16MiB limit for requests and a 2GiB limit for responses by
default.
# Get POST parameter names and values
my $hash = $msg->body_params->to_hash;
=head2 body_size
my $size = $msg->body_size;
Content size in bytes.
=head2 build_body
my $bytes = $msg->build_body;
Render whole body with L</"get_body_chunk">.
=head2 build_headers
my $bytes = $msg->build_headers;
Render all headers with L</"get_header_chunk">.
=head2 build_start_line
my $bytes = $msg->build_start_line;
Render start-line with L</"get_start_line_chunk">.
=head2 cookie
my $cookie = $msg->cookie('foo');
Access message cookies, usually L<Mojo::Cookie::Request> or L<Mojo::Cookie::Response> objects. If there are multiple
cookies sharing the same name, and you want to access more than just the last one, you can use L</"every_cookie">. Note
that this method caches all data, so it should not be called before all headers have been received.
# Get cookie value
say $msg->cookie('foo')->value;
=head2 cookies
my $cookies = $msg->cookies;
Access message cookies. Meant to be overloaded in a subclass.
=head2 dom
my $dom = $msg->dom;
my $collection = $msg->dom('a[href]');
Retrieve message body from L</"text"> and turn it into a L<Mojo::DOM> object, an optional selector can be used to call
the method L<Mojo::DOM/"find"> on it right away, which then returns a L<Mojo::Collection> object. Note that this method
caches all data, so it should not be called before the entire message body has been received. The whole message body
needs to be loaded into memory to parse it, so you have to make sure it is not excessively large. There's a 16MiB limit
for requests and a 2GiB limit for responses by default.
# Perform "find" right away
say $msg->dom('h1, h2, h3')->map('text')->join("\n");
# Use everything else Mojo::DOM has to offer
say $msg->dom->at('title')->text;
say $msg->dom->at('body')->children->map('tag')->uniq->join("\n");
=head2 error
my $err = $msg->error;
$msg = $msg->error({message => 'Parser error'});
Get or set message error, an C<undef> return value indicates that there is no error.
# Connection or parser error
$msg->error({message => 'Connection refused'});
# 4xx/5xx response
$msg->error({message => 'Internal Server Error', code => 500});
=head2 every_cookie
my $cookies = $msg->every_cookie('foo');
Similar to L</"cookie">, but returns all message cookies sharing the same name as an array reference.
# Get first cookie value
say $msg->every_cookie('foo')->[0]->value;
=head2 every_upload
my $uploads = $msg->every_upload('foo');
Similar to L</"upload">, but returns all file uploads sharing the same name as an array reference.
# Get content of first uploaded file
say $msg->every_upload('foo')->[0]->asset->slurp;
=head2 extract_start_line
my $bool = $msg->extract_start_line(\$str);
Extract start-line from string. Meant to be overloaded in a subclass.
=head2 finish
$msg = $msg->finish;
Finish message parser/generator.
=head2 fix_headers
$msg = $msg->fix_headers;
Make sure message has all required headers.
=head2 get_body_chunk
my $bytes = $msg->get_body_chunk($offset);
Get a chunk of body data starting from a specific position. Note that it might not be possible to get the same chunk
twice if content was generated dynamically.
=head2 get_header_chunk
my $bytes = $msg->get_header_chunk($offset);
Get a chunk of header data, starting from a specific position. Note that this method finalizes the message.
=head2 get_start_line_chunk
my $bytes = $msg->get_start_line_chunk($offset);
Get a chunk of start-line data starting from a specific position. Meant to be overloaded in a subclass.
=head2 header_size
my $size = $msg->header_size;
Size of headers in bytes. Note that this method finalizes the message.
=head2 headers
my $headers = $msg->headers;
Message headers, usually a L<Mojo::Headers> object.
# Longer version
my $headers = $msg->content->headers;
=head2 is_finished
my $bool = $msg->is_finished;
Check if message parser/generator is finished.
=head2 is_limit_exceeded
my $bool = $msg->is_limit_exceeded;
Check if message has exceeded L</"max_line_size">, L</"max_message_size">, L<Mojo::Content/"max_buffer_size"> or
L<Mojo::Headers/"max_line_size">.
=head2 json
my $value = $msg->json;
my $value = $msg->json('/foo/bar');
Decode JSON message body directly using L<Mojo::JSON> if possible, an C<undef> return value indicates a bare C<null> or
that decoding failed. An optional JSON Pointer can be used to extract a specific value with L<Mojo::JSON::Pointer>.
Note that this method caches all data, so it should not be called before the entire message body has been received. The
whole message body needs to be loaded into memory to parse it, so you have to make sure it is not excessively large.
There's a 16MiB limit for requests and a 2GiB limit for responses by default.
# Extract JSON values
say $msg->json->{foo}{bar}[23];
say $msg->json('/foo/bar/23');
=head2 parse
$msg = $msg->parse('HTTP/1.1 200 OK...');
Parse message chunk.
=head2 save_to
$msg = $msg->save_to('/some/path/index.html');
Save message body to a file.
=head2 start_line_size
my $size = $msg->start_line_size;
Size of the start-line in bytes. Meant to be overloaded in a subclass.
=head2 text
my $str = $msg->text;
Retrieve L</"body"> and try to decode it with L<Mojo::Content/"charset"> or L</"default_charset">.
=head2 to_string
my $str = $msg->to_string;
Render whole message. Note that this method finalizes the message, and that it might not be possible to render the same
message twice if content was generated dynamically.
=head2 upload
my $upload = $msg->upload('foo');
Access C<multipart/form-data> file uploads, usually L<Mojo::Upload> objects. If there are multiple uploads sharing the
same name, and you want to access more than just the last one, you can use L</"every_upload">. Note that this method
caches all data, so it should not be called before the entire message body has been received.
# Get content of uploaded file
say $msg->upload('foo')->asset->slurp;
=head2 uploads
my $uploads = $msg->uploads;
All C<multipart/form-data> file uploads, usually L<Mojo::Upload> objects.
# Names of all uploads
say $_->name for @{$msg->uploads};
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,446 @@
package Mojo::Message::Request;
use Mojo::Base 'Mojo::Message';
use Digest::SHA qw(sha1_base64);
use Mojo::Cookie::Request;
use Mojo::Util qw(b64_encode b64_decode sha1_sum);
use Mojo::URL;
has env => sub { {} };
has method => 'GET';
has [qw(proxy reverse_proxy)];
has request_id => sub {
state $seed = $$ . time . rand;
state $counter = int rand 0xffffff;
my $b64 = substr(sha1_base64($seed . ($counter = ($counter + 1) % 0xffffff)), 0, 8);
$b64 =~ tr!+/!-_!;
return $b64;
};
has url => sub { Mojo::URL->new };
has via_proxy => 1;
sub clone {
my $self = shift;
# Dynamic requests cannot be cloned
return undef unless my $content = $self->content->clone;
my $clone
= $self->new(content => $content, method => $self->method, url => $self->url->clone, version => $self->version);
$clone->{proxy} = $self->{proxy}->clone if $self->{proxy};
return $clone;
}
sub cookies {
my $self = shift;
# Parse cookies
my $headers = $self->headers;
return [map { @{Mojo::Cookie::Request->parse($_)} } $headers->cookie] unless @_;
# Add cookies
my @cookies = map { ref $_ eq 'HASH' ? Mojo::Cookie::Request->new($_) : $_ } $headers->cookie || (), @_;
$headers->cookie(join '; ', @cookies);
return $self;
}
sub every_param { shift->params->every_param(@_) }
sub extract_start_line {
my ($self, $bufref) = @_;
# Ignore any leading empty lines
return undef unless $$bufref =~ s/^\s*(.*?)\x0d?\x0a//;
# We have a (hopefully) full request-line
return !$self->error({message => 'Bad request start-line'}) unless $1 =~ /^(\S+)\s+(\S+)\s+HTTP\/(\d\.\d)$/;
my $url = $self->method($1)->version($3)->url;
my $target = $2;
return !!$url->host_port($target) if $1 eq 'CONNECT';
return !!$url->parse($target)->fragment(undef) if $target =~ /^[^:\/?#]+:/;
return !!$url->path_query($target);
}
sub fix_headers {
my $self = shift;
$self->{fix} ? return $self : $self->SUPER::fix_headers(@_);
# Host
my $url = $self->url;
my $headers = $self->headers;
$headers->host($url->host_port) unless $headers->host;
# Basic authentication
if ((my $info = $url->userinfo) && !$headers->authorization) {
$headers->authorization('Basic ' . b64_encode($info, ''));
}
# Basic proxy authentication
return $self unless (my $proxy = $self->proxy) && $self->via_proxy;
return $self unless my $info = $proxy->userinfo;
$headers->proxy_authorization('Basic ' . b64_encode($info, '')) unless $headers->proxy_authorization;
return $self;
}
sub get_start_line_chunk {
my ($self, $offset) = @_;
$self->_start_line->emit(progress => 'start_line', $offset);
return substr $self->{start_buffer}, $offset, 131072;
}
sub is_handshake { lc($_[0]->headers->upgrade // '') eq 'websocket' }
sub is_secure {
my $url = shift->url;
return ($url->protocol || $url->base->protocol) eq 'https';
}
sub is_xhr { (shift->headers->header('X-Requested-With') // '') =~ /XMLHttpRequest/i }
sub param { shift->params->param(@_) }
sub params { $_[0]->{params} ||= $_[0]->body_params->clone->append($_[0]->query_params) }
sub parse {
my ($self, $env, $chunk) = (shift, ref $_[0] ? (shift, '') : (undef, shift));
# Parse CGI environment
$self->env($env)->_parse_env($env) if $env;
# Parse normal message
if (($self->{state} // '') ne 'cgi') { $self->SUPER::parse($chunk) }
# Parse CGI content
else { $self->content($self->content->parse_body($chunk))->SUPER::parse('') }
# Check if we can fix things that require all headers
return $self unless $self->is_finished;
# Base URL
my $base = $self->url->base;
$base->scheme('http') unless $base->scheme;
my $headers = $self->headers;
if (!$base->host && (my $host = $headers->host)) { $base->host_port($host) }
# Basic authentication
if (my $basic = _basic($headers->authorization)) { $base->userinfo($basic) }
# Basic proxy authentication
my $basic = _basic($headers->proxy_authorization);
$self->proxy(Mojo::URL->new->userinfo($basic)) if $basic;
# "X-Forwarded-Proto"
$base->scheme('https') if $self->reverse_proxy && ($headers->header('X-Forwarded-Proto') // '') eq 'https';
return $self;
}
sub query_params { shift->url->query }
sub start_line_size { length shift->_start_line->{start_buffer} }
sub _basic { $_[0] && $_[0] =~ /Basic (.+)$/ ? b64_decode $1 : undef }
sub _parse_env {
my ($self, $env) = @_;
# Bypass normal message parser
$self->{state} = 'cgi';
# Extract headers
my $headers = $self->headers;
my $url = $self->url;
my $base = $url->base;
for my $name (keys %$env) {
my $value = $env->{$name};
next unless $name =~ s/^HTTP_//i;
$name =~ y/_/-/;
$headers->header($name => $value);
# Host/Port
$value =~ s/:(\d+)$// ? $base->host($value)->port($1) : $base->host($value) if $name eq 'HOST';
}
# Content-Type is a special case on some servers
$headers->content_type($env->{CONTENT_TYPE}) if $env->{CONTENT_TYPE};
# Content-Length is a special case on some servers
$headers->content_length($env->{CONTENT_LENGTH}) if $env->{CONTENT_LENGTH};
# Query
$url->query->parse($env->{QUERY_STRING}) if $env->{QUERY_STRING};
# Method
$self->method($env->{REQUEST_METHOD}) if $env->{REQUEST_METHOD};
# Scheme/Version
$base->scheme($1) and $self->version($2) if ($env->{SERVER_PROTOCOL} // '') =~ m!^([^/]+)/([^/]+)$!;
# HTTPS
$base->scheme('https') if uc($env->{HTTPS} // '') eq 'ON';
# Path
my $path = $url->path->parse($env->{PATH_INFO} ? $env->{PATH_INFO} : '');
# Base path
if (my $value = $env->{SCRIPT_NAME}) {
# Make sure there is a trailing slash (important for merging)
$base->path->parse($value =~ m!/$! ? $value : "$value/");
# Remove SCRIPT_NAME prefix if necessary
my $buffer = $path->to_string;
$value =~ s!^/|/$!!g;
$buffer =~ s!^/?\Q$value\E/?!!;
$buffer =~ s!^/!!;
$path->parse($buffer);
}
}
sub _start_line {
my $self = shift;
return $self if defined $self->{start_buffer};
# Path
my $url = $self->url;
my $path = $url->path_query;
$path = "/$path" unless $path =~ m!^/!;
# CONNECT
my $method = uc $self->method;
if ($method eq 'CONNECT') {
my $port = $url->port // ($url->protocol eq 'https' ? '443' : '80');
$path = $url->ihost . ":$port";
}
# Proxy
elsif ($self->proxy && $self->via_proxy && $url->protocol ne 'https') {
$path = $url->clone->userinfo(undef) unless $self->is_handshake;
}
$self->{start_buffer} = "$method $path HTTP/@{[$self->version]}\x0d\x0a";
return $self;
}
1;
=encoding utf8
=head1 NAME
Mojo::Message::Request - HTTP request
=head1 SYNOPSIS
use Mojo::Message::Request;
# Parse
my $req = Mojo::Message::Request->new;
$req->parse("GET /foo HTTP/1.0\x0d\x0a");
$req->parse("Content-Length: 12\x0d\x0a");
$req->parse("Content-Type: text/plain\x0d\x0a\x0d\x0a");
$req->parse('Hello World!');
say $req->method;
say $req->headers->content_type;
say $req->body;
# Build
my $req = Mojo::Message::Request->new;
$req->url->parse('http://127.0.0.1/foo/bar');
$req->method('GET');
say $req->to_string;
=head1 DESCRIPTION
L<Mojo::Message::Request> is a container for HTTP requests, based on L<RFC 7230|https://tools.ietf.org/html/rfc7230>,
L<RFC 7231|https://tools.ietf.org/html/rfc7231>, L<RFC 7235|https://tools.ietf.org/html/rfc7235> and L<RFC
2817|https://tools.ietf.org/html/rfc2817>.
=head1 EVENTS
L<Mojo::Message::Request> inherits all events from L<Mojo::Message>.
=head1 ATTRIBUTES
L<Mojo::Message::Request> inherits all attributes from L<Mojo::Message> and implements the following new ones.
=head2 env
my $env = $req->env;
$req = $req->env({PATH_INFO => '/'});
Direct access to the C<CGI> or C<PSGI> environment hash if available.
# Check CGI version
my $version = $req->env->{GATEWAY_INTERFACE};
# Check PSGI version
my $version = $req->env->{'psgi.version'};
=head2 method
my $method = $req->method;
$req = $req->method('POST');
HTTP request method, defaults to C<GET>.
=head2 proxy
my $url = $req->proxy;
$req = $req->proxy(Mojo::URL->new('http://127.0.0.1:3000'));
Proxy URL for request.
=head2 reverse_proxy
my $bool = $req->reverse_proxy;
$req = $req->reverse_proxy($bool);
Request has been performed through a reverse proxy.
=head2 request_id
my $id = $req->request_id;
$req = $req->request_id('aee7d5d8');
Request ID, defaults to a reasonably unique value.
=head2 url
my $url = $req->url;
$req = $req->url(Mojo::URL->new);
HTTP request URL, defaults to a L<Mojo::URL> object.
# Get request information
my $info = $req->url->to_abs->userinfo;
my $host = $req->url->to_abs->host;
my $path = $req->url->to_abs->path;
=head2 via_proxy
my $bool = $req->via_proxy;
$req = $req->via_proxy($bool);
Request can be performed through a proxy server.
=head1 METHODS
L<Mojo::Message::Request> inherits all methods from L<Mojo::Message> and implements the following new ones.
=head2 clone
my $clone = $req->clone;
Return a new L<Mojo::Message::Request> object cloned from this request if possible, otherwise return C<undef>.
=head2 cookies
my $cookies = $req->cookies;
$req = $req->cookies(Mojo::Cookie::Request->new);
$req = $req->cookies({name => 'foo', value => 'bar'});
Access request cookies, usually L<Mojo::Cookie::Request> objects.
# Names of all cookies
say $_->name for @{$req->cookies};
=head2 every_param
my $values = $req->every_param('foo');
Similar to L</"param">, but returns all values sharing the same name as an array reference.
# Get first value
say $req->every_param('foo')->[0];
=head2 extract_start_line
my $bool = $req->extract_start_line(\$str);
Extract request-line from string.
=head2 fix_headers
$req = $req->fix_headers;
Make sure request has all required headers.
=head2 get_start_line_chunk
my $bytes = $req->get_start_line_chunk($offset);
Get a chunk of request-line data starting from a specific position. Note that this method finalizes the request.
=head2 is_handshake
my $bool = $req->is_handshake;
Check C<Upgrade> header for C<websocket> value.
=head2 is_secure
my $bool = $req->is_secure;
Check if connection is secure.
=head2 is_xhr
my $bool = $req->is_xhr;
Check C<X-Requested-With> header for C<XMLHttpRequest> value.
=head2 param
my $value = $req->param('foo');
Access C<GET> and C<POST> parameters extracted from the query string and C<application/x-www-form-urlencoded> or
C<multipart/form-data> message body. If there are multiple values sharing the same name, and you want to access more
than just the last one, you can use L</"every_param">. Note that this method caches all data, so it should not be
called before the entire request body has been received. Parts of the request body need to be loaded into memory to
parse C<POST> parameters, so you have to make sure it is not excessively large. There's a 16MiB limit for requests by
default.
=head2 params
my $params = $req->params;
All C<GET> and C<POST> parameters extracted from the query string and C<application/x-www-form-urlencoded> or
C<multipart/form-data> message body, usually a L<Mojo::Parameters> object. Note that this method caches all data, so it
should not be called before the entire request body has been received. Parts of the request body need to be loaded into
memory to parse C<POST> parameters, so you have to make sure it is not excessively large. There's a 16MiB limit for
requests by default.
# Get parameter names and values
my $hash = $req->params->to_hash;
=head2 parse
$req = $req->parse('GET /foo/bar HTTP/1.1');
$req = $req->parse({PATH_INFO => '/'});
Parse HTTP request chunks or environment hash.
=head2 query_params
my $params = $req->query_params;
All C<GET> parameters, usually a L<Mojo::Parameters> object.
# Turn GET parameters to hash and extract value
say $req->query_params->to_hash->{foo};
=head2 start_line_size
my $size = $req->start_line_size;
Size of the request-line in bytes. Note that this method finalizes the request.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,315 @@
package Mojo::Message::Response;
use Mojo::Base 'Mojo::Message';
use Mojo::Cookie::Response;
use Mojo::Date;
has [qw(code message)];
has max_message_size => sub { $ENV{MOJO_MAX_MESSAGE_SIZE} // 2147483648 };
# Unmarked codes are from RFC 7231
my %MESSAGES = (
100 => 'Continue',
101 => 'Switching Protocols',
102 => 'Processing', # RFC 2518 (WebDAV)
103 => 'Early Hints', # RFC 8297
200 => 'OK',
201 => 'Created',
202 => 'Accepted',
203 => 'Non-Authoritative Information',
204 => 'No Content',
205 => 'Reset Content',
206 => 'Partial Content',
207 => 'Multi-Status', # RFC 2518 (WebDAV)
208 => 'Already Reported', # RFC 5842
226 => 'IM Used', # RFC 3229
300 => 'Multiple Choices',
301 => 'Moved Permanently',
302 => 'Found',
303 => 'See Other',
304 => 'Not Modified',
305 => 'Use Proxy',
307 => 'Temporary Redirect',
308 => 'Permanent Redirect', # RFC 7538
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 Long',
415 => 'Unsupported Media Type',
416 => 'Request Range Not Satisfiable',
417 => 'Expectation Failed',
418 => "I'm a teapot", # RFC 2324 :)
421 => 'Misdirected Request', # RFC 7540
422 => 'Unprocessable Entity', # RFC 2518 (WebDAV)
423 => 'Locked', # RFC 2518 (WebDAV)
424 => 'Failed Dependency', # RFC 2518 (WebDAV)
425 => 'Too Early', # RFC 8470
426 => 'Upgrade Required', # RFC 2817
428 => 'Precondition Required', # RFC 6585
429 => 'Too Many Requests', # RFC 6585
431 => 'Request Header Fields Too Large', # RFC 6585
451 => 'Unavailable For Legal Reasons', # RFC 7725
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Timeout',
505 => 'HTTP Version Not Supported',
506 => 'Variant Also Negotiates', # RFC 2295
507 => 'Insufficient Storage', # RFC 2518 (WebDAV)
508 => 'Loop Detected', # RFC 5842
509 => 'Bandwidth Limit Exceeded', # Unofficial
510 => 'Not Extended', # RFC 2774
511 => 'Network Authentication Required' # RFC 6585
);
sub cookies {
my $self = shift;
# Parse cookies
my $headers = $self->headers;
return [@{Mojo::Cookie::Response->parse($headers->set_cookie)}] unless @_;
# Add cookies
$headers->add('Set-Cookie' => "$_") for map { ref $_ eq 'HASH' ? Mojo::Cookie::Response->new($_) : $_ } @_;
return $self;
}
sub default_message { $MESSAGES{$_[1] || $_[0]->code // 404} || '' }
sub extract_start_line {
my ($self, $bufref) = @_;
# We have a full response line
return undef unless $$bufref =~ s/^(.*?)\x0d?\x0a//;
return !$self->error({message => 'Bad response start-line'}) unless $1 =~ m!^\s*HTTP/(\d\.\d)\s+(\d\d\d)\s*(.+)?$!;
my $content = $self->content;
$content->skip_body(1) if $self->code($2)->is_empty;
defined $content->$_ or $content->$_(1) for qw(auto_decompress auto_relax);
return !!$self->version($1)->message($3);
}
sub fix_headers {
my $self = shift;
$self->{fix} ? return $self : $self->SUPER::fix_headers(@_);
# Date
my $headers = $self->headers;
$headers->date(Mojo::Date->new->to_string) unless $headers->date;
# RFC 7230 3.3.2
$headers->remove('Content-Length') if $self->is_empty;
return $self;
}
sub get_start_line_chunk {
my ($self, $offset) = @_;
$self->_start_line->emit(progress => 'start_line', $offset);
return substr $self->{start_buffer}, $offset, 131072;
}
sub is_client_error { shift->_status_class(400) }
sub is_empty {
my $self = shift;
return undef unless my $code = $self->code;
return $self->is_info || $code == 204 || $code == 304;
}
sub is_error { shift->_status_class(400, 500) }
sub is_info { shift->_status_class(100) }
sub is_redirect { shift->_status_class(300) }
sub is_server_error { shift->_status_class(500) }
sub is_success { shift->_status_class(200) }
sub start_line_size { length shift->_start_line->{start_buffer} }
sub _start_line {
my $self = shift;
return $self if defined $self->{start_buffer};
my $code = $self->code || 404;
my $msg = $self->message || $self->default_message;
$self->{start_buffer} = "HTTP/@{[$self->version]} $code $msg\x0d\x0a";
return $self;
}
sub _status_class {
my ($self, @classes) = @_;
return undef unless my $code = $self->code;
return !!grep { $code >= $_ && $code < ($_ + 100) } @classes;
}
1;
=encoding utf8
=head1 NAME
Mojo::Message::Response - HTTP response
=head1 SYNOPSIS
use Mojo::Message::Response;
# Parse
my $res = Mojo::Message::Response->new;
$res->parse("HTTP/1.0 200 OK\x0d\x0a");
$res->parse("Content-Length: 12\x0d\x0a");
$res->parse("Content-Type: text/plain\x0d\x0a\x0d\x0a");
$res->parse('Hello World!');
say $res->code;
say $res->headers->content_type;
say $res->body;
# Build
my $res = Mojo::Message::Response->new;
$res->code(200);
$res->headers->content_type('text/plain');
$res->body('Hello World!');
say $res->to_string;
=head1 DESCRIPTION
L<Mojo::Message::Response> is a container for HTTP responses, based on L<RFC 7230|https://tools.ietf.org/html/rfc7230>
and L<RFC 7231|https://tools.ietf.org/html/rfc7231>.
=head1 EVENTS
L<Mojo::Message::Response> inherits all events from L<Mojo::Message>.
=head1 ATTRIBUTES
L<Mojo::Message::Response> inherits all attributes from L<Mojo::Message> and implements the following new ones.
=head2 code
my $code = $res->code;
$res = $res->code(200);
HTTP response status code.
=head2 max_message_size
my $size = $res->max_message_size;
$res = $res->max_message_size(1024);
Maximum message size in bytes, defaults to the value of the C<MOJO_MAX_MESSAGE_SIZE> environment variable or
C<2147483648> (2GiB). Setting the value to C<0> will allow messages of indefinite size.
=head2 message
my $msg = $res->message;
$res = $res->message('OK');
HTTP response status message.
=head1 METHODS
L<Mojo::Message::Response> inherits all methods from L<Mojo::Message> and implements the following new ones.
=head2 cookies
my $cookies = $res->cookies;
$res = $res->cookies(Mojo::Cookie::Response->new);
$res = $res->cookies({name => 'foo', value => 'bar'});
Access response cookies, usually L<Mojo::Cookie::Response> objects.
# Names of all cookies
say $_->name for @{$res->cookies};
=head2 default_message
my $msg = $res->default_message;
my $msg = $res->default_message(418);
Generate default response message for status code, defaults to using L</"code">.
=head2 extract_start_line
my $bool = $res->extract_start_line(\$str);
Extract status-line from string.
=head2 fix_headers
$res = $res->fix_headers;
Make sure response has all required headers.
=head2 get_start_line_chunk
my $bytes = $res->get_start_line_chunk($offset);
Get a chunk of status-line data starting from a specific position. Note that this method finalizes the response.
=head2 is_client_error
my $bool = $res->is_client_error;
Check if this response has a C<4xx> status L</"code">.
=head2 is_empty
my $bool = $res->is_empty;
Check if this response has a C<1xx>, C<204> or C<304> status L</"code">.
=head2 is_error
my $bool = $res->is_error;
Check if this response has a C<4xx> or C<5xx> status L</"code">.
=head2 is_info
my $bool = $res->is_info;
Check if this response has a C<1xx> status L</"code">.
=head2 is_redirect
my $bool = $res->is_redirect;
Check if this response has a C<3xx> status L</"code">.
=head2 is_server_error
my $bool = $res->is_server_error;
Check if this response has a C<5xx> status L</"code">.
=head2 is_success
my $bool = $res->is_success;
Check if this response has a C<2xx> status L</"code">.
=head2 start_line_size
my $size = $req->start_line_size;
Size of the status-line in bytes. Note that this method finalizes the response.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,376 @@
package Mojo::Parameters;
use Mojo::Base -base;
use overload '@{}' => sub { shift->pairs }, bool => sub {1}, '""' => sub { shift->to_string }, fallback => 1;
use Mojo::Util qw(decode encode url_escape url_unescape);
has charset => 'UTF-8';
sub append {
my $self = shift;
my $old = $self->pairs;
my @new = @_ == 1 ? @{shift->pairs} : @_;
while (my ($name, $value) = splice @new, 0, 2) {
# Multiple values
if (ref $value eq 'ARRAY') { push @$old, $name => $_ // '' for @$value }
# Single value
elsif (defined $value) { push @$old, $name => $value }
}
return $self;
}
sub clone {
my $self = shift;
my $clone = $self->new;
if (exists $self->{charset}) { $clone->{charset} = $self->{charset} }
if (defined $self->{string}) { $clone->{string} = $self->{string} }
else { $clone->{pairs} = [@{$self->pairs}] }
return $clone;
}
sub every_param {
my ($self, $name) = @_;
my @values;
my $pairs = $self->pairs;
for (my $i = 0; $i < @$pairs; $i += 2) {
push @values, $pairs->[$i + 1] if $pairs->[$i] eq $name;
}
return \@values;
}
sub merge {
my $self = shift;
my $merge = @_ == 1 ? shift->to_hash : {@_};
for my $name (sort keys %$merge) {
my $value = $merge->{$name};
defined $value ? $self->param($name => $value) : $self->remove($name);
}
return $self;
}
sub names { [sort keys %{shift->to_hash}] }
sub new { @_ > 1 ? shift->SUPER::new->parse(@_) : shift->SUPER::new }
sub pairs {
my $self = shift;
# Replace parameters
if (@_) {
$self->{pairs} = shift;
delete $self->{string};
return $self;
}
# Parse string
if (defined(my $str = delete $self->{string})) {
my $pairs = $self->{pairs} = [];
return $pairs unless length $str;
my $charset = $self->charset;
for my $pair (split /&/, $str) {
next unless $pair =~ /^([^=]+)(?:=(.*))?$/;
my ($name, $value) = ($1, $2 // '');
# Replace "+" with whitespace, unescape and decode
s/\+/ /g for $name, $value;
$name = url_unescape $name;
$name = decode($charset, $name) // $name if $charset;
$value = url_unescape $value;
$value = decode($charset, $value) // $value if $charset;
push @$pairs, $name, $value;
}
}
return $self->{pairs} //= [];
}
sub param {
my ($self, $name) = (shift, shift);
return $self->every_param($name)->[-1] unless @_;
$self->remove($name);
return $self->append($name => ref $_[0] eq 'ARRAY' ? $_[0] : [@_]);
}
sub parse {
my $self = shift;
# Pairs
return $self->append(@_) if @_ > 1;
# String
$self->{string} = shift;
return $self;
}
sub remove {
my ($self, $name) = @_;
my $pairs = $self->pairs;
my $i = 0;
$pairs->[$i] eq $name ? splice @$pairs, $i, 2 : ($i += 2) while $i < @$pairs;
return $self;
}
sub to_hash {
my $self = shift;
my %hash;
my $pairs = $self->pairs;
for (my $i = 0; $i < @$pairs; $i += 2) {
my ($name, $value) = @{$pairs}[$i, $i + 1];
# Array
if (exists $hash{$name}) {
$hash{$name} = [$hash{$name}] if ref $hash{$name} ne 'ARRAY';
push @{$hash{$name}}, $value;
}
# String
else { $hash{$name} = $value }
}
return \%hash;
}
sub to_string {
my $self = shift;
# String (RFC 3986)
my $charset = $self->charset;
if (defined(my $str = $self->{string})) {
$str = encode $charset, $str if $charset;
return url_escape $str, '^A-Za-z0-9\-._~%!$&\'()*+,;=:@/?';
}
# Build pairs (HTML Living Standard)
my $pairs = $self->pairs;
return '' unless @$pairs;
my @pairs;
for (my $i = 0; $i < @$pairs; $i += 2) {
my ($name, $value) = @{$pairs}[$i, $i + 1];
# Escape and replace whitespace with "+"
$name = encode $charset, $name if $charset;
$name = url_escape $name, '^*\-.0-9A-Z_a-z';
$value = encode $charset, $value if $charset;
$value = url_escape $value, '^*\-.0-9A-Z_a-z';
s/\%20/\+/g for $name, $value;
push @pairs, "$name=$value";
}
return join '&', @pairs;
}
1;
=encoding utf8
=head1 NAME
Mojo::Parameters - Parameters
=head1 SYNOPSIS
use Mojo::Parameters;
# Parse
my $params = Mojo::Parameters->new('foo=bar&baz=23');
say $params->param('baz');
# Build
my $params = Mojo::Parameters->new(foo => 'bar', baz => 23);
push @$params, i => '♥ mojolicious';
say "$params";
=head1 DESCRIPTION
L<Mojo::Parameters> is a container for form parameters used by L<Mojo::URL>, based on L<RFC
3986|https://tools.ietf.org/html/rfc3986> and the L<HTML Living Standard|https://html.spec.whatwg.org>.
=head1 ATTRIBUTES
L<Mojo::Parameters> implements the following attributes.
=head2 charset
my $charset = $params->charset;
$params = $params->charset('UTF-8');
Charset used for encoding and decoding parameters, defaults to C<UTF-8>.
# Disable encoding and decoding
$params->charset(undef);
=head1 METHODS
L<Mojo::Parameters> inherits all methods from L<Mojo::Base> and implements the following new ones.
=head2 append
$params = $params->append(foo => 'ba&r');
$params = $params->append(foo => ['ba&r', 'baz']);
$params = $params->append(foo => ['bar', 'baz'], bar => 23);
$params = $params->append(Mojo::Parameters->new);
Append parameters. Note that this method will normalize the parameters.
# "foo=bar&foo=baz"
Mojo::Parameters->new('foo=bar')->append(Mojo::Parameters->new('foo=baz'));
# "foo=bar&foo=baz"
Mojo::Parameters->new('foo=bar')->append(foo => 'baz');
# "foo=bar&foo=baz&foo=yada"
Mojo::Parameters->new('foo=bar')->append(foo => ['baz', 'yada']);
# "foo=bar&foo=baz&foo=yada&bar=23"
Mojo::Parameters->new('foo=bar')->append(foo => ['baz', 'yada'], bar => 23);
=head2 clone
my $params2 = $params->clone;
Return a new L<Mojo::Parameters> object cloned from these parameters.
=head2 every_param
my $values = $params->every_param('foo');
Similar to L</"param">, but returns all values sharing the same name as an array reference. Note that this method will
normalize the parameters.
# Get first value
say $params->every_param('foo')->[0];
=head2 merge
$params = $params->merge(foo => 'ba&r');
$params = $params->merge(foo => ['ba&r', 'baz']);
$params = $params->merge(foo => ['bar', 'baz'], bar => 23);
$params = $params->merge(Mojo::Parameters->new);
Merge parameters. Note that this method will normalize the parameters.
# "foo=baz"
Mojo::Parameters->new('foo=bar')->merge(Mojo::Parameters->new('foo=baz'));
# "yada=yada&foo=baz"
Mojo::Parameters->new('foo=bar&yada=yada')->merge(foo => 'baz');
# "yada=yada"
Mojo::Parameters->new('foo=bar&yada=yada')->merge(foo => undef);
=head2 names
my $names = $params->names;
Return an array reference with all parameter names.
# Names of all parameters
say for @{$params->names};
=head2 new
my $params = Mojo::Parameters->new;
my $params = Mojo::Parameters->new('foo=b%3Bar&baz=23');
my $params = Mojo::Parameters->new(foo => 'b&ar');
my $params = Mojo::Parameters->new(foo => ['ba&r', 'baz']);
my $params = Mojo::Parameters->new(foo => ['bar', 'baz'], bar => 23);
Construct a new L<Mojo::Parameters> object and L</"parse"> parameters if necessary.
=head2 pairs
my $array = $params->pairs;
$params = $params->pairs([foo => 'b&ar', baz => 23]);
Parsed parameter pairs. Note that this method will normalize the parameters.
# Remove all parameters
$params->pairs([]);
=head2 param
my $value = $params->param('foo');
$params = $params->param(foo => 'ba&r');
$params = $params->param(foo => qw(ba&r baz));
$params = $params->param(foo => ['ba;r', 'baz']);
Access parameter values. If there are multiple values sharing the same name, and you want to access more than just the
last one, you can use L</"every_param">. Note that this method will normalize the parameters.
=head2 parse
$params = $params->parse('foo=b%3Bar&baz=23');
Parse parameters.
=head2 remove
$params = $params->remove('foo');
Remove parameters. Note that this method will normalize the parameters.
# "bar=yada"
Mojo::Parameters->new('foo=bar&foo=baz&bar=yada')->remove('foo');
=head2 to_hash
my $hash = $params->to_hash;
Turn parameters into a hash reference. Note that this method will normalize the parameters.
# "baz"
Mojo::Parameters->new('foo=bar&foo=baz')->to_hash->{foo}[1];
=head2 to_string
my $str = $params->to_string;
Turn parameters into a string.
# "foo=bar&baz=23"
Mojo::Parameters->new->pairs([foo => 'bar', baz => 23])->to_string;
=head1 OPERATORS
L<Mojo::Parameters> overloads the following operators.
=head2 array
my @pairs = @$params;
Alias for L</"pairs">. Note that this will normalize the parameters.
say $params->[0];
say for @$params;
=head2 bool
my $bool = !!$params;
Always true.
=head2 stringify
my $str = "$params";
Alias for L</"to_string">.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

343
database/perl/vendor/lib/Mojo/Path.pm vendored Normal file
View File

@@ -0,0 +1,343 @@
package Mojo::Path;
use Mojo::Base -base;
use overload '@{}' => sub { shift->parts }, bool => sub {1}, '""' => sub { shift->to_string }, fallback => 1;
use Mojo::Util qw(decode encode url_escape url_unescape);
has charset => 'UTF-8';
sub canonicalize {
my $self = shift;
my $parts = $self->parts;
for (my $i = 0; $i <= $#$parts;) {
if (!length $parts->[$i] || $parts->[$i] eq '.' || $parts->[$i] eq '...') { splice @$parts, $i, 1 }
elsif ($i < 1 || $parts->[$i] ne '..' || $parts->[$i - 1] eq '..') { $i++ }
else { splice @$parts, --$i, 2 }
}
return @$parts ? $self : $self->trailing_slash(undef);
}
sub clone {
my $self = shift;
my $clone = $self->new;
if (exists $self->{charset}) { $clone->{charset} = $self->{charset} }
if (my $parts = $self->{parts}) {
$clone->{$_} = $self->{$_} for qw(leading_slash trailing_slash);
$clone->{parts} = [@$parts];
}
else { $clone->{path} = $self->{path} }
return $clone;
}
sub contains { $_[1] eq '/' || $_[0]->to_route =~ m!^\Q$_[1]\E(?:/|$)! }
sub leading_slash { shift->_parse(leading_slash => @_) }
sub merge {
my ($self, $path) = @_;
# Replace
return $self->parse($path) if $path =~ m!^/!;
# Merge
pop @{$self->parts} unless $self->trailing_slash;
$path = $self->new($path);
push @{$self->parts}, @{$path->parts};
return $self->trailing_slash($path->trailing_slash);
}
sub new { @_ > 1 ? shift->SUPER::new->parse(@_) : shift->SUPER::new }
sub parse {
my $self = shift;
$self->{path} = shift;
delete @$self{qw(leading_slash parts trailing_slash)};
return $self;
}
sub parts { shift->_parse(parts => @_) }
sub to_abs_string {
my $path = shift->to_string;
return $path =~ m!^/! ? $path : "/$path";
}
sub to_dir {
my $clone = shift->clone;
pop @{$clone->parts} unless $clone->trailing_slash;
return $clone->trailing_slash(!!@{$clone->parts});
}
sub to_route {
my $clone = shift->clone;
return '/' . join '/', @{$clone->parts}, $clone->trailing_slash ? '' : ();
}
sub to_string {
my $self = shift;
# Path
my $charset = $self->charset;
if (defined(my $path = $self->{path})) {
$path = encode $charset, $path if $charset;
return url_escape $path, '^A-Za-z0-9\-._~!$&\'()*+,;=%:@/';
}
# Build path
my @parts = @{$self->parts};
@parts = map { encode $charset, $_ } @parts if $charset;
my $path = join '/', map { url_escape $_, '^A-Za-z0-9\-._~!$&\'()*+,;=:@' } @parts;
$path = "/$path" if $self->leading_slash;
$path = "$path/" if $self->trailing_slash;
return $path;
}
sub trailing_slash { shift->_parse(trailing_slash => @_) }
sub _parse {
my ($self, $name) = (shift, shift);
unless ($self->{parts}) {
my $path = url_unescape delete($self->{path}) // '';
my $charset = $self->charset;
$path = decode($charset, $path) // $path if $charset;
$self->{leading_slash} = $path =~ s!^/!!;
$self->{trailing_slash} = $path =~ s!/$!!;
$self->{parts} = [split /\//, $path, -1];
}
return $self->{$name} unless @_;
$self->{$name} = shift;
return $self;
}
1;
=encoding utf8
=head1 NAME
Mojo::Path - Path
=head1 SYNOPSIS
use Mojo::Path;
# Parse
my $path = Mojo::Path->new('/foo%2Fbar%3B/baz.html');
say $path->[0];
# Build
my $path = Mojo::Path->new('/i/♥');
push @$path, 'mojolicious';
say "$path";
=head1 DESCRIPTION
L<Mojo::Path> is a container for paths used by L<Mojo::URL>, based on L<RFC 3986|https://tools.ietf.org/html/rfc3986>.
=head1 ATTRIBUTES
L<Mojo::Path> implements the following attributes.
=head2 charset
my $charset = $path->charset;
$path = $path->charset('UTF-8');
Charset used for encoding and decoding, defaults to C<UTF-8>.
# Disable encoding and decoding
$path->charset(undef);
=head1 METHODS
L<Mojo::Path> inherits all methods from L<Mojo::Base> and implements the following new ones.
=head2 canonicalize
$path = $path->canonicalize;
Canonicalize path by resolving C<.> and C<..>, in addition C<...> will be treated as C<.> to protect from path
traversal attacks.
# "/foo/baz"
Mojo::Path->new('/foo/./bar/../baz')->canonicalize;
# "/../baz"
Mojo::Path->new('/foo/../bar/../../baz')->canonicalize;
# "/foo/bar"
Mojo::Path->new('/foo/.../bar')->canonicalize;
=head2 clone
my $clone = $path->clone;
Return a new L<Mojo::Path> object cloned from this path.
=head2 contains
my $bool = $path->contains('/i/♥/mojolicious');
Check if path contains given prefix.
# True
Mojo::Path->new('/foo/bar')->contains('/');
Mojo::Path->new('/foo/bar')->contains('/foo');
Mojo::Path->new('/foo/bar')->contains('/foo/bar');
# False
Mojo::Path->new('/foo/bar')->contains('/f');
Mojo::Path->new('/foo/bar')->contains('/bar');
Mojo::Path->new('/foo/bar')->contains('/whatever');
=head2 leading_slash
my $bool = $path->leading_slash;
$path = $path->leading_slash($bool);
Path has a leading slash. Note that this method will normalize the path and that C<%2F> will be treated as C</> for
security reasons.
# "/foo/bar"
Mojo::Path->new('foo/bar')->leading_slash(1);
# "foo/bar"
Mojo::Path->new('/foo/bar')->leading_slash(0);
=head2 merge
$path = $path->merge('/foo/bar');
$path = $path->merge('foo/bar');
$path = $path->merge(Mojo::Path->new);
Merge paths. Note that this method will normalize both paths if necessary and that C<%2F> will be treated as C</> for
security reasons.
# "/baz/yada"
Mojo::Path->new('/foo/bar')->merge('/baz/yada');
# "/foo/baz/yada"
Mojo::Path->new('/foo/bar')->merge('baz/yada');
# "/foo/bar/baz/yada"
Mojo::Path->new('/foo/bar/')->merge('baz/yada');
=head2 new
my $path = Mojo::Path->new;
my $path = Mojo::Path->new('/foo%2Fbar%3B/baz.html');
Construct a new L<Mojo::Path> object and L</"parse"> path if necessary.
=head2 parse
$path = $path->parse('/foo%2Fbar%3B/baz.html');
Parse path.
=head2 to_abs_string
my $str = $path->to_abs_string;
Turn path into an absolute string.
# "/i/%E2%99%A5/mojolicious"
Mojo::Path->new('/i/%E2%99%A5/mojolicious')->to_abs_string;
Mojo::Path->new('i/%E2%99%A5/mojolicious')->to_abs_string;
=head2 parts
my $parts = $path->parts;
$path = $path->parts([qw(foo bar baz)]);
The path parts. Note that this method will normalize the path and that C<%2F> will be treated as C</> for security
reasons.
# Part with slash
push @{$path->parts}, 'foo/bar';
=head2 to_dir
my $dir = $route->to_dir;
Clone path and remove everything after the right-most slash.
# "/i/%E2%99%A5/"
Mojo::Path->new('/i/%E2%99%A5/mojolicious')->to_dir->to_abs_string;
# "i/%E2%99%A5/"
Mojo::Path->new('i/%E2%99%A5/mojolicious')->to_dir->to_abs_string;
=head2 to_route
my $route = $path->to_route;
Turn path into a route.
# "/i/♥/mojolicious"
Mojo::Path->new('/i/%E2%99%A5/mojolicious')->to_route;
Mojo::Path->new('i/%E2%99%A5/mojolicious')->to_route;
=head2 to_string
my $str = $path->to_string;
Turn path into a string.
# "/i/%E2%99%A5/mojolicious"
Mojo::Path->new('/i/%E2%99%A5/mojolicious')->to_string;
# "i/%E2%99%A5/mojolicious"
Mojo::Path->new('i/%E2%99%A5/mojolicious')->to_string;
=head2 trailing_slash
my $bool = $path->trailing_slash;
$path = $path->trailing_slash($bool);
Path has a trailing slash. Note that this method will normalize the path and that C<%2F> will be treated as C</> for
security reasons.
# "/foo/bar/"
Mojo::Path->new('/foo/bar')->trailing_slash(1);
# "/foo/bar"
Mojo::Path->new('/foo/bar/')->trailing_slash(0);
=head1 OPERATORS
L<Mojo::Path> overloads the following operators.
=head2 array
my @parts = @$path;
Alias for L</"parts">. Note that this will normalize the path and that C<%2F> will be treated as C</> for security
reasons.
say $path->[0];
say for @$path;
=head2 bool
my $bool = !!$path;
Always true.
=head2 stringify
my $str = "$path";
Alias for L</"to_string">.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

541
database/perl/vendor/lib/Mojo/Promise.pm vendored Normal file
View File

@@ -0,0 +1,541 @@
package Mojo::Promise;
use Mojo::Base -base;
use Carp qw(carp);
use Mojo::Exception;
use Mojo::IOLoop;
use Scalar::Util qw(blessed);
use constant DEBUG => $ENV{MOJO_PROMISE_DEBUG} || 0;
has ioloop => sub { Mojo::IOLoop->singleton }, weak => 1;
sub AWAIT_CHAIN_CANCEL { }
sub AWAIT_CLONE { _await('clone', @_) }
sub AWAIT_DONE { shift->resolve(@_) }
sub AWAIT_FAIL { shift->reject(@_) }
sub AWAIT_GET {
my $self = shift;
my @results = @{$self->{results} // []};
die $results[0] unless $self->{status} eq 'resolve';
return wantarray ? @results : $results[0];
}
sub AWAIT_IS_CANCELLED {undef}
sub AWAIT_IS_READY {
my $self = shift;
$self->{handled} = 1;
return !!$self->{results} && !@{$self->{resolve}} && !@{$self->{reject}};
}
sub AWAIT_NEW_DONE { _await('resolve', @_) }
sub AWAIT_NEW_FAIL { _await('reject', @_) }
sub AWAIT_ON_CANCEL { }
sub AWAIT_ON_READY {
shift->_finally(0, @_)->catch(sub { });
}
sub DESTROY {
my $self = shift;
return if $self->{handled} || ($self->{status} // '') ne 'reject' || !$self->{results};
carp "Unhandled rejected promise: @{$self->{results}}";
warn $self->{debug}->message("-- Destroyed promise\n")->verbose(1)->to_string if DEBUG;
}
sub all { _all(2, @_) }
sub all_settled { _all(0, @_) }
sub any { _all(3, @_) }
sub catch { shift->then(undef, shift) }
sub clone { $_[0]->new->ioloop($_[0]->ioloop) }
sub finally { shift->_finally(1, @_) }
sub map {
my ($class, $options, $cb, @items) = (shift, ref $_[0] eq 'HASH' ? shift : {}, @_);
return $class->all(map { $_->$cb } @items) if !$options->{concurrency} || @items <= $options->{concurrency};
my @start = map { $_->$cb } splice @items, 0, $options->{concurrency};
my @wait = map { $start[0]->clone } 0 .. $#items;
my $start_next = sub {
return () unless my $item = shift @items;
my ($start_next, $chain) = (__SUB__, shift @wait);
$_->$cb->then(sub { $chain->resolve(@_); $start_next->() }, sub { $chain->reject(@_); @items = () }) for $item;
return ();
};
$_->then($start_next, sub { }) for @start;
return $class->all(@start, @wait);
}
sub new {
my $self = shift->SUPER::new;
$self->{debug} = Mojo::Exception->new->trace if DEBUG;
shift->(sub { $self->resolve(@_) }, sub { $self->reject(@_) }) if @_;
return $self;
}
sub race { _all(1, @_) }
sub reject { shift->_settle('reject', @_) }
sub resolve { shift->_settle('resolve', @_) }
sub then {
my ($self, $resolve, $reject) = @_;
my $new = $self->clone;
$self->{handled} = 1;
push @{$self->{resolve}}, sub { _then_cb($new, $resolve, 'resolve', @_) };
push @{$self->{reject}}, sub { _then_cb($new, $reject, 'reject', @_) };
$self->_defer if $self->{results};
return $new;
}
sub timer { shift->_timer('resolve', @_) }
sub timeout { shift->_timer('reject', @_) }
sub wait {
my $self = shift;
return if (my $loop = $self->ioloop)->is_running;
my $done;
$self->_finally(0, sub { $done++; $loop->stop })->catch(sub { });
$loop->start until $done;
}
sub _all {
my ($type, $class, @promises) = @_;
my $all = $promises[0]->clone;
my $results = [];
my $remaining = scalar @promises;
for my $i (0 .. $#promises) {
# "race"
if ($type == 1) {
$promises[$i]->then(sub { $all->resolve(@_); () }, sub { $all->reject(@_); () });
}
# "all"
elsif ($type == 2) {
$promises[$i]->then(
sub {
$results->[$i] = [@_];
$all->resolve(@$results) if --$remaining <= 0;
return ();
},
sub { $all->reject(@_); () }
);
}
# "any"
elsif ($type == 3) {
$promises[$i]->then(
sub { $all->resolve(@_); () },
sub {
$results->[$i] = [@_];
$all->reject(@$results) if --$remaining <= 0;
return ();
}
);
}
# "all_settled"
else {
$promises[$i]->then(
sub {
$results->[$i] = {status => 'fulfilled', value => [@_]};
$all->resolve(@$results) if --$remaining <= 0;
return ();
},
sub {
$results->[$i] = {status => 'rejected', reason => [@_]};
$all->resolve(@$results) if --$remaining <= 0;
return ();
}
);
}
}
return $all;
}
sub _await {
my ($method, $class) = (shift, shift);
my $promise = $class->$method(@_);
$promise->{cycle} = $promise;
return $promise;
}
sub _defer {
my $self = shift;
return unless my $results = $self->{results};
my $cbs = $self->{status} eq 'resolve' ? $self->{resolve} : $self->{reject};
@{$self}{qw(cycle resolve reject)} = (undef, [], []);
$self->ioloop->next_tick(sub { $_->(@$results) for @$cbs });
}
sub _finally {
my ($self, $handled, $finally) = @_;
my $new = $self->clone;
$self->{handled} = 1 if $handled;
push @{$self->{resolve}}, sub { _finally_cb($new, $finally, 'resolve', @_) };
push @{$self->{reject}}, sub { _finally_cb($new, $finally, 'reject', @_) };
$self->_defer if $self->{results};
return $new;
}
sub _finally_cb {
my ($new, $finally, $method, @results) = @_;
return $new->reject($@) unless eval { $finally->(); 1 };
return $new->$method(@results);
}
sub _settle {
my ($self, $status, @results) = @_;
my $thenable = blessed $results[0] && $results[0]->can('then');
unless (ref $self) {
return $results[0] if $thenable && $status eq 'resolve' && $results[0]->isa('Mojo::Promise');
$self = $self->new;
}
if ($thenable && $status eq 'resolve') {
$results[0]->then(sub { $self->resolve(@_); () }, sub { $self->reject(@_); () });
}
elsif (!$self->{results}) {
@{$self}{qw(results status)} = (\@results, $status);
$self->_defer;
}
return $self;
}
sub _then_cb {
my ($new, $cb, $method, @results) = @_;
return $new->$method(@results) unless defined $cb;
my @res;
return $new->reject($@) unless eval { @res = $cb->(@results); 1 };
return $new->resolve(@res);
}
sub _timer {
my ($self, $method, $after, @results) = @_;
$self = $self->new unless ref $self;
$results[0] = 'Promise timeout' if $method eq 'reject' && !@results;
$self->ioloop->timer($after => sub { $self->$method(@results) });
return $self;
}
1;
=encoding utf8
=head1 NAME
Mojo::Promise - Promises/A+
=head1 SYNOPSIS
use Mojo::Promise;
use Mojo::UserAgent;
# Wrap continuation-passing style APIs with promises
my $ua = Mojo::UserAgent->new;
sub get_p {
my $promise = Mojo::Promise->new;
$ua->get(@_ => sub ($ua, $tx) {
my $err = $tx->error;
if (!$err || $err->{code}) { $promise->resolve($tx) }
else { $promise->reject($err->{message}) }
});
return $promise;
}
# Perform non-blocking operations sequentially
get_p('https://mojolicious.org')->then(sub ($mojo) {
say $mojo->res->code;
return get_p('https://metacpan.org');
})->then(sub ($cpan) {
say $cpan->res->code;
})->catch(sub ($err) {
warn "Something went wrong: $err";
})->wait;
# Synchronize non-blocking operations (all)
my $mojo = get_p('https://mojolicious.org');
my $cpan = get_p('https://metacpan.org');
Mojo::Promise->all($mojo, $cpan)->then(sub ($mojo, $cpan) {
say $mojo->[0]->res->code;
say $cpan->[0]->res->code;
})->catch(sub ($err) {
warn "Something went wrong: $err";
})->wait;
# Synchronize non-blocking operations (race)
my $mojo = get_p('https://mojolicious.org');
my $cpan = get_p('https://metacpan.org');
Mojo::Promise->race($mojo, $cpan)->then(sub ($tx) {
say $tx->req->url, ' won!';
})->catch(sub ($err) {
warn "Something went wrong: $err";
})->wait;
=head1 DESCRIPTION
L<Mojo::Promise> is a Perl-ish implementation of L<Promises/A+|https://promisesaplus.com> and a superset of L<ES6
Promises|https://duckduckgo.com/?q=\mdn%20Promise>.
=head1 STATES
A promise is an object representing the eventual completion or failure of a non-blocking operation. It allows
non-blocking functions to return values, like blocking functions. But instead of immediately returning the final value,
the non-blocking function returns a promise to supply the value at some point in the future.
A promise can be in one of three states:
=over 2
=item pending
Initial state, neither fulfilled nor rejected.
=item fulfilled
Meaning that the operation completed successfully.
=item rejected
Meaning that the operation failed.
=back
A pending promise can either be fulfilled with a value or rejected with a reason. When either happens, the associated
handlers queued up by a promise's L</"then"> method are called.
=head1 ATTRIBUTES
L<Mojo::Promise> implements the following attributes.
=head2 ioloop
my $loop = $promise->ioloop;
$promise = $promise->ioloop(Mojo::IOLoop->new);
Event loop object to control, defaults to the global L<Mojo::IOLoop> singleton. Note that this attribute is weakened.
=head1 METHODS
L<Mojo::Promise> inherits all methods from L<Mojo::Base> and implements the following new ones.
=head2 all
my $new = Mojo::Promise->all(@promises);
Returns a new L<Mojo::Promise> object that either fulfills when all of the passed L<Mojo::Promise> objects have
fulfilled or rejects as soon as one of them rejects. If the returned promise fulfills, it is fulfilled with the values
from the fulfilled promises in the same order as the passed promises.
=head2 all_settled
my $new = Mojo::Promise->all_settled(@promises);
Returns a new L<Mojo::Promise> object that fulfills when all of the passed L<Mojo::Promise> objects have fulfilled or
rejected, with hash references that describe the outcome of each promise.
=head2 any
my $new = Mojo::Promise->any(@promises);
Returns a new L<Mojo::Promise> object that fulfills as soon as one of the passed L<Mojo::Promise> objects fulfills,
with the value from that promise. If no promises fulfill, it is rejected with the reasons from the rejected promises in
the same order as the passed promises. Note that this method is B<EXPERIMENTAL> and might change without warning!
=head2 catch
my $new = $promise->catch(sub {...});
Appends a rejection handler callback to the promise, and returns a new L<Mojo::Promise> object resolving to the return
value of the callback if it is called, or to its original fulfillment value if the promise is instead fulfilled.
# Longer version
my $new = $promise->then(undef, sub {...});
# Pass along the rejection reason
$promise->catch(sub (@reason) {
warn "Something went wrong: $reason[0]";
return @reason;
});
# Change the rejection reason
$promise->catch(sub (@reason) { "This is bad: $reason[0]" });
=head2 clone
my $new = $promise->clone;
Return a new L<Mojo::Promise> object cloned from this promise that is still pending.
=head2 finally
my $new = $promise->finally(sub {...});
Appends a fulfillment and rejection handler to the promise, and returns a new L<Mojo::Promise> object resolving to the
original fulfillment value or rejection reason.
# Do something on fulfillment and rejection
$promise->finally(sub { say "We are done!" });
=head2 map
my $new = Mojo::Promise->map(sub {...}, @items);
my $new = Mojo::Promise->map({concurrency => 3}, sub {...}, @items);
Apply a function that returns a L<Mojo::Promise> to each item in a list of items while optionally limiting concurrency.
Returns a L<Mojo::Promise> that collects the results in the same manner as L</all>. If any item's promise is rejected,
any remaining items which have not yet been mapped will not be. Note that this method is B<EXPERIMENTAL> and might
change without warning!
# Perform 3 requests at a time concurrently
Mojo::Promise->map({concurrency => 3}, sub { $ua->get_p($_) }, @urls)
->then(sub{ say $_->[0]->res->dom->at('title')->text for @_ });
These options are currently available:
=over 2
=item concurrency
concurrency => 3
The maximum number of items that are in progress at the same time.
=back
=head2 new
my $promise = Mojo::Promise->new;
my $promise = Mojo::Promise->new(sub {...});
Construct a new L<Mojo::Promise> object.
# Wrap a continuation-passing style API
my $promise = Mojo::Promise->new(sub ($resolve, $reject) {
Mojo::IOLoop->timer(5 => sub {
if (int rand 2) { $resolve->('Lucky!') }
else { $reject->('Unlucky!') }
});
});
=head2 race
my $new = Mojo::Promise->race(@promises);
Returns a new L<Mojo::Promise> object that fulfills or rejects as soon as one of the passed L<Mojo::Promise> objects
fulfills or rejects, with the value or reason from that promise.
=head2 reject
my $new = Mojo::Promise->reject(@reason);
$promise = $promise->reject(@reason);
Build rejected L<Mojo::Promise> object or reject the promise with one or more rejection reasons.
# Longer version
my $promise = Mojo::Promise->new->reject(@reason);
=head2 resolve
my $new = Mojo::Promise->resolve(@value);
$promise = $promise->resolve(@value);
Build resolved L<Mojo::Promise> object or resolve the promise with one or more fulfillment values.
# Longer version
my $promise = Mojo::Promise->new->resolve(@value);
=head2 then
my $new = $promise->then(sub {...});
my $new = $promise->then(sub {...}, sub {...});
my $new = $promise->then(undef, sub {...});
Appends fulfillment and rejection handlers to the promise, and returns a new L<Mojo::Promise> object resolving to the
return value of the called handler.
# Pass along the fulfillment value or rejection reason
$promise->then(
sub (@value) {
say "The result is $value[0]";
return @value;
},
sub (@reason) {
warn "Something went wrong: $reason[0]";
return @reason;
}
);
# Change the fulfillment value or rejection reason
$promise->then(
sub (@value) { return "This is good: $value[0]" },
sub (@reason) { return "This is bad: $reason[0]" }
);
=head2 timer
my $new = Mojo::Promise->timer(5 => 'Success!');
$promise = $promise->timer(5 => 'Success!');
$promise = $promise->timer(5);
Create a new L<Mojo::Promise> object with a timer or attach a timer to an existing promise. The promise will be
resolved after the given amount of time in seconds with or without a value. Note that this method is B<EXPERIMENTAL>
and might change without warning!
=head2 timeout
my $new = Mojo::Promise->timeout(5 => 'Timeout!');
$promise = $promise->timeout(5 => 'Timeout!');
$promise = $promise->timeout(5);
Create a new L<Mojo::Promise> object with a timeout or attach a timeout to an existing promise. The promise will be
rejected after the given amount of time in seconds with a reason, which defaults to C<Promise timeout>. Note that this
method is B<EXPERIMENTAL> and might change without warning!
=head2 wait
$promise->wait;
Start L</"ioloop"> and stop it again once the promise has been fulfilled or rejected, does nothing when L</"ioloop"> is
already running.
=head1 DEBUGGING
You can set the C<MOJO_PROMISE_DEBUG> environment variable to get some advanced diagnostics information printed to
C<STDERR>.
MOJO_PROMISE_DEBUG=1
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

199
database/perl/vendor/lib/Mojo/Reactor.pm vendored Normal file
View File

@@ -0,0 +1,199 @@
package Mojo::Reactor;
use Mojo::Base 'Mojo::EventEmitter';
use Carp qw(croak);
use Config;
use Mojo::Loader qw(load_class);
my %DETECTED;
sub again { croak 'Method "again" not implemented by subclass' }
sub detect {
my $default = 'Mojo::Reactor::' . ($Config{d_pseudofork} ? 'Poll' : 'EV');
my $try = $ENV{MOJO_REACTOR} || $default;
return $DETECTED{$try} ||= load_class($try) ? 'Mojo::Reactor::Poll' : $try;
}
sub io { croak 'Method "io" not implemented by subclass' }
sub is_running { croak 'Method "is_running" not implemented by subclass' }
sub next_tick { croak 'Method "next_tick" not implemented by subclass' }
sub one_tick { croak 'Method "one_tick" not implemented by subclass' }
sub recurring { croak 'Method "recurring" not implemented by subclass' }
sub remove { croak 'Method "remove" not implemented by subclass' }
sub reset { croak 'Method "reset" not implemented by subclass' }
sub start { croak 'Method "start" not implemented by subclass' }
sub stop { croak 'Method "stop" not implemented by subclass' }
sub timer { croak 'Method "timer" not implemented by subclass' }
sub watch { croak 'Method "watch" not implemented by subclass' }
1;
=encoding utf8
=head1 NAME
Mojo::Reactor - Low-level event reactor base class
=head1 SYNOPSIS
package Mojo::Reactor::MyEventLoop;
use Mojo::Base 'Mojo::Reactor';
sub again {...}
sub io {...}
sub is_running {...}
sub next_tick {...}
sub one_tick {...}
sub recurring {...}
sub remove {...}
sub reset {...}
sub start {...}
sub stop {...}
sub timer {...}
sub watch {...}
=head1 DESCRIPTION
L<Mojo::Reactor> is an abstract base class for low-level event reactors, like L<Mojo::Reactor::EV> and
L<Mojo::Reactor::Poll>.
=head1 EVENTS
L<Mojo::Reactor> inherits all events from L<Mojo::EventEmitter> and can emit the following new ones.
=head2 error
$reactor->on(error => sub ($reactor, $err) {...});
Emitted for exceptions caught in callbacks, fatal if unhandled. Note that if this event is unhandled or fails it might
kill your program, so you need to be careful.
$reactor->on(error => sub ($reactor, $err) { say "Something very bad happened: $err" });
=head1 METHODS
L<Mojo::Reactor> inherits all methods from L<Mojo::EventEmitter> and implements the following new ones.
=head2 again
$reactor->again($id);
$reactor->again($id, 0.5);
Restart timer and optionally change the invocation time. Meant to be overloaded in a subclass. Note that this method
requires an active timer.
=head2 detect
my $class = Mojo::Reactor->detect;
Detect and load the best reactor implementation available, will try the value of the C<MOJO_REACTOR> environment
variable, L<Mojo::Reactor::EV> or L<Mojo::Reactor::Poll>.
# Instantiate best reactor implementation available
my $reactor = Mojo::Reactor->detect->new;
=head2 io
$reactor = $reactor->io($handle => sub {...});
Watch handle for I/O events, invoking the callback whenever handle becomes readable or writable. Meant to be overloaded
in a subclass.
# Callback will be executed twice if handle becomes readable and writable
$reactor->io($handle => sub ($reactor, $writable) {
say $writable ? 'Handle is writable' : 'Handle is readable';
});
=head2 is_running
my $bool = $reactor->is_running;
Check if reactor is running. Meant to be overloaded in a subclass.
=head2 next_tick
my $undef = $reactor->next_tick(sub {...});
Execute callback as soon as possible, but not before returning or other callbacks that have been registered with this
method, always returns C<undef>. Meant to be overloaded in a subclass.
=head2 one_tick
$reactor->one_tick;
Run reactor until an event occurs. Note that this method can recurse back into the reactor, so you need to be careful.
Meant to be overloaded in a subclass.
# Don't block longer than 0.5 seconds
my $id = $reactor->timer(0.5 => sub {});
$reactor->one_tick;
$reactor->remove($id);
=head2 recurring
my $id = $reactor->recurring(0.25 => sub {...});
Create a new recurring timer, invoking the callback repeatedly after a given amount of time in seconds. Meant to be
overloaded in a subclass.
=head2 remove
my $bool = $reactor->remove($handle);
my $bool = $reactor->remove($id);
Remove handle or timer. Meant to be overloaded in a subclass.
=head2 reset
$reactor->reset;
Remove all handles and timers. Meant to be overloaded in a subclass.
=head2 start
$reactor->start;
Start watching for I/O and timer events, this will block until L</"stop"> is called. Note that some reactors stop
automatically if there are no events being watched anymore. Meant to be overloaded in a subclass.
# Start reactor only if it is not running already
$reactor->start unless $reactor->is_running;
=head2 stop
$reactor->stop;
Stop watching for I/O and timer events. Meant to be overloaded in a subclass.
=head2 timer
my $id = $reactor->timer(0.5 => sub {...});
Create a new timer, invoking the callback after a given amount of time in seconds. Meant to be overloaded in a
subclass.
=head2 watch
$reactor = $reactor->watch($handle, $readable, $writable);
Change I/O events to watch handle for with true and false values. Meant to be overloaded in a subclass. Note that this
method requires an active I/O watcher.
# Watch only for readable events
$reactor->watch($handle, 1, 0);
# Watch only for writable events
$reactor->watch($handle, 0, 1);
# Watch for readable and writable events
$reactor->watch($handle, 1, 1);
# Pause watching for events
$reactor->watch($handle, 0, 0);
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,201 @@
package Mojo::Reactor::EV;
use Mojo::Base 'Mojo::Reactor::Poll';
use Carp qw(croak);
use EV 4.32;
my $EV;
sub DESTROY { undef $EV }
sub again {
my ($self, $id, $after) = @_;
croak 'Timer not active' unless my $timer = $self->{timers}{$id};
my $w = $timer->{watcher};
defined $after ? $w->set($after, $w->repeat ? $after : 0) : $w->again;
}
# We have to fall back to Mojo::Reactor::Poll, since EV is unique
sub new { $EV++ ? Mojo::Reactor::Poll->new : shift->SUPER::new }
sub one_tick {
my $self = shift;
local $self->{running} = 1 unless $self->{running};
EV::run(EV::RUN_ONCE);
}
sub recurring { shift->_timer(1, @_) }
sub start {
my $self = shift;
local $self->{running} = 1 unless $self->{running};
EV::run;
}
sub stop { EV::break(EV::BREAK_ALL) }
sub timer { shift->_timer(0, @_) }
sub watch {
my ($self, $handle, $read, $write) = @_;
my $fd = fileno $handle;
croak 'I/O watcher not active' unless my $io = $self->{io}{$fd};
my $mode = 0;
$mode |= EV::READ if $read;
$mode |= EV::WRITE if $write;
if ($mode == 0) { delete $io->{watcher} }
elsif (my $w = $io->{watcher}) { $w->events($mode) }
else {
my $cb = sub {
my ($w, $revents) = @_;
$self->_try('I/O watcher', $self->{io}{$fd}{cb}, 0) if EV::READ & $revents;
$self->_try('I/O watcher', $self->{io}{$fd}{cb}, 1) if EV::WRITE & $revents && $self->{io}{$fd};
};
$io->{watcher} = EV::io($fd, $mode, $cb);
}
return $self;
}
sub _timer {
my ($self, $recurring, $after, $cb) = @_;
$after ||= 0.0001 if $recurring;
my $id = $self->_id;
my $wrapper = sub {
delete $self->{timers}{$id} unless $recurring;
$self->_try('Timer', $cb);
};
EV::now_update() if $after > 0;
$self->{timers}{$id}{watcher} = EV::timer($after, $after, $wrapper);
return $id;
}
1;
=encoding utf8
=head1 NAME
Mojo::Reactor::EV - Low-level event reactor with libev support
=head1 SYNOPSIS
use Mojo::Reactor::EV;
# Watch if handle becomes readable or writable
my $reactor = Mojo::Reactor::EV->new;
$reactor->io($first => sub ($reactor, $writable) {
say $writable ? 'First handle is writable' : 'First handle is readable';
});
# Change to watching only if handle becomes writable
$reactor->watch($first, 0, 1);
# Turn file descriptor into handle and watch if it becomes readable
my $second = IO::Handle->new_from_fd($fd, 'r');
$reactor->io($second => sub ($reactor, $writable) {
say $writable ? 'Second handle is writable' : 'Second handle is readable';
})->watch($second, 1, 0);
# Add a timer
$reactor->timer(15 => sub ($reactor) {
$reactor->remove($first);
$reactor->remove($second);
say 'Timeout!';
});
# Start reactor if necessary
$reactor->start unless $reactor->is_running;
=head1 DESCRIPTION
L<Mojo::Reactor::EV> is a low-level event reactor based on L<EV> (4.32+).
=head1 EVENTS
L<Mojo::Reactor::EV> inherits all events from L<Mojo::Reactor::Poll>.
=head1 METHODS
L<Mojo::Reactor::EV> inherits all methods from L<Mojo::Reactor::Poll> and implements the following new ones.
=head2 again
$reactor->again($id);
$reactor->again($id, 0.5);
Restart timer and optionally change the invocation time. Note that this method requires an active timer.
=head2 new
my $reactor = Mojo::Reactor::EV->new;
Construct a new L<Mojo::Reactor::EV> object.
=head2 one_tick
$reactor->one_tick;
Run reactor until an event occurs or no events are being watched anymore.
# Don't block longer than 0.5 seconds
my $id = $reactor->timer(0.5 => sub {});
$reactor->one_tick;
$reactor->remove($id);
=head2 recurring
my $id = $reactor->recurring(0.25 => sub {...});
Create a new recurring timer, invoking the callback repeatedly after a given amount of time in seconds.
=head2 start
$reactor->start;
Start watching for I/O and timer events, this will block until L</"stop"> is called or no events are being watched
anymore.
# Start reactor only if it is not running already
$reactor->start unless $reactor->is_running;
=head2 stop
$reactor->stop;
Stop watching for I/O and timer events.
=head2 timer
my $id = $reactor->timer(0.5 => sub {...});
Create a new timer, invoking the callback after a given amount of time in seconds.
=head2 watch
$reactor = $reactor->watch($handle, $readable, $writable);
Change I/O events to watch handle for with true and false values. Note that this method requires an active I/O watcher.
# Watch only for readable events
$reactor->watch($handle, 1, 0);
# Watch only for writable events
$reactor->watch($handle, 0, 1);
# Watch for readable and writable events
$reactor->watch($handle, 1, 1);
# Pause watching for events
$reactor->watch($handle, 0, 0);
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,303 @@
package Mojo::Reactor::Poll;
use Mojo::Base 'Mojo::Reactor';
use Carp qw(croak);
use IO::Poll qw(POLLERR POLLHUP POLLIN POLLNVAL POLLOUT POLLPRI);
use List::Util qw(min);
use Mojo::Util qw(md5_sum steady_time);
use Time::HiRes qw(usleep);
sub again {
my ($self, $id, $after) = @_;
croak 'Timer not active' unless my $timer = $self->{timers}{$id};
$timer->{after} = $after if defined $after;
$timer->{time} = steady_time + $timer->{after};
}
sub io {
my ($self, $handle, $cb) = @_;
$self->{io}{fileno($handle) // croak 'Handle is closed'} = {cb => $cb};
return $self->watch($handle, 1, 1);
}
sub is_running { !!shift->{running} }
sub next_tick {
my ($self, $cb) = @_;
push @{$self->{next_tick}}, $cb;
$self->{next_timer} //= $self->timer(0 => \&_next);
return undef;
}
sub one_tick {
my $self = shift;
# Just one tick
local $self->{running} = 1 unless $self->{running};
# Wait for one event
my $i;
until ($i || !$self->{running}) {
# Stop automatically if there is nothing to watch
return $self->stop unless keys %{$self->{timers}} || keys %{$self->{io}};
# Calculate ideal timeout based on timers and round up to next millisecond
my $min = min map { $_->{time} } values %{$self->{timers}};
my $timeout = defined $min ? $min - steady_time : 0.5;
$timeout = $timeout <= 0 ? 0 : int($timeout * 1000) + 1;
# I/O
if (keys %{$self->{io}}) {
my @poll = map { $_ => $self->{io}{$_}{mode} } keys %{$self->{io}};
# This may break in the future, but is worth it for performance
if (IO::Poll::_poll($timeout, @poll) > 0) {
while (my ($fd, $mode) = splice @poll, 0, 2) {
if ($mode & (POLLIN | POLLPRI | POLLNVAL | POLLHUP | POLLERR)) {
next unless my $io = $self->{io}{$fd};
++$i and $self->_try('I/O watcher', $io->{cb}, 0);
}
next unless $mode & POLLOUT && (my $io = $self->{io}{$fd});
++$i and $self->_try('I/O watcher', $io->{cb}, 1);
}
}
}
# Wait for timeout if poll can't be used
elsif ($timeout) { usleep($timeout * 1000) }
# Timers (time should not change in between timers)
my $now = steady_time;
for my $id (keys %{$self->{timers}}) {
next unless my $t = $self->{timers}{$id};
next unless $t->{time} <= $now;
# Recurring timer
if ($t->{recurring}) { $t->{time} = $now + $t->{after} }
# Normal timer
else { $self->remove($id) }
++$i and $self->_try('Timer', $t->{cb}) if $t->{cb};
}
}
}
sub recurring { shift->_timer(1, @_) }
sub remove {
my ($self, $remove) = @_;
return !!delete $self->{timers}{$remove} unless ref $remove;
return !!delete $self->{io}{fileno($remove) // croak 'Handle is closed'};
}
sub reset {
delete @{shift()}{qw(events io next_tick next_timer running timers)};
}
sub start {
my $self = shift;
local $self->{running} = ($self->{running} || 0) + 1;
$self->one_tick while $self->{running};
}
sub stop { delete shift->{running} }
sub timer { shift->_timer(0, @_) }
sub watch {
my ($self, $handle, $read, $write) = @_;
croak 'I/O watcher not active' unless my $io = $self->{io}{fileno $handle};
$io->{mode} = 0;
$io->{mode} |= POLLIN | POLLPRI if $read;
$io->{mode} |= POLLOUT if $write;
return $self;
}
sub _id {
my $self = shift;
my $id;
do { $id = md5_sum 't' . steady_time . rand } while $self->{timers}{$id};
return $id;
}
sub _next {
my $self = shift;
delete $self->{next_timer};
while (my $cb = shift @{$self->{next_tick}}) { $self->$cb }
}
sub _timer {
my ($self, $recurring, $after, $cb) = @_;
my $id = $self->_id;
my $timer = $self->{timers}{$id}
= {cb => $cb, after => $after, recurring => $recurring, time => steady_time + $after};
return $id;
}
sub _try {
my ($self, $what, $cb) = (shift, shift, shift);
eval { $self->$cb(@_); 1 } or $self->emit(error => "$what failed: $@");
}
1;
=encoding utf8
=head1 NAME
Mojo::Reactor::Poll - Low-level event reactor with poll support
=head1 SYNOPSIS
use Mojo::Reactor::Poll;
# Watch if handle becomes readable or writable
my $reactor = Mojo::Reactor::Poll->new;
$reactor->io($first => sub ($reactor, $writable) {
say $writable ? 'First handle is writable' : 'First handle is readable';
});
# Change to watching only if handle becomes writable
$reactor->watch($first, 0, 1);
# Turn file descriptor into handle and watch if it becomes readable
my $second = IO::Handle->new_from_fd($fd, 'r');
$reactor->io($second => sub ($reactor, $writable) {
say $writable ? 'Second handle is writable' : 'Second handle is readable';
})->watch($second, 1, 0);
# Add a timer
$reactor->timer(15 => sub ($reactor) {
$reactor->remove($first);
$reactor->remove($second);
say 'Timeout!';
});
# Start reactor if necessary
$reactor->start unless $reactor->is_running;
=head1 DESCRIPTION
L<Mojo::Reactor::Poll> is a low-level event reactor based on L<IO::Poll>.
=head1 EVENTS
L<Mojo::Reactor::Poll> inherits all events from L<Mojo::Reactor>.
=head1 METHODS
L<Mojo::Reactor::Poll> inherits all methods from L<Mojo::Reactor> and implements the following new ones.
=head2 again
$reactor->again($id);
$reactor->again($id, 0.5);
Restart timer and optionally change the invocation time. Note that this method requires an active timer.
=head2 io
$reactor = $reactor->io($handle => sub {...});
Watch handle for I/O events, invoking the callback whenever handle becomes readable or writable.
# Callback will be executed twice if handle becomes readable and writable
$reactor->io($handle => sub ($reactor, $writable) {
say $writable ? 'Handle is writable' : 'Handle is readable';
});
=head2 is_running
my $bool = $reactor->is_running;
Check if reactor is running.
=head2 next_tick
my $undef = $reactor->next_tick(sub {...});
Execute callback as soon as possible, but not before returning or other callbacks that have been registered with this
method, always returns C<undef>.
=head2 one_tick
$reactor->one_tick;
Run reactor until an event occurs or no events are being watched anymore.
# Don't block longer than 0.5 seconds
my $id = $reactor->timer(0.5 => sub {});
$reactor->one_tick;
$reactor->remove($id);
=head2 recurring
my $id = $reactor->recurring(0.25 => sub {...});
Create a new recurring timer, invoking the callback repeatedly after a given amount of time in seconds.
=head2 remove
my $bool = $reactor->remove($handle);
my $bool = $reactor->remove($id);
Remove handle or timer.
=head2 reset
$reactor->reset;
Remove all handles and timers.
=head2 start
$reactor->start;
Start watching for I/O and timer events, this will block until L</"stop"> is called or no events are being watched
anymore.
# Start reactor only if it is not running already
$reactor->start unless $reactor->is_running;
=head2 stop
$reactor->stop;
Stop watching for I/O and timer events.
=head2 timer
my $id = $reactor->timer(0.5 => sub {...});
Create a new timer, invoking the callback after a given amount of time in seconds.
=head2 watch
$reactor = $reactor->watch($handle, $readable, $writable);
Change I/O events to watch handle for with true and false values. Note that this method requires an active I/O watcher.
# Watch only for readable events
$reactor->watch($handle, 1, 0);
# Watch only for writable events
$reactor->watch($handle, 0, 1);
# Watch for readable and writable events
$reactor->watch($handle, 1, 1);
# Pause watching for events
$reactor->watch($handle, 0, 0);
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

185
database/perl/vendor/lib/Mojo/Server.pm vendored Normal file
View File

@@ -0,0 +1,185 @@
package Mojo::Server;
use Mojo::Base 'Mojo::EventEmitter';
use Carp qw(croak);
use Mojo::File qw(path);
use Mojo::Loader qw(load_class);
use Mojo::Util qw(md5_sum);
use POSIX ();
use Scalar::Util qw(blessed);
has app => sub { shift->build_app('Mojo::HelloWorld') };
has reverse_proxy => sub { $ENV{MOJO_REVERSE_PROXY} };
sub build_app {
my ($self, $app) = (shift, shift);
local $ENV{MOJO_EXE};
return $self->app($app->new(@_))->app unless my $e = load_class $app;
die ref $e ? $e : qq{Can't find application class "$app" in \@INC. (@INC)\n};
}
sub build_tx {
my $self = shift;
my $tx = $self->app->build_tx;
$tx->req->reverse_proxy(1) if $self->reverse_proxy;
return $tx;
}
sub daemonize {
# Fork and kill parent
die "Can't fork: $!" unless defined(my $pid = fork);
exit 0 if $pid;
POSIX::setsid == -1 and die "Can't start a new session: $!";
# Close filehandles
open STDIN, '<', '/dev/null';
open STDOUT, '>', '/dev/null';
open STDERR, '>&', STDOUT;
}
sub load_app {
my ($self, $path) = @_;
# Clean environment (reset FindBin defensively)
{
local $0 = $path = path($path)->to_abs->to_string;
require FindBin;
FindBin->again;
local $ENV{MOJO_APP_LOADER} = 1;
local $ENV{MOJO_EXE};
# Try to load application from script into sandbox
delete $INC{$path};
my $app = eval "package Mojo::Server::Sandbox::@{[md5_sum $path]}; require \$path";
die qq{Can't load application from file "$path": $@} if $@;
die qq{File "$path" did not return an application object.\n} unless blessed $app && $app->can('handler');
$self->app($app);
};
FindBin->again;
return $self->app;
}
sub new {
my $self = shift->SUPER::new(@_);
$self->on(request => sub { shift->app->handler(shift) });
return $self;
}
sub run { croak 'Method "run" not implemented by subclass' }
1;
=encoding utf8
=head1 NAME
Mojo::Server - HTTP/WebSocket server base class
=head1 SYNOPSIS
package Mojo::Server::MyServer;
use Mojo::Base 'Mojo::Server', -signatures;
sub run ($self) {
# Get a transaction
my $tx = $self->build_tx;
# Emit "request" event
$self->emit(request => $tx);
}
=head1 DESCRIPTION
L<Mojo::Server> is an abstract base class for HTTP/WebSocket servers and server interfaces, like L<Mojo::Server::CGI>,
L<Mojo::Server::Daemon>, L<Mojo::Server::Hypnotoad>, L<Mojo::Server::Morbo>, L<Mojo::Server::Prefork> and
L<Mojo::Server::PSGI>.
=head1 EVENTS
L<Mojo::Server> inherits all events from L<Mojo::EventEmitter> and can emit the following new ones.
=head2 request
$server->on(request => sub ($server, $tx) {...});
Emitted when a request is ready and needs to be handled.
$server->on(request => sub ($server, $tx) {
$tx->res->code(200);
$tx->res->headers->content_type('text/plain');
$tx->res->body('Hello World!');
$tx->resume;
});
=head1 ATTRIBUTES
L<Mojo::Server> implements the following attributes.
=head2 app
my $app = $server->app;
$server = $server->app(MojoSubclass->new);
Application this server handles, defaults to a L<Mojo::HelloWorld> object.
=head2 reverse_proxy
my $bool = $server->reverse_proxy;
$server = $server->reverse_proxy($bool);
This server operates behind a reverse proxy, defaults to the value of the C<MOJO_REVERSE_PROXY> environment variable.
=head1 METHODS
L<Mojo::Server> inherits all methods from L<Mojo::EventEmitter> and implements the following new ones.
=head2 build_app
my $app = $server->build_app('MyApp');
my $app = $server->build_app('MyApp', log => Mojo::Log->new);
my $app = $server->build_app('MyApp', {log => Mojo::Log->new});
Build application from class and assign it to L</"app">.
=head2 build_tx
my $tx = $server->build_tx;
Let application build a transaction.
=head2 daemonize
$server->daemonize;
Daemonize server process.
=head2 load_app
my $app = $server->load_app('/home/sri/myapp.pl');
Load application from script and assign it to L</"app">.
say Mojo::Server->new->load_app('./myapp.pl')->home;
=head2 new
my $server = Mojo::Server->new;
my $server = Mojo::Server->new(reverse_proxy => 1);
my $server = Mojo::Server->new({reverse_proxy => 1});
Construct a new L<Mojo::Server> object and subscribe to L</"request"> event with default request handling.
=head2 run
$server->run;
Run server. Meant to be overloaded in a subclass.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,131 @@
package Mojo::Server::CGI;
use Mojo::Base 'Mojo::Server';
has 'nph';
sub run {
my $self = shift;
my $tx = $self->build_tx;
my $req = $tx->req->parse(\%ENV);
$tx->local_port($ENV{SERVER_PORT})->remote_address($ENV{REMOTE_ADDR});
# Request body (may block if we try to read too much)
binmode STDIN;
my $len = $req->headers->content_length;
until ($req->is_finished) {
my $chunk = ($len && $len < 131072) ? $len : 131072;
last unless my $read = STDIN->read(my $buffer, $chunk, 0);
$req->parse($buffer);
last if ($len -= $read) <= 0;
}
$self->emit(request => $tx);
# Response start-line
STDOUT->autoflush(1);
binmode STDOUT;
my $res = $tx->res->fix_headers;
return undef if $self->nph && !_write($res, 'get_start_line_chunk');
# Response headers
my $code = $res->code || 404;
my $msg = $res->message || $res->default_message;
$res->headers->status("$code $msg") unless $self->nph;
return undef unless _write($res, 'get_header_chunk');
# Response body
return undef unless $tx->is_empty || _write($res, 'get_body_chunk');
# Finish transaction
$tx->closed;
return $res->code;
}
sub _write {
my ($res, $method) = @_;
my $offset = 0;
while (1) {
# No chunk yet, try again
sleep 1 and next unless defined(my $chunk = $res->$method($offset));
# End of part
last unless my $len = length $chunk;
# Make sure we can still write
$offset += $len;
return undef unless STDOUT->opened;
print STDOUT $chunk;
}
return 1;
}
1;
=encoding utf8
=head1 NAME
Mojo::Server::CGI - CGI server
=head1 SYNOPSIS
use Mojo::Server::CGI;
my $cgi = Mojo::Server::CGI->new;
$cgi->unsubscribe('request')->on(request => sub ($cgi, $tx) {
# Request
my $method = $tx->req->method;
my $path = $tx->req->url->path;
# Response
$tx->res->code(200);
$tx->res->headers->content_type('text/plain');
$tx->res->body("$method request for $path!");
# Resume transaction
$tx->resume;
});
$cgi->run;
=head1 DESCRIPTION
L<Mojo::Server::CGI> is a simple and portable implementation of L<RFC 3875|https://tools.ietf.org/html/rfc3875>.
See L<Mojolicious::Guides::Cookbook/"DEPLOYMENT"> for more.
=head1 EVENTS
L<Mojo::Server::CGI> inherits all events from L<Mojo::Server>.
=head1 ATTRIBUTES
L<Mojo::Server::CGI> inherits all attributes from L<Mojo::Server> and implements the following new ones.
=head2 nph
my $bool = $cgi->nph;
$cgi = $cgi->nph($bool);
Activate non-parsed header mode.
=head1 METHODS
L<Mojo::Server::CGI> inherits all methods from L<Mojo::Server> and implements the following new ones.
=head2 run
my $status = $cgi->run;
Run CGI.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,521 @@
package Mojo::Server::Daemon;
use Mojo::Base 'Mojo::Server';
use Carp qw(croak);
use Mojo::IOLoop;
use Mojo::Transaction::WebSocket;
use Mojo::URL;
use Mojo::Util qw(term_escape);
use Mojo::WebSocket qw(server_handshake);
use Scalar::Util qw(weaken);
use constant DEBUG => $ENV{MOJO_SERVER_DEBUG} || 0;
has acceptors => sub { [] };
has [qw(backlog max_clients silent)];
has inactivity_timeout => sub { $ENV{MOJO_INACTIVITY_TIMEOUT} // 30 };
has ioloop => sub { Mojo::IOLoop->singleton };
has keep_alive_timeout => sub { $ENV{MOJO_KEEP_ALIVE_TIMEOUT} // 5 };
has listen => sub { [split /,/, $ENV{MOJO_LISTEN} || 'http://*:3000'] };
has max_requests => 100;
sub DESTROY {
return if Mojo::Util::_global_destruction();
my $self = shift;
my $loop = $self->ioloop;
$loop->remove($_) for keys %{$self->{connections} // {}}, @{$self->acceptors};
}
sub ports { [map { $_[0]->ioloop->acceptor($_)->port } @{$_[0]->acceptors}] }
sub run {
my $self = shift;
# Make sure the event loop can be stopped in regular intervals
my $loop = $self->ioloop;
my $int = $loop->recurring(1 => sub { });
local $SIG{INT} = local $SIG{TERM} = sub { $loop->stop };
$self->start->ioloop->start;
$loop->remove($int);
}
sub start {
my $self = shift;
my $loop = $self->ioloop;
if (my $max = $self->max_clients) { $loop->max_connections($max) }
# Resume accepting connections
if (my $servers = $self->{servers}) {
push @{$self->acceptors}, $loop->acceptor(delete $servers->{$_}) for keys %$servers;
}
# Start listening
elsif (!@{$self->acceptors}) {
$self->app->server($self);
$self->_listen($_) for @{$self->listen};
}
return $self;
}
sub stop {
my $self = shift;
# Suspend accepting connections but keep listen sockets open
my $loop = $self->ioloop;
while (my $id = shift @{$self->acceptors}) {
my $server = $self->{servers}{$id} = $loop->acceptor($id);
$loop->remove($id);
$server->stop;
}
return $self;
}
sub _build_tx {
my ($self, $id, $c) = @_;
my $tx = $self->build_tx->connection($id);
$tx->res->headers->server('Mojolicious (Perl)');
my $handle = $self->ioloop->stream($id)->timeout($self->inactivity_timeout)->handle;
unless ($handle->isa('IO::Socket::UNIX')) {
$tx->local_address($handle->sockhost)->local_port($handle->sockport);
$tx->remote_address($handle->peerhost)->remote_port($handle->peerport);
}
$tx->req->url->base->scheme('https') if $c->{tls};
weaken $self;
$tx->on(
request => sub {
my $tx = shift;
my $req = $tx->req;
if (my $error = $req->error) { $self->_debug($id, $error->{message}) }
# WebSocket
if ($req->is_handshake) {
my $ws = $self->{connections}{$id}{next} = Mojo::Transaction::WebSocket->new(handshake => $tx);
$self->emit(request => server_handshake $ws);
}
# HTTP
else { $self->emit(request => $tx) }
# Last keep-alive request or corrupted connection
my $c = $self->{connections}{$id};
$tx->res->headers->connection('close') if ($c->{requests} || 1) >= $self->max_requests || $req->error;
$tx->on(resume => sub { $self->_write($id) });
$self->_write($id);
}
);
# Kept alive if we have more than one request on the connection
return ++$c->{requests} > 1 ? $tx->kept_alive(1) : $tx;
}
sub _close {
my ($self, $id) = @_;
if (my $tx = $self->{connections}{$id}{tx}) { $tx->closed }
delete $self->{connections}{$id};
}
sub _debug { $_[0]->app->log->debug($_[2]) if $_[0]{connections}{$_[1]}{tx} }
sub _finish {
my ($self, $id) = @_;
# Always remove connection for WebSockets
my $c = $self->{connections}{$id};
return unless my $tx = $c->{tx};
return $self->_remove($id) if $tx->is_websocket;
# Finish transaction
delete($c->{tx})->closed;
# Upgrade connection to WebSocket
if (my $ws = delete $c->{next}) {
# Successful upgrade
if ($ws->handshake->res->code == 101) {
$c->{tx} = $ws->established(1);
weaken $self;
$ws->on(resume => sub { $self->_write($id) });
$self->_write($id);
}
# Failed upgrade
else { $ws->closed }
}
# Close connection if necessary
return $self->_remove($id) if $tx->error || !$tx->keep_alive;
# Build new transaction for leftovers
if (length(my $leftovers = $tx->req->content->leftovers)) {
$tx = $c->{tx} = $self->_build_tx($id, $c);
$tx->server_read($leftovers);
}
# Keep-alive connection
$self->ioloop->stream($id)->timeout($self->keep_alive_timeout) unless $c->{tx};
}
sub _listen {
my ($self, $listen) = @_;
my $url = Mojo::URL->new($listen);
my $proto = $url->protocol;
croak qq{Invalid listen location "$listen"} unless $proto eq 'http' || $proto eq 'https' || $proto eq 'http+unix';
my $query = $url->query;
my $options = {backlog => $self->backlog};
$options->{$_} = $query->param($_) for qw(fd single_accept reuse);
if ($proto eq 'http+unix') { $options->{path} = $url->host }
else {
if ((my $host = $url->host) ne '*') { $options->{address} = $host }
if (my $port = $url->port) { $options->{port} = $port }
}
$options->{"tls_$_"} = $query->param($_) for qw(ca ciphers version);
/^(.*)_(cert|key)$/ and $options->{"tls_$2"}{$1} = $query->param($_) for @{$query->names};
if (my $cert = $query->param('cert')) { $options->{'tls_cert'}{''} = $cert }
if (my $key = $query->param('key')) { $options->{'tls_key'}{''} = $key }
my $verify = $query->param('verify');
$options->{tls_verify} = hex $verify if defined $verify;
my $tls = $options->{tls} = $proto eq 'https';
weaken $self;
push @{$self->acceptors}, $self->ioloop->server(
$options => sub {
my ($loop, $stream, $id) = @_;
$self->{connections}{$id} = {tls => $tls};
warn "-- Accept $id (@{[$stream->handle->peerhost]})\n" if DEBUG;
$stream->timeout($self->inactivity_timeout);
$stream->on(close => sub { $self && $self->_close($id) });
$stream->on(error => sub { $self && $self->app->log->error(pop) && $self->_close($id) });
$stream->on(read => sub { $self->_read($id => pop) });
$stream->on(timeout => sub { $self->_debug($id, 'Inactivity timeout') });
}
);
return if $self->silent;
$self->app->log->info(qq{Listening at "$url"});
$query->pairs([]);
$url->host('127.0.0.1') if $url->host eq '*';
say 'Web application available at ', $options->{path} // $url;
}
sub _read {
my ($self, $id, $chunk) = @_;
# Make sure we have a transaction
my $c = $self->{connections}{$id};
my $tx = $c->{tx} ||= $self->_build_tx($id, $c);
warn term_escape "-- Server <<< Client (@{[_url($tx)]})\n$chunk\n" if DEBUG;
$tx->server_read($chunk);
}
sub _remove {
my ($self, $id) = @_;
$self->ioloop->remove($id);
$self->_close($id);
}
sub _url { shift->req->url->to_abs }
sub _write {
my ($self, $id) = @_;
# Protect from resume event recursion
my $c = $self->{connections}{$id};
return if !(my $tx = $c->{tx}) || $c->{writing};
local $c->{writing} = 1;
my $chunk = $tx->server_write;
warn term_escape "-- Server >>> Client (@{[_url($tx)]})\n$chunk\n" if DEBUG;
my $next = $tx->is_finished ? '_finish' : length $chunk ? '_write' : undef;
return $self->ioloop->stream($id)->write($chunk) unless $next;
weaken $self;
$self->ioloop->stream($id)->write($chunk => sub { $self->$next($id) });
}
1;
=encoding utf8
=head1 NAME
Mojo::Server::Daemon - Non-blocking I/O HTTP and WebSocket server
=head1 SYNOPSIS
use Mojo::Server::Daemon;
my $daemon = Mojo::Server::Daemon->new(listen => ['http://*:8080']);
$daemon->unsubscribe('request')->on(request => sub ($daemon, $tx) {
# Request
my $method = $tx->req->method;
my $path = $tx->req->url->path;
# Response
$tx->res->code(200);
$tx->res->headers->content_type('text/plain');
$tx->res->body("$method request for $path!");
# Resume transaction
$tx->resume;
});
$daemon->run;
=head1 DESCRIPTION
L<Mojo::Server::Daemon> is a full featured, highly portable non-blocking I/O HTTP and WebSocket server, with IPv6, TLS,
SNI, Comet (long polling), keep-alive and multiple event loop support.
For better scalability (epoll, kqueue) and to provide non-blocking name resolution, SOCKS5 as well as TLS support, the
optional modules L<EV> (4.32+), L<Net::DNS::Native> (0.15+), L<IO::Socket::Socks> (0.64+) and L<IO::Socket::SSL>
(2.009+) will be used automatically if possible. Individual features can also be disabled with the C<MOJO_NO_NNR>,
C<MOJO_NO_SOCKS> and C<MOJO_NO_TLS> environment variables.
See L<Mojolicious::Guides::Cookbook/"DEPLOYMENT"> for more.
=head1 SIGNALS
The L<Mojo::Server::Daemon> process can be controlled at runtime with the following signals.
=head2 INT, TERM
Shut down server immediately.
=head1 EVENTS
L<Mojo::Server::Daemon> inherits all events from L<Mojo::Server>.
=head1 ATTRIBUTES
L<Mojo::Server::Daemon> inherits all attributes from L<Mojo::Server> and implements the following new ones.
=head2 acceptors
my $acceptors = $daemon->acceptors;
$daemon = $daemon->acceptors(['6be0c140ef00a389c5d039536b56d139']);
Active acceptor ids.
# Check port
mu $port = $daemon->ioloop->acceptor($daemon->acceptors->[0])->port;
=head2 backlog
my $backlog = $daemon->backlog;
$daemon = $daemon->backlog(128);
Listen backlog size, defaults to C<SOMAXCONN>.
=head2 inactivity_timeout
my $timeout = $daemon->inactivity_timeout;
$daemon = $daemon->inactivity_timeout(5);
Maximum amount of time in seconds a connection with an active request can be inactive before getting closed, defaults
to the value of the C<MOJO_INACTIVITY_TIMEOUT> environment variable or C<30>. Setting the value to C<0> will allow
connections to be inactive indefinitely.
=head2 ioloop
my $loop = $daemon->ioloop;
$daemon = $daemon->ioloop(Mojo::IOLoop->new);
Event loop object to use for I/O operations, defaults to the global L<Mojo::IOLoop> singleton.
=head2 keep_alive_timeout
my $timeout = $daemon->keep_alive_timeout;
$daemon = $daemon->keep_alive_timeout(10);
Maximum amount of time in seconds a connection without an active request can be inactive before getting closed,
defaults to the value of the C<MOJO_KEEP_ALIVE_TIMEOUT> environment variable or C<5>. Setting the value to C<0> will
allow connections to be inactive indefinitely.
=head2 listen
my $listen = $daemon->listen;
$daemon = $daemon->listen(['https://127.0.0.1:8080']);
Array reference with one or more locations to listen on, defaults to the value of the C<MOJO_LISTEN> environment
variable or C<http://*:3000> (shortcut for C<http://0.0.0.0:3000>).
# Listen on all IPv4 interfaces
$daemon->listen(['http://*:3000']);
# Listen on all IPv4 and IPv6 interfaces
$daemon->listen(['http://[::]:3000']);
# Listen on IPv6 interface
$daemon->listen(['http://[::1]:4000']);
# Listen on IPv4 and IPv6 interfaces
$daemon->listen(['http://127.0.0.1:3000', 'http://[::1]:3000']);
# Listen on UNIX domain socket "/tmp/myapp.sock" (percent encoded slash)
$daemon->listen(['http+unix://%2Ftmp%2Fmyapp.sock']);
# File descriptor, as used by systemd
$daemon->listen(['http://127.0.0.1?fd=3']);
# Allow multiple servers to use the same port (SO_REUSEPORT)
$daemon->listen(['http://*:8080?reuse=1']);
# Listen on two ports with HTTP and HTTPS at the same time
$daemon->listen(['http://*:3000', 'https://*:4000']);
# Use a custom certificate and key
$daemon->listen(['https://*:3000?cert=/x/server.crt&key=/y/server.key']);
# Domain specific certificates and keys (SNI)
$daemon->listen(
['https://*:3000?example.com_cert=/x/my.crt&example.com_key=/y/my.key']);
# Or even a custom certificate authority
$daemon->listen(
['https://*:3000?cert=/x/server.crt&key=/y/server.key&ca=/z/ca.crt']);
These parameters are currently available:
=over 2
=item ca
ca=/etc/tls/ca.crt
Path to TLS certificate authority file used to verify the peer certificate.
=item cert
cert=/etc/tls/server.crt
mojolicious.org_cert=/etc/tls/mojo.crt
Path to the TLS cert file, defaults to a built-in test certificate.
=item ciphers
ciphers=AES128-GCM-SHA256:RC4:HIGH:!MD5:!aNULL:!EDH
TLS cipher specification string. For more information about the format see
L<https://www.openssl.org/docs/manmaster/man1/ciphers.html#CIPHER-STRINGS>.
=item fd
fd=3
File descriptor with an already prepared listen socket.
=item key
key=/etc/tls/server.key
mojolicious.org_key=/etc/tls/mojo.key
Path to the TLS key file, defaults to a built-in test key.
=item reuse
reuse=1
Allow multiple servers to use the same port with the C<SO_REUSEPORT> socket option.
=item single_accept
single_accept=1
Only accept one connection at a time.
=item verify
verify=0x00
TLS verification mode.
=item version
version=TLSv1_2
TLS protocol version.
=back
=head2 max_clients
my $max = $daemon->max_clients;
$daemon = $daemon->max_clients(100);
Maximum number of accepted connections this server is allowed to handle concurrently, before stopping to accept new
incoming connections, passed along to L<Mojo::IOLoop/"max_connections">.
=head2 max_requests
my $max = $daemon->max_requests;
$daemon = $daemon->max_requests(250);
Maximum number of keep-alive requests per connection, defaults to C<100>.
=head2 silent
my $bool = $daemon->silent;
$daemon = $daemon->silent($bool);
Disable console messages.
=head1 METHODS
L<Mojo::Server::Daemon> inherits all methods from L<Mojo::Server> and implements the following new ones.
=head2 ports
my $ports = $daemon->ports;
Get all ports this server is currently listening on.
# All ports
say for @{$daemon->ports};
=head2 run
$daemon->run;
Run server and wait for L</"SIGNALS">.
=head2 start
$daemon = $daemon->start;
Start or resume accepting connections through L</"ioloop">.
# Listen on random port
my $port = $daemon->listen(['http://127.0.0.1'])->start->ports->[0];
# Run multiple web servers concurrently
my $daemon1 = Mojo::Server::Daemon->new(listen => ['http://*:3000'])->start;
my $daemon2 = Mojo::Server::Daemon->new(listen => ['http://*:4000'])->start;
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
=head2 stop
$daemon = $daemon->stop;
Stop accepting connections through L</"ioloop">.
=head1 DEBUGGING
You can set the C<MOJO_SERVER_DEBUG> environment variable to get some advanced diagnostics information printed to
C<STDERR>.
MOJO_SERVER_DEBUG=1
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,389 @@
package Mojo::Server::Hypnotoad;
use Mojo::Base -base;
# "Bender: I was God once.
# God: Yes, I saw. You were doing well, until everyone died."
use Config;
use Mojo::File qw(path);
use Mojo::Server::Prefork;
use Mojo::Util qw(steady_time);
use Scalar::Util qw(weaken);
has prefork => sub { Mojo::Server::Prefork->new(listen => ['http://*:8080']) };
has upgrade_timeout => 180;
sub configure {
my ($self, $name) = @_;
# Hypnotoad settings
my $prefork = $self->prefork;
my $c = $prefork->app->config($name) // {};
$self->upgrade_timeout($c->{upgrade_timeout}) if $c->{upgrade_timeout};
# Pre-fork settings
$prefork->reverse_proxy($c->{proxy}) if defined $c->{proxy};
$prefork->max_clients($c->{clients}) if $c->{clients};
$prefork->max_requests($c->{requests}) if $c->{requests};
defined $c->{$_} and $prefork->$_($c->{$_})
for qw(accepts backlog graceful_timeout heartbeat_interval heartbeat_timeout inactivity_timeout keep_alive_timeout),
qw(listen pid_file spare workers);
}
sub run {
my ($self, $app) = @_;
# No fork emulation support
_exit('Hypnotoad does not support fork emulation.') if $Config{d_pseudofork};
# Remember executable and application for later
$ENV{HYPNOTOAD_EXE} ||= $0;
$0 = $ENV{HYPNOTOAD_APP} ||= path($app)->to_abs->to_string;
# This is a production server
$ENV{MOJO_MODE} ||= 'production';
# Clean start (to make sure everything works)
die "Can't exec: $!" if !$ENV{HYPNOTOAD_REV}++ && !exec $^X, $ENV{HYPNOTOAD_EXE};
# Preload application and configure server
my $prefork = $self->prefork->cleanup(0);
$app = $prefork->load_app($app);
$app->config->{hypnotoad}{pid_file} //= path($ENV{HYPNOTOAD_APP})->sibling('hypnotoad.pid')->to_string;
$self->configure('hypnotoad');
weaken $self;
$prefork->on(wait => sub { $self->_manage });
$prefork->on(reap => sub { $self->_cleanup(pop) });
$prefork->on(finish => sub { $self->_finish });
# Testing
_exit('Everything looks good!') if $ENV{HYPNOTOAD_TEST};
# Stop running server
$self->_stop if $ENV{HYPNOTOAD_STOP};
# Initiate hot deployment
$self->_hot_deploy unless $ENV{HYPNOTOAD_PID};
# Daemonize as early as possible (but not for restarts)
local $SIG{USR2} = sub { $self->{upgrade} ||= steady_time };
$prefork->start;
$prefork->daemonize if !$ENV{HYPNOTOAD_FOREGROUND} && $ENV{HYPNOTOAD_REV} < 3;
# Start accepting connections
$prefork->cleanup(1)->run;
}
sub _cleanup {
my ($self, $pid) = @_;
# Clean up failed upgrade
return unless ($self->{new} || '') eq $pid;
$self->prefork->app->log->error('Zero downtime software upgrade failed');
delete @$self{qw(new upgrade)};
}
sub _exit { say shift and exit 0 }
sub _finish {
my $self = shift;
$self->{finish} = 1;
return unless my $new = $self->{new};
my $prefork = $self->prefork->cleanup(0);
path($prefork->pid_file)->remove;
$prefork->ensure_pid_file($new);
}
sub _hot_deploy {
# Make sure server is running
return unless my $pid = shift->prefork->check_pid;
# Start hot deployment
kill 'USR2', $pid;
_exit("Starting hot deployment for Hypnotoad server $pid.");
}
sub _manage {
my $self = shift;
# Upgraded (wait for all workers to send a heartbeat)
my $prefork = $self->prefork;
my $log = $prefork->app->log;
if ($ENV{HYPNOTOAD_PID} && $ENV{HYPNOTOAD_PID} ne $$) {
return unless $prefork->healthy == $prefork->workers;
$log->info("Upgrade successful, stopping $ENV{HYPNOTOAD_PID}");
kill 'QUIT', $ENV{HYPNOTOAD_PID};
}
$ENV{HYPNOTOAD_PID} = $$ unless ($ENV{HYPNOTOAD_PID} // '') eq $$;
# Upgrade
if ($self->{upgrade} && !$self->{finished}) {
# Fresh start
my $ut = $self->upgrade_timeout;
unless ($self->{new}) {
$log->info("Starting zero downtime software upgrade ($ut seconds)");
die "Can't fork: $!" unless defined(my $pid = $self->{new} = fork);
exec $^X, $ENV{HYPNOTOAD_EXE} or die "Can't exec: $!" unless $pid;
}
# Timeout
kill 'KILL', $self->{new} if $self->{upgrade} + $ut <= steady_time;
}
}
sub _stop {
_exit('Hypnotoad server not running.') unless my $pid = shift->prefork->check_pid;
kill 'QUIT', $pid;
_exit("Stopping Hypnotoad server $pid gracefully.");
}
1;
=encoding utf8
=head1 NAME
Mojo::Server::Hypnotoad - A production web serv...ALL GLORY TO THE HYPNOTOAD!
=head1 SYNOPSIS
use Mojo::Server::Hypnotoad;
my $hypnotoad = Mojo::Server::Hypnotoad->new;
$hypnotoad->run('/home/sri/myapp.pl');
=head1 DESCRIPTION
L<Mojo::Server::Hypnotoad> is a full featured, UNIX optimized, pre-forking non-blocking I/O HTTP and WebSocket server,
built around the very well tested and reliable L<Mojo::Server::Prefork>, with IPv6, TLS, SNI, UNIX domain socket, Comet
(long polling), keep-alive, multiple event loop and hot deployment support that just works. Note that the server uses
signals for process management, so you should avoid modifying signal handlers in your applications.
To start applications with it you can use the L<hypnotoad> script, which listens on port C<8080>, automatically
daemonizes the server process and defaults to C<production> mode for L<Mojolicious> and L<Mojolicious::Lite>
applications.
$ hypnotoad ./myapp.pl
You can run the same command again for automatic hot deployment.
$ hypnotoad ./myapp.pl
Starting hot deployment for Hypnotoad server 31841.
This second invocation will load the application again, detect the process id file with it, and send a L</"USR2">
signal to the already running server.
For better scalability (epoll, kqueue) and to provide non-blocking name resolution, SOCKS5 as well as TLS support, the
optional modules L<EV> (4.32+), L<Net::DNS::Native> (0.15+), L<IO::Socket::Socks> (0.64+) and L<IO::Socket::SSL>
(2.009+) will be used automatically if possible. Individual features can also be disabled with the C<MOJO_NO_NNR>,
C<MOJO_NO_SOCKS> and C<MOJO_NO_TLS> environment variables.
See L<Mojolicious::Guides::Cookbook/"DEPLOYMENT"> for more.
=head1 MANAGER SIGNALS
The L<Mojo::Server::Hypnotoad> manager process can be controlled at runtime with the following signals.
=head2 INT, TERM
Shut down server immediately.
=head2 QUIT
Shut down server gracefully.
=head2 TTIN
Increase worker pool by one.
=head2 TTOU
Decrease worker pool by one.
=head2 USR2
Attempt zero downtime software upgrade (hot deployment) without losing any incoming connections.
Manager (old)
|- Worker [1]
|- Worker [2]
|- Worker [3]
|- Worker [4]
+- Manager (new)
|- Worker [1]
|- Worker [2]
|- Worker [3]
+- Worker [4]
The new manager will automatically send a L</"QUIT"> signal to the old manager and take over serving requests after
starting up successfully.
=head1 WORKER SIGNALS
L<Mojo::Server::Hypnotoad> worker processes can be controlled at runtime with the following signals.
=head2 QUIT
Stop worker gracefully.
=head1 SETTINGS
L<Mojo::Server::Hypnotoad> can be configured with the following settings, see
L<Mojolicious::Guides::Cookbook/"Hypnotoad"> for examples.
=head2 accepts
accepts => 100
Maximum number of connections a worker is allowed to accept, before stopping gracefully and then getting replaced with
a newly started worker, defaults to the value of L<Mojo::Server::Prefork/"accepts">. Setting the value to C<0> will
allow workers to accept new connections indefinitely. Note that up to half of this value can be subtracted randomly to
improve load balancing, and to make sure that not all workers restart at the same time.
=head2 backlog
backlog => 128
Listen backlog size, defaults to the value of L<Mojo::Server::Daemon/"backlog">.
=head2 clients
clients => 100
Maximum number of accepted connections each worker process is allowed to handle concurrently, before stopping to accept
new incoming connections, defaults to the value of L<Mojo::IOLoop/"max_connections">. Note that high concurrency works
best with applications that perform mostly non-blocking operations, to optimize for blocking operations you can
decrease this value and increase L</"workers"> instead for better performance.
=head2 graceful_timeout
graceful_timeout => 15
Maximum amount of time in seconds stopping a worker gracefully may take before being forced, defaults to the value of
L<Mojo::Server::Prefork/"graceful_timeout">. Note that this value should usually be a little larger than the maximum
amount of time you expect any one request to take.
=head2 heartbeat_interval
heartbeat_interval => 3
Heartbeat interval in seconds, defaults to the value of L<Mojo::Server::Prefork/"heartbeat_interval">.
=head2 heartbeat_timeout
heartbeat_timeout => 2
Maximum amount of time in seconds before a worker without a heartbeat will be stopped gracefully, defaults to the value
of L<Mojo::Server::Prefork/"heartbeat_timeout">. Note that this value should usually be a little larger than the
maximum amount of time you expect any one operation to block the event loop.
=head2 inactivity_timeout
inactivity_timeout => 10
Maximum amount of time in seconds a connection with an active request can be inactive before getting closed, defaults
to the value of L<Mojo::Server::Daemon/"inactivity_timeout">. Setting the value to C<0> will allow connections to be
inactive indefinitely.
=head2 keep_alive_timeout
keep_alive_timeout => 10
Maximum amount of time in seconds a connection without an active request can be inactive before getting closed,
defaults to the value of L<Mojo::Server::Daemon/"keep_alive_timeout">. Setting the value to C<0> will allow connections
to be inactive indefinitely.
=head2 listen
listen => ['http://*:80']
Array reference with one or more locations to listen on, defaults to C<http://*:8080>. See also
L<Mojo::Server::Daemon/"listen"> for more examples.
=head2 pid_file
pid_file => '/var/run/hypnotoad.pid'
Full path to process id file, defaults to C<hypnotoad.pid> in the same directory as the application. Note that this
value can only be changed after the server has been stopped.
=head2 proxy
proxy => 1
Activate reverse proxy support, which allows for the C<X-Forwarded-For> and C<X-Forwarded-Proto> headers to be picked
up automatically, defaults to the value of L<Mojo::Server/"reverse_proxy">.
=head2 requests
requests => 50
Number of keep-alive requests per connection, defaults to the value of L<Mojo::Server::Daemon/"max_requests">.
=head2 spare
spare => 4
Temporarily spawn up to this number of additional workers if there is a need, defaults to the value of
L<Mojo::Server::Prefork/"spare">. This allows for new workers to be started while old ones are still shutting down
gracefully, drastically reducing the performance cost of worker restarts.
=head2 upgrade_timeout
upgrade_timeout => 45
Maximum amount of time in seconds a zero downtime software upgrade may take before getting canceled, defaults to
C<180>.
=head2 workers
workers => 10
Number of worker processes, defaults to the value of L<Mojo::Server::Prefork/"workers">. A good rule of thumb is two
worker processes per CPU core for applications that perform mostly non-blocking operations, blocking operations often
require more and benefit from decreasing concurrency with L</"clients"> (often as low as C<1>). Note that during zero
downtime software upgrades there will be twice as many workers active for a short amount of time.
=head1 ATTRIBUTES
L<Mojo::Server::Hypnotoad> implements the following attributes.
=head2 prefork
my $prefork = $hypnotoad->prefork;
$hypnotoad = $hypnotoad->prefork(Mojo::Server::Prefork->new);
L<Mojo::Server::Prefork> object this server manages.
=head2 upgrade_timeout
my $timeout = $hypnotoad->upgrade_timeout;
$hypnotoad = $hypnotoad->upgrade_timeout(15);
Maximum amount of time in seconds a zero downtime software upgrade may take before getting canceled, defaults to
C<180>.
=head1 METHODS
L<Mojo::Server::Hypnotoad> inherits all methods from L<Mojo::Base> and implements the following new ones.
=head2 configure
$hypnotoad->configure('hypnotoad');
Configure server from application settings.
=head2 run
$hypnotoad->run('script/my_app');
Run server for application and wait for L</"MANAGER SIGNALS">.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,147 @@
package Mojo::Server::Morbo;
use Mojo::Base -base;
# "Linda: With Haley's Comet out of ice, Earth is experiencing the devastating
# effects of sudden, intense global warming.
# Morbo: Morbo is pleased but sticky."
use Mojo::Loader qw(load_class);
use Mojo::Server::Daemon;
use POSIX qw(WNOHANG);
has backend => sub {
my $backend = $ENV{MOJO_MORBO_BACKEND} || 'Poll';
$backend = "Mojo::Server::Morbo::Backend::$backend";
return $backend->new unless my $e = load_class $backend;
die $e if ref $e;
die qq{Can't find Morbo backend class "$backend" in \@INC. (@INC)\n};
};
has daemon => sub { Mojo::Server::Daemon->new };
sub run {
my ($self, $app) = @_;
# Clean manager environment
local $SIG{INT} = local $SIG{TERM} = sub {
$self->{finished} = 1;
kill 'TERM', $self->{worker} if $self->{worker};
};
unshift @{$self->backend->watch}, $0 = $app;
$self->{modified} = 1;
# Prepare and cache listen sockets for smooth restarting
$self->daemon->start->stop;
$self->_manage until $self->{finished} && !$self->{worker};
exit 0;
}
sub _manage {
my $self = shift;
if (my @files = @{$self->backend->modified_files}) {
say @files == 1
? qq{File "@{[$files[0]]}" changed, restarting.}
: qq{@{[scalar @files]} files changed, restarting.}
if $ENV{MORBO_VERBOSE};
kill 'TERM', $self->{worker} if $self->{worker};
$self->{modified} = 1;
}
if (my $pid = $self->{worker}) {
delete $self->{worker} if waitpid($pid, WNOHANG) == $pid;
}
$self->_spawn if !$self->{worker} && delete $self->{modified};
}
sub _spawn {
my $self = shift;
# Manager
my $manager = $$;
die "Can't fork: $!" unless defined(my $pid = $self->{worker} = fork);
return if $pid;
# Worker
my $daemon = $self->daemon;
$daemon->load_app($self->backend->watch->[0])->server($daemon);
$daemon->ioloop->recurring(1 => sub { shift->stop unless kill 0, $manager });
$daemon->run;
exit 0;
}
1;
=encoding utf8
=head1 NAME
Mojo::Server::Morbo - Tonight at 11...DOOOOOOOOOOOOOOOM!
=head1 SYNOPSIS
use Mojo::Server::Morbo;
my $morbo = Mojo::Server::Morbo->new;
$morbo->run('/home/sri/myapp.pl');
=head1 DESCRIPTION
L<Mojo::Server::Morbo> is a full featured, self-restart capable non-blocking I/O HTTP and WebSocket server, built
around the very well tested and reliable L<Mojo::Server::Daemon>, with IPv6, TLS, SNI, UNIX domain socket, Comet (long
polling), keep-alive and multiple event loop support. Note that the server uses signals for process management, so you
should avoid modifying signal handlers in your applications.
To start applications with it you can use the L<morbo> script.
$ morbo ./myapp.pl
Web application available at http://127.0.0.1:3000
For better scalability (epoll, kqueue) and to provide non-blocking name resolution, SOCKS5 as well as TLS support, the
optional modules L<EV> (4.32+), L<Net::DNS::Native> (0.15+), L<IO::Socket::Socks> (0.64+) and L<IO::Socket::SSL>
(2.009+) will be used automatically if possible. Individual features can also be disabled with the C<MOJO_NO_NNR>,
C<MOJO_NO_SOCKS> and C<MOJO_NO_TLS> environment variables.
See L<Mojolicious::Guides::Cookbook/"DEPLOYMENT"> for more.
=head1 SIGNALS
The L<Mojo::Server::Morbo> process can be controlled at runtime with the following signals.
=head2 INT, TERM
Shut down server immediately.
=head1 ATTRIBUTES
L<Mojo::Server::Morbo> implements the following attributes.
=head2 backend
my $backend = $morbo->backend;
$morbo = $morbo->backend(Mojo::Server::Morbo::Backend::Poll->new);
Backend, usually a L<Mojo::Server::Morbo::Backend::Poll> object.
=head2 daemon
my $daemon = $morbo->daemon;
$morbo = $morbo->daemon(Mojo::Server::Daemon->new);
L<Mojo::Server::Daemon> object this server manages.
=head1 METHODS
L<Mojo::Server::Morbo> inherits all methods from L<Mojo::Base> and implements the following new ones.
=head2 run
$morbo->run('script/my_app');
Run server for application and wait for L</"SIGNALS">.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,69 @@
package Mojo::Server::Morbo::Backend;
use Mojo::Base -base;
use Carp qw(croak);
has watch => sub { [qw(lib templates)] };
has watch_timeout => sub { $ENV{MOJO_MORBO_TIMEOUT} || 1 };
sub modified_files { croak 'Method "modified_files" not implemented by subclass' }
1;
=encoding utf8
=head1 NAME
Mojo::Server::Morbo::Backend - Morbo backend base class
=head1 SYNOPSIS
package Mojo::Server::Morbo::Backend::Inotify:
use Mojo::Base 'Mojo::Server::Morbo::Backend';
sub modified_files {...}
=head1 DESCRIPTION
L<Mojo::Server::Morbo::Backend> is an abstract base class for Morbo backends, like
L<Mojo::Server::Morbo::Backend::Poll>.
=head1 ATTRIBUTES
L<Mojo::Server::Morbo::Backend> implements the following attributes.
=head2 watch
my $watch = $backend->watch;
$backend = $backend->watch(['/home/sri/my_app']);
Files and directories to watch for changes, defaults to the application script as well as the C<lib> and C<templates>
directories in the current working directory.
=head2 watch_timeout
my $timeout = $backend->watch_timeout;
$backend = $backend->watch_timeout(10);
Maximum amount of time in seconds a backend may block when waiting for files to change, defaults to the value of the
C<MOJO_MORBO_TIMEOUT> environment variable or C<1>.
=head1 METHODS
L<Mojo::Server::Morbo::Backend> inherits all methods from L<Mojo::Base> and implements the following new ones.
=head2 modified_files
my $files = $backend->modified_files;
Check if files from L</"watch"> have been modified since the last check and return an array reference with the results.
Meant to be overloaded in a subclass.
# All files that have been modified
say for @{$backend->modified_files};
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,69 @@
package Mojo::Server::Morbo::Backend::Poll;
use Mojo::Base 'Mojo::Server::Morbo::Backend';
use Mojo::File qw(path);
sub modified_files {
my $self = shift;
my $cache = $self->{cache} //= {};
my @files;
for my $file (map { -f $_ && -r _ ? $_ : _list($_) } @{$self->watch}) {
my ($size, $mtime) = (stat $file)[7, 9];
next unless defined $size and defined $mtime;
my $stats = $cache->{$file} ||= [$^T, $size];
next if $mtime <= $stats->[0] && $size == $stats->[1];
@$stats = ($mtime, $size);
push @files, $file;
}
sleep $self->watch_timeout unless @files;
return \@files;
}
sub _list { path(shift)->list_tree->map('to_string')->each }
1;
=encoding utf8
=head1 NAME
Mojo::Server::Morbo::Backend::Poll - Morbo default backend
=head1 SYNOPSIS
use Mojo::Server::Morbo::Backend::Poll;
my $backend = Mojo::Server::Morbo::Backend::Poll->new;
if (my $files = $backend->modified_files) {
...
}
=head1 DESCRIPTION
L<Mojo::Server::Morbo::Backend:Poll> is the default backend for L<Mojo::Server::Morbo>.
=head1 ATTRIBUTES
L<Mojo::Server::Morbo::Backend::Poll> inherits all attributes from L<Mojo::Server::Morbo::Backend>.
=head1 METHODS
L<Mojo::Server::Morbo::Backend::Poll> inherits all methods from L<Mojo::Server::Morbo::Backend> and implements the
following new ones.
=head2 modified_files
my $files = $backend->modified_files;
Check file size and mtime to determine which files have changed, this is not particularly efficient, but very portable.
# All files that have been modified
say for @{$backend->modified_files};
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,127 @@
package Mojo::Server::PSGI;
use Mojo::Base 'Mojo::Server';
sub run {
my ($self, $env) = @_;
my $tx = $self->build_tx;
my $req = $tx->req->parse($env);
$tx->local_port($env->{SERVER_PORT})->remote_address($env->{REMOTE_ADDR});
# Request body (may block if we try to read too much)
my $len = $env->{CONTENT_LENGTH};
until ($req->is_finished) {
my $chunk = ($len && $len < 131072) ? $len : 131072;
last unless my $read = $env->{'psgi.input'}->read(my $buffer, $chunk, 0);
$req->parse($buffer);
last if ($len -= $read) <= 0;
}
$self->emit(request => $tx);
# Response headers
my $res = $tx->res->fix_headers;
my $hash = $res->headers->to_hash(1);
my @headers;
for my $name (keys %$hash) { push @headers, $name, $_ for @{$hash->{$name}} }
# PSGI response
my $io = Mojo::Server::PSGI::_IO->new(tx => $tx, empty => $tx->is_empty);
return [$res->code // 404, \@headers, $io];
}
sub to_psgi_app {
my $self = shift;
# Preload application and wrap it
$self->app->server($self);
return sub { $self->run(@_) }
}
package Mojo::Server::PSGI::_IO;
use Mojo::Base -base;
# Finish transaction
sub close { shift->{tx}->closed }
sub getline {
my $self = shift;
# Empty
return undef if $self->{empty};
# No content yet, try again later
my $chunk = $self->{tx}->res->get_body_chunk($self->{offset} //= 0);
return '' unless defined $chunk;
# End of content
return undef unless length $chunk;
$self->{offset} += length $chunk;
return $chunk;
}
1;
=encoding utf8
=head1 NAME
Mojo::Server::PSGI - PSGI server
=head1 SYNOPSIS
use Mojo::Server::PSGI;
my $psgi = Mojo::Server::PSGI->new;
$psgi->unsubscribe('request')->on(request => sub ($psgi, $tx) {
# Request
my $method = $tx->req->method;
my $path = $tx->req->url->path;
# Response
$tx->res->code(200);
$tx->res->headers->content_type('text/plain');
$tx->res->body("$method request for $path!");
# Resume transaction
$tx->resume;
});
my $app = $psgi->to_psgi_app;
=head1 DESCRIPTION
L<Mojo::Server::PSGI> allows L<Mojolicious> applications to run on all L<PSGI> compatible servers.
See L<Mojolicious::Guides::Cookbook/"DEPLOYMENT"> for more.
=head1 EVENTS
L<Mojo::Server::PSGI> inherits all events from L<Mojo::Server>.
=head1 ATTRIBUTES
L<Mojo::Server::PSGI> inherits all attributes from L<Mojo::Server>.
=head1 METHODS
L<Mojo::Server::PSGI> inherits all methods from L<Mojo::Server> and implements the following new ones.
=head2 run
my $res = $psgi->run($env);
Run L<PSGI>.
=head2 to_psgi_app
my $app = $psgi->to_psgi_app;
Turn L<Mojolicious> application into L<PSGI> application.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,412 @@
package Mojo::Server::Prefork;
use Mojo::Base 'Mojo::Server::Daemon';
use Config;
use File::Spec::Functions qw(tmpdir);
use Mojo::File qw(path);
use Mojo::Util qw(steady_time);
use POSIX qw(WNOHANG);
use Scalar::Util qw(weaken);
has accepts => 10000;
has cleanup => 1;
has graceful_timeout => 120;
has heartbeat_timeout => 50;
has heartbeat_interval => 5;
has pid_file => sub { path(tmpdir, 'prefork.pid')->to_string };
has spare => 2;
has workers => 4;
sub DESTROY { path($_[0]->pid_file)->remove if $_[0]->cleanup }
sub check_pid {
return undef unless -r (my $file = path(shift->pid_file));
my $pid = $file->slurp;
chomp $pid;
# Running
return $pid if $pid && kill 0, $pid;
# Not running
$file->remove;
return undef;
}
sub ensure_pid_file {
my ($self, $pid) = @_;
# Check if PID file already exists
return if -e (my $file = path($self->pid_file));
# Create PID file
if (my $err = eval { $file->spurt("$pid\n")->chmod(0644) } ? undef : $@) {
$self->app->log->error(qq{Can't create process id file "$file": $err})
and die qq{Can't create process id file "$file": $err};
}
$self->app->log->info(qq{Creating process id file "$file"});
}
sub healthy {
scalar grep { $_->{healthy} } values %{shift->{pool}};
}
sub run {
my $self = shift;
# No fork emulation support
say 'Pre-forking does not support fork emulation.' and exit 0 if $Config{d_pseudofork};
# Pipe for worker communication
pipe($self->{reader}, $self->{writer}) or die "Can't create pipe: $!";
# Clean manager environment
local $SIG{CHLD} = sub {
while ((my $pid = waitpid -1, WNOHANG) > 0) { $self->emit(reap => $pid)->_stopped($pid) }
};
local $SIG{INT} = local $SIG{TERM} = sub { $self->_term };
local $SIG{QUIT} = sub { $self->_term(1) };
local $SIG{TTIN} = sub { $self->workers($self->workers + 1) };
local $SIG{TTOU} = sub {
$self->workers > 0 ? $self->workers($self->workers - 1) : return;
for my $w (values %{$self->{pool}}) { ($w->{graceful} = steady_time) and last unless $w->{graceful} }
};
# Preload application before starting workers
$self->start->app->log->info("Manager $$ started");
$self->ioloop->max_accepts($self->accepts);
$self->{running} = 1;
$self->_manage while $self->{running};
$self->app->log->info("Manager $$ stopped");
}
sub _heartbeat { shift->{writer}->syswrite("$$:$_[0]\n") or exit 0 }
sub _manage {
my $self = shift;
# Spawn more workers if necessary and check PID file
if (!$self->{finished}) {
my $graceful = grep { $_->{graceful} } values %{$self->{pool}};
my $spare = $self->spare;
$spare = $graceful ? $graceful > $spare ? $spare : $graceful : 0;
my $need = ($self->workers - keys %{$self->{pool}}) + $spare;
$self->_spawn while $need-- > 0;
$self->ensure_pid_file($$);
}
# Shutdown
elsif (!keys %{$self->{pool}}) { return delete $self->{running} }
# Wait for heartbeats
$self->_wait;
my $interval = $self->heartbeat_interval;
my $ht = $self->heartbeat_timeout;
my $gt = $self->graceful_timeout;
my $log = $self->app->log;
my $time = steady_time;
for my $pid (keys %{$self->{pool}}) {
next unless my $w = $self->{pool}{$pid};
# No heartbeat (graceful stop)
$log->error("Worker $pid has no heartbeat ($ht seconds), restarting") and $w->{graceful} = $time
if !$w->{graceful} && ($w->{time} + $interval + $ht <= $time);
# Graceful stop with timeout
my $graceful = $w->{graceful} ||= $self->{graceful} ? $time : undef;
$log->info("Stopping worker $pid gracefully ($gt seconds)") and (kill 'QUIT', $pid or $self->_stopped($pid))
if $graceful && !$w->{quit}++;
$w->{force} = 1 if $graceful && $graceful + $gt <= $time;
# Normal stop
$log->warn("Stopping worker $pid immediately") and (kill 'KILL', $pid or $self->_stopped($pid))
if $w->{force} || ($self->{finished} && !$graceful);
}
}
sub _spawn {
my $self = shift;
# Manager
die "Can't fork: $!" unless defined(my $pid = fork);
return $self->emit(spawn => $pid)->{pool}{$pid} = {time => steady_time} if $pid;
# Heartbeat messages
my $loop = $self->cleanup(0)->ioloop;
my $finished = 0;
$loop->on(finish => sub { $finished = 1 });
weaken $self;
my $cb = sub { $self->_heartbeat($finished) };
$loop->next_tick($cb);
$loop->recurring($self->heartbeat_interval => $cb);
# Clean worker environment
$SIG{$_} = 'DEFAULT' for qw(CHLD INT TERM TTIN TTOU);
$SIG{QUIT} = sub { $loop->stop_gracefully };
$loop->on(finish => sub { $self->max_requests(1) });
delete $self->{reader};
srand;
$self->app->log->info("Worker $$ started");
$loop->start;
exit 0;
}
sub _stopped {
my ($self, $pid) = @_;
return unless my $w = delete $self->{pool}{$pid};
my $log = $self->app->log;
$log->info("Worker $pid stopped");
$log->error("Worker $pid stopped too early, shutting down") and $self->_term unless $w->{healthy};
}
sub _term {
my ($self, $graceful) = @_;
@{$self->emit(finish => $graceful)}{qw(finished graceful)} = (1, $graceful);
}
sub _wait {
my $self = shift;
# Poll for heartbeats
my $reader = $self->emit('wait')->{reader};
return unless Mojo::Util::_readable(1000, fileno($reader));
return unless $reader->sysread(my $chunk, 4194304);
# Update heartbeats (and stop gracefully if necessary)
my $time = steady_time;
while ($chunk =~ /(\d+):(\d)\n/g) {
next unless my $w = $self->{pool}{$1};
@$w{qw(healthy time)} = (1, $time) and $self->emit(heartbeat => $1);
$w->{graceful} ||= $time if $2;
}
}
1;
=encoding utf8
=head1 NAME
Mojo::Server::Prefork - Pre-forking non-blocking I/O HTTP and WebSocket server
=head1 SYNOPSIS
use Mojo::Server::Prefork;
my $prefork = Mojo::Server::Prefork->new(listen => ['http://*:8080']);
$prefork->unsubscribe('request')->on(request => sub ($prefork, $tx) {
# Request
my $method = $tx->req->method;
my $path = $tx->req->url->path;
# Response
$tx->res->code(200);
$tx->res->headers->content_type('text/plain');
$tx->res->body("$method request for $path!");
# Resume transaction
$tx->resume;
});
$prefork->run;
=head1 DESCRIPTION
L<Mojo::Server::Prefork> is a full featured, UNIX optimized, pre-forking non-blocking I/O HTTP and WebSocket server,
built around the very well tested and reliable L<Mojo::Server::Daemon>, with IPv6, TLS, SNI, UNIX domain socket, Comet
(long polling), keep-alive and multiple event loop support. Note that the server uses signals for process management,
so you should avoid modifying signal handlers in your applications.
For better scalability (epoll, kqueue) and to provide non-blocking name resolution, SOCKS5 as well as TLS support, the
optional modules L<EV> (4.32+), L<Net::DNS::Native> (0.15+), L<IO::Socket::Socks> (0.64+) and L<IO::Socket::SSL>
(1.84+) will be used automatically if possible. Individual features can also be disabled with the C<MOJO_NO_NNR>,
C<MOJO_NO_SOCKS> and C<MOJO_NO_TLS> environment variables.
See L<Mojolicious::Guides::Cookbook/"DEPLOYMENT"> for more.
=head1 MANAGER SIGNALS
The L<Mojo::Server::Prefork> manager process can be controlled at runtime with the following signals.
=head2 INT, TERM
Shut down server immediately.
=head2 QUIT
Shut down server gracefully.
=head2 TTIN
Increase worker pool by one.
=head2 TTOU
Decrease worker pool by one.
=head1 WORKER SIGNALS
L<Mojo::Server::Prefork> worker processes can be controlled at runtime with the following signals.
=head2 QUIT
Stop worker gracefully.
=head1 EVENTS
L<Mojo::Server::Prefork> inherits all events from L<Mojo::Server::Daemon> and can emit the following new ones.
=head2 finish
$prefork->on(finish => sub ($prefork, $graceful) {...});
Emitted when the server shuts down.
$prefork->on(finish => sub ($prefork, $graceful) {
say $graceful ? 'Graceful server shutdown' : 'Server shutdown';
});
=head2 heartbeat
$prefork->on(heartbeat => sub ($prefork, $pid) {...});
Emitted when a heartbeat message has been received from a worker.
$prefork->on(heartbeat => sub ($prefork, $pid) { say "Worker $pid has a heartbeat" });
=head2 reap
$prefork->on(reap => sub ($prefork, $pid) {...});
Emitted when a child process exited.
$prefork->on(reap => sub ($prefork, $pid) { say "Worker $pid stopped" });
=head2 spawn
$prefork->on(spawn => sub ($prefork, $pid) {...});
Emitted when a worker process is spawned.
$prefork->on(spawn => sub ($prefork, $pid) { say "Worker $pid started" });
=head2 wait
$prefork->on(wait => sub ($prefork) {...});
Emitted when the manager starts waiting for new heartbeat messages.
$prefork->on(wait => sub ($prefork) {
my $workers = $prefork->workers;
say "Waiting for heartbeat messages from $workers workers";
});
=head1 ATTRIBUTES
L<Mojo::Server::Prefork> inherits all attributes from L<Mojo::Server::Daemon> and implements the following new ones.
=head2 accepts
my $accepts = $prefork->accepts;
$prefork = $prefork->accepts(100);
Maximum number of connections a worker is allowed to accept, before stopping gracefully and then getting replaced with
a newly started worker, passed along to L<Mojo::IOLoop/"max_accepts">, defaults to C<10000>. Setting the value to C<0>
will allow workers to accept new connections indefinitely. Note that up to half of this value can be subtracted
randomly to improve load balancing, and to make sure that not all workers restart at the same time.
=head2 cleanup
my $bool = $prefork->cleanup;
$prefork = $prefork->cleanup($bool);
Delete L</"pid_file"> automatically once it is not needed anymore, defaults to a true value.
=head2 graceful_timeout
my $timeout = $prefork->graceful_timeout;
$prefork = $prefork->graceful_timeout(15);
Maximum amount of time in seconds stopping a worker gracefully may take before being forced, defaults to C<120>. Note
that this value should usually be a little larger than the maximum amount of time you expect any one request to take.
=head2 heartbeat_interval
my $interval = $prefork->heartbeat_interval;
$prefork = $prefork->heartbeat_interval(3);
Heartbeat interval in seconds, defaults to C<5>.
=head2 heartbeat_timeout
my $timeout = $prefork->heartbeat_timeout;
$prefork = $prefork->heartbeat_timeout(2);
Maximum amount of time in seconds before a worker without a heartbeat will be stopped gracefully, defaults to C<50>.
Note that this value should usually be a little larger than the maximum amount of time you expect any one operation to
block the event loop.
=head2 pid_file
my $file = $prefork->pid_file;
$prefork = $prefork->pid_file('/tmp/prefork.pid');
Full path of process id file, defaults to C<prefork.pid> in a temporary directory.
=head2 spare
my $spare = $prefork->spare;
$prefork = $prefork->spare(4);
Temporarily spawn up to this number of additional workers if there is a need, defaults to C<2>. This allows for new
workers to be started while old ones are still shutting down gracefully, drastically reducing the performance cost of
worker restarts.
=head2 workers
my $workers = $prefork->workers;
$prefork = $prefork->workers(10);
Number of worker processes, defaults to C<4>. A good rule of thumb is two worker processes per CPU core for
applications that perform mostly non-blocking operations, blocking operations often require more and benefit from
decreasing concurrency with L<Mojo::Server::Daemon/"max_clients"> (often as low as C<1>).
=head1 METHODS
L<Mojo::Server::Prefork> inherits all methods from L<Mojo::Server::Daemon> and implements the following new ones.
=head2 check_pid
my $pid = $prefork->check_pid;
Get process id for running server from L</"pid_file"> or delete it if server is not running.
say 'Server is not running' unless $prefork->check_pid;
=head2 ensure_pid_file
$prefork->ensure_pid_file($pid);
Ensure L</"pid_file"> exists.
=head2 healthy
my $healthy = $prefork->healthy;
Number of currently active worker processes with a heartbeat.
=head2 run
$prefork->run;
Run server and wait for L</"MANAGER SIGNALS">.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,672 @@
package Mojo::Template;
use Mojo::Base -base;
use Carp qw(croak);
use Mojo::ByteStream;
use Mojo::Exception;
use Mojo::File qw(path);
use Mojo::Util qw(decode encode monkey_patch);
use constant DEBUG => $ENV{MOJO_TEMPLATE_DEBUG} || 0;
has [qw(append code prepend unparsed)] => '';
has [qw(auto_escape compiled vars)];
has capture_end => 'end';
has capture_start => 'begin';
has comment_mark => '#';
has encoding => 'UTF-8';
has escape => sub { \&Mojo::Util::xml_escape };
has [qw(escape_mark expression_mark trim_mark)] => '=';
has [qw(line_start replace_mark)] => '%';
has name => 'template';
has namespace => 'Mojo::Template::Sandbox';
has tag_start => '<%';
has tag_end => '%>';
has tree => sub { [] };
sub parse {
my ($self, $template) = @_;
# Clean start
$self->unparsed($template)->tree(\my @tree)->compiled(undef);
my $tag = $self->tag_start;
my $replace = $self->replace_mark;
my $expr = $self->expression_mark;
my $escp = $self->escape_mark;
my $cpen = $self->capture_end;
my $cmnt = $self->comment_mark;
my $cpst = $self->capture_start;
my $trim = $self->trim_mark;
my $end = $self->tag_end;
my $start = $self->line_start;
my $line_re = qr/^(\s*)\Q$start\E(?:(\Q$replace\E)|(\Q$cmnt\E)|(\Q$expr\E))?(.*)$/;
my $token_re = qr/
(
\Q$tag\E(?:\Q$replace\E|\Q$cmnt\E) # Replace
|
\Q$tag$expr\E(?:\Q$escp\E)?(?:\s*\Q$cpen\E(?!\w))? # Expression
|
\Q$tag\E(?:\s*\Q$cpen\E(?!\w))? # Code
|
(?:(?<!\w)\Q$cpst\E\s*)?(?:\Q$trim\E)?\Q$end\E # End
)
/x;
my $cpen_re = qr/^\Q$tag\E(?:\Q$expr\E)?(?:\Q$escp\E)?\s*\Q$cpen\E(.*)$/;
my $end_re = qr/^(?:(\Q$cpst\E)\s*)?(\Q$trim\E)?\Q$end\E$/;
# Split lines
my $op = 'text';
my ($trimming, $capture);
for my $line (split /\n/, $template) {
# Turn Perl line into mixed line
if ($op eq 'text' && $line =~ $line_re) {
# Escaped start
if ($2) { $line = "$1$start$5" }
# Comment
elsif ($3) { $line = "$tag$3 $trim$end" }
# Expression or code
else { $line = $4 ? "$1$tag$4$5 $end" : "$tag$5 $trim$end" }
}
# Escaped line ending
$line .= "\n" if $line !~ s/\\\\$/\\\n/ && $line !~ s/\\$//;
# Mixed line
for my $token (split $token_re, $line) {
# Capture end
($token, $capture) = ("$tag$1", 1) if $token =~ $cpen_re;
# End
if ($op ne 'text' && $token =~ $end_re) {
# Capture start
splice @tree, -1, 0, ['cpst'] if $1;
# Trim left side
_trim(\@tree) if ($trimming = $2) && @tree > 1;
# Hint at end
push @tree, [$op = 'text', ''];
}
# Code
elsif ($token eq $tag) { $op = 'code' }
# Expression
elsif ($token eq "$tag$expr") { $op = 'expr' }
# Expression that needs to be escaped
elsif ($token eq "$tag$expr$escp") { $op = 'escp' }
# Comment
elsif ($token eq "$tag$cmnt") { $op = 'cmnt' }
# Text (comments are just ignored)
elsif ($op ne 'cmnt') {
# Replace
$token = $tag if $token eq "$tag$replace";
# Trim right side (convert whitespace to line noise)
if ($trimming && $token =~ s/^(\s+)//) {
push @tree, ['code', $1];
$trimming = 0;
}
# Token (with optional capture end)
push @tree, $capture ? ['cpen'] : (), [$op, $token];
$capture = 0;
}
}
# Optimize successive text lines separated by a newline
push @tree, ['line'] and next
if $tree[-4] && $tree[-4][0] ne 'line'
|| (!$tree[-3] || $tree[-3][0] ne 'text' || $tree[-3][1] !~ /\n$/)
|| ($tree[-2][0] ne 'line' || $tree[-1][0] ne 'text');
$tree[-3][1] .= pop(@tree)->[1];
}
return $self;
}
sub process {
my $self = shift;
# Use a local stack trace for compile exceptions
my $compiled = $self->compiled;
unless ($compiled) {
my $code = $self->_compile->code;
monkey_patch $self->namespace, '_escape', $self->escape;
return Mojo::Exception->new($@)->inspect($self->unparsed, $code)->trace->verbose(1)
unless $compiled = eval $self->_wrap($code, @_);
$self->compiled($compiled);
}
# Use a real stack trace for normal exceptions
local $SIG{__DIE__} = sub {
CORE::die $_[0] if ref $_[0];
CORE::die Mojo::Exception->new(shift)->trace->inspect($self->unparsed, $self->code)->verbose(1);
};
my $output;
return eval { $output = $compiled->(@_); 1 } ? $output : $@;
}
sub render { shift->parse(shift)->process(@_) }
sub render_file {
my ($self, $path) = (shift, shift);
$self->name($path) unless defined $self->{name};
my $template = path($path)->slurp;
my $encoding = $self->encoding;
croak qq{Template "$path" has invalid encoding} if $encoding && !defined($template = decode $encoding, $template);
return $self->render($template, @_);
}
sub _compile {
my $self = shift;
my $tree = $self->tree;
my $escape = $self->auto_escape;
my @blocks = ('');
my ($i, $capture, $multi);
while (++$i <= @$tree && (my $next = $tree->[$i])) {
my ($op, $value) = @{$tree->[$i - 1]};
push @blocks, '' and next if $op eq 'line';
my $newline = chomp($value //= '');
# Text (quote and fix line ending)
if ($op eq 'text') {
$value = join "\n", map { quotemeta $_ } split(/\n/, $value, -1);
$value .= '\n' if $newline;
$blocks[-1] .= "\$_O .= \"" . $value . "\";" if length $value;
}
# Code or multi-line expression
elsif ($op eq 'code' || $multi) { $blocks[-1] .= $value }
# Capture end
elsif ($op eq 'cpen') {
$blocks[-1] .= 'return Mojo::ByteStream->new($_O) }';
# No following code
$blocks[-1] .= ';' if $next->[0] ne 'cpst' && ($next->[1] // '') =~ /^\s*$/;
}
# Expression
if ($op eq 'expr' || $op eq 'escp') {
# Escaped
if (!$multi && ($op eq 'escp' && !$escape || $op eq 'expr' && $escape)) {
$blocks[-1] .= "\$_O .= _escape scalar + $value";
}
# Raw
elsif (!$multi) { $blocks[-1] .= "\$_O .= scalar + $value" }
# Multi-line
$multi = !$next || $next->[0] ne 'text';
# Append semicolon
$blocks[-1] .= ';' unless $multi || $capture;
}
# Capture start
if ($op eq 'cpst') { $capture = 1 }
elsif ($capture) {
$blocks[-1] .= "sub { my \$_O = ''; ";
$capture = 0;
}
}
return $self->code(join "\n", @blocks)->tree([]);
}
sub _line {
my $name = shift->name;
$name =~ y/"//d;
return qq{#line @{[shift]} "$name"};
}
sub _trim {
my $tree = shift;
# Skip captures
my $i = $tree->[-2][0] eq 'cpst' || $tree->[-2][0] eq 'cpen' ? -3 : -2;
# Only trim text
return unless $tree->[$i][0] eq 'text';
# Convert whitespace text to line noise
splice @$tree, $i, 0, ['code', $1] if $tree->[$i][1] =~ s/(\s+)$//;
}
sub _wrap {
my ($self, $body, $vars) = @_;
# Variables
my $args = '';
if ($self->vars && (my @vars = grep {/^\w+$/} keys %$vars)) {
$args = 'my (' . join(',', map {"\$$_"} @vars) . ')';
$args .= '= @{shift()}{qw(' . join(' ', @vars) . ')};';
}
# Wrap lines
my $num = () = $body =~ /\n/g;
my $code = $self->_line(1) . "\npackage @{[$self->namespace]};";
$code .= "use Mojo::Base -strict; no warnings 'ambiguous';";
$code .= "sub { my \$_O = ''; @{[$self->prepend]};{ $args { $body\n";
$code .= $self->_line($num + 1) . "\n;}@{[$self->append]}; } \$_O };";
warn "-- Code for @{[$self->name]}\n@{[encode 'UTF-8', $code]}\n\n" if DEBUG;
return $code;
}
1;
=encoding utf8
=head1 NAME
Mojo::Template - Perl-ish templates
=head1 SYNOPSIS
use Mojo::Template;
# Use Perl modules
my $mt = Mojo::Template->new;
say $mt->render(<<'EOF');
% use Time::Piece;
<div>
% my $now = localtime;
Time: <%= $now->hms %>
</div>
EOF
# Render with arguments
say $mt->render(<<'EOF', [1 .. 13], 'Hello World!');
% my ($numbers, $title) = @_;
<div>
<h1><%= $title %></h1>
% for my $i (@$numbers) {
Test <%= $i %>
% }
</div>
EOF
# Render with named variables
say $mt->vars(1)->render(<<'EOF', {title => 'Hello World!'});
<div>
<h1><%= $title %></h1>
%= 5 + 5
</div>
EOF
=head1 DESCRIPTION
L<Mojo::Template> is a minimalistic, fast, and very Perl-ish template engine, designed specifically for all those small
tasks that come up during big projects. Like preprocessing a configuration file, generating text from heredocs and
stuff like that.
See L<Mojolicious::Guides::Rendering> for information on how to generate content with the L<Mojolicious> renderer.
=head1 SYNTAX
For all templates L<strict>, L<warnings>, L<utf8> and Perl 5.16 L<features|feature> are automatically enabled.
<% Perl code %>
<%= Perl expression, replaced with result %>
<%== Perl expression, replaced with XML escaped result %>
<%# Comment, useful for debugging %>
<%% Replaced with "<%", useful for generating templates %>
% Perl code line, treated as "<% line =%>" (explained later)
%= Perl expression line, treated as "<%= line %>"
%== Perl expression line, treated as "<%== line %>"
%# Comment line, useful for debugging
%% Replaced with "%", useful for generating templates
Escaping behavior can be reversed with the L</"auto_escape"> attribute, this is the default in L<Mojolicious> C<.ep>
templates, for example.
<%= Perl expression, replaced with XML escaped result %>
<%== Perl expression, replaced with result %>
L<Mojo::ByteStream> objects are always excluded from automatic escaping.
% use Mojo::ByteStream qw(b);
<%= b('<div>excluded!</div>') %>
Whitespace characters around tags can be trimmed by adding an additional equal sign to the end of a tag.
<% for (1 .. 3) { %>
<%= 'Trim all whitespace characters around this expression' =%>
<% } %>
Newline characters can be escaped with a backslash.
This is <%= 1 + 1 %> a\
single line
And a backslash in front of a newline character can be escaped with another backslash.
This will <%= 1 + 1 %> result\\
in multiple\\
lines
A newline character gets appended automatically to every template, unless the last character is a backslash. And empty
lines at the end of a template are ignored.
There is <%= 1 + 1 %> no newline at the end here\
You can capture whole template blocks for reuse later with the C<begin> and C<end> keywords. Just be aware that both
keywords are part of the surrounding tag and not actual Perl code, so there can only be whitespace after C<begin> and
before C<end>.
<% my $block = begin %>
<% my $name = shift; =%>
Hello <%= $name %>.
<% end %>
<%= $block->('Baerbel') %>
<%= $block->('Wolfgang') %>
Perl lines can also be indented freely.
% my $block = begin
% my $name = shift;
Hello <%= $name %>.
% end
%= $block->('Baerbel')
%= $block->('Wolfgang')
L<Mojo::Template> templates get compiled to a Perl subroutine, that means you can access arguments simply via C<@_>.
% my ($foo, $bar) = @_;
% my $x = shift;
test 123 <%= $foo %>
The compilation of templates to Perl code can make debugging a bit tricky, but L<Mojo::Template> will return
L<Mojo::Exception> objects that stringify to error messages with context.
Bareword "xx" not allowed while "strict subs" in use at template line 4.
Context:
2: </head>
3: <body>
4: % my $i = 2; xx
5: %= $i * 2
6: </body>
Traceback (most recent call first):
File "template", line 4, in "Mojo::Template::Sandbox"
File "path/to/Mojo/Template.pm", line 123, in "Mojo::Template"
File "path/to/myapp.pl", line 123, in "main"
=head1 ATTRIBUTES
L<Mojo::Template> implements the following attributes.
=head2 auto_escape
my $bool = $mt->auto_escape;
$mt = $mt->auto_escape($bool);
Activate automatic escaping.
# "&lt;html&gt;"
Mojo::Template->new(auto_escape => 1)->render("<%= '<html>' %>");
=head2 append
my $code = $mt->append;
$mt = $mt->append('warn "Processed template"');
Append Perl code to compiled template. Note that this code should not contain newline characters, or line numbers in
error messages might end up being wrong.
=head2 capture_end
my $end = $mt->capture_end;
$mt = $mt->capture_end('end');
Keyword indicating the end of a capture block, defaults to C<end>.
<% my $block = begin %>
Some data!
<% end %>
=head2 capture_start
my $start = $mt->capture_start;
$mt = $mt->capture_start('begin');
Keyword indicating the start of a capture block, defaults to C<begin>.
<% my $block = begin %>
Some data!
<% end %>
=head2 code
my $code = $mt->code;
$mt = $mt->code($code);
Perl code for template if available.
=head2 comment_mark
my $mark = $mt->comment_mark;
$mt = $mt->comment_mark('#');
Character indicating the start of a comment, defaults to C<#>.
<%# This is a comment %>
=head2 compiled
my $compiled = $mt->compiled;
$mt = $mt->compiled($compiled);
Compiled template code if available.
=head2 encoding
my $encoding = $mt->encoding;
$mt = $mt->encoding('UTF-8');
Encoding used for template files, defaults to C<UTF-8>.
=head2 escape
my $cb = $mt->escape;
$mt = $mt->escape(sub {...});
A callback used to escape the results of escaped expressions, defaults to L<Mojo::Util/"xml_escape">.
$mt->escape(sub ($str) { return reverse $str });
=head2 escape_mark
my $mark = $mt->escape_mark;
$mt = $mt->escape_mark('=');
Character indicating the start of an escaped expression, defaults to C<=>.
<%== $foo %>
=head2 expression_mark
my $mark = $mt->expression_mark;
$mt = $mt->expression_mark('=');
Character indicating the start of an expression, defaults to C<=>.
<%= $foo %>
=head2 line_start
my $start = $mt->line_start;
$mt = $mt->line_start('%');
Character indicating the start of a code line, defaults to C<%>.
% $foo = 23;
=head2 name
my $name = $mt->name;
$mt = $mt->name('foo.mt');
Name of template currently being processed, defaults to C<template>. Note that this value should not contain quotes or
newline characters, or error messages might end up being wrong.
=head2 namespace
my $namespace = $mt->namespace;
$mt = $mt->namespace('main');
Namespace used to compile templates, defaults to C<Mojo::Template::Sandbox>. Note that namespaces should only be shared
very carefully between templates, since functions and global variables will not be cleared automatically.
=head2 prepend
my $code = $mt->prepend;
$mt = $mt->prepend('my $self = shift;');
Prepend Perl code to compiled template. Note that this code should not contain newline characters, or line numbers in
error messages might end up being wrong.
=head2 replace_mark
my $mark = $mt->replace_mark;
$mt = $mt->replace_mark('%');
Character used for escaping the start of a tag or line, defaults to C<%>.
<%% my $foo = 23; %>
=head2 tag_start
my $start = $mt->tag_start;
$mt = $mt->tag_start('<%');
Characters indicating the start of a tag, defaults to C<E<lt>%>.
<% $foo = 23; %>
=head2 tag_end
my $end = $mt->tag_end;
$mt = $mt->tag_end('%>');
Characters indicating the end of a tag, defaults to C<%E<gt>>.
<%= $foo %>
=head2 tree
my $tree = $mt->tree;
$mt = $mt->tree([['text', 'foo'], ['line']]);
Template in parsed form if available. Note that this structure should only be used very carefully since it is very
dynamic.
=head2 trim_mark
my $mark = $mt->trim_mark;
$mt = $mt->trim_mark('-');
Character activating automatic whitespace trimming, defaults to C<=>.
<%= $foo =%>
=head2 unparsed
my $unparsed = $mt->unparsed;
$mt = $mt->unparsed('<%= 1 + 1 %>');
Raw unparsed template if available.
=head2 vars
my $bool = $mt->vars;
$mt = $mt->vars($bool);
Instead of a list of values, use a hash reference with named variables to pass data to templates.
# "works!"
Mojo::Template->new(vars => 1)->render('<%= $test %>!', {test => 'works'});
=head1 METHODS
L<Mojo::Template> inherits all methods from L<Mojo::Base> and implements the following new ones.
=head2 parse
$mt = $mt->parse('<%= 1 + 1 %>');
Parse template into L</"tree">.
=head2 process
my $output = $mt->process;
my $output = $mt->process(@args);
my $output = $mt->process({foo => 'bar'});
Process previously parsed template and return the result, or a L<Mojo::Exception> object if rendering failed.
# Parse and process
say Mojo::Template->new->parse('Hello <%= $_[0] %>')->process('Bender');
# Reuse template (for much better performance)
my $mt = Mojo::Template->new;
say $mt->render('Hello <%= $_[0] %>!', 'Bender');
say $mt->process('Fry');
say $mt->process('Leela');
=head2 render
my $output = $mt->render('<%= 1 + 1 %>');
my $output = $mt->render('<%= shift() + shift() %>', @args);
my $output = $mt->render('<%= $foo %>', {foo => 'bar'});
Render template and return the result, or a L<Mojo::Exception> object if rendering failed.
# Longer version
my $output = $mt->parse('<%= 1 + 1 %>')->process;
# Render with arguments
say Mojo::Template->new->render('<%= $_[0] %>', 'bar');
# Render with named variables
say Mojo::Template->new(vars => 1)->render('<%= $foo %>', {foo => 'bar'});
=head2 render_file
my $output = $mt->render_file('/tmp/foo.mt');
my $output = $mt->render_file('/tmp/foo.mt', @args);
my $output = $mt->render_file('/tmp/bar.mt', {foo => 'bar'});
Same as L</"render">, but renders a template file.
=head1 DEBUGGING
You can set the C<MOJO_TEMPLATE_DEBUG> environment variable to get some advanced diagnostics information printed to
C<STDERR>.
MOJO_TEMPLATE_DEBUG=1
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,279 @@
package Mojo::Transaction;
use Mojo::Base 'Mojo::EventEmitter';
use Carp qw(croak);
use Mojo::Message::Request;
use Mojo::Message::Response;
use Mojo::Util qw(deprecated);
has [qw(kept_alive local_address local_port original_remote_address remote_port)];
has req => sub { Mojo::Message::Request->new };
has res => sub { Mojo::Message::Response->new };
sub client_read { croak 'Method "client_read" not implemented by subclass' }
sub client_write { croak 'Method "client_write" not implemented by subclass' }
sub closed { shift->completed->emit('finish') }
sub completed { ++$_[0]{completed} and return $_[0] }
sub connection {
my $self = shift;
return $self->emit(connection => $self->{connection} = shift) if @_;
return $self->{connection};
}
sub error { $_[0]->req->error || $_[0]->res->error }
sub is_finished { !!shift->{completed} }
sub is_websocket {undef}
sub remote_address {
my $self = shift;
return $self->original_remote_address(@_) if @_;
return $self->original_remote_address unless $self->req->reverse_proxy;
# Reverse proxy
return ($self->req->headers->header('X-Forwarded-For') // '') =~ /([^,\s]+)$/ ? $1 : $self->original_remote_address;
}
sub result {
my $self = shift;
my $err = $self->error;
return !$err || $err->{code} ? $self->res : croak $err->{message};
}
sub server_read { croak 'Method "server_read" not implemented by subclass' }
sub server_write { croak 'Method "server_write" not implemented by subclass' }
# DEPRECATED!
sub success {
deprecated 'Mojo::Transaction::success is DEPRECATED'
. ' in favor of Mojo::Transaction::result and Mojo::Transaction::error';
$_[0]->error ? undef : $_[0]->res;
}
1;
=encoding utf8
=head1 NAME
Mojo::Transaction - Transaction base class
=head1 SYNOPSIS
package Mojo::Transaction::MyTransaction;
use Mojo::Base 'Mojo::Transaction';
sub client_read {...}
sub client_write {...}
sub server_read {...}
sub server_write {...}
=head1 DESCRIPTION
L<Mojo::Transaction> is an abstract base class for transactions, like L<Mojo::Transaction::HTTP> and
L<Mojo::Transaction::WebSocket>.
=head1 EVENTS
L<Mojo::Transaction> inherits all events from L<Mojo::EventEmitter> and can emit the following new ones.
=head2 connection
$tx->on(connection => sub ($tx, $connection) {...});
Emitted when a connection has been assigned to transaction.
=head2 finish
$tx->on(finish => sub ($tx) {...});
Emitted when transaction is finished.
=head1 ATTRIBUTES
L<Mojo::Transaction> implements the following attributes.
=head2 kept_alive
my $bool = $tx->kept_alive;
$tx = $tx->kept_alive($bool);
Connection has been kept alive.
=head2 local_address
my $address = $tx->local_address;
$tx = $tx->local_address('127.0.0.1');
Local interface address.
=head2 local_port
my $port = $tx->local_port;
$tx = $tx->local_port(8080);
Local interface port.
=head2 original_remote_address
my $address = $tx->original_remote_address;
$tx = $tx->original_remote_address('127.0.0.1');
Remote interface address.
=head2 remote_port
my $port = $tx->remote_port;
$tx = $tx->remote_port(8081);
Remote interface port.
=head2 req
my $req = $tx->req;
$tx = $tx->req(Mojo::Message::Request->new);
HTTP request, defaults to a L<Mojo::Message::Request> object.
# Access request information
my $method = $tx->req->method;
my $url = $tx->req->url->to_abs;
my $info = $tx->req->url->to_abs->userinfo;
my $host = $tx->req->url->to_abs->host;
my $agent = $tx->req->headers->user_agent;
my $custom = $tx->req->headers->header('Custom-Header');
my $bytes = $tx->req->body;
my $str = $tx->req->text;
my $hash = $tx->req->params->to_hash;
my $all = $tx->req->uploads;
my $value = $tx->req->json;
my $foo = $tx->req->json('/23/foo');
my $dom = $tx->req->dom;
my $bar = $tx->req->dom('div.bar')->first->text;
=head2 res
my $res = $tx->res;
$tx = $tx->res(Mojo::Message::Response->new);
HTTP response, defaults to a L<Mojo::Message::Response> object.
# Access response information
my $code = $tx->res->code;
my $message = $tx->res->message;
my $server = $tx->res->headers->server;
my $custom = $tx->res->headers->header('Custom-Header');
my $bytes = $tx->res->body;
my $str = $tx->res->text;
my $value = $tx->res->json;
my $foo = $tx->res->json('/23/foo');
my $dom = $tx->res->dom;
my $bar = $tx->res->dom('div.bar')->first->text;
=head1 METHODS
L<Mojo::Transaction> inherits all methods from L<Mojo::EventEmitter> and implements the following new ones.
=head2 client_read
$tx->client_read($bytes);
Read data client-side, used to implement user agents such as L<Mojo::UserAgent>. Meant to be overloaded in a subclass.
=head2 client_write
my $bytes = $tx->client_write;
Write data client-side, used to implement user agents such as L<Mojo::UserAgent>. Meant to be overloaded in a subclass.
=head2 closed
$tx = $tx->closed;
Same as L</"completed">, but also indicates that all transaction data has been sent.
=head2 completed
$tx = $tx->completed;
Low-level method to finalize transaction.
=head2 connection
my $id = $tx->connection;
$tx = $tx->connection($id);
Connection identifier.
=head2 error
my $err = $tx->error;
Get request or response error and return C<undef> if there is no error.
# Longer version
my $err = $tx->req->error || $tx->res->error;
# Check for 4xx/5xx response and connection errors
if (my $err = $tx->error) {
die "$err->{code} response: $err->{message}" if $err->{code};
die "Connection error: $err->{message}";
}
=head2 is_finished
my $bool = $tx->is_finished;
Check if transaction is finished.
=head2 is_websocket
my $bool = $tx->is_websocket;
False, this is not a L<Mojo::Transaction::WebSocket> object.
=head2 remote_address
my $address = $tx->remote_address;
$tx = $tx->remote_address('127.0.0.1');
Same as L</"original_remote_address"> or the last value of the C<X-Forwarded-For> header if L</"req"> has been
performed through a reverse proxy.
=head2 result
my $res = $tx->result;
Returns the L<Mojo::Message::Response> object from L</"res"> or dies if a connection error has occurred.
# Fine grained response handling (dies on connection errors)
my $res = $tx->result;
if ($res->is_success) { say $res->body }
elsif ($res->is_error) { say $res->message }
elsif ($res->code == 301) { say $res->headers->location }
else { say 'Whatever...' }
=head2 server_read
$tx->server_read($bytes);
Read data server-side, used to implement web servers such as L<Mojo::Server::Daemon>. Meant to be overloaded in a
subclass.
=head2 server_write
my $bytes = $tx->server_write;
Write data server-side, used to implement web servers such as L<Mojo::Server::Daemon>. Meant to be overloaded in a
subclass.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,277 @@
package Mojo::Transaction::HTTP;
use Mojo::Base 'Mojo::Transaction';
has 'previous';
sub client_read {
my ($self, $chunk) = @_;
# Skip body for HEAD request
my $res = $self->res;
$res->content->skip_body(1) if uc $self->req->method eq 'HEAD';
return undef unless $res->parse($chunk)->is_finished;
# Unexpected 1xx response
return $self->completed if !$res->is_info || $res->headers->upgrade;
$self->res($res->new)->emit(unexpected => $res);
return undef unless length(my $leftovers = $res->content->leftovers);
$self->client_read($leftovers);
}
sub client_write { shift->_write(0) }
sub is_empty { !!(uc $_[0]->req->method eq 'HEAD' || $_[0]->res->is_empty) }
sub keep_alive {
my $self = shift;
# Close
my $req = $self->req;
my $res = $self->res;
my $req_conn = lc($req->headers->connection // '');
my $res_conn = lc($res->headers->connection // '');
return undef if $req_conn eq 'close' || $res_conn eq 'close';
# Keep-alive is optional for 1.0
return $res_conn eq 'keep-alive' if $res->version eq '1.0';
return $req_conn eq 'keep-alive' if $req->version eq '1.0';
# Keep-alive is the default for 1.1
return 1;
}
sub redirects {
my $previous = shift;
my @redirects;
unshift @redirects, $previous while $previous = $previous->previous;
return \@redirects;
}
sub resume { ++$_[0]{writing} and return $_[0]->emit('resume') }
sub server_read {
my ($self, $chunk) = @_;
# Parse request
my $req = $self->req;
$req->parse($chunk) unless $req->error;
# Generate response
$self->emit('request') if $req->is_finished && !$self->{handled}++;
}
sub server_write { shift->_write(1) }
sub _body {
my ($self, $msg, $finish) = @_;
# Prepare body chunk
my $buffer = $msg->get_body_chunk($self->{offset});
$self->{offset} += defined $buffer ? length $buffer : 0;
# Delayed
$self->{writing} = 0 unless defined $buffer;
# Finished
$finish ? $self->completed : ($self->{writing} = 0) if defined $buffer && !length $buffer;
return $buffer // '';
}
sub _headers {
my ($self, $msg, $head) = @_;
# Prepare header chunk
my $buffer = $msg->get_header_chunk($self->{offset});
my $written = defined $buffer ? length $buffer : 0;
$self->{write} -= $written;
$self->{offset} += $written;
# Switch to body
if ($self->{write} <= 0) {
@$self{qw(http_state offset)} = ('body', 0);
# Response without body
$self->completed->{http_state} = 'empty' if $head && $self->is_empty;
}
return $buffer;
}
sub _start_line {
my ($self, $msg) = @_;
# Prepare start-line chunk
my $buffer = $msg->get_start_line_chunk($self->{offset});
my $written = defined $buffer ? length $buffer : 0;
$self->{write} -= $written;
$self->{offset} += $written;
# Switch to headers
@$self{qw(http_state write offset)} = ('headers', $msg->header_size, 0) if $self->{write} <= 0;
return $buffer;
}
sub _write {
my ($self, $server) = @_;
# Client starts writing right away
return '' unless $server ? $self->{writing} : ($self->{writing} //= 1);
# Nothing written yet
$self->{$_} ||= 0 for qw(offset write);
my $msg = $server ? $self->res : $self->req;
@$self{qw(http_state write)} = ('start_line', $msg->start_line_size) unless $self->{http_state};
# Start-line
my $chunk = '';
$chunk .= $self->_start_line($msg) if $self->{http_state} eq 'start_line';
# Headers
$chunk .= $self->_headers($msg, $server) if $self->{http_state} eq 'headers';
# Body
$chunk .= $self->_body($msg, $server) if $self->{http_state} eq 'body';
return $chunk;
}
1;
=encoding utf8
=head1 NAME
Mojo::Transaction::HTTP - HTTP transaction
=head1 SYNOPSIS
use Mojo::Transaction::HTTP;
# Client
my $tx = Mojo::Transaction::HTTP->new;
$tx->req->method('GET');
$tx->req->url->parse('http://example.com');
$tx->req->headers->accept('application/json');
say $tx->res->code;
say $tx->res->headers->content_type;
say $tx->res->body;
say $tx->remote_address;
# Server
my $tx = Mojo::Transaction::HTTP->new;
say $tx->req->method;
say $tx->req->url->to_abs;
say $tx->req->headers->accept;
say $tx->remote_address;
$tx->res->code(200);
$tx->res->headers->content_type('text/plain');
$tx->res->body('Hello World!');
=head1 DESCRIPTION
L<Mojo::Transaction::HTTP> is a container for HTTP transactions, based on L<RFC
7230|https://tools.ietf.org/html/rfc7230> and L<RFC 7231|https://tools.ietf.org/html/rfc7231>.
=head1 EVENTS
L<Mojo::Transaction::HTTP> inherits all events from L<Mojo::Transaction> and can emit the following new ones.
=head2 request
$tx->on(request => sub ($tx) {...});
Emitted when a request is ready and needs to be handled.
$tx->on(request => sub ($tx) { $tx->res->headers->header('X-Bender' => 'Bite my shiny metal ass!') });
=head2 resume
$tx->on(resume => sub ($tx) {...});
Emitted when transaction is resumed.
=head2 unexpected
$tx->on(unexpected => sub ($tx, $res) {...});
Emitted for unexpected C<1xx> responses that will be ignored.
$tx->on(unexpected => sub ($tx) { $tx->res->on(finish => sub { say 'Follow-up response is finished.' }) });
=head1 ATTRIBUTES
L<Mojo::Transaction::HTTP> inherits all attributes from L<Mojo::Transaction> and implements the following new ones.
=head2 previous
my $previous = $tx->previous;
$tx = $tx->previous(Mojo::Transaction::HTTP->new);
Previous transaction that triggered this follow-up transaction, usually a L<Mojo::Transaction::HTTP> object.
# Paths of previous requests
say $tx->previous->previous->req->url->path;
say $tx->previous->req->url->path;
=head1 METHODS
L<Mojo::Transaction::HTTP> inherits all methods from L<Mojo::Transaction> and implements the following new ones.
=head2 client_read
$tx->client_read($bytes);
Read data client-side, used to implement user agents such as L<Mojo::UserAgent>.
=head2 client_write
my $bytes = $tx->client_write;
Write data client-side, used to implement user agents such as L<Mojo::UserAgent>.
=head2 is_empty
my $bool = $tx->is_empty;
Check transaction for C<HEAD> request and C<1xx>, C<204> or C<304> response.
=head2 keep_alive
my $bool = $tx->keep_alive;
Check if connection can be kept alive.
=head2 redirects
my $redirects = $tx->redirects;
Return an array reference with all previous transactions that preceded this follow-up transaction.
# Paths of all previous requests
say $_->req->url->path for @{$tx->redirects};
=head2 resume
$tx = $tx->resume;
Resume transaction.
=head2 server_read
$tx->server_read($bytes);
Read data server-side, used to implement web servers such as L<Mojo::Server::Daemon>.
=head2 server_write
my $bytes = $tx->server_write;
Write data server-side, used to implement web servers such as L<Mojo::Server::Daemon>.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,453 @@
package Mojo::Transaction::WebSocket;
use Mojo::Base 'Mojo::Transaction';
use Compress::Raw::Zlib qw(Z_SYNC_FLUSH);
use List::Util qw(first);
use Mojo::JSON qw(encode_json j);
use Mojo::Util qw(decode encode trim);
use Mojo::WebSocket qw(WS_BINARY WS_CLOSE WS_CONTINUATION WS_PING WS_PONG WS_TEXT);
has [qw(compressed established handshake masked)];
has max_websocket_size => sub { $ENV{MOJO_MAX_WEBSOCKET_SIZE} || 262144 };
sub build_message {
my ($self, $frame) = @_;
# Text
$frame = {text => encode('UTF-8', $frame)} if ref $frame ne 'HASH';
# JSON
$frame->{text} = encode_json($frame->{json}) if exists $frame->{json};
# Raw text or binary
if (exists $frame->{text}) { $frame = [1, 0, 0, 0, WS_TEXT, $frame->{text}] }
else { $frame = [1, 0, 0, 0, WS_BINARY, $frame->{binary}] }
# "permessage-deflate" extension
return $frame unless $self->compressed;
my $deflate = $self->{deflate}
||= Compress::Raw::Zlib::Deflate->new(AppendOutput => 1, MemLevel => 8, WindowBits => -15);
$deflate->deflate($frame->[5], my $out);
$deflate->flush($out, Z_SYNC_FLUSH);
@$frame[1, 5] = (1, substr($out, 0, length($out) - 4));
return $frame;
}
sub client_read { shift->server_read(@_) }
sub client_write { shift->server_write(@_) }
sub closed {
my $self = shift->completed;
return $self->emit(finish => $self->{close} ? (@{$self->{close}}) : 1006);
}
sub connection { shift->handshake->connection }
sub finish {
my $self = shift;
my $close = $self->{close} = [@_];
my $payload = $close->[0] ? pack('n', $close->[0]) : '';
$payload .= encode 'UTF-8', $close->[1] if defined $close->[1];
$close->[0] //= 1005;
$self->send([1, 0, 0, 0, WS_CLOSE, $payload])->{closing} = 1;
return $self;
}
sub is_websocket {1}
sub kept_alive { shift->handshake->kept_alive }
sub local_address { shift->handshake->local_address }
sub local_port { shift->handshake->local_port }
sub parse_message {
my ($self, $frame) = @_;
$self->emit(frame => $frame);
# Ping/Pong
my $op = $frame->[4];
return $self->send([1, 0, 0, 0, WS_PONG, $frame->[5]]) if $op == WS_PING;
return undef if $op == WS_PONG;
# Close
if ($op == WS_CLOSE) {
return $self->finish unless length $frame->[5] >= 2;
return $self->finish(unpack('n', substr($frame->[5], 0, 2, '')), decode('UTF-8', $frame->[5]));
}
# Append chunk and check message size
@{$self}{qw(op pmc)} = ($op, $self->compressed && $frame->[1]) unless exists $self->{op};
$self->{message} .= $frame->[5];
my $max = $self->max_websocket_size;
return $self->finish(1009) if length $self->{message} > $max;
# No FIN bit (Continuation)
return undef unless $frame->[0];
# "permessage-deflate" extension (handshake and RSV1)
my $msg = delete $self->{message};
if ($self->compressed && $self->{pmc}) {
my $inflate = $self->{inflate}
||= Compress::Raw::Zlib::Inflate->new(Bufsize => $max, LimitOutput => 1, WindowBits => -15);
$inflate->inflate(($msg .= "\x00\x00\xff\xff"), my $out);
return $self->finish(1009) if length $msg;
$msg = $out;
}
$self->emit(json => j($msg)) if $self->has_subscribers('json');
$op = delete $self->{op};
$self->emit($op == WS_TEXT ? 'text' : 'binary' => $msg);
$self->emit(message => $op == WS_TEXT ? decode 'UTF-8', $msg : $msg) if $self->has_subscribers('message');
}
sub protocol { shift->res->headers->sec_websocket_protocol }
sub remote_address { shift->handshake->remote_address }
sub remote_port { shift->handshake->remote_port }
sub req { shift->handshake->req }
sub res { shift->handshake->res }
sub resume { $_[0]->handshake->resume and return $_[0] }
sub send {
my ($self, $msg, $cb) = @_;
$self->once(drain => $cb) if $cb;
$msg = $self->build_message($msg) unless ref $msg eq 'ARRAY';
$self->{write} .= Mojo::WebSocket::build_frame($self->masked, @$msg);
return $self->emit('resume');
}
sub server_read {
my ($self, $chunk) = @_;
$self->{read} .= $chunk;
my $max = $self->max_websocket_size;
while (my $frame = Mojo::WebSocket::parse_frame(\$self->{read}, $max)) {
$self->finish(1009) and last unless ref $frame;
$self->parse_message($frame);
}
$self->emit('resume');
}
sub server_write {
my $self = shift;
$self->emit('drain') unless length($self->{write} //= '');
$self->completed if !length $self->{write} && $self->{closing};
return delete $self->{write};
}
sub with_compression {
my $self = shift;
# "permessage-deflate" extension
$self->compressed(1) and $self->res->headers->sec_websocket_extensions('permessage-deflate')
if ($self->req->headers->sec_websocket_extensions // '') =~ /permessage-deflate/;
}
sub with_protocols {
my $self = shift;
my %protos = map { trim($_) => 1 } split /,/, $self->req->headers->sec_websocket_protocol // '';
return undef unless defined(my $proto = first { $protos{$_} } @_);
$self->res->headers->sec_websocket_protocol($proto);
return $proto;
}
1;
=encoding utf8
=head1 NAME
Mojo::Transaction::WebSocket - WebSocket transaction
=head1 SYNOPSIS
use Mojo::Transaction::WebSocket;
# Send and receive WebSocket messages
my $ws = Mojo::Transaction::WebSocket->new;
$ws->send('Hello World!');
$ws->on(message => sub ($ws, $msg) { say "Message: $msg" });
$ws->on(finish => sub ($ws, $code, $reason) { say "WebSocket closed with status $code." });
=head1 DESCRIPTION
L<Mojo::Transaction::WebSocket> is a container for WebSocket transactions, based on L<RFC
6455|https://tools.ietf.org/html/rfc6455> and L<RFC 7692|https://tools.ietf.org/html/rfc7692>.
=head1 EVENTS
L<Mojo::Transaction::WebSocket> inherits all events from L<Mojo::Transaction> and can emit the following new ones.
=head2 binary
$ws->on(binary => sub ($ws, $bytes) {...});
Emitted when a complete WebSocket binary message has been received.
$ws->on(binary => sub ($ws, $bytes) { say "Binary: $bytes" });
=head2 drain
$ws->on(drain => sub ($ws) {...});
Emitted once all data has been sent.
$ws->on(drain => sub ($ws) { $ws->send(time) });
=head2 finish
$ws->on(finish => sub ($ws, $code, $reason) {...});
Emitted when the WebSocket connection has been closed.
=head2 frame
$ws->on(frame => sub ($ws, $frame) {...});
Emitted when a WebSocket frame has been received.
$ws->on(frame => sub ($ws, $frame) {
say "FIN: $frame->[0]";
say "RSV1: $frame->[1]";
say "RSV2: $frame->[2]";
say "RSV3: $frame->[3]";
say "Opcode: $frame->[4]";
say "Payload: $frame->[5]";
});
=head2 json
$ws->on(json => sub ($ws, $json) {...});
Emitted when a complete WebSocket message has been received, all text and binary messages will be automatically JSON
decoded. Note that this event only gets emitted when it has at least one subscriber.
$ws->on(json => sub ($ws, $hash) { say "Message: $hash->{msg}" });
=head2 message
$ws->on(message => sub ($ws, $msg) {...});
Emitted when a complete WebSocket message has been received, text messages will be automatically decoded. Note that
this event only gets emitted when it has at least one subscriber.
$ws->on(message => sub ($ws, $msg) { say "Message: $msg" });
=head2 resume
$tx->on(resume => sub ($tx) {...});
Emitted when transaction is resumed.
=head2 text
$ws->on(text => sub ($ws, $bytes) {...});
Emitted when a complete WebSocket text message has been received.
$ws->on(text => sub ($ws, $bytes) { say "Text: $bytes" });
=head1 ATTRIBUTES
L<Mojo::Transaction::WebSocket> inherits all attributes from L<Mojo::Transaction> and implements the following new
ones.
=head2 compressed
my $bool = $ws->compressed;
$ws = $ws->compressed($bool);
Compress messages with C<permessage-deflate> extension.
=head2 established
my $bool = $ws->established;
$ws = $ws->established($bool);
WebSocket connection established.
=head2 handshake
my $handshake = $ws->handshake;
$ws = $ws->handshake(Mojo::Transaction::HTTP->new);
The original handshake transaction, usually a L<Mojo::Transaction::HTTP> object.
=head2 masked
my $bool = $ws->masked;
$ws = $ws->masked($bool);
Mask outgoing frames with XOR cipher and a random 32-bit key.
=head2 max_websocket_size
my $size = $ws->max_websocket_size;
$ws = $ws->max_websocket_size(1024);
Maximum WebSocket message size in bytes, defaults to the value of the C<MOJO_MAX_WEBSOCKET_SIZE> environment variable
or C<262144> (256KiB).
=head1 METHODS
L<Mojo::Transaction::WebSocket> inherits all methods from L<Mojo::Transaction> and implements the following new ones.
=head2 build_message
my $frame = $ws->build_message({binary => $bytes});
my $frame = $ws->build_message({text => $bytes});
my $frame = $ws->build_message({json => {test => [1, 2, 3]}});
my $frame = $ws->build_message($chars);
Build WebSocket message.
=head2 client_read
$ws->client_read($data);
Read data client-side, used to implement user agents such as L<Mojo::UserAgent>.
=head2 client_write
my $bytes = $ws->client_write;
Write data client-side, used to implement user agents such as L<Mojo::UserAgent>.
=head2 closed
$tx = $tx->closed;
Same as L<Mojo::Transaction/"completed">, but also indicates that all transaction data has been sent.
=head2 connection
my $id = $ws->connection;
Connection identifier.
=head2 finish
$ws = $ws->finish;
$ws = $ws->finish(1000);
$ws = $ws->finish(1003 => 'Cannot accept data!');
Close WebSocket connection gracefully.
=head2 is_websocket
my $bool = $ws->is_websocket;
True, this is a L<Mojo::Transaction::WebSocket> object.
=head2 kept_alive
my $bool = $ws->kept_alive;
Connection has been kept alive.
=head2 local_address
my $address = $ws->local_address;
Local interface address.
=head2 local_port
my $port = $ws->local_port;
Local interface port.
=head2 parse_message
$ws->parse_message([$fin, $rsv1, $rsv2, $rsv3, $op, $payload]);
Parse WebSocket message.
=head2 protocol
my $proto = $ws->protocol;
Return negotiated subprotocol or C<undef>.
=head2 remote_address
my $address = $ws->remote_address;
Remote interface address.
=head2 remote_port
my $port = $ws->remote_port;
Remote interface port.
=head2 req
my $req = $ws->req;
Handshake request, usually a L<Mojo::Message::Request> object.
=head2 res
my $res = $ws->res;
Handshake response, usually a L<Mojo::Message::Response> object.
=head2 resume
$ws = $ws->resume;
Resume L</"handshake"> transaction.
=head2 send
$ws = $ws->send({binary => $bytes});
$ws = $ws->send({text => $bytes});
$ws = $ws->send({json => {test => [1, 2, 3]}});
$ws = $ws->send([$fin, $rsv1, $rsv2, $rsv3, $op, $payload]);
$ws = $ws->send($chars);
$ws = $ws->send($chars => sub {...});
Send message or frame non-blocking via WebSocket, the optional drain callback will be executed once all data has been
written.
# Send "Ping" frame
use Mojo::WebSocket qw(WS_PING);
$ws->send([1, 0, 0, 0, WS_PING, 'Hello World!']);
=head2 server_read
$ws->server_read($data);
Read data server-side, used to implement web servers such as L<Mojo::Server::Daemon>.
=head2 server_write
my $bytes = $ws->server_write;
Write data server-side, used to implement web servers such as L<Mojo::Server::Daemon>.
=head2 with_compression
$ws->with_compression;
Negotiate C<permessage-deflate> extension for this WebSocket connection.
=head2 with_protocols
my $proto = $ws->with_protocols('v2.proto', 'v1.proto');
Negotiate subprotocol for this WebSocket connection.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

521
database/perl/vendor/lib/Mojo/URL.pm vendored Normal file
View File

@@ -0,0 +1,521 @@
package Mojo::URL;
use Mojo::Base -base;
use overload bool => sub {1}, '""' => sub { shift->to_string }, fallback => 1;
use Mojo::Parameters;
use Mojo::Path;
use Mojo::Util qw(decode encode punycode_decode punycode_encode url_escape url_unescape);
has base => sub { Mojo::URL->new };
has [qw(fragment host port scheme userinfo)];
sub clone {
my $self = shift;
my $clone = $self->new;
@$clone{keys %$self} = values %$self;
$clone->{$_} && ($clone->{$_} = $clone->{$_}->clone) for qw(base path query);
return $clone;
}
sub host_port {
my ($self, $host_port) = @_;
if (defined $host_port) {
$self->port($1) if $host_port =~ s/:(\d+)$//;
my $host = url_unescape $host_port;
return $host =~ /[^\x00-\x7f]/ ? $self->ihost($host) : $self->host($host);
}
return undef unless defined(my $host = $self->ihost);
return $host unless defined(my $port = $self->port);
return "$host:$port";
}
sub ihost {
my $self = shift;
# Decode
return $self->host(join '.', map { /^xn--(.+)$/ ? punycode_decode $1 : $_ } split(/\./, shift, -1)) if @_;
# Check if host needs to be encoded
return undef unless defined(my $host = $self->host);
return $host unless $host =~ /[^\x00-\x7f]/;
# Encode
return join '.', map { /[^\x00-\x7f]/ ? ('xn--' . punycode_encode $_) : $_ } split(/\./, $host, -1);
}
sub is_abs { !!shift->scheme }
sub new { @_ > 1 ? shift->SUPER::new->parse(@_) : shift->SUPER::new }
sub parse {
my ($self, $url) = @_;
# Official regex from RFC 3986
$url =~ m!^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?!;
$self->scheme($2) if defined $2;
$self->path($5) if defined $5;
$self->query($7) if defined $7;
$self->fragment(_decode(url_unescape $9)) if defined $9;
if (defined(my $auth = $4)) {
$self->userinfo(_decode(url_unescape $1)) if $auth =~ s/^([^\@]+)\@//;
$self->host_port($auth);
}
return $self;
}
sub password { (shift->userinfo // '') =~ /:(.*)$/ ? $1 : undef }
sub path {
my $self = shift;
# Old path
$self->{path} ||= Mojo::Path->new;
return $self->{path} unless @_;
# New path
$self->{path} = ref $_[0] ? $_[0] : $self->{path}->merge($_[0]);
return $self;
}
sub path_query {
my ($self, $pq) = @_;
if (defined $pq) {
return $self unless $pq =~ /^([^?#]*)(?:\?([^#]*))?/;
return defined $2 ? $self->path($1)->query($2) : $self->path($1);
}
my $query = $self->query->to_string;
return $self->path->to_string . (length $query ? "?$query" : '');
}
sub protocol { lc(shift->scheme // '') }
sub query {
my $self = shift;
# Old parameters
my $q = $self->{query} ||= Mojo::Parameters->new;
return $q unless @_;
# Replace with list
if (@_ > 1) { $q->pairs([])->parse(@_) }
# Merge with hash
elsif (ref $_[0] eq 'HASH') { $q->merge(%{$_[0]}) }
# Append array
elsif (ref $_[0] eq 'ARRAY') { $q->append(@{$_[0]}) }
# New parameters
else { $self->{query} = ref $_[0] ? $_[0] : $q->parse($_[0]) }
return $self;
}
sub to_abs {
my $self = shift;
my $abs = $self->clone;
return $abs if $abs->is_abs;
# Scheme
my $base = shift || $abs->base;
$abs->base($base)->scheme($base->scheme);
# Authority
return $abs if $abs->host;
$abs->userinfo($base->userinfo)->host($base->host)->port($base->port);
# Absolute path
my $path = $abs->path;
return $abs if $path->leading_slash;
# Inherit path
if (!@{$path->parts}) {
$abs->path($base->path->clone->canonicalize);
# Query
$abs->query($base->query->clone) unless length $abs->query->to_string;
}
# Merge paths
else { $abs->path($base->path->clone->merge($path)->canonicalize) }
return $abs;
}
sub to_string { shift->_string(0) }
sub to_unsafe_string { shift->_string(1) }
sub username { (shift->userinfo // '') =~ /^([^:]+)/ ? $1 : undef }
sub _decode { decode('UTF-8', $_[0]) // $_[0] }
sub _encode { url_escape encode('UTF-8', $_[0]), $_[1] }
sub _string {
my ($self, $unsafe) = @_;
# Scheme
my $url = '';
if (my $proto = $self->protocol) { $url .= "$proto:" }
# Authority
my $auth = $self->host_port;
$auth = _encode($auth, '^A-Za-z0-9\-._~!$&\'()*+,;=:\[\]') if defined $auth;
if ($unsafe && defined(my $info = $self->userinfo)) {
$auth = _encode($info, '^A-Za-z0-9\-._~!$&\'()*+,;=:') . '@' . $auth;
}
$url .= "//$auth" if defined $auth;
# Path and query
my $path = $self->path_query;
$url .= !$auth || !length $path || $path =~ m!^[/?]! ? $path : "/$path";
# Fragment
return $url unless defined(my $fragment = $self->fragment);
return $url . '#' . _encode($fragment, '^A-Za-z0-9\-._~!$&\'()*+,;=:@/?');
}
1;
=encoding utf8
=head1 NAME
Mojo::URL - Uniform Resource Locator
=head1 SYNOPSIS
use Mojo::URL;
# Parse
my $url = Mojo::URL->new('http://sri:foo@example.com:3000/foo?foo=bar#23');
say $url->scheme;
say $url->userinfo;
say $url->host;
say $url->port;
say $url->path;
say $url->query;
say $url->fragment;
# Build
my $url = Mojo::URL->new;
$url->scheme('http');
$url->host('example.com');
$url->port(3000);
$url->path('/foo/bar');
$url->query(foo => 'bar');
$url->fragment(23);
say "$url";
=head1 DESCRIPTION
L<Mojo::URL> implements a subset of L<RFC 3986|https://tools.ietf.org/html/rfc3986>, L<RFC
3987|https://tools.ietf.org/html/rfc3987> and the L<URL Living Standard|https://url.spec.whatwg.org> for Uniform
Resource Locators with support for IDNA and IRIs.
=head1 ATTRIBUTES
L<Mojo::URL> implements the following attributes.
=head2 base
my $base = $url->base;
$url = $url->base(Mojo::URL->new);
Base of this URL, defaults to a L<Mojo::URL> object.
"http://example.com/a/b?c"
Mojo::URL->new("/a/b?c")->base(Mojo::URL->new("http://example.com"))->to_abs;
=head2 fragment
my $fragment = $url->fragment;
$url = $url->fragment('♥mojolicious♥');
Fragment part of this URL.
# "yada"
Mojo::URL->new('http://example.com/foo?bar=baz#yada')->fragment;
=head2 host
my $host = $url->host;
$url = $url->host('127.0.0.1');
Host part of this URL.
# "example.com"
Mojo::URL->new('http://sri:t3st@example.com:8080/foo')->host;
=head2 port
my $port = $url->port;
$url = $url->port(8080);
Port part of this URL.
# "8080"
Mojo::URL->new('http://sri:t3st@example.com:8080/foo')->port;
=head2 scheme
my $scheme = $url->scheme;
$url = $url->scheme('http');
Scheme part of this URL.
# "http"
Mojo::URL->new('http://example.com/foo')->scheme;
=head2 userinfo
my $info = $url->userinfo;
$url = $url->userinfo('root:♥');
Userinfo part of this URL.
# "sri:t3st"
Mojo::URL->new('https://sri:t3st@example.com/foo')->userinfo;
=head1 METHODS
L<Mojo::URL> inherits all methods from L<Mojo::Base> and implements the following new ones.
=head2 clone
my $url2 = $url->clone;
Return a new L<Mojo::URL> object cloned from this URL.
=head2 host_port
my $host_port = $url->host_port;
$url = $url->host_port('example.com:8080');
Normalized version of L</"host"> and L</"port">.
# "xn--n3h.net:8080"
Mojo::URL->new('http://☃.net:8080/test')->host_port;
# "example.com"
Mojo::URL->new('http://example.com/test')->host_port;
=head2 ihost
my $ihost = $url->ihost;
$url = $url->ihost('xn--bcher-kva.ch');
Host part of this URL in punycode format.
# "xn--n3h.net"
Mojo::URL->new('http://☃.net')->ihost;
# "example.com"
Mojo::URL->new('http://example.com')->ihost;
=head2 is_abs
my $bool = $url->is_abs;
Check if URL is absolute.
# True
Mojo::URL->new('http://example.com')->is_abs;
Mojo::URL->new('http://example.com/test/index.html')->is_abs;
# False
Mojo::URL->new('test/index.html')->is_abs;
Mojo::URL->new('/test/index.html')->is_abs;
Mojo::URL->new('//example.com/test/index.html')->is_abs;
=head2 new
my $url = Mojo::URL->new;
my $url = Mojo::URL->new('http://127.0.0.1:3000/foo?f=b&baz=2#foo');
Construct a new L<Mojo::URL> object and L</"parse"> URL if necessary.
=head2 parse
$url = $url->parse('http://127.0.0.1:3000/foo/bar?fo=o&baz=23#foo');
Parse relative or absolute URL.
# "/test/123"
$url->parse('/test/123?foo=bar')->path;
# "example.com"
$url->parse('http://example.com/test/123?foo=bar')->host;
# "sri@example.com"
$url->parse('mailto:sri@example.com')->path;
=head2 password
my $password = $url->password;
Password part of L</"userinfo">.
# "s3cret"
Mojo::URL->new('http://isabel:s3cret@mojolicious.org')->password;
# "s:3:c:r:e:t"
Mojo::URL->new('http://isabel:s:3:c:r:e:t@mojolicious.org')->password;
=head2 path
my $path = $url->path;
$url = $url->path('foo/bar');
$url = $url->path('/foo/bar');
$url = $url->path(Mojo::Path->new);
Path part of this URL, relative paths will be merged with L<Mojo::Path/"merge">, defaults to a L<Mojo::Path> object.
# "test"
Mojo::URL->new('http://example.com/test/Mojo')->path->parts->[0];
# "/test/DOM/HTML"
Mojo::URL->new('http://example.com/test/Mojo')->path->merge('DOM/HTML');
# "http://example.com/DOM/HTML"
Mojo::URL->new('http://example.com/test/Mojo')->path('/DOM/HTML');
# "http://example.com/test/DOM/HTML"
Mojo::URL->new('http://example.com/test/Mojo')->path('DOM/HTML');
# "http://example.com/test/Mojo/DOM/HTML"
Mojo::URL->new('http://example.com/test/Mojo/')->path('DOM/HTML');
=head2 path_query
my $path_query = $url->path_query;
$url = $url->path_query('/foo/bar?a=1&b=2');
Normalized version of L</"path"> and L</"query">.
# "/test?a=1&b=2"
Mojo::URL->new('http://example.com/test?a=1&b=2')->path_query;
# "/"
Mojo::URL->new('http://example.com/')->path_query;
=head2 protocol
my $proto = $url->protocol;
Normalized version of L</"scheme">.
# "http"
Mojo::URL->new('HtTp://example.com')->protocol;
=head2 query
my $query = $url->query;
$url = $url->query({merge => 'to'});
$url = $url->query([append => 'with']);
$url = $url->query(replace => 'with');
$url = $url->query('a=1&b=2');
$url = $url->query(Mojo::Parameters->new);
Query part of this URL, key/value pairs in an array reference will be appended with L<Mojo::Parameters/"append">, and
key/value pairs in a hash reference merged with L<Mojo::Parameters/"merge">, defaults to a L<Mojo::Parameters> object.
# "2"
Mojo::URL->new('http://example.com?a=1&b=2')->query->param('b');
# "a=2&b=2&c=3"
Mojo::URL->new('http://example.com?a=1&b=2')->query->merge(a => 2, c => 3);
# "http://example.com?a=2&c=3"
Mojo::URL->new('http://example.com?a=1&b=2')->query(a => 2, c => 3);
# "http://example.com?a=2&a=3"
Mojo::URL->new('http://example.com?a=1&b=2')->query(a => [2, 3]);
# "http://example.com?a=2&b=2&c=3"
Mojo::URL->new('http://example.com?a=1&b=2')->query({a => 2, c => 3});
# "http://example.com?b=2"
Mojo::URL->new('http://example.com?a=1&b=2')->query({a => undef});
# "http://example.com?a=1&b=2&a=2&c=3"
Mojo::URL->new('http://example.com?a=1&b=2')->query([a => 2, c => 3]);
=head2 to_abs
my $abs = $url->to_abs;
my $abs = $url->to_abs(Mojo::URL->new('http://example.com/foo'));
Return a new L<Mojo::URL> object cloned from this relative URL and turn it into an absolute one using L</"base"> or
provided base URL.
# "http://example.com/foo/baz.xml?test=123"
Mojo::URL->new('baz.xml?test=123')
->to_abs(Mojo::URL->new('http://example.com/foo/bar.html'));
# "http://example.com/baz.xml?test=123"
Mojo::URL->new('/baz.xml?test=123')
->to_abs(Mojo::URL->new('http://example.com/foo/bar.html'));
# "http://example.com/foo/baz.xml?test=123"
Mojo::URL->new('//example.com/foo/baz.xml?test=123')
->to_abs(Mojo::URL->new('http://example.com/foo/bar.html'));
=head2 to_string
my $str = $url->to_string;
Turn URL into a string. Note that L</"userinfo"> will not be included for security reasons.
# "http://mojolicious.org"
Mojo::URL->new->scheme('http')->host('mojolicious.org')->to_string;
# "http://mojolicious.org"
Mojo::URL->new('http://daniel:s3cret@mojolicious.org')->to_string;
=head2 to_unsafe_string
my $str = $url->to_unsafe_string;
Same as L</"to_string">, but includes L</"userinfo">.
# "http://daniel:s3cret@mojolicious.org"
Mojo::URL->new('http://daniel:s3cret@mojolicious.org')->to_unsafe_string;
=head2 username
my $username = $url->username;
Username part of L</"userinfo">.
# "isabel"
Mojo::URL->new('http://isabel:s3cret@mojolicious.org')->username;
=head1 OPERATORS
L<Mojo::URL> overloads the following operators.
=head2 bool
my $bool = !!$url;
Always true.
=head2 stringify
my $str = "$url";
Alias for L</"to_string">.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

89
database/perl/vendor/lib/Mojo/Upload.pm vendored Normal file
View File

@@ -0,0 +1,89 @@
package Mojo::Upload;
use Mojo::Base -base;
has [qw(asset filename headers name)];
sub move_to { $_[0]->asset->move_to($_[1]) and return $_[0] }
sub size { shift->asset->size }
sub slurp { shift->asset->slurp }
1;
=encoding utf8
=head1 NAME
Mojo::Upload - Upload
=head1 SYNOPSIS
use Mojo::Upload;
my $upload = Mojo::Upload->new;
say $upload->filename;
$upload->move_to('/home/sri/foo.txt');
=head1 DESCRIPTION
L<Mojo::Upload> is a container for uploaded files.
=head1 ATTRIBUTES
L<Mojo::Upload> implements the following attributes.
=head2 asset
my $asset = $upload->asset;
$upload = $upload->asset(Mojo::Asset::File->new);
Asset containing the uploaded data, usually a L<Mojo::Asset::File> or L<Mojo::Asset::Memory> object.
=head2 filename
my $filename = $upload->filename;
$upload = $upload->filename('foo.txt');
Name of the uploaded file.
=head2 headers
my $headers = $upload->headers;
$upload = $upload->headers(Mojo::Headers->new);
Headers for upload, usually a L<Mojo::Headers> object.
=head2 name
my $name = $upload->name;
$upload = $upload->name('foo');
Name of the upload.
=head1 METHODS
L<Mojo::Upload> inherits all methods from L<Mojo::Base> and implements the following new ones.
=head2 move_to
$upload = $upload->move_to('/home/sri/foo.txt');
Move uploaded data into a specific file.
=head2 size
my $size = $upload->size;
Size of uploaded data in bytes.
=head2 slurp
my $bytes = $upload->slurp;
Read all uploaded data at once.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,986 @@
package Mojo::UserAgent;
use Mojo::Base 'Mojo::EventEmitter';
# "Fry: Since when is the Internet about robbing people of their privacy?
# Bender: August 6, 1991."
use Mojo::IOLoop;
use Mojo::Promise;
use Mojo::Util qw(monkey_patch term_escape);
use Mojo::UserAgent::CookieJar;
use Mojo::UserAgent::Proxy;
use Mojo::UserAgent::Server;
use Mojo::UserAgent::Transactor;
use Scalar::Util qw(weaken);
use constant DEBUG => $ENV{MOJO_CLIENT_DEBUG} || 0;
has ca => sub { $ENV{MOJO_CA_FILE} };
has cert => sub { $ENV{MOJO_CERT_FILE} };
has connect_timeout => sub { $ENV{MOJO_CONNECT_TIMEOUT} || 10 };
has cookie_jar => sub { Mojo::UserAgent::CookieJar->new };
has inactivity_timeout => sub { $ENV{MOJO_INACTIVITY_TIMEOUT} // 40 };
has insecure => sub { $ENV{MOJO_INSECURE} };
has [qw(local_address max_response_size)];
has ioloop => sub { Mojo::IOLoop->new };
has key => sub { $ENV{MOJO_KEY_FILE} };
has max_connections => 5;
has max_redirects => sub { $ENV{MOJO_MAX_REDIRECTS} || 0 };
has proxy => sub { Mojo::UserAgent::Proxy->new };
has request_timeout => sub { $ENV{MOJO_REQUEST_TIMEOUT} // 0 };
has server => sub { Mojo::UserAgent::Server->new(ioloop => shift->ioloop) };
has transactor => sub { Mojo::UserAgent::Transactor->new };
# Common HTTP methods
for my $name (qw(DELETE GET HEAD OPTIONS PATCH POST PUT)) {
monkey_patch __PACKAGE__, lc $name, sub {
my ($self, $cb) = (shift, ref $_[-1] eq 'CODE' ? pop : undef);
return $self->start($self->build_tx($name, @_), $cb);
};
monkey_patch __PACKAGE__, lc($name) . '_p', sub {
my $self = shift;
return $self->start_p($self->build_tx($name, @_));
};
}
sub DESTROY { Mojo::Util::_global_destruction() or shift->_cleanup }
sub build_tx { shift->transactor->tx(@_) }
sub build_websocket_tx { shift->transactor->websocket(@_) }
sub start {
my ($self, $tx, $cb) = @_;
# Fork-safety
$self->_cleanup->server->restart if $self->{pid} && $self->{pid} ne $$;
$self->{pid} //= $$;
# Non-blocking
if ($cb) {
warn "-- Non-blocking request (@{[_url($tx)]})\n" if DEBUG;
return $self->_start(Mojo::IOLoop->singleton, $tx, $cb);
}
# Blocking
warn "-- Blocking request (@{[_url($tx)]})\n" if DEBUG;
$self->_start($self->ioloop, $tx => sub { shift->ioloop->stop; $tx = shift });
$self->ioloop->start;
return $tx;
}
sub start_p {
my ($self, $tx) = @_;
my $promise = Mojo::Promise->new;
$self->start($tx => sub { shift->transactor->promisify($promise, shift) });
return $promise;
}
sub websocket {
my ($self, $cb) = (shift, pop);
$self->start($self->build_websocket_tx(@_), $cb);
}
sub websocket_p {
my $self = shift;
return $self->start_p($self->build_websocket_tx(@_));
}
sub _cleanup {
my $self = shift;
delete $self->{pid};
$self->_finish($_, 1) for keys %{$self->{connections} // {}};
return $self;
}
sub _connect {
my ($self, $loop, $tx, $handle) = @_;
my $t = $self->transactor;
my ($proto, $host, $port) = $handle ? $t->endpoint($tx) : $t->peer($tx);
my %options = (timeout => $self->connect_timeout);
if ($proto eq 'http+unix') { $options{path} = $host }
else { @options{qw(address port)} = ($host, $port) }
if (my $local = $self->local_address) { $options{local_address} = $local }
$options{handle} = $handle if $handle;
# SOCKS
if ($proto eq 'socks') {
@options{qw(socks_address socks_port)} = @options{qw(address port)};
($proto, @options{qw(address port)}) = $t->endpoint($tx);
my $userinfo = $tx->req->via_proxy(0)->proxy->userinfo;
@options{qw(socks_user socks_pass)} = split /:/, $userinfo if $userinfo;
}
# TLS
if ($options{tls} = $proto eq 'https') {
map { $options{"tls_$_"} = $self->$_ } qw(ca cert key);
$options{tls_verify} = 0x00 if $self->insecure;
}
weaken $self;
my $id;
return $id = $loop->client(
%options => sub {
my ($loop, $err, $stream) = @_;
# Connection error
return unless $self;
return $self->_error($id, $err) if $err;
# Connection established
$stream->on(timeout => sub { $self->_error($id, 'Inactivity timeout') });
$stream->on(close => sub { $self && $self->_finish($id, 1) });
$stream->on(error => sub { $self && $self->_error($id, pop) });
$stream->on(read => sub { $self->_read($id, pop) });
$self->_process($id);
}
);
}
sub _connect_proxy {
my ($self, $loop, $old, $cb) = @_;
# Start CONNECT request
return undef unless my $new = $self->transactor->proxy_connect($old);
my $id;
return $id = $self->_start(
($loop, $new) => sub {
my ($self, $tx) = @_;
# Real transaction
$old->previous($tx)->req->via_proxy(0);
my $c = $self->{connections}{$id} = {cb => $cb, ioloop => $loop, tx => $old};
# CONNECT failed
return $self->_error($id, 'Proxy connection failed') if $tx->error || !$tx->res->is_success || !$tx->keep_alive;
# Start real transaction without TLS upgrade
return $self->_process($id) unless $tx->req->url->protocol eq 'https';
# TLS upgrade before starting the real transaction
my $handle = $loop->stream($id)->steal_handle;
$self->_remove($id);
$id = $self->_connect($loop, $old, $handle);
$self->{connections}{$id} = $c;
}
);
}
sub _connection {
my ($self, $loop, $tx, $cb) = @_;
# Reuse connection
my ($proto, $host, $port) = $self->transactor->endpoint($tx);
my $id;
if ($id = $self->_dequeue($loop, "$proto:$host:$port", 1)) {
warn "-- Reusing connection $id ($proto://$host:$port)\n" if DEBUG;
@{$self->{connections}{$id}}{qw(cb tx)} = ($cb, $tx);
$tx->kept_alive(1) unless $tx->connection;
$self->_process($id);
return $id;
}
# CONNECT request to proxy required
if (my $id = $self->_connect_proxy($loop, $tx, $cb)) { return $id }
# New connection
$tx->res->error({message => "Unsupported protocol: $proto"}) and return $loop->next_tick(sub { $self->$cb($tx) })
unless $proto eq 'http' || $proto eq 'https' || $proto eq 'http+unix';
$id = $self->_connect($loop, $tx);
warn "-- Connect $id ($proto://$host:$port)\n" if DEBUG;
$self->{connections}{$id} = {cb => $cb, ioloop => $loop, tx => $tx};
return $id;
}
sub _dequeue {
my ($self, $loop, $name, $test) = @_;
my $old = $self->{queue}{$loop} //= [];
my ($found, @new);
for my $queued (@$old) {
push @new, $queued and next if $found || !grep { $_ eq $name } @$queued;
# Search for id/name and sort out corrupted connections if necessary
next unless my $stream = $loop->stream($queued->[1]);
$test && $stream->is_readable ? $stream->close : ($found = $queued->[1]);
}
@$old = @new;
return $found;
}
sub _error {
my ($self, $id, $err) = @_;
my $tx = $self->{connections}{$id}{tx};
$tx->res->error({message => $err}) if $tx;
$self->_finish($id, 1);
}
sub _finish {
my ($self, $id, $close) = @_;
# Remove request timeout and finish transaction
return undef unless my $c = $self->{connections}{$id};
$c->{ioloop}->remove(delete $c->{timeout}) if $c->{timeout};
return $self->_reuse($id, $close) unless my $old = $c->{tx};
# Premature connection close
my $res = $old->closed->res->finish;
$res->error({message => 'Premature connection close'}) if $close && !$res->code && !$res->error;
# Always remove connection for WebSockets
return $self->_remove($id) if $old->is_websocket;
$self->cookie_jar->collect($old);
# Upgrade connection to WebSocket
if (my $new = $self->transactor->upgrade($old)) {
weaken $self;
$new->on(resume => sub { $self->_write($id) });
$c->{cb}($self, $c->{tx} = $new);
return $new->client_read($old->res->content->leftovers);
}
# CONNECT requests always have a follow-up request
$self->_reuse($id, $close) unless uc $old->req->method eq 'CONNECT';
$res->error({message => $res->message, code => $res->code}) if $res->is_error;
$c->{cb}($self, $old) unless $self->_redirect($c, $old);
}
sub _process {
my ($self, $id) = @_;
my $c = $self->{connections}{$id};
my $stream = $c->{ioloop}->stream($id)->timeout($self->inactivity_timeout);
my $tx = $c->{tx}->connection($id);
my $handle = $stream->handle;
unless ($handle->isa('IO::Socket::UNIX')) {
$tx->local_address($handle->sockhost)->local_port($handle->sockport);
$tx->remote_address($handle->peerhost)->remote_port($handle->peerport);
}
weaken $self;
$tx->on(resume => sub { $self->_write($id) });
$self->_write($id);
}
sub _read {
my ($self, $id, $chunk) = @_;
# Corrupted connection
return $self->_remove($id) unless my $tx = $self->{connections}{$id}{tx};
warn term_escape "-- Client <<< Server (@{[_url($tx)]})\n$chunk\n" if DEBUG;
$tx->client_read($chunk);
$self->_finish($id) if $tx->is_finished;
}
sub _redirect {
my ($self, $c, $old) = @_;
return undef unless my $new = $self->transactor->redirect($old);
return undef unless @{$old->redirects} < $self->max_redirects;
return $self->_start($c->{ioloop}, $new, delete $c->{cb});
}
sub _remove {
my ($self, $id) = @_;
my $c = delete $self->{connections}{$id};
$self->_dequeue($c->{ioloop}, $id);
$c->{ioloop}->remove($id);
}
sub _reuse {
my ($self, $id, $close) = @_;
# Connection close
my $c = $self->{connections}{$id};
my $tx = delete $c->{tx};
my $max = $self->max_connections;
return $self->_remove($id) if $close || !$tx || !$max || !$tx->keep_alive || $tx->error;
# Keep connection alive
my $queue = $self->{queue}{$c->{ioloop}} //= [];
$self->_remove(shift(@$queue)->[1]) while @$queue && @$queue >= $max;
push @$queue, [join(':', $self->transactor->endpoint($tx)), $id];
}
sub _start {
my ($self, $loop, $tx, $cb) = @_;
# Application server
$self->emit(prepare => $tx);
my $url = $tx->req->url;
if (!$url->is_abs && (my $server = $self->server)) {
my $base = $loop == $self->ioloop ? $server->url : $server->nb_url;
$url->scheme($base->scheme)->host($base->host)->port($base->port);
}
$_->prepare($tx) for $self->proxy, $self->cookie_jar;
my $max = $self->max_response_size;
$tx->res->max_message_size($max) if defined $max;
$self->emit(start => $tx);
# Allow test servers sharing the same event loop to clean up connections
!$loop->next_tick(sub { }) and $loop->one_tick unless $loop->is_running;
return undef unless my $id = $self->_connection($loop, $tx, $cb);
if (my $t = $self->request_timeout) {
weaken $self;
$self->{connections}{$id}{timeout} ||= $loop->timer($t => sub { $self->_error($id, 'Request timeout') });
}
return $id;
}
sub _url { shift->req->url->to_abs }
sub _write {
my ($self, $id) = @_;
# Protect from resume event recursion
my $c = $self->{connections}{$id};
return if !(my $tx = $c->{tx}) || $c->{writing};
local $c->{writing} = 1;
my $chunk = $tx->client_write;
warn term_escape "-- Client >>> Server (@{[_url($tx)]})\n$chunk\n" if DEBUG;
return unless length $chunk;
weaken $self;
$c->{ioloop}->stream($id)->write($chunk => sub { $self->_write($id) });
}
1;
=encoding utf8
=head1 NAME
Mojo::UserAgent - Non-blocking I/O HTTP and WebSocket user agent
=head1 SYNOPSIS
use Mojo::UserAgent;
# Fine grained response handling (dies on connection errors)
my $ua = Mojo::UserAgent->new;
my $res = $ua->get('docs.mojolicious.org')->result;
if ($res->is_success) { say $res->body }
elsif ($res->is_error) { say $res->message }
elsif ($res->code == 301) { say $res->headers->location }
else { say 'Whatever...' }
# Say hello to the Unicode snowman and include an Accept header
say $ua->get('www.☃.net?hello=there' => {Accept => '*/*'})->result->body;
# Extract data from HTML and XML resources with CSS selectors
say $ua->get('www.perl.org')->result->dom->at('title')->text;
# Scrape the latest headlines from a news site
say $ua->get('blogs.perl.org')->result->dom->find('h2 > a')->map('text')->join("\n");
# IPv6 PUT request with Content-Type header and content
my $tx = $ua->put('[::1]:3000' => {'Content-Type' => 'text/plain'} => 'Hi!');
# Quick JSON API request with Basic authentication
my $url = Mojo::URL->new('https://example.com/test.json')->userinfo('sri:☃');
my $value = $ua->get($url)->result->json;
# JSON POST (application/json) with TLS certificate authentication
my $tx = $ua->cert('tls.crt')->key('tls.key')->post('https://example.com' => json => {top => 'secret'});
# Form POST (application/x-www-form-urlencoded)
my $tx = $ua->post('https://metacpan.org/search' => form => {q => 'mojo'});
# Search DuckDuckGo anonymously through Tor
$ua->proxy->http('socks://127.0.0.1:9050');
say $ua->get('api.3g2upl4pq6kufc4m.onion/?q=mojolicious&format=json')->result->json('/Abstract');
# GET request via UNIX domain socket "/tmp/myapp.sock" (percent encoded slash)
say $ua->get('http+unix://%2Ftmp%2Fmyapp.sock/test')->result->body;
# Follow redirects to download Mojolicious from GitHub
$ua->max_redirects(5)
->get('https://www.github.com/mojolicious/mojo/tarball/master')
->result->save_to('/home/sri/mojo.tar.gz');
# Non-blocking request
$ua->get('mojolicious.org' => sub ($ua, $tx) { say $tx->result->dom->at('title')->text });
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
# Concurrent non-blocking requests (synchronized with promises)
my $mojo_promise = $ua->get_p('mojolicious.org');
my $cpan_promise = $ua->get_p('cpan.org');
Mojo::Promise->all($mojo_promise, $cpan_promise)->then(sub ($mojo, $cpan) {
say $mojo->[0]->result->dom->at('title')->text;
say $cpan->[0]->result->dom->at('title')->text;
})->wait;
# WebSocket connection sending and receiving JSON via UNIX domain socket
$ua->websocket('ws+unix://%2Ftmp%2Fmyapp.sock/echo.json' => sub ($ua, $tx) {
say 'WebSocket handshake failed!' and return unless $tx->is_websocket;
$tx->on(json => sub ($tx, $hash) {
say "WebSocket message via JSON: $hash->{msg}";
$tx->finish;
});
$tx->send({json => {msg => 'Hello World!'}});
});
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
=head1 DESCRIPTION
L<Mojo::UserAgent> is a full featured non-blocking I/O HTTP and WebSocket user agent, with IPv6, TLS, SNI, IDNA,
HTTP/SOCKS5 proxy, UNIX domain socket, Comet (long polling), Promises/A+, keep-alive, connection pooling, timeout,
cookie, multipart, gzip compression and multiple event loop support.
All connections will be reset automatically if a new process has been forked, this allows multiple processes to share
the same L<Mojo::UserAgent> object safely.
For better scalability (epoll, kqueue) and to provide non-blocking name resolution, SOCKS5 as well as TLS support, the
optional modules L<EV> (4.32+), L<Net::DNS::Native> (0.15+), L<IO::Socket::Socks> (0.64+) and L<IO::Socket::SSL>
(2.009+) will be used automatically if possible. Individual features can also be disabled with the C<MOJO_NO_NNR>,
C<MOJO_NO_SOCKS> and C<MOJO_NO_TLS> environment variables.
See L<Mojolicious::Guides::Cookbook/"USER AGENT"> for more.
=head1 EVENTS
L<Mojo::UserAgent> inherits all events from L<Mojo::EventEmitter> and can emit the following new ones.
=head2 prepare
$ua->on(prepare => sub ($ua, $tx) {...});
Emitted whenever a new transaction is being prepared, before relative URLs are rewritten and cookies added. This
includes automatically prepared proxy C<CONNECT> requests and followed redirects.
$ua->on(prepare => sub ($ua, $tx) {
$tx->req->url(Mojo::URL->new('/mock-mojolicious')) if $tx->req->url->host eq 'mojolicious.org';
});
=head2 start
$ua->on(start => sub ($ua, $tx) {...});
Emitted whenever a new transaction is about to start. This includes automatically prepared proxy C<CONNECT> requests
and followed redirects.
$ua->on(start => sub ($ua, $tx) {
$tx->req->headers->header('X-Bender' => 'Bite my shiny metal ass!');
});
=head1 ATTRIBUTES
L<Mojo::UserAgent> implements the following attributes.
=head2 ca
my $ca = $ua->ca;
$ua = $ua->ca('/etc/tls/ca.crt');
Path to TLS certificate authority file used to verify the peer certificate, defaults to the value of the
C<MOJO_CA_FILE> environment variable.
# Show certificate authorities for debugging
IO::Socket::SSL::set_defaults(SSL_verify_callback => sub { say "Authority: $_[2]" and return $_[0] });
=head2 cert
my $cert = $ua->cert;
$ua = $ua->cert('/etc/tls/client.crt');
Path to TLS certificate file, defaults to the value of the C<MOJO_CERT_FILE> environment variable.
=head2 connect_timeout
my $timeout = $ua->connect_timeout;
$ua = $ua->connect_timeout(5);
Maximum amount of time in seconds establishing a connection may take before getting canceled, defaults to the value of
the C<MOJO_CONNECT_TIMEOUT> environment variable or C<10>.
=head2 cookie_jar
my $cookie_jar = $ua->cookie_jar;
$ua = $ua->cookie_jar(Mojo::UserAgent::CookieJar->new);
Cookie jar to use for requests performed by this user agent, defaults to a L<Mojo::UserAgent::CookieJar> object.
# Ignore all cookies
$ua->cookie_jar->ignore(sub { 1 });
# Ignore cookies for public suffixes
my $ps = IO::Socket::SSL::PublicSuffix->default;
$ua->cookie_jar->ignore(sub ($cookie) {
return undef unless my $domain = $cookie->domain;
return ($ps->public_suffix($domain))[0] eq '';
});
# Add custom cookie to the jar
$ua->cookie_jar->add(
Mojo::Cookie::Response->new(
name => 'foo',
value => 'bar',
domain => 'docs.mojolicious.org',
path => '/Mojolicious'
)
);
=head2 inactivity_timeout
my $timeout = $ua->inactivity_timeout;
$ua = $ua->inactivity_timeout(15);
Maximum amount of time in seconds a connection can be inactive before getting closed, defaults to the value of the
C<MOJO_INACTIVITY_TIMEOUT> environment variable or C<40>. Setting the value to C<0> will allow connections to be
inactive indefinitely.
=head2 insecure
my $bool = $ua->insecure;
$ua = $ua->insecure($bool);
Do not require a valid TLS certificate to access HTTPS/WSS sites, defaults to the value of the C<MOJO_INSECURE>
environment variable.
# Disable TLS certificate verification for testing
say $ua->insecure(1)->get('https://127.0.0.1:3000')->result->code;
=head2 ioloop
my $loop = $ua->ioloop;
$ua = $ua->ioloop(Mojo::IOLoop->new);
Event loop object to use for blocking I/O operations, defaults to a L<Mojo::IOLoop> object.
=head2 key
my $key = $ua->key;
$ua = $ua->key('/etc/tls/client.crt');
Path to TLS key file, defaults to the value of the C<MOJO_KEY_FILE> environment variable.
=head2 local_address
my $address = $ua->local_address;
$ua = $ua->local_address('127.0.0.1');
Local address to bind to.
=head2 max_connections
my $max = $ua->max_connections;
$ua = $ua->max_connections(5);
Maximum number of keep-alive connections that the user agent will retain before it starts closing the oldest ones,
defaults to C<5>. Setting the value to C<0> will prevent any connections from being kept alive.
=head2 max_redirects
my $max = $ua->max_redirects;
$ua = $ua->max_redirects(3);
Maximum number of redirects the user agent will follow before it fails, defaults to the value of the
C<MOJO_MAX_REDIRECTS> environment variable or C<0>.
=head2 max_response_size
my $max = $ua->max_response_size;
$ua = $ua->max_response_size(16777216);
Maximum response size in bytes, defaults to the value of L<Mojo::Message::Response/"max_message_size">. Setting the
value to C<0> will allow responses of indefinite size. Note that increasing this value can also drastically increase
memory usage, should you for example attempt to parse an excessively large response body with the methods
L<Mojo::Message/"dom"> or L<Mojo::Message/"json">.
=head2 proxy
my $proxy = $ua->proxy;
$ua = $ua->proxy(Mojo::UserAgent::Proxy->new);
Proxy manager, defaults to a L<Mojo::UserAgent::Proxy> object.
# Detect proxy servers from environment
$ua->proxy->detect;
# Manually configure HTTP proxy (using CONNECT for HTTPS/WebSockets)
$ua->proxy->http('http://127.0.0.1:8080')->https('http://127.0.0.1:8080');
# Manually configure Tor (SOCKS5)
$ua->proxy->http('socks://127.0.0.1:9050')->https('socks://127.0.0.1:9050');
# Manually configure UNIX domain socket (using CONNECT for HTTPS/WebSockets)
$ua->proxy->http('http+unix://%2Ftmp%2Fproxy.sock') ->https('http+unix://%2Ftmp%2Fproxy.sock');
=head2 request_timeout
my $timeout = $ua->request_timeout;
$ua = $ua->request_timeout(5);
Maximum amount of time in seconds establishing a connection, sending the request and receiving a whole response may
take before getting canceled, defaults to the value of the C<MOJO_REQUEST_TIMEOUT> environment variable or C<0>.
Setting the value to C<0> will allow the user agent to wait indefinitely. The timeout will reset for every followed
redirect.
# Total limit of 5 seconds, of which 3 seconds may be spent connecting
$ua->max_redirects(0)->connect_timeout(3)->request_timeout(5);
=head2 server
my $server = $ua->server;
$ua = $ua->server(Mojo::UserAgent::Server->new);
Application server relative URLs will be processed with, defaults to a L<Mojo::UserAgent::Server> object.
# Mock web service
$ua->server->app(Mojolicious->new);
$ua->server->app->routes->get('/time' => sub ($c) {
$c->render(json => {now => time});
});
my $time = $ua->get('/time')->result->json->{now};
# Change log level
$ua->server->app->log->level('fatal');
# Port currently used for processing relative URLs blocking
say $ua->server->url->port;
# Port currently used for processing relative URLs non-blocking
say $ua->server->nb_url->port;
=head2 transactor
my $t = $ua->transactor;
$ua = $ua->transactor(Mojo::UserAgent::Transactor->new);
Transaction builder, defaults to a L<Mojo::UserAgent::Transactor> object.
# Change name of user agent
$ua->transactor->name('MyUA 1.0');
# Disable compression
$ua->transactor->compressed(0);
=head1 METHODS
L<Mojo::UserAgent> inherits all methods from L<Mojo::EventEmitter> and implements the following new ones.
=head2 build_tx
my $tx = $ua->build_tx(GET => 'example.com');
my $tx = $ua->build_tx(PUT => 'http://example.com' => {Accept => '*/*'} => 'Content!');
my $tx = $ua->build_tx(PUT => 'http://example.com' => {Accept => '*/*'} => form => {a => 'b'});
my $tx = $ua->build_tx(PUT => 'http://example.com' => {Accept => '*/*'} => json => {a => 'b'});
Generate L<Mojo::Transaction::HTTP> object with L<Mojo::UserAgent::Transactor/"tx">.
# Request with custom cookie
my $tx = $ua->build_tx(GET => 'https://example.com/account');
$tx->req->cookies({name => 'user', value => 'sri'});
$tx = $ua->start($tx);
# Deactivate gzip compression
my $tx = $ua->build_tx(GET => 'example.com');
$tx->req->headers->remove('Accept-Encoding');
$tx = $ua->start($tx);
# Interrupt response by raising an error
my $tx = $ua->build_tx(GET => 'http://example.com');
$tx->res->on(progress => sub ($res) {
return unless my $server = $res->headers->server;
$res->error({message => 'Oh noes, it is IIS!'}) if $server =~ /IIS/;
});
$tx = $ua->start($tx);
=head2 build_websocket_tx
my $tx = $ua->build_websocket_tx('ws://example.com');
my $tx = $ua->build_websocket_tx( 'ws://example.com' => {DNT => 1} => ['v1.proto']);
Generate L<Mojo::Transaction::HTTP> object with L<Mojo::UserAgent::Transactor/"websocket">.
# Custom WebSocket handshake with cookie
my $tx = $ua->build_websocket_tx('wss://example.com/echo');
$tx->req->cookies({name => 'user', value => 'sri'});
$ua->start($tx => sub ($ua, $tx) {
say 'WebSocket handshake failed!' and return unless $tx->is_websocket;
$tx->on(message => sub ($tx, $msg) {
say "WebSocket message: $msg";
$tx->finish;
});
$tx->send('Hi!');
});
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
=head2 delete
my $tx = $ua->delete('example.com');
my $tx = $ua->delete('http://example.com' => {Accept => '*/*'} => 'Content!');
my $tx = $ua->delete('http://example.com' => {Accept => '*/*'} => form => {a => 'b'});
my $tx = $ua->delete('http://example.com' => {Accept => '*/*'} => json => {a => 'b'});
Perform blocking C<DELETE> request and return resulting L<Mojo::Transaction::HTTP> object, takes the same arguments as
L<Mojo::UserAgent::Transactor/"tx"> (except for the C<DELETE> method, which is implied). You can also append a callback
to perform requests non-blocking.
$ua->delete('http://example.com' => json => {a => 'b'} => sub ($ua, $tx) { say $tx->result->body });
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
=head2 delete_p
my $promise = $ua->delete_p('http://example.com');
Same as L</"delete">, but performs all requests non-blocking and returns a L<Mojo::Promise> object instead of accepting
a callback.
$ua->delete_p('http://example.com' => json => {a => 'b'})->then(sub ($tx) {
say $tx->result->body;
})->catch(sub ($err) {
warn "Connection error: $err";
})->wait;
=head2 get
my $tx = $ua->get('example.com');
my $tx = $ua->get('http://example.com' => {Accept => '*/*'} => 'Content!');
my $tx = $ua->get('http://example.com' => {Accept => '*/*'} => form => {a => 'b'});
my $tx = $ua->get('http://example.com' => {Accept => '*/*'} => json => {a => 'b'});
Perform blocking C<GET> request and return resulting L<Mojo::Transaction::HTTP> object, takes the same arguments as
L<Mojo::UserAgent::Transactor/"tx"> (except for the C<GET> method, which is implied). You can also append a callback to
perform requests non-blocking.
$ua->get('http://example.com' => json => {a => 'b'} => sub ($ua, $tx) { say $tx->result->body });
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
=head2 get_p
my $promise = $ua->get_p('http://example.com');
Same as L</"get">, but performs all requests non-blocking and returns a L<Mojo::Promise> object instead of accepting a
callback.
$ua->get_p('http://example.com' => json => {a => 'b'})->then(sub ($tx) {
say $tx->result->body;
})->catch(sub ($err) {
warn "Connection error: $err";
})->wait;
=head2 head
my $tx = $ua->head('example.com');
my $tx = $ua->head('http://example.com' => {Accept => '*/*'} => 'Content!');
my $tx = $ua->head('http://example.com' => {Accept => '*/*'} => form => {a => 'b'});
my $tx = $ua->head('http://example.com' => {Accept => '*/*'} => json => {a => 'b'});
Perform blocking C<HEAD> request and return resulting L<Mojo::Transaction::HTTP> object, takes the same arguments as
L<Mojo::UserAgent::Transactor/"tx"> (except for the C<HEAD> method, which is implied). You can also append a callback
to perform requests non-blocking.
$ua->head('http://example.com' => json => {a => 'b'} => sub ($ua, $tx) { say $tx->result->body });
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
=head2 head_p
my $promise = $ua->head_p('http://example.com');
Same as L</"head">, but performs all requests non-blocking and returns a L<Mojo::Promise> object instead of accepting a
callback.
$ua->head_p('http://example.com' => json => {a => 'b'})->then(sub ($tx) {
say $tx->result->body;
})->catch(sub ($err) {
warn "Connection error: $err";
})->wait;
=head2 options
my $tx = $ua->options('example.com');
my $tx = $ua->options('http://example.com' => {Accept => '*/*'} => 'Content!');
my $tx = $ua->options('http://example.com' => {Accept => '*/*'} => form => {a => 'b'});
my $tx = $ua->options('http://example.com' => {Accept => '*/*'} => json => {a => 'b'});
Perform blocking C<OPTIONS> request and return resulting L<Mojo::Transaction::HTTP> object, takes the same arguments as
L<Mojo::UserAgent::Transactor/"tx"> (except for the C<OPTIONS> method, which is implied). You can also append a
callback to perform requests non-blocking.
$ua->options('http://example.com' => json => {a => 'b'} => sub ($ua, $tx) { say $tx->result->body });
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
=head2 options_p
my $promise = $ua->options_p('http://example.com');
Same as L</"options">, but performs all requests non-blocking and returns a L<Mojo::Promise> object instead of
accepting a callback.
$ua->options_p('http://example.com' => json => {a => 'b'})->then(sub ($tx) {
say $tx->result->body;
})->catch(sub ($err) {
warn "Connection error: $err";
})->wait;
=head2 patch
my $tx = $ua->patch('example.com');
my $tx = $ua->patch('http://example.com' => {Accept => '*/*'} => 'Content!');
my $tx = $ua->patch('http://example.com' => {Accept => '*/*'} => form => {a => 'b'});
my $tx = $ua->patch('http://example.com' => {Accept => '*/*'} => json => {a => 'b'});
Perform blocking C<PATCH> request and return resulting L<Mojo::Transaction::HTTP> object, takes the same arguments as
L<Mojo::UserAgent::Transactor/"tx"> (except for the C<PATCH> method, which is implied). You can also append a callback
to perform requests non-blocking.
$ua->patch('http://example.com' => json => {a => 'b'} => sub ($ua, $tx) { say $tx->result->body });
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
=head2 patch_p
my $promise = $ua->patch_p('http://example.com');
Same as L</"patch">, but performs all requests non-blocking and returns a L<Mojo::Promise> object instead of accepting
a callback.
$ua->patch_p('http://example.com' => json => {a => 'b'})->then(sub ($tx) {
say $tx->result->body;
})->catch(sub ($err) {
warn "Connection error: $err";
})->wait;
=head2 post
my $tx = $ua->post('example.com');
my $tx = $ua->post('http://example.com' => {Accept => '*/*'} => 'Content!');
my $tx = $ua->post('http://example.com' => {Accept => '*/*'} => form => {a => 'b'});
my $tx = $ua->post('http://example.com' => {Accept => '*/*'} => json => {a => 'b'});
Perform blocking C<POST> request and return resulting L<Mojo::Transaction::HTTP> object, takes the same arguments as
L<Mojo::UserAgent::Transactor/"tx"> (except for the C<POST> method, which is implied). You can also append a callback
to perform requests non-blocking.
$ua->post('http://example.com' => json => {a => 'b'} => sub ($ua, $tx) { say $tx->result->body });
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
=head2 post_p
my $promise = $ua->post_p('http://example.com');
Same as L</"post">, but performs all requests non-blocking and returns a L<Mojo::Promise> object instead of accepting a
callback.
$ua->post_p('http://example.com' => json => {a => 'b'})->then(sub ($tx) {
say $tx->result->body;
})->catch(sub ($err) {
warn "Connection error: $err";
})->wait;
=head2 put
my $tx = $ua->put('example.com');
my $tx = $ua->put('http://example.com' => {Accept => '*/*'} => 'Content!');
my $tx = $ua->put('http://example.com' => {Accept => '*/*'} => form => {a => 'b'});
my $tx = $ua->put('http://example.com' => {Accept => '*/*'} => json => {a => 'b'});
Perform blocking C<PUT> request and return resulting L<Mojo::Transaction::HTTP> object, takes the same arguments as
L<Mojo::UserAgent::Transactor/"tx"> (except for the C<PUT> method, which is implied). You can also append a callback to
perform requests non-blocking.
$ua->put('http://example.com' => json => {a => 'b'} => sub ($ua, $tx) { say $tx->result->body });
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
=head2 put_p
my $promise = $ua->put_p('http://example.com');
Same as L</"put">, but performs all requests non-blocking and returns a L<Mojo::Promise> object instead of accepting a
callback.
$ua->put_p('http://example.com' => json => {a => 'b'})->then(sub ($tx) {
say $tx->result->body;
})->catch(sub ($err) {
warn "Connection error: $err";
})->wait;
=head2 start
my $tx = $ua->start(Mojo::Transaction::HTTP->new);
Perform blocking request for a custom L<Mojo::Transaction::HTTP> object, which can be prepared manually or with
L</"build_tx">. You can also append a callback to perform requests non-blocking.
my $tx = $ua->build_tx(GET => 'http://example.com');
$ua->start($tx => sub ($ua, $tx) { say $tx->result->body });
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
=head2 start_p
my $promise = $ua->start_p(Mojo::Transaction::HTTP->new);
Same as L</"start">, but performs all requests non-blocking and returns a L<Mojo::Promise> object instead of accepting
a callback.
my $tx = $ua->build_tx(GET => 'http://example.com');
$ua->start_p($tx)->then(sub ($tx) {
say $tx->result->body;
})->catch(sub ($err) {
warn "Connection error: $err";
})->wait;
=head2 websocket
$ua->websocket('ws://example.com' => sub {...});
$ua->websocket('ws://example.com' => {DNT => 1} => ['v1.proto'] => sub {...});
Open a non-blocking WebSocket connection with transparent handshake, takes the same arguments as
L<Mojo::UserAgent::Transactor/"websocket">. The callback will receive either a L<Mojo::Transaction::WebSocket> or
L<Mojo::Transaction::HTTP> object, depending on if the handshake was successful.
$ua->websocket('wss://example.com/echo' => ['v1.proto'] => sub ($ua, $tx) {
say 'WebSocket handshake failed!' and return unless $tx->is_websocket;
say 'Subprotocol negotiation failed!' and return unless $tx->protocol;
$tx->on(finish => sub ($tx, $code, $reason) { say "WebSocket closed with status $code." });
$tx->on(message => sub ($tx, $msg) {
say "WebSocket message: $msg";
$tx->finish;
});
$tx->send('Hi!');
});
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
You can activate C<permessage-deflate> compression by setting the C<Sec-WebSocket-Extensions> header, this can result
in much better performance, but also increases memory usage by up to 300KiB per connection.
$ua->websocket('ws://example.com/foo' => {
'Sec-WebSocket-Extensions' => 'permessage-deflate'
} => sub {...});
=head2 websocket_p
my $promise = $ua->websocket_p('ws://example.com');
Same as L</"websocket">, but returns a L<Mojo::Promise> object instead of accepting a callback.
$ua->websocket_p('wss://example.com/echo')->then(sub ($tx) {
my $promise = Mojo::Promise->new;
$tx->on(finish => sub { $promise->resolve });
$tx->on(message => sub ($tx, $msg) {
say "WebSocket message: $msg";
$tx->finish;
});
$tx->send('Hi!');
return $promise;
})->catch(sub ($err) {
warn "WebSocket error: $err";
})->wait;
=head1 DEBUGGING
You can set the C<MOJO_CLIENT_DEBUG> environment variable to get some advanced diagnostics information printed to
C<STDERR>.
MOJO_CLIENT_DEBUG=1
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,221 @@
package Mojo::UserAgent::CookieJar;
use Mojo::Base -base;
use Mojo::Cookie::Request;
use Mojo::Path;
use Scalar::Util qw(looks_like_number);
has 'ignore';
has max_cookie_size => 4096;
sub add {
my ($self, @cookies) = @_;
my $size = $self->max_cookie_size;
for my $cookie (@cookies) {
# Convert max age to expires
my $age = $cookie->max_age;
$cookie->expires($age <= 0 ? 0 : $age + time) if looks_like_number $age;
# Check cookie size
next if length($cookie->value // '') > $size;
# Replace cookie
next unless my $domain = lc($cookie->domain // '');
next unless my $path = $cookie->path;
next unless length(my $name = $cookie->name // '');
my $jar = $self->{jar}{$domain} //= [];
@$jar = (grep({ _compare($_, $path, $name, $domain) } @$jar), $cookie);
}
return $self;
}
sub all {
my $jar = shift->{jar};
return [map { @{$jar->{$_}} } sort keys %$jar];
}
sub collect {
my ($self, $tx) = @_;
my $url = $tx->req->url;
for my $cookie (@{$tx->res->cookies}) {
# Validate domain
my $host = lc $url->ihost;
$cookie->domain($host)->host_only(1) unless $cookie->domain;
my $domain = lc $cookie->domain;
if (my $cb = $self->ignore) { next if $cb->($cookie) }
next if $host ne $domain && ($host !~ /\Q.$domain\E$/ || $host =~ /\.\d+$/);
# Validate path
my $path = $cookie->path // $url->path->to_dir->to_abs_string;
$path = Mojo::Path->new($path)->trailing_slash(0)->to_abs_string;
next unless _path($path, $url->path->to_abs_string);
$self->add($cookie->path($path));
}
}
sub empty { delete shift->{jar} }
sub find {
my ($self, $url) = @_;
my @found;
my $domain = my $host = lc $url->ihost;
my $path = $url->path->to_abs_string;
while ($domain) {
next unless my $old = $self->{jar}{$domain};
# Grab cookies
my $new = $self->{jar}{$domain} = [];
for my $cookie (@$old) {
next if $cookie->host_only && $host ne $cookie->domain;
# Check if cookie has expired
if (defined(my $expires = $cookie->expires)) { next if time > $expires }
push @$new, $cookie;
# Taste cookie
next if $cookie->secure && $url->protocol ne 'https';
next unless _path($cookie->path, $path);
my $name = $cookie->name;
my $value = $cookie->value;
push @found, Mojo::Cookie::Request->new(name => $name, value => $value);
}
}
# Remove another part
continue { $domain =~ s/^[^.]*\.*// }
return \@found;
}
sub prepare {
my ($self, $tx) = @_;
return unless keys %{$self->{jar}};
my $req = $tx->req;
$req->cookies(@{$self->find($req->url)});
}
sub _compare {
my ($cookie, $path, $name, $domain) = @_;
return $cookie->path ne $path || $cookie->name ne $name || $cookie->domain ne $domain;
}
sub _path { $_[0] eq '/' || $_[0] eq $_[1] || index($_[1], "$_[0]/") == 0 }
1;
=encoding utf8
=head1 NAME
Mojo::UserAgent::CookieJar - Cookie jar for HTTP user agents
=head1 SYNOPSIS
use Mojo::UserAgent::CookieJar;
# Add response cookies
my $jar = Mojo::UserAgent::CookieJar->new;
$jar->add(
Mojo::Cookie::Response->new(
name => 'foo',
value => 'bar',
domain => 'localhost',
path => '/test'
)
);
# Find request cookies
for my $cookie (@{$jar->find(Mojo::URL->new('http://localhost/test'))}) {
say $cookie->name;
say $cookie->value;
}
=head1 DESCRIPTION
L<Mojo::UserAgent::CookieJar> is a minimalistic and relaxed cookie jar used by L<Mojo::UserAgent>, based on L<RFC
6265|https://tools.ietf.org/html/rfc6265>.
=head1 ATTRIBUTES
L<Mojo::UserAgent::CookieJar> implements the following attributes.
=head2 ignore
my $ignore = $jar->ignore;
$jar = $jar->ignore(sub {...});
A callback used to decide if a cookie should be ignored by L</"collect">.
# Ignore all cookies
$jar->ignore(sub { 1 });
# Ignore cookies for domains "com", "net" and "org"
$jar->ignore(sub ($cookie) {
return undef unless my $domain = $cookie->domain;
return $domain eq 'com' || $domain eq 'net' || $domain eq 'org';
});
=head2 max_cookie_size
my $size = $jar->max_cookie_size;
$jar = $jar->max_cookie_size(4096);
Maximum cookie size in bytes, defaults to C<4096> (4KiB).
=head1 METHODS
L<Mojo::UserAgent::CookieJar> inherits all methods from L<Mojo::Base> and implements the following new ones.
=head2 add
$jar = $jar->add(@cookies);
Add multiple L<Mojo::Cookie::Response> objects to the jar.
=head2 all
my $cookies = $jar->all;
Return all L<Mojo::Cookie::Response> objects that are currently stored in the jar.
# Names of all cookies
say $_->name for @{$jar->all};
=head2 collect
$jar->collect(Mojo::Transaction::HTTP->new);
Collect response cookies from transaction.
=head2 empty
$jar->empty;
Empty the jar.
=head2 find
my $cookies = $jar->find(Mojo::URL->new);
Find L<Mojo::Cookie::Request> objects in the jar for L<Mojo::URL> object.
# Names of all cookies found
say $_->name for @{$jar->find(Mojo::URL->new('http://example.com/foo'))};
=head2 prepare
$jar->prepare(Mojo::Transaction::HTTP->new);
Prepare request cookies for transaction.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,109 @@
package Mojo::UserAgent::Proxy;
use Mojo::Base -base;
use Mojo::URL;
has [qw(http https not)];
sub detect {
my $self = shift;
$self->http($ENV{HTTP_PROXY} || $ENV{http_proxy});
$self->https($ENV{HTTPS_PROXY} || $ENV{https_proxy});
return $self->not([split /,/, $ENV{NO_PROXY} || $ENV{no_proxy} || '']);
}
sub is_needed {
!grep { $_[1] =~ /\Q$_\E$/ } @{$_[0]->not // []};
}
sub prepare {
my ($self, $tx) = @_;
$self->detect if $ENV{MOJO_PROXY};
my $req = $tx->req;
my $url = $req->url;
return unless $self->is_needed($url->host);
# HTTP proxy
my $proto = $url->protocol;
my $http = $self->http;
$req->proxy(Mojo::URL->new($http)) if $http && $proto eq 'http';
# HTTPS proxy
my $https = $self->https;
$req->proxy(Mojo::URL->new($https)) if $https && $proto eq 'https';
}
1;
=encoding utf8
=head1 NAME
Mojo::UserAgent::Proxy - User agent proxy manager
=head1 SYNOPSIS
use Mojo::UserAgent::Proxy;
my $proxy = Mojo::UserAgent::Proxy->new;
$proxy->detect;
say $proxy->http;
=head1 DESCRIPTION
L<Mojo::UserAgent::Proxy> manages proxy servers for L<Mojo::UserAgent>.
=head1 ATTRIBUTES
L<Mojo::UserAgent::Proxy> implements the following attributes.
=head2 http
my $http = $proxy->http;
$proxy = $proxy->http('socks://sri:secret@127.0.0.1:8080');
Proxy server to use for HTTP and WebSocket requests.
=head2 https
my $https = $proxy->https;
$proxy = $proxy->https('http://sri:secret@127.0.0.1:8080');
Proxy server to use for HTTPS and WebSocket requests.
=head2 not
my $not = $proxy->not;
$proxy = $proxy->not(['localhost', 'intranet.mojolicious.org']);
Domains that don't require a proxy server to be used.
=head1 METHODS
L<Mojo::UserAgent::Proxy> inherits all methods from L<Mojo::Base> and implements the following new ones.
=head2 detect
$proxy = $proxy->detect;
Check environment variables C<HTTP_PROXY>, C<http_proxy>, C<HTTPS_PROXY>, C<https_proxy>, C<NO_PROXY> and C<no_proxy>
for proxy information. Automatic proxy detection can be enabled with the C<MOJO_PROXY> environment variable.
=head2 is_needed
my $bool = $proxy->is_needed('intranet.example.com');
Check if request for domain would use a proxy server.
=head2 prepare
$proxy->prepare(Mojo::Transaction::HTTP->new);
Prepare proxy server information for transaction.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,127 @@
package Mojo::UserAgent::Server;
use Mojo::Base -base;
use Mojo::IOLoop;
use Mojo::Server::Daemon;
use Scalar::Util qw(weaken);
has ioloop => sub { Mojo::IOLoop->singleton };
sub app {
my ($self, $app) = @_;
# Singleton application
state $singleton;
return $singleton = $app ? $app : $singleton unless ref $self;
# Default to singleton application
return $self->{app} || $singleton unless $app;
$self->{app} = $app;
return $self;
}
sub nb_url { shift->_url(1, @_) }
sub restart { delete @{$_[0]}{qw(nb_port nb_server port server)} }
sub url { shift->_url(0, @_) }
sub _url {
my ($self, $nb, $proto) = @_;
if (!$self->{server} || $proto) {
$proto = $self->{proto} = $proto || 'http';
# Blocking
my $server = $self->{server} = Mojo::Server::Daemon->new(ioloop => $self->ioloop, silent => 1);
weaken $server->app($self->app)->{app};
my $port = $self->{port} ? ":$self->{port}" : '';
$self->{port} = $server->listen(["$proto://127.0.0.1$port"])->start->ports->[0];
# Non-blocking
$server = $self->{nb_server} = Mojo::Server::Daemon->new(silent => 1);
weaken $server->app($self->app)->{app};
$port = $self->{nb_port} ? ":$self->{nb_port}" : '';
$self->{nb_port} = $server->listen(["$proto://127.0.0.1$port"])->start->ports->[0];
}
my $port = $nb ? $self->{nb_port} : $self->{port};
return Mojo::URL->new("$self->{proto}://127.0.0.1:$port/");
}
1;
=encoding utf8
=head1 NAME
Mojo::UserAgent::Server - Application server
=head1 SYNOPSIS
use Mojo::UserAgent::Server;
my $server = Mojo::UserAgent::Server->new;
say $server->url;
=head1 DESCRIPTION
L<Mojo::UserAgent::Server> is an embedded web server based on L<Mojo::Server::Daemon> that processes requests for
L<Mojo::UserAgent>.
=head1 ATTRIBUTES
L<Mojo::UserAgent::Server> implements the following attributes.
=head2 ioloop
my $loop = $server->ioloop;
$server = $server->ioloop(Mojo::IOLoop->new);
Event loop object to use for I/O operations, defaults to the global L<Mojo::IOLoop> singleton.
=head1 METHODS
L<Mojo::UserAgent::Server> inherits all methods from L<Mojo::Base> and implements the following new ones.
=head2 app
my $app = Mojo::UserAgent::Server->app;
Mojo::UserAgent::Server->app(Mojolicious->new);
my $app = $server->app;
$server = $server->app(Mojolicious->new);
Application this server handles, instance specific applications override the global default.
# Change application behavior
$server->app->defaults(testing => 'oh yea!');
=head2 nb_url
my $url = $server->nb_url;
my $url = $server->nb_url('http');
my $url = $server->nb_url('https');
Get absolute L<Mojo::URL> object for server processing non-blocking requests with L</"app"> and switch protocol if
necessary.
=head2 restart
$server->restart;
Restart server with new port.
=head2 url
my $url = $server->url;
my $url = $server->url('http');
my $url = $server->url('https');
Get absolute L<Mojo::URL> object for server processing blocking requests with L</"app"> and switch protocol if
necessary.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,544 @@
package Mojo::UserAgent::Transactor;
use Mojo::Base -base;
use Mojo::Asset::File;
use Mojo::Asset::Memory;
use Mojo::Content::MultiPart;
use Mojo::Content::Single;
use Mojo::File qw(path);
use Mojo::JSON qw(encode_json);
use Mojo::Parameters;
use Mojo::Transaction::HTTP;
use Mojo::Transaction::WebSocket;
use Mojo::URL;
use Mojo::Util qw(encode url_escape);
use Mojo::WebSocket qw(challenge client_handshake);
has compressed => sub { $ENV{MOJO_GZIP} // 1 };
has generators => sub { {form => \&_form, json => \&_json, multipart => \&_multipart} };
has name => 'Mojolicious (Perl)';
sub add_generator { $_[0]->generators->{$_[1]} = $_[2] and return $_[0] }
sub endpoint {
my ($self, $tx) = @_;
# Basic endpoint
my $req = $tx->req;
my $url = $req->url;
my $proto = $url->protocol || 'http';
my $host = $url->ihost;
my $port = $url->port // ($proto eq 'https' ? 443 : 80);
# Proxy for normal HTTP requests
my $socks;
if (my $proxy = $req->proxy) { $socks = $proxy->protocol eq 'socks' }
return _proxy($tx, $proto, $host, $port) if $proto eq 'http' && !$req->is_handshake && !$socks;
return $proto, $host, $port;
}
sub peer { _proxy($_[1], $_[0]->endpoint($_[1])) }
sub promisify {
my ($self, $promise, $tx) = @_;
my $err = $tx->error;
return $promise->reject($err->{message}) if $err && !$err->{code};
return $promise->reject('WebSocket handshake failed') if $tx->req->is_handshake && !$tx->is_websocket;
$promise->resolve($tx);
}
sub proxy_connect {
my ($self, $old) = @_;
# Already a CONNECT request
my $req = $old->req;
return undef if uc $req->method eq 'CONNECT';
# No proxy
return undef unless (my $proxy = $req->proxy) && $req->via_proxy;
return undef if $proxy->protocol eq 'socks';
# WebSocket and/or HTTPS
my $url = $req->url;
return undef unless $req->is_handshake || $url->protocol eq 'https';
# CONNECT request (expect a bad response)
my $new = $self->tx(CONNECT => $url->clone->userinfo(undef));
$new->req->proxy($proxy);
$new->res->content->auto_relax(0)->headers->connection('keep-alive');
return $new;
}
sub redirect {
my ($self, $old) = @_;
# Commonly used codes
my $res = $old->res;
my $code = $res->code // 0;
return undef unless grep { $_ == $code } 301, 302, 303, 307, 308;
# CONNECT requests cannot be redirected
my $req = $old->req;
return undef if uc $req->method eq 'CONNECT';
# Fix location without authority and/or scheme
return undef unless my $location = $res->headers->every_header('Location')->[0];
$location = Mojo::URL->new($location);
$location = $location->base($req->url)->to_abs unless $location->is_abs;
my $proto = $location->protocol;
return undef if ($proto ne 'http' && $proto ne 'https') || !$location->host;
# Clone request if necessary
my $new = Mojo::Transaction::HTTP->new;
if ($code == 307 || $code == 308) {
return undef unless my $clone = $req->clone;
$new->req($clone);
}
else {
my $method = uc $req->method;
$method = $code == 303 || $method eq 'POST' ? 'GET' : $method;
$new->req->method($method)->content->headers(my $headers = $req->headers->clone);
$headers->remove($_) for grep {/^content-/i} @{$headers->names};
}
$new->res->content->auto_decompress(0) unless $self->compressed;
my $headers = $new->req->url($location)->headers;
$headers->remove($_) for qw(Authorization Cookie Host Referer);
return $new->previous($old);
}
sub tx {
my ($self, $method, $url) = (shift, shift, shift);
# Method and URL
my $tx = Mojo::Transaction::HTTP->new;
my $req = $tx->req->method($method);
ref $url ? $req->url($url) : $req->url->parse($url =~ m!^/|://! ? $url : "http://$url");
# Headers (we identify ourselves and accept gzip compression)
my $headers = $req->headers;
$headers->from_hash(shift) if ref $_[0] eq 'HASH';
$headers->user_agent($self->name) unless $headers->user_agent;
if (!$self->compressed) { $tx->res->content->auto_decompress(0) }
elsif (!$headers->accept_encoding) { $headers->accept_encoding('gzip') }
# Generator
if (@_ > 1) {
my $cb = $self->generators->{shift()};
$self->$cb($tx, @_);
}
# Body
elsif (@_) { $req->body(shift) }
return $tx;
}
sub upgrade {
my ($self, $tx) = @_;
my $code = $tx->res->code // 0;
return undef unless $tx->req->is_handshake && $code == 101;
my $ws = Mojo::Transaction::WebSocket->new(handshake => $tx, masked => 1);
return challenge($ws) ? $ws->established(1) : undef;
}
sub websocket {
my $self = shift;
# New WebSocket transaction
my $sub = ref $_[-1] eq 'ARRAY' ? pop : [];
my $tx = $self->tx(GET => @_);
my $req = $tx->req;
$req->headers->sec_websocket_protocol(join ', ', @$sub) if @$sub;
# Handshake protocol
my $url = $req->url;
my $proto = $url->protocol // '';
if ($proto eq 'ws') { $url->scheme('http') }
elsif ($proto eq 'wss') { $url->scheme('https') }
elsif ($proto eq 'ws+unix') { $url->scheme('http+unix') }
return client_handshake $tx;
}
sub _content { Mojo::Content::MultiPart->new(headers => $_[0], parts => $_[1]) }
sub _form {
my ($self, $tx, $form, %options) = @_;
$options{charset} = 'UTF-8' unless exists $options{charset};
# Check for uploads and force multipart if necessary
my $req = $tx->req;
my $headers = $req->headers;
my $multipart = ($headers->content_type // '') =~ m!multipart/form-data!i;
for my $value (map { ref $_ eq 'ARRAY' ? @$_ : $_ } values %$form) {
++$multipart and last if ref $value eq 'HASH';
}
# Multipart
if ($multipart) {
$req->content(_content($headers, _form_parts($options{charset}, $form)));
_type($headers, 'multipart/form-data');
return $tx;
}
# Query parameters or urlencoded
my $method = uc $req->method;
my @form = map { $_ => $form->{$_} } sort keys %$form;
if ($method eq 'GET' || $method eq 'HEAD') { $req->url->query->merge(@form) }
else {
$req->body(Mojo::Parameters->new(@form)->charset($options{charset})->to_string);
_type($headers, 'application/x-www-form-urlencoded');
}
return $tx;
}
sub _form_parts {
my ($charset, $form) = @_;
my @parts;
for my $name (sort keys %$form) {
next unless defined(my $values = $form->{$name});
$values = [$values] unless ref $values eq 'ARRAY';
push @parts, @{_parts($charset, $name, $values)};
}
return \@parts;
}
sub _json {
my ($self, $tx, $data) = @_;
_type($tx->req->body(encode_json $data)->headers, 'application/json');
return $tx;
}
sub _multipart {
my ($self, $tx, $parts) = @_;
my $req = $tx->req;
$req->content(_content($req->headers, _parts(undef, undef, $parts)));
return $tx;
}
sub _parts {
my ($charset, $name, $values) = @_;
my @parts;
for my $value (@$values) {
push @parts, my $part = Mojo::Content::Single->new;
my $filename;
my $headers = $part->headers;
if (ref $value eq 'HASH') {
# File
if (my $file = delete $value->{file}) {
$file = Mojo::Asset::File->new(path => $file) unless ref $file;
$part->asset($file);
$value->{filename} //= path($file->path)->basename if $file->isa('Mojo::Asset::File');
}
# Memory
elsif (defined(my $content = delete $value->{content})) {
$part->asset(Mojo::Asset::Memory->new->add_chunk($content));
}
# Filename and headers
$filename = delete $value->{filename};
$headers->from_hash($value);
next unless defined $name;
$filename = url_escape $filename // $name, '"';
$filename = encode $charset, $filename if $charset;
}
# Field
else {
$value = encode $charset, $value if $charset;
$part->asset(Mojo::Asset::Memory->new->add_chunk($value));
}
# Content-Disposition
next unless defined $name;
$name = url_escape $name, '"';
$name = encode $charset, $name if $charset;
my $disposition = qq{form-data; name="$name"};
$disposition .= qq{; filename="$filename"} if defined $filename;
$headers->content_disposition($disposition);
}
return \@parts;
}
sub _proxy {
my ($tx, $proto, $host, $port) = @_;
my $req = $tx->req;
if ($req->via_proxy && (my $proxy = $req->proxy)) {
return $proxy->protocol, $proxy->ihost, $proxy->port // ($proto eq 'https' ? 443 : 80);
}
return $proto, $host, $port;
}
sub _type { $_[0]->content_type($_[1]) unless $_[0]->content_type }
1;
=encoding utf8
=head1 NAME
Mojo::UserAgent::Transactor - User agent transactor
=head1 SYNOPSIS
use Mojo::UserAgent::Transactor;
# GET request with Accept header
my $t = Mojo::UserAgent::Transactor->new;
say $t->tx(GET => 'http://example.com' => {Accept => '*/*'})->req->to_string;
# POST request with form-data
say $t->tx(POST => 'example.com' => form => {a => 'b'})->req->to_string;
# PUT request with JSON data
say $t->tx(PUT => 'example.com' => json => {a => 'b'})->req->to_string;
=head1 DESCRIPTION
L<Mojo::UserAgent::Transactor> is the transaction building and manipulation framework used by L<Mojo::UserAgent>.
=head1 GENERATORS
These content generators are available by default.
=head2 form
$t->tx(POST => 'http://example.com' => form => {a => 'b'});
Generate query string, C<application/x-www-form-urlencoded> or C<multipart/form-data> content. See L</"tx"> for more.
=head2 json
$t->tx(PATCH => 'http://example.com' => json => {a => 'b'});
Generate JSON content with L<Mojo::JSON>. See L</"tx"> for more.
=head2 multipart
$t->tx(PUT => 'http://example.com' => multipart => ['Hello', 'World!']);
Generate multipart content. See L</"tx"> for more.
=head1 ATTRIBUTES
L<Mojo::UserAgent::Transactor> implements the following attributes.
=head2 compressed
my $bool = $t->compressed;
$t = $t->compressed($bool);
Try to negotiate compression for the response content and decompress it automatically, defaults to the value of the
C<MOJO_GZIP> environment variable or true.
=head2 generators
my $generators = $t->generators;
$t = $t->generators({foo => sub {...}});
Registered content generators, by default only C<form>, C<json> and C<multipart> are already defined.
=head2 name
my $name = $t->name;
$t = $t->name('Mojolicious');
Value for C<User-Agent> request header of generated transactions, defaults to C<Mojolicious (Perl)>.
=head1 METHODS
L<Mojo::UserAgent::Transactor> inherits all methods from L<Mojo::Base> and implements the following new ones.
=head2 add_generator
$t = $t->add_generator(foo => sub {...});
Register a content generator.
$t->add_generator(foo => sub ($t, $tx, @args) {...});
=head2 endpoint
my ($proto, $host, $port) = $t->endpoint(Mojo::Transaction::HTTP->new);
Actual endpoint for transaction.
=head2 peer
my ($proto, $host, $port) = $t->peer(Mojo::Transaction::HTTP->new);
Actual peer for transaction.
=head2 promisify
$t->promisify(Mojo::Promise->new, Mojo::Transaction::HTTP->new);
Resolve or reject L<Mojo::Promise> object with L<Mojo::Transaction::HTTP> object.
=head2 proxy_connect
my $tx = $t->proxy_connect(Mojo::Transaction::HTTP->new);
Build L<Mojo::Transaction::HTTP> proxy C<CONNECT> request for transaction if possible.
=head2 redirect
my $tx = $t->redirect(Mojo::Transaction::HTTP->new);
Build L<Mojo::Transaction::HTTP> follow-up request for C<301>, C<302>, C<303>, C<307> or C<308> redirect response if
possible.
=head2 tx
my $tx = $t->tx(GET => 'example.com');
my $tx = $t->tx(POST => 'http://example.com');
my $tx = $t->tx(GET => 'http://example.com' => {Accept => '*/*'});
my $tx = $t->tx(PUT => 'http://example.com' => 'Content!');
my $tx = $t->tx(PUT => 'http://example.com' => form => {a => 'b'});
my $tx = $t->tx(PUT => 'http://example.com' => json => {a => 'b'});
my $tx = $t->tx(PUT => 'https://example.com' => multipart => ['a', 'b']);
my $tx = $t->tx(POST => 'example.com' => {Accept => '*/*'} => 'Content!');
my $tx = $t->tx(PUT => 'example.com' => {Accept => '*/*'} => form => {a => 'b'});
my $tx = $t->tx(PUT => 'example.com' => {Accept => '*/*'} => json => {a => 'b'});
my $tx = $t->tx(PUT => 'example.com' => {Accept => '*/*'} => multipart => ['a', 'b']);
Versatile general purpose L<Mojo::Transaction::HTTP> transaction builder for requests, with support for
L</"GENERATORS">.
# Generate and inspect custom GET request with DNT header and content
say $t->tx(GET => 'example.com' => {DNT => 1} => 'Bye!')->req->to_string;
# Stream response content to STDOUT
my $tx = $t->tx(GET => 'http://example.com');
$tx->res->content->unsubscribe('read')->on(read => sub { say $_[1] });
# PUT request with content streamed from file
my $tx = $t->tx(PUT => 'http://example.com');
$tx->req->content->asset(Mojo::Asset::File->new(path => '/foo.txt'));
The C<json> content generator uses L<Mojo::JSON> for encoding and sets the content type to C<application/json>.
# POST request with "application/json" content
my $tx = $t->tx(POST => 'http://example.com' => json => {a => 'b', c => [1, 2, 3]});
The C<form> content generator will automatically use query parameters for C<GET> and C<HEAD> requests.
# GET request with query parameters
my $tx = $t->tx(GET => 'http://example.com' => form => {a => 'b'});
For all other request methods the C<application/x-www-form-urlencoded> content type is used.
# POST request with "application/x-www-form-urlencoded" content
my $tx = $t->tx(POST => 'http://example.com' => form => {a => 'b', c => 'd'});
Parameters may be encoded with the C<charset> option.
# PUT request with Shift_JIS encoded form values
my $tx = $t->tx(PUT => 'example.com' => form => {a => 'b'} => charset => 'Shift_JIS');
An array reference can be used for multiple form values sharing the same name.
# POST request with form values sharing the same name
my $tx = $t->tx(POST => 'http://example.com' => form => {a => ['b', 'c', 'd']});
A hash reference with a C<content> or C<file> value can be used to switch to the C<multipart/form-data> content type
for file uploads.
# POST request with "multipart/form-data" content
my $tx = $t->tx(POST => 'http://example.com' => form => {mytext => {content => 'lala'}});
# POST request with multiple files sharing the same name
my $tx = $t->tx(POST => 'http://example.com' => form => {mytext => [{content => 'first'}, {content => 'second'}]});
The C<file> value should contain the path to the file you want to upload or an asset object, like L<Mojo::Asset::File>
or L<Mojo::Asset::Memory>.
# POST request with upload streamed from file
my $tx = $t->tx(POST => 'http://example.com' => form => {mytext => {file => '/foo.txt'}});
# POST request with upload streamed from asset
my $asset = Mojo::Asset::Memory->new->add_chunk('lalala');
my $tx = $t->tx(POST => 'http://example.com' => form => {mytext => {file => $asset}});
A C<filename> value will be generated automatically, but can also be set manually if necessary. All remaining values in
the hash reference get merged into the C<multipart/form-data> content as headers.
# POST request with form values and customized upload (filename and header)
my $tx = $t->tx(POST => 'http://example.com' => form => {
a => 'b',
c => 'd',
mytext => {
content => 'lalala',
filename => 'foo.txt',
'Content-Type' => 'text/plain'
}
});
The C<multipart/form-data> content type can also be enforced by setting the C<Content-Type> header manually.
# Force "multipart/form-data"
my $headers = {'Content-Type' => 'multipart/form-data'};
my $tx = $t->tx(POST => 'example.com' => $headers => form => {a => 'b'});
The C<multipart> content generator can be used to build custom multipart requests and does not set a content type.
# POST request with multipart content ("foo" and "bar")
my $tx = $t->tx(POST => 'http://example.com' => multipart => ['foo', 'bar']);
Similar to the C<form> content generator you can also pass hash references with C<content> or C<file> values, as well
as headers.
# POST request with multipart content streamed from file
my $tx = $t->tx(POST => 'http://example.com' => multipart => [{file => '/foo.txt'}]);
# PUT request with multipart content streamed from asset
my $headers = {'Content-Type' => 'multipart/custom'};
my $asset = Mojo::Asset::Memory->new->add_chunk('lalala');
my $tx = $t->tx(PUT => 'http://example.com' => $headers => multipart => [{file => $asset}]);
# POST request with multipart content and custom headers
my $tx = $t->tx(POST => 'http://example.com' => multipart => [
{
content => 'Hello',
'Content-Type' => 'text/plain',
'Content-Language' => 'en-US'
},
{
content => 'World!',
'Content-Type' => 'text/plain',
'Content-Language' => 'en-US'
}
]);
=head2 upgrade
my $tx = $t->upgrade(Mojo::Transaction::HTTP->new);
Build L<Mojo::Transaction::WebSocket> follow-up transaction for WebSocket handshake if possible.
=head2 websocket
my $tx = $t->websocket('ws://example.com');
my $tx = $t->websocket('ws://example.com' => {DNT => 1} => ['v1.proto']);
Versatile L<Mojo::Transaction::HTTP> transaction builder for WebSocket handshake requests.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

950
database/perl/vendor/lib/Mojo/Util.pm vendored Normal file
View File

@@ -0,0 +1,950 @@
package Mojo::Util;
use Mojo::Base -strict;
use Carp qw(carp croak);
use Data::Dumper ();
use Digest::MD5 qw(md5 md5_hex);
use Digest::SHA qw(hmac_sha1_hex sha1 sha1_hex);
use Encode qw(find_encoding);
use Exporter qw(import);
use File::Basename qw(dirname);
use Getopt::Long qw(GetOptionsFromArray);
use IO::Compress::Gzip;
use IO::Poll qw(POLLIN POLLPRI);
use IO::Uncompress::Gunzip;
use List::Util qw(min);
use MIME::Base64 qw(decode_base64 encode_base64);
use Pod::Usage qw(pod2usage);
use Sub::Util qw(set_subname);
use Symbol qw(delete_package);
use Time::HiRes ();
use Unicode::Normalize ();
# Check for monotonic clock support
use constant MONOTONIC => eval { !!Time::HiRes::clock_gettime(Time::HiRes::CLOCK_MONOTONIC()) };
# Punycode bootstring parameters
use constant {
PC_BASE => 36,
PC_TMIN => 1,
PC_TMAX => 26,
PC_SKEW => 38,
PC_DAMP => 700,
PC_INITIAL_BIAS => 72,
PC_INITIAL_N => 128
};
# To generate a new HTML entity table run this command
# perl examples/entities.pl > lib/Mojo/resources/html_entities.txt
my %ENTITIES;
{
# Don't use Mojo::File here due to circular dependencies
my $path = File::Spec->catfile(dirname(__FILE__), 'resources', 'html_entities.txt');
open my $file, '<', $path or croak "Unable to open html entities file ($path): $!";
my $lines = do { local $/; <$file> };
for my $line (split /\n/, $lines) {
next unless $line =~ /^(\S+)\s+U\+(\S+)(?:\s+U\+(\S+))?/;
$ENTITIES{$1} = defined $3 ? (chr(hex $2) . chr(hex $3)) : chr(hex $2);
}
}
# Characters that should be escaped in XML
my %XML = ('&' => '&amp;', '<' => '&lt;', '>' => '&gt;', '"' => '&quot;', '\'' => '&#39;');
# "Sun, 06 Nov 1994 08:49:37 GMT" and "Sunday, 06-Nov-94 08:49:37 GMT"
my $EXPIRES_RE = qr/(\w+\W+\d+\W+\w+\W+\d+\W+\d+:\d+:\d+\W*\w+)/;
# HTML entities
my $ENTITY_RE = qr/&(?:\#((?:[0-9]{1,7}|x[0-9a-fA-F]{1,6}));|(\w+[;=]?))/;
# Encoding and pattern cache
my (%ENCODING, %PATTERN);
our @EXPORT_OK = (
qw(b64_decode b64_encode camelize class_to_file class_to_path decamelize decode deprecated dumper encode),
qw(extract_usage getopt gunzip gzip hmac_sha1_sum html_attr_unescape html_unescape humanize_bytes md5_bytes md5_sum),
qw(monkey_patch punycode_decode punycode_encode quote scope_guard secure_compare sha1_bytes sha1_sum slugify),
qw(split_cookie_header split_header steady_time tablify term_escape trim unindent unquote url_escape url_unescape),
qw(xml_escape xor_encode)
);
# Aliases
monkey_patch(__PACKAGE__, 'b64_decode', \&decode_base64);
monkey_patch(__PACKAGE__, 'b64_encode', \&encode_base64);
monkey_patch(__PACKAGE__, 'hmac_sha1_sum', \&hmac_sha1_hex);
monkey_patch(__PACKAGE__, 'md5_bytes', \&md5);
monkey_patch(__PACKAGE__, 'md5_sum', \&md5_hex);
monkey_patch(__PACKAGE__, 'sha1_bytes', \&sha1);
monkey_patch(__PACKAGE__, 'sha1_sum', \&sha1_hex);
# Use a monotonic clock if possible
monkey_patch(__PACKAGE__, 'steady_time',
MONOTONIC ? sub () { Time::HiRes::clock_gettime(Time::HiRes::CLOCK_MONOTONIC()) } : \&Time::HiRes::time);
sub camelize {
my $str = shift;
return $str if $str =~ /^[A-Z]/;
# CamelCase words
return join '::', map {
join('', map { ucfirst lc } split /_/)
} split /-/, $str;
}
sub class_to_file {
my $class = shift;
$class =~ s/::|'//g;
$class =~ s/([A-Z])([A-Z]*)/$1 . lc $2/ge;
return decamelize($class);
}
sub class_to_path { join '.', join('/', split(/::|'/, shift)), 'pm' }
sub decamelize {
my $str = shift;
return $str if $str !~ /^[A-Z]/;
# snake_case words
return join '-', map {
join('_', map {lc} grep {length} split /([A-Z]{1}[^A-Z]*)/)
} split /::/, $str;
}
sub decode {
my ($encoding, $bytes) = @_;
return undef unless eval { $bytes = _encoding($encoding)->decode("$bytes", 1); 1 };
return $bytes;
}
sub deprecated {
local $Carp::CarpLevel = 1;
$ENV{MOJO_FATAL_DEPRECATIONS} ? croak @_ : carp @_;
}
sub dumper { Data::Dumper->new([@_])->Indent(1)->Sortkeys(1)->Terse(1)->Useqq(1)->Dump }
sub encode { _encoding($_[0])->encode("$_[1]", 0) }
sub extract_usage {
my $file = @_ ? "$_[0]" : (caller)[1];
open my $handle, '>', \my $output;
pod2usage -exitval => 'noexit', -input => $file, -output => $handle;
$output =~ s/^.*\n|\n$//;
$output =~ s/\n$//;
return unindent($output);
}
sub getopt {
my ($array, $opts) = map { ref $_[0] eq 'ARRAY' ? shift : $_ } \@ARGV, [];
my $save = Getopt::Long::Configure(qw(default no_auto_abbrev no_ignore_case), @$opts);
my $result = GetOptionsFromArray $array, @_;
Getopt::Long::Configure($save);
return $result;
}
sub gunzip {
my $compressed = shift;
IO::Uncompress::Gunzip::gunzip \$compressed, \my $uncompressed
or croak "Couldn't gunzip: $IO::Uncompress::Gunzip::GzipError";
return $uncompressed;
}
sub gzip {
my $uncompressed = shift;
IO::Compress::Gzip::gzip \$uncompressed, \my $compressed or croak "Couldn't gzip: $IO::Compress::Gzip::GzipError";
return $compressed;
}
sub html_attr_unescape { _html(shift, 1) }
sub html_unescape { _html(shift, 0) }
sub humanize_bytes {
my $size = shift;
my $prefix = $size < 0 ? '-' : '';
return "$prefix${size}B" if ($size = abs $size) < 1024;
return $prefix . _round($size) . 'KiB' if ($size /= 1024) < 1024;
return $prefix . _round($size) . 'MiB' if ($size /= 1024) < 1024;
return $prefix . _round($size) . 'GiB' if ($size /= 1024) < 1024;
return $prefix . _round($size /= 1024) . 'TiB';
}
sub monkey_patch {
my ($class, %patch) = @_;
no strict 'refs';
no warnings 'redefine';
*{"${class}::$_"} = set_subname("${class}::$_", $patch{$_}) for keys %patch;
}
# Direct translation of RFC 3492
sub punycode_decode {
my $input = shift;
use integer;
my ($n, $i, $bias, @output) = (PC_INITIAL_N, 0, PC_INITIAL_BIAS);
# Consume all code points before the last delimiter
push @output, split(//, $1) if $input =~ s/(.*)\x2d//s;
while (length $input) {
my ($oldi, $w) = ($i, 1);
# Base to infinity in steps of base
for (my $k = PC_BASE; 1; $k += PC_BASE) {
my $digit = ord substr $input, 0, 1, '';
$digit = $digit < 0x40 ? $digit + (26 - 0x30) : ($digit & 0x1f) - 1;
$i += $digit * $w;
my $t = $k - $bias;
$t = $t < PC_TMIN ? PC_TMIN : $t > PC_TMAX ? PC_TMAX : $t;
last if $digit < $t;
$w *= PC_BASE - $t;
}
$bias = _adapt($i - $oldi, @output + 1, $oldi == 0);
$n += $i / (@output + 1);
$i = $i % (@output + 1);
splice @output, $i++, 0, chr $n;
}
return join '', @output;
}
# Direct translation of RFC 3492
sub punycode_encode {
my $output = shift;
use integer;
my ($n, $delta, $bias) = (PC_INITIAL_N, 0, PC_INITIAL_BIAS);
# Extract basic code points
my @input = map {ord} split //, $output;
$output =~ s/[^\x00-\x7f]+//gs;
my $h = my $basic = length $output;
$output .= "\x2d" if $basic > 0;
for my $m (sort grep { $_ >= PC_INITIAL_N } @input) {
next if $m < $n;
$delta += ($m - $n) * ($h + 1);
$n = $m;
for my $c (@input) {
if ($c < $n) { $delta++ }
elsif ($c == $n) {
my $q = $delta;
# Base to infinity in steps of base
for (my $k = PC_BASE; 1; $k += PC_BASE) {
my $t = $k - $bias;
$t = $t < PC_TMIN ? PC_TMIN : $t > PC_TMAX ? PC_TMAX : $t;
last if $q < $t;
my $o = $t + (($q - $t) % (PC_BASE - $t));
$output .= chr $o + ($o < 26 ? 0x61 : 0x30 - 26);
$q = ($q - $t) / (PC_BASE - $t);
}
$output .= chr $q + ($q < 26 ? 0x61 : 0x30 - 26);
$bias = _adapt($delta, $h + 1, $h == $basic);
$delta = 0;
$h++;
}
}
$delta++;
$n++;
}
return $output;
}
sub quote {
my $str = shift;
$str =~ s/(["\\])/\\$1/g;
return qq{"$str"};
}
sub scope_guard { Mojo::Util::_Guard->new(cb => shift) }
sub secure_compare {
my ($one, $two) = @_;
my $r = length $one != length $two;
$two = $one if $r;
$r |= ord(substr $one, $_) ^ ord(substr $two, $_) for 0 .. length($one) - 1;
return $r == 0;
}
sub slugify {
my ($value, $allow_unicode) = @_;
if ($allow_unicode) {
# Force unicode semantics by upgrading string
utf8::upgrade($value = Unicode::Normalize::NFKC($value));
$value =~ s/[^\w\s-]+//g;
}
else {
$value = Unicode::Normalize::NFKD($value);
$value =~ s/[^a-zA-Z0-9_\p{PosixSpace}-]+//g;
}
(my $new = lc trim($value)) =~ s/[-\s]+/-/g;
return $new;
}
sub split_cookie_header { _header(shift, 1) }
sub split_header { _header(shift, 0) }
sub tablify {
my $rows = shift;
my @spec;
for my $row (@$rows) {
for my $i (0 .. $#$row) {
($row->[$i] //= '') =~ y/\r\n//d;
my $len = length $row->[$i];
$spec[$i] = $len if $len >= ($spec[$i] // 0);
}
}
my @fm = (map({"\%-${_}s"} @spec[0 .. $#spec - 1]), '%s');
return join '', map { sprintf join(' ', @fm[0 .. $#$_]) . "\n", @$_ } @$rows;
}
sub term_escape {
my $str = shift;
$str =~ s/([\x00-\x09\x0b-\x1f\x7f\x80-\x9f])/sprintf '\\x%02x', ord $1/ge;
return $str;
}
sub trim {
my $str = shift;
$str =~ s/^\s+//;
$str =~ s/\s+$//;
return $str;
}
sub unindent {
my $str = shift;
my $min = min map { m/^([ \t]*)/; length $1 || () } split /\n/, $str;
$str =~ s/^[ \t]{0,$min}//gm if $min;
return $str;
}
sub unquote {
my $str = shift;
return $str unless $str =~ s/^"(.*)"$/$1/g;
$str =~ s/\\\\/\\/g;
$str =~ s/\\"/"/g;
return $str;
}
sub url_escape {
my ($str, $pattern) = @_;
if ($pattern) {
unless (exists $PATTERN{$pattern}) {
(my $quoted = $pattern) =~ s!([/\$\[])!\\$1!g;
$PATTERN{$pattern} = eval "sub { \$_[0] =~ s/([$quoted])/sprintf '%%%02X', ord \$1/ge }" or croak $@;
}
$PATTERN{$pattern}->($str);
}
else { $str =~ s/([^A-Za-z0-9\-._~])/sprintf '%%%02X', ord $1/ge }
return $str;
}
sub url_unescape {
my $str = shift;
$str =~ s/%([0-9a-fA-F]{2})/chr hex $1/ge;
return $str;
}
sub xml_escape {
return $_[0] if ref $_[0] && ref $_[0] eq 'Mojo::ByteStream';
my $str = shift // '';
$str =~ s/([&<>"'])/$XML{$1}/ge;
return $str;
}
sub xor_encode {
my ($input, $key) = @_;
# Encode with variable key length
my $len = length $key;
my $buffer = my $output = '';
$output .= $buffer ^ $key while length($buffer = substr($input, 0, $len, '')) == $len;
return $output .= $buffer ^ substr($key, 0, length $buffer, '');
}
sub _adapt {
my ($delta, $numpoints, $firsttime) = @_;
use integer;
$delta = $firsttime ? $delta / PC_DAMP : $delta / 2;
$delta += $delta / $numpoints;
my $k = 0;
while ($delta > ((PC_BASE - PC_TMIN) * PC_TMAX) / 2) {
$delta /= PC_BASE - PC_TMIN;
$k += PC_BASE;
}
return $k + (((PC_BASE - PC_TMIN + 1) * $delta) / ($delta + PC_SKEW));
}
sub _encoding { $ENCODING{$_[0]} //= find_encoding($_[0]) // croak "Unknown encoding '$_[0]'" }
sub _entity {
my ($point, $name, $attr) = @_;
# Code point
return chr($point !~ /^x/ ? $point : hex $point) unless defined $name;
# Named character reference
my $rest = my $last = '';
while (length $name) {
return $ENTITIES{$name} . reverse $rest
if exists $ENTITIES{$name} && (!$attr || $name =~ /;$/ || $last !~ /[A-Za-z0-9=]/);
$rest .= $last = chop $name;
}
return '&' . reverse $rest;
}
# Supported on Perl 5.14+
sub _global_destruction { defined ${^GLOBAL_PHASE} && ${^GLOBAL_PHASE} eq 'DESTRUCT' }
sub _header {
my ($str, $cookie) = @_;
my (@tree, @part);
while ($str =~ /\G[,;\s]*([^=;, ]+)\s*/gc) {
push @part, $1, undef;
my $expires = $cookie && @part > 2 && lc $1 eq 'expires';
# Special "expires" value
if ($expires && $str =~ /\G=\s*$EXPIRES_RE/gco) { $part[-1] = $1 }
# Quoted value
elsif ($str =~ /\G=\s*("(?:\\\\|\\"|[^"])*")/gc) { $part[-1] = unquote $1 }
# Unquoted value
elsif ($str =~ /\G=\s*([^;, ]*)/gc) { $part[-1] = $1 }
# Separator
next unless $str =~ /\G[;\s]*,\s*/gc;
push @tree, [@part];
@part = ();
}
# Take care of final part
return [@part ? (@tree, \@part) : @tree];
}
sub _html {
my ($str, $attr) = @_;
$str =~ s/$ENTITY_RE/_entity($1, $2, $attr)/geo;
return $str;
}
sub _options {
# Hash or name (one)
return ref $_[0] eq 'HASH' ? (undef, %{shift()}) : @_ if @_ == 1;
# Name and values (odd)
return shift, @_ if @_ % 2;
# Name and hash or just values (even)
return ref $_[1] eq 'HASH' ? (shift, %{shift()}) : (undef, @_);
}
# This may break in the future, but is worth it for performance
sub _readable { !!(IO::Poll::_poll(@_[0, 1], my $m = POLLIN | POLLPRI) > 0) }
sub _round { $_[0] < 10 ? int($_[0] * 10 + 0.5) / 10 : int($_[0] + 0.5) }
sub _stash {
my ($name, $object) = (shift, shift);
# Hash
return $object->{$name} //= {} unless @_;
# Get
return $object->{$name}{$_[0]} unless @_ > 1 || ref $_[0];
# Set
my $values = ref $_[0] ? $_[0] : {@_};
@{$object->{$name}}{keys %$values} = values %$values;
return $object;
}
sub _teardown {
return unless my $class = shift;
# @ISA has to be cleared first because of circular references
no strict 'refs';
@{"${class}::ISA"} = ();
delete_package $class;
}
package Mojo::Util::_Guard;
use Mojo::Base -base;
sub DESTROY { shift->{cb}() }
1;
=encoding utf8
=head1 NAME
Mojo::Util - Portable utility functions
=head1 SYNOPSIS
use Mojo::Util qw(b64_encode url_escape url_unescape);
my $str = 'test=23';
my $escaped = url_escape $str;
say url_unescape $escaped;
say b64_encode $escaped, '';
=head1 DESCRIPTION
L<Mojo::Util> provides portable utility functions for L<Mojo>.
=head1 FUNCTIONS
L<Mojo::Util> implements the following functions, which can be imported individually.
=head2 b64_decode
my $bytes = b64_decode $b64;
Base64 decode bytes with L<MIME::Base64>.
=head2 b64_encode
my $b64 = b64_encode $bytes;
my $b64 = b64_encode $bytes, "\n";
Base64 encode bytes with L<MIME::Base64>, the line ending defaults to a newline.
=head2 camelize
my $camelcase = camelize $snakecase;
Convert C<snake_case> string to C<CamelCase> and replace C<-> with C<::>.
# "FooBar"
camelize 'foo_bar';
# "FooBar::Baz"
camelize 'foo_bar-baz';
# "FooBar::Baz"
camelize 'FooBar::Baz';
=head2 class_to_file
my $file = class_to_file 'Foo::Bar';
Convert a class name to a file.
# "foo_bar"
class_to_file 'Foo::Bar';
# "foobar"
class_to_file 'FOO::Bar';
# "foo_bar"
class_to_file 'FooBar';
# "foobar"
class_to_file 'FOOBar';
=head2 class_to_path
my $path = class_to_path 'Foo::Bar';
Convert class name to path, as used by C<%INC>.
# "Foo/Bar.pm"
class_to_path 'Foo::Bar';
# "FooBar.pm"
class_to_path 'FooBar';
=head2 decamelize
my $snakecase = decamelize $camelcase;
Convert C<CamelCase> string to C<snake_case> and replace C<::> with C<->.
# "foo_bar"
decamelize 'FooBar';
# "foo_bar-baz"
decamelize 'FooBar::Baz';
# "foo_bar-baz"
decamelize 'foo_bar-baz';
=head2 decode
my $chars = decode 'UTF-8', $bytes;
Decode bytes to characters with L<Encode>, or return C<undef> if decoding failed.
=head2 deprecated
deprecated 'foo is DEPRECATED in favor of bar';
Warn about deprecated feature from perspective of caller. You can also set the C<MOJO_FATAL_DEPRECATIONS> environment
variable to make them die instead with L<Carp>.
=head2 dumper
my $perl = dumper {some => 'data'};
Dump a Perl data structure with L<Data::Dumper>.
=head2 encode
my $bytes = encode 'UTF-8', $chars;
Encode characters to bytes with L<Encode>.
=head2 extract_usage
my $usage = extract_usage;
my $usage = extract_usage '/home/sri/foo.pod';
Extract usage message from the SYNOPSIS section of a file containing POD documentation, defaults to using the file this
function was called from.
# "Usage: APPLICATION test [OPTIONS]\n"
extract_usage;
=head1 SYNOPSIS
Usage: APPLICATION test [OPTIONS]
=cut
=head2 getopt
getopt
'H|headers=s' => \my @headers,
't|timeout=i' => \my $timeout,
'v|verbose' => \my $verbose;
getopt $array,
'H|headers=s' => \my @headers,
't|timeout=i' => \my $timeout,
'v|verbose' => \my $verbose;
getopt $array, ['pass_through'],
'H|headers=s' => \my @headers,
't|timeout=i' => \my $timeout,
'v|verbose' => \my $verbose;
Extract options from an array reference with L<Getopt::Long>, but without changing its global configuration, defaults
to using C<@ARGV>. The configuration options C<no_auto_abbrev> and C<no_ignore_case> are enabled by default.
# Extract "charset" option
getopt ['--charset', 'UTF-8'], 'charset=s' => \my $charset;
say $charset;
=head2 gunzip
my $uncompressed = gunzip $compressed;
Uncompress bytes with L<IO::Compress::Gunzip>.
=head2 gzip
my $compressed = gzip $uncompressed;
Compress bytes with L<IO::Compress::Gzip>.
=head2 hmac_sha1_sum
my $checksum = hmac_sha1_sum $bytes, 'passw0rd';
Generate HMAC-SHA1 checksum for bytes with L<Digest::SHA>.
# "11cedfd5ec11adc0ec234466d8a0f2a83736aa68"
hmac_sha1_sum 'foo', 'passw0rd';
=head2 html_attr_unescape
my $str = html_attr_unescape $escaped;
Same as L</"html_unescape">, but handles special rules from the L<HTML Living Standard|https://html.spec.whatwg.org>
for HTML attributes.
# "foo=bar&ltest=baz"
html_attr_unescape 'foo=bar&ltest=baz';
# "foo=bar<est=baz"
html_attr_unescape 'foo=bar&lt;est=baz';
=head2 html_unescape
my $str = html_unescape $escaped;
Unescape all HTML entities in string.
# "<div>"
html_unescape '&lt;div&gt;';
=head2 humanize_bytes
my $str = humanize_bytes 1234;
Turn number of bytes into a simplified human readable format.
# "1B"
humanize_bytes 1;
# "7.5GiB"
humanize_bytes 8007188480;
# "13GiB"
humanize_bytes 13443399680;
# "-685MiB"
humanize_bytes -717946880;
=head2 md5_bytes
my $checksum = md5_bytes $bytes;
Generate binary MD5 checksum for bytes with L<Digest::MD5>.
=head2 md5_sum
my $checksum = md5_sum $bytes;
Generate MD5 checksum for bytes with L<Digest::MD5>.
# "acbd18db4cc2f85cedef654fccc4a4d8"
md5_sum 'foo';
=head2 monkey_patch
monkey_patch $package, foo => sub {...};
monkey_patch $package, foo => sub {...}, bar => sub {...};
Monkey patch functions into package.
monkey_patch 'MyApp',
one => sub { say 'One!' },
two => sub { say 'Two!' },
three => sub { say 'Three!' };
=head2 punycode_decode
my $str = punycode_decode $punycode;
Punycode decode string as described in L<RFC 3492|https://tools.ietf.org/html/rfc3492>.
# "bücher"
punycode_decode 'bcher-kva';
=head2 punycode_encode
my $punycode = punycode_encode $str;
Punycode encode string as described in L<RFC 3492|https://tools.ietf.org/html/rfc3492>.
# "bcher-kva"
punycode_encode 'bücher';
=head2 quote
my $quoted = quote $str;
Quote string.
=head2 scope_guard
my $guard = scope_guard sub {...};
Create anonymous scope guard object that will execute the passed callback when the object is destroyed.
# Execute closure at end of scope
{
my $guard = scope_guard sub { say "Mojo!" };
say "Hello";
}
=head2 secure_compare
my $bool = secure_compare $str1, $str2;
Constant time comparison algorithm to prevent timing attacks. The secret string should be the second argument, to avoid
leaking information about the length of the string.
=head2 sha1_bytes
my $checksum = sha1_bytes $bytes;
Generate binary SHA1 checksum for bytes with L<Digest::SHA>.
=head2 sha1_sum
my $checksum = sha1_sum $bytes;
Generate SHA1 checksum for bytes with L<Digest::SHA>.
# "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"
sha1_sum 'foo';
=head2 slugify
my $slug = slugify $string;
my $slug = slugify $string, $bool;
Returns a URL slug generated from the input string. Non-word characters are removed, the string is trimmed and
lowercased, and whitespace characters are replaced by a dash. By default, non-ASCII characters are normalized to ASCII
word characters or removed, but if a true value is passed as the second parameter, all word characters will be allowed
in the result according to unicode semantics.
# "joel-is-a-slug"
slugify 'Joel is a slug';
# "this-is-my-resume"
slugify 'This is: my - résumé! ☃ ';
# "this-is-my-résumé"
slugify 'This is: my - résumé! ☃ ', 1;
=head2 split_cookie_header
my $tree = split_cookie_header 'a=b; expires=Thu, 07 Aug 2008 07:07:59 GMT';
Same as L</"split_header">, but handles C<expires> values from L<RFC 6265|https://tools.ietf.org/html/rfc6265>.
=head2 split_header
my $tree = split_header 'foo="bar baz"; test=123, yada';
Split HTTP header value into key/value pairs, each comma separated part gets its own array reference, and keys without
a value get C<undef> assigned.
# "one"
split_header('one; two="three four", five=six')->[0][0];
# "two"
split_header('one; two="three four", five=six')->[0][2];
# "three four"
split_header('one; two="three four", five=six')->[0][3];
# "five"
split_header('one; two="three four", five=six')->[1][0];
# "six"
split_header('one; two="three four", five=six')->[1][1];
=head2 steady_time
my $time = steady_time;
High resolution time elapsed from an arbitrary fixed point in the past, resilient to time jumps if a monotonic clock is
available through L<Time::HiRes>.
=head2 tablify
my $table = tablify [['foo', 'bar'], ['baz', 'yada']];
Row-oriented generator for text tables.
# "foo bar\nyada yada\nbaz yada\n"
tablify [['foo', 'bar'], ['yada', 'yada'], ['baz', 'yada']];
=head2 term_escape
my $escaped = term_escape $str;
Escape all POSIX control characters except for C<\n>.
# "foo\\x09bar\\x0d\n"
term_escape "foo\tbar\r\n";
=head2 trim
my $trimmed = trim $str;
Trim whitespace characters from both ends of string.
# "foo bar"
trim ' foo bar ';
=head2 unindent
my $unindented = unindent $str;
Unindent multi-line string.
# "foo\nbar\nbaz\n"
unindent " foo\n bar\n baz\n";
=head2 unquote
my $str = unquote $quoted;
Unquote string.
=head2 url_escape
my $escaped = url_escape $str;
my $escaped = url_escape $str, '^A-Za-z0-9\-._~';
Percent encode unsafe characters in string as described in L<RFC 3986|https://tools.ietf.org/html/rfc3986>, the pattern
used defaults to C<^A-Za-z0-9\-._~>.
# "foo%3Bbar"
url_escape 'foo;bar';
=head2 url_unescape
my $str = url_unescape $escaped;
Decode percent encoded characters in string as described in L<RFC 3986|https://tools.ietf.org/html/rfc3986>.
# "foo;bar"
url_unescape 'foo%3Bbar';
=head2 xml_escape
my $escaped = xml_escape $str;
Escape unsafe characters C<&>, C<E<lt>>, C<E<gt>>, C<"> and C<'> in string, but do not escape L<Mojo::ByteStream>
objects.
# "&lt;div&gt;"
xml_escape '<div>';
# "<div>"
use Mojo::ByteStream qw(b);
xml_escape b('<div>');
=head2 xor_encode
my $encoded = xor_encode $str, $key;
XOR encode string with variable length key.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

View File

@@ -0,0 +1,277 @@
package Mojo::WebSocket;
use Mojo::Base -strict;
use Config;
use Exporter qw(import);
use Mojo::Util qw(b64_encode dumper sha1_bytes xor_encode);
use constant DEBUG => $ENV{MOJO_WEBSOCKET_DEBUG} || 0;
# Unique value from RFC 6455
use constant GUID => '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
# Perl with support for quads
use constant MODERN => (($Config{use64bitint} // '') eq 'define' || $Config{longsize} >= 8);
# Opcodes
use constant {WS_CONTINUATION => 0x0, WS_TEXT => 0x1, WS_BINARY => 0x2, WS_CLOSE => 0x8, WS_PING => 0x9,
WS_PONG => 0xa};
our @EXPORT_OK = (
qw(WS_BINARY WS_CLOSE WS_CONTINUATION WS_PING WS_PONG WS_TEXT build_frame challenge client_handshake parse_frame),
qw(server_handshake)
);
sub build_frame {
my ($masked, $fin, $rsv1, $rsv2, $rsv3, $op, $payload) = @_;
warn "-- Building frame ($fin, $rsv1, $rsv2, $rsv3, $op)\n" if DEBUG;
# Head
my $head = $op + ($fin ? 128 : 0);
$head |= 0b01000000 if $rsv1;
$head |= 0b00100000 if $rsv2;
$head |= 0b00010000 if $rsv3;
my $frame = pack 'C', $head;
# Small payload
my $len = length $payload;
if ($len < 126) {
warn "-- Small payload ($len)\n@{[dumper $payload]}" if DEBUG;
$frame .= pack 'C', $masked ? ($len | 128) : $len;
}
# Extended payload (16-bit)
elsif ($len < 65536) {
warn "-- Extended 16-bit payload ($len)\n@{[dumper $payload]}" if DEBUG;
$frame .= pack 'Cn', $masked ? (126 | 128) : 126, $len;
}
# Extended payload (64-bit with 32-bit fallback)
else {
warn "-- Extended 64-bit payload ($len)\n@{[dumper $payload]}" if DEBUG;
$frame .= pack 'C', $masked ? (127 | 128) : 127;
$frame .= MODERN ? pack('Q>', $len) : pack('NN', 0, $len & 0xffffffff);
}
# Mask payload
if ($masked) {
my $mask = pack 'N', int(rand 9 x 7);
$payload = $mask . xor_encode($payload, $mask x 128);
}
return $frame . $payload;
}
sub challenge {
my $tx = shift;
# "permessage-deflate" extension
my $headers = $tx->res->headers;
$tx->compressed(1) if ($headers->sec_websocket_extensions // '') =~ /permessage-deflate/;
return _challenge($tx->req->headers->sec_websocket_key) eq $headers->sec_websocket_accept;
}
sub client_handshake {
my $tx = shift;
my $headers = $tx->req->headers;
$headers->upgrade('websocket') unless $headers->upgrade;
$headers->connection('Upgrade') unless $headers->connection;
$headers->sec_websocket_version(13) unless $headers->sec_websocket_version;
# Generate 16 byte WebSocket challenge
my $challenge = b64_encode sprintf('%16u', int(rand 9 x 16)), '';
$headers->sec_websocket_key($challenge) unless $headers->sec_websocket_key;
return $tx;
}
sub parse_frame {
my ($buffer, $max) = @_;
# Head
return undef unless length $$buffer >= 2;
my ($first, $second) = unpack 'C2', $$buffer;
# FIN
my $fin = ($first & 0b10000000) == 0b10000000 ? 1 : 0;
# RSV1-3
my $rsv1 = ($first & 0b01000000) == 0b01000000 ? 1 : 0;
my $rsv2 = ($first & 0b00100000) == 0b00100000 ? 1 : 0;
my $rsv3 = ($first & 0b00010000) == 0b00010000 ? 1 : 0;
# Opcode
my $op = $first & 0b00001111;
warn "-- Parsing frame ($fin, $rsv1, $rsv2, $rsv3, $op)\n" if DEBUG;
# Small payload
my ($hlen, $len) = (2, $second & 0b01111111);
if ($len < 126) { warn "-- Small payload ($len)\n" if DEBUG }
# Extended payload (16-bit)
elsif ($len == 126) {
return undef unless length $$buffer > 4;
$hlen = 4;
$len = unpack 'x2n', $$buffer;
warn "-- Extended 16-bit payload ($len)\n" if DEBUG;
}
# Extended payload (64-bit with 32-bit fallback)
elsif ($len == 127) {
return undef unless length $$buffer > 10;
$hlen = 10;
$len = MODERN ? unpack('x2Q>', $$buffer) : unpack('x2x4N', $$buffer);
warn "-- Extended 64-bit payload ($len)\n" if DEBUG;
}
# Check message size
return 1 if $len > $max;
# Check if whole packet has arrived
$len += 4 if my $masked = $second & 0b10000000;
return undef if length $$buffer < ($hlen + $len);
substr $$buffer, 0, $hlen, '';
# Payload
my $payload = $len ? substr($$buffer, 0, $len, '') : '';
$payload = xor_encode($payload, substr($payload, 0, 4, '') x 128) if $masked;
warn dumper $payload if DEBUG;
return [$fin, $rsv1, $rsv2, $rsv3, $op, $payload];
}
sub server_handshake {
my $tx = shift;
my $headers = $tx->res->headers;
$headers->upgrade('websocket')->connection('Upgrade');
$headers->sec_websocket_accept(_challenge($tx->req->headers->sec_websocket_key));
return $tx;
}
sub _challenge { b64_encode(sha1_bytes(($_[0] || '') . GUID), '') }
1;
=encoding utf8
=head1 NAME
Mojo::WebSocket - The WebSocket protocol
=head1 SYNOPSIS
use Mojo::WebSocket qw(WS_TEXT build_frame parse_frame);
my $bytes = build_frame 0, 1, 0, 0, 0, WS_TEXT, 'Hello World!';
my $frame = parse_frame \$bytes, 262144;
=head1 DESCRIPTION
L<Mojo::WebSocket> implements the WebSocket protocol as described in L<RFC 6455|https://tools.ietf.org/html/rfc6455>.
Note that 64-bit frames require a Perl with support for quads or they are limited to 32-bit.
=head1 FUNCTIONS
L<Mojo::WebSocket> implements the following functions, which can be imported individually.
=head2 build_frame
my $bytes = build_frame $masked, $fin, $rsv1, $rsv2, $rsv3, $op, $payload;
Build WebSocket frame.
# Masked binary frame with FIN bit and payload
say build_frame 1, 1, 0, 0, 0, WS_BINARY, 'Hello World!';
# Text frame with payload but without FIN bit
say build_frame 0, 0, 0, 0, 0, WS_TEXT, 'Hello ';
# Continuation frame with FIN bit and payload
say build_frame 0, 1, 0, 0, 0, WS_CONTINUATION, 'World!';
# Close frame with FIN bit and without payload
say build_frame 0, 1, 0, 0, 0, WS_CLOSE, '';
# Ping frame with FIN bit and payload
say build_frame 0, 1, 0, 0, 0, WS_PING, 'Test 123';
# Pong frame with FIN bit and payload
say build_frame 0, 1, 0, 0, 0, WS_PONG, 'Test 123';
=head2 challenge
my $bool = challenge Mojo::Transaction::WebSocket->new;
Check WebSocket handshake challenge.
=head2 client_handshake
my $tx = client_handshake Mojo::Transaction::HTTP->new;
Perform WebSocket handshake client-side.
=head2 parse_frame
my $frame = parse_frame \$bytes, $limit;
Parse WebSocket frame.
# Parse single frame and remove it from buffer
my $frame = parse_frame \$buffer, 262144;
say "FIN: $frame->[0]";
say "RSV1: $frame->[1]";
say "RSV2: $frame->[2]";
say "RSV3: $frame->[3]";
say "Opcode: $frame->[4]";
say "Payload: $frame->[5]";
=head2 server_handshake
my $tx = server_handshake Mojo::Transaction::HTTP->new;
Perform WebSocket handshake server-side.
=head1 CONSTANTS
L<Mojo::WebSocket> implements the following constants, which can be imported individually.
=head2 WS_BINARY
Opcode for C<Binary> frames.
=head2 WS_CLOSE
Opcode for C<Close> frames.
=head2 WS_CONTINUATION
Opcode for C<Continuation> frames.
=head2 WS_PING
Opcode for C<Ping> frames.
=head2 WS_PONG
Opcode for C<Pong> frames.
=head2 WS_TEXT
Opcode for C<Text> frames.
=head1 DEBUGGING
You can set the C<MOJO_WEBSOCKET_DEBUG> environment variable to get some advanced diagnostics information printed to
C<STDERR>.
MOJO_WEBSOCKET_DEBUG=1
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut

File diff suppressed because it is too large Load Diff