line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package GuacLite::Client::Guacd; |
2
|
|
|
|
|
|
|
|
3
|
2
|
|
|
2
|
|
470363
|
use Mojo::Base 'Mojo::EventEmitter'; |
|
2
|
|
|
|
|
18
|
|
|
2
|
|
|
|
|
14
|
|
4
|
|
|
|
|
|
|
|
5
|
2
|
|
|
2
|
|
3617
|
use Mojo::Util; |
|
2
|
|
|
|
|
6
|
|
|
2
|
|
|
|
|
76
|
|
6
|
2
|
|
|
2
|
|
1049
|
use Mojo::Promise; |
|
2
|
|
|
|
|
342627
|
|
|
2
|
|
|
|
|
14
|
|
7
|
|
|
|
|
|
|
|
8
|
2
|
|
|
2
|
|
80
|
use Carp (); |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
32
|
|
9
|
2
|
|
|
2
|
|
9
|
use Scalar::Util (); |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
54
|
|
10
|
|
|
|
|
|
|
|
11
|
2
|
|
|
2
|
|
10
|
use constant DEBUG => $ENV{GUACLITE_GUACD_DEBUG}; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
4722
|
|
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
has host => $ENV{GUACLITE_GUACD_HOST} || 'localhost'; |
14
|
|
|
|
|
|
|
has port => $ENV{GUACLITE_GUACD_PORT} || '4822'; |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
# the following should probably all be required parameters, but for now, do this |
17
|
|
|
|
|
|
|
has protocol => 'vnc'; |
18
|
|
|
|
|
|
|
has connection_args => sub { {} }; |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
has width => 1024; |
21
|
|
|
|
|
|
|
has height => 768; |
22
|
|
|
|
|
|
|
has dpi => 96; |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
has audio_mimetypes => sub { [] }; |
25
|
|
|
|
|
|
|
has image_mimetypes => sub { [] }; |
26
|
|
|
|
|
|
|
has video_mimetypes => sub { [] }; |
27
|
|
|
|
|
|
|
has timezone => ''; |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
# supported version of guacamole protocol |
30
|
|
|
|
|
|
|
my @v = (1,3,0); |
31
|
|
|
|
|
|
|
my $v = do { local $" = '_'; "VERSION_@v" }; |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
sub _check_version { |
34
|
4
|
|
|
4
|
|
10
|
my $version = shift; |
35
|
4
|
50
|
|
|
|
35
|
return 0 unless |
36
|
|
|
|
|
|
|
$version =~ /^VERSION_(\d+)_(\d+)_(\d+)$/; |
37
|
4
|
50
|
|
|
|
43
|
return 0 unless $1 >= $v[0]; |
38
|
4
|
100
|
|
|
|
25
|
return 0 unless $2 >= $v[1]; |
39
|
3
|
50
|
|
|
|
12
|
return 0 unless $3 >= $v[2]; |
40
|
3
|
|
|
|
|
10
|
return 1; |
41
|
|
|
|
|
|
|
} |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
sub close { |
44
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
45
|
0
|
0
|
|
|
|
0
|
return unless my $s = $self->{stream}; |
46
|
0
|
|
|
|
|
0
|
$s->close; |
47
|
|
|
|
|
|
|
} |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
sub connect_p { |
50
|
7
|
|
|
7
|
0
|
28108
|
my $self = shift; |
51
|
7
|
|
50
|
|
|
66
|
my $connect = shift || {}; |
52
|
7
|
|
|
|
|
31
|
Scalar::Util::weaken($self); |
53
|
|
|
|
|
|
|
return Mojo::Promise->new(sub { |
54
|
7
|
|
|
7
|
|
318
|
my ($res, $rej) = @_; |
55
|
7
|
|
33
|
|
|
45
|
$connect->{address} ||= $self->host; |
56
|
7
|
|
33
|
|
|
75
|
$connect->{port} ||= $self->port; |
57
|
|
|
|
|
|
|
Mojo::IOLoop->client($connect, sub { |
58
|
7
|
|
|
|
|
16096
|
my (undef, $err, $stream) = @_; |
59
|
7
|
50
|
|
|
|
22
|
return $rej->("Connect error: $err") if $err; |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
#TODO configurable timeout |
62
|
7
|
|
|
|
|
25
|
$stream->timeout(0); |
63
|
7
|
|
|
|
|
181
|
$self->{stream} = $stream; |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
$stream->on(read => sub { |
66
|
10
|
|
|
|
|
9726
|
my (undef, $bytes) = @_; |
67
|
10
|
|
|
|
|
18
|
print STDERR '<- ' . Mojo::Util::term_escape($bytes) . "\n" if DEBUG; |
68
|
10
|
|
|
|
|
32
|
$self->{buffer} .= $bytes; |
69
|
10
|
|
|
|
|
94
|
while($self->{buffer} =~ s/^([^;]+;)//) { |
70
|
10
|
|
|
|
|
22
|
eval { $self->emit(instruction => $1) }; |
|
10
|
|
|
|
|
39
|
|
71
|
|
|
|
|
|
|
} |
72
|
7
|
|
|
|
|
52
|
}); |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
$stream->on(error => sub { |
75
|
0
|
|
|
|
|
0
|
$self->emit(error => $_[1]); |
76
|
7
|
|
|
|
|
65
|
}); |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
$stream->on(close => sub { |
79
|
0
|
|
|
|
|
0
|
print STDERR "Connection to guacd closed\n" if DEBUG; |
80
|
0
|
0
|
|
|
|
0
|
return unless $self; |
81
|
0
|
|
|
|
|
0
|
delete @{$self}{qw(buffer id stream)}; |
|
0
|
|
|
|
|
0
|
|
82
|
0
|
|
|
|
|
0
|
$self->emit('close'); |
83
|
7
|
|
|
|
|
72
|
}); |
84
|
|
|
|
|
|
|
|
85
|
7
|
|
|
|
|
44
|
$res->(); |
86
|
7
|
|
|
|
|
79
|
}); |
87
|
7
|
|
|
|
|
60
|
}); |
88
|
|
|
|
|
|
|
} |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
sub handshake_p { |
91
|
7
|
|
|
7
|
0
|
1780
|
Scalar::Util::weaken(my $self = shift); |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
return Mojo::Promise->reject('Not connected') |
94
|
7
|
50
|
|
|
|
27
|
unless my $stream = $self->{stream}; |
95
|
|
|
|
|
|
|
|
96
|
7
|
|
|
|
|
16
|
my $args; |
97
|
|
|
|
|
|
|
return $self->_expect(args => [select => $self->protocol]) |
98
|
|
|
|
|
|
|
->then(sub { |
99
|
4
|
|
|
4
|
|
787
|
my $got = shift; |
100
|
4
|
|
|
|
|
9
|
my $version = shift @$got; |
101
|
4
|
100
|
|
|
|
24
|
return Mojo::Promise->reject("Version $version less than supported ($v)") |
102
|
|
|
|
|
|
|
unless _check_version($version); |
103
|
3
|
|
|
|
|
6
|
$args = $got; |
104
|
3
|
|
|
|
|
15
|
$self->write_p(encode([size => $self->width, $self->height, $self->dpi])); |
105
|
|
|
|
|
|
|
}) |
106
|
3
|
|
|
3
|
|
1103
|
->then(sub{ $self->write_p(encode([audio => @{ $self->audio_mimetypes } ])) }) |
|
3
|
|
|
|
|
15
|
|
107
|
3
|
|
|
3
|
|
1122
|
->then(sub{ $self->write_p(encode([image => @{ $self->image_mimetypes } ])) }) |
|
3
|
|
|
|
|
61
|
|
108
|
3
|
|
|
3
|
|
971
|
->then(sub{ $self->write_p(encode([video => @{ $self->video_mimetypes } ])) }) |
|
3
|
|
|
|
|
12
|
|
109
|
|
|
|
|
|
|
->then(sub{ |
110
|
3
|
|
|
3
|
|
1160
|
my @connect = (connect => $v); |
111
|
3
|
|
|
|
|
16
|
my $proto = $self->connection_args; |
112
|
3
|
|
100
|
|
|
14
|
push @connect, map { $proto->{$_} // '' } @$args; |
|
6
|
|
|
|
|
31
|
|
113
|
3
|
|
|
|
|
11
|
$self->_expect(ready => \@connect); |
114
|
|
|
|
|
|
|
}) |
115
|
|
|
|
|
|
|
->then(sub { |
116
|
2
|
|
|
2
|
|
735
|
my $id = shift; |
117
|
2
|
|
|
|
|
5
|
print STDERR "Session $id->[0] is ready" if DEBUG; |
118
|
2
|
|
|
|
|
20
|
$self->{id} = $id->[0]; |
119
|
2
|
|
|
|
|
10
|
return $id->[0]; |
120
|
7
|
|
|
5
|
|
30
|
})->catch(sub { Mojo::Promise->reject("Handshake error: $_[0]") }); |
|
5
|
|
|
|
|
3168
|
|
121
|
|
|
|
|
|
|
} |
122
|
|
|
|
|
|
|
|
123
|
1
|
|
|
1
|
0
|
3
|
sub stream { shift->{stream} } |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
sub write { |
126
|
0
|
|
|
0
|
0
|
0
|
my ($self, $bytes) = @_; |
127
|
|
|
|
|
|
|
Carp::croak('Not connected') |
128
|
0
|
0
|
|
|
|
0
|
unless my $s = $self->{stream}; |
129
|
0
|
|
|
|
|
0
|
print STDERR '-> ' . Mojo::Util::term_escape($bytes) . "\n" if DEBUG; |
130
|
0
|
|
|
|
|
0
|
$self->{stream}->write($bytes); |
131
|
|
|
|
|
|
|
} |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
sub write_p { |
134
|
22
|
|
|
22
|
0
|
232
|
my ($self, $bytes) = @_; |
135
|
|
|
|
|
|
|
return Mojo::Promise->reject('Not connected') |
136
|
22
|
50
|
|
|
|
69
|
unless my $s = $self->{stream}; |
137
|
|
|
|
|
|
|
|
138
|
22
|
|
|
|
|
61
|
my $p = Mojo::Promise->new; |
139
|
22
|
|
|
|
|
534
|
print STDERR '-> ' . Mojo::Util::term_escape($bytes) . "\n" if DEBUG; |
140
|
22
|
|
|
22
|
|
120
|
$self->{stream}->write($bytes, sub { $p->resolve }); |
|
22
|
|
|
|
|
11167
|
|
141
|
22
|
|
|
|
|
994
|
return $p; |
142
|
|
|
|
|
|
|
} |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
sub _expect { |
145
|
10
|
|
|
10
|
|
63
|
my ($self, $command, $send) = @_; |
146
|
10
|
|
|
|
|
48
|
my $p = Mojo::Promise->new; |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
$self->once(instruction => sub { |
149
|
10
|
|
|
10
|
|
355
|
my (undef, $raw) = @_; |
150
|
10
|
|
|
|
|
20
|
my $instruction; |
151
|
10
|
100
|
|
|
|
16
|
eval { |
152
|
10
|
|
|
|
|
27
|
$instruction = decode($raw); 1; |
|
8
|
|
|
|
|
21
|
|
153
|
|
|
|
|
|
|
} or return $p->reject($@); |
154
|
8
|
|
|
|
|
17
|
my $got = shift @$instruction; |
155
|
8
|
100
|
|
|
|
28
|
if ($got eq $command) { |
156
|
6
|
|
|
|
|
23
|
$p->resolve($instruction); |
157
|
|
|
|
|
|
|
} else { |
158
|
2
|
|
|
|
|
14
|
$p->reject(qq[Unexpected command "$got" received, expected "$command"]); |
159
|
|
|
|
|
|
|
} |
160
|
10
|
|
|
|
|
324
|
}); |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
$self->write_p(encode($send)) |
163
|
10
|
|
|
0
|
|
262
|
->catch(sub { $p->reject("Send failed: $_[0]") }); |
|
0
|
|
|
|
|
0
|
|
164
|
|
|
|
|
|
|
|
165
|
10
|
|
|
|
|
637
|
return $p; |
166
|
|
|
|
|
|
|
} |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
## FUNCTIONS! |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
sub encode { |
171
|
23
|
|
|
23
|
0
|
100
|
my $words = shift; |
172
|
23
|
|
50
|
|
|
48
|
return join(',', map { $_ //= ''; length . '.' . Mojo::Util::encode('UTF-8', $_) } @$words) . ";"; |
|
49
|
|
|
|
|
291
|
|
|
49
|
|
|
|
|
133
|
|
173
|
|
|
|
|
|
|
} |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
sub decode { |
176
|
10
|
|
|
10
|
0
|
46
|
my $line = Mojo::Util::decode('UTF-8', shift); |
177
|
10
|
50
|
|
|
|
370
|
Carp::croak 'Instruction does not end with ;' |
178
|
|
|
|
|
|
|
unless $line =~ s/;$//; |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
my @words = |
181
|
|
|
|
|
|
|
map { |
182
|
10
|
|
|
|
|
90
|
my ($l, $s) = split /\./, $_, 2; |
|
22
|
|
|
|
|
75
|
|
183
|
22
|
100
|
66
|
|
|
479
|
Carp::croak 'Invalid instruction encoding' |
|
|
|
66
|
|
|
|
|
184
|
|
|
|
|
|
|
unless defined $l && defined $s && Scalar::Util::looks_like_number($l); |
185
|
21
|
100
|
|
|
|
252
|
Carp::croak 'Word length mismatch' |
186
|
|
|
|
|
|
|
unless length($s) == $l; |
187
|
20
|
|
|
|
|
55
|
$s; |
188
|
|
|
|
|
|
|
} |
189
|
|
|
|
|
|
|
split ',', $line; |
190
|
|
|
|
|
|
|
|
191
|
8
|
|
|
|
|
23
|
return \@words; |
192
|
|
|
|
|
|
|
} |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
1; |
196
|
|
|
|
|
|
|
|