diff --git a/Makefile b/Makefile
index cbe0645..a56e3cb 100644
--- a/Makefile
+++ b/Makefile
@@ -22,6 +22,12 @@ deb ${DEB}:
install:
install -d -m 755 ${PERL5DIR}/PVE/APIServer
install -m 0644 PVE/APIServer/AnyEvent.pm ${PERL5DIR}/PVE/APIServer
+ install -m 0644 PVE/APIServer/Formatter.pm ${PERL5DIR}/PVE/APIServer
+ install -d -m 755 ${PERL5DIR}/PVE/APIServer/Formatter
+ install -m 0644 PVE/APIServer/Formatter/Standard.pm ${PERL5DIR}/PVE/APIServer/Formatter
+ install -m 0644 PVE/APIServer/Formatter/Bootstrap.pm ${PERL5DIR}/PVE/APIServer/Formatter
+ install -m 0644 PVE/APIServer/Formatter/HTML.pm ${PERL5DIR}/PVE/APIServer/Formatter
+
.PHONY: upload
upload: ${DEB}
diff --git a/PVE/APIServer/AnyEvent.pm b/PVE/APIServer/AnyEvent.pm
index 2ac3e39..d818e61 100755
--- a/PVE/APIServer/AnyEvent.pm
+++ b/PVE/APIServer/AnyEvent.pm
@@ -24,6 +24,7 @@ use Compress::Zlib;
use PVE::SafeSyslog;
use PVE::INotify;
use PVE::Tools;
+use PVE::APIServer::Formatter;
use Net::IP;
use URI;
@@ -39,7 +40,6 @@ my $limit_max_headers = 30;
my $limit_max_header_size = 8*1024;
my $limit_max_post = 16*1024;
-
my $known_methods = {
GET => 1,
POST => 1,
@@ -56,57 +56,6 @@ my $split_abs_uri = sub {
return wantarray ? ($rel_uri, $format) : $rel_uri;
};
-# generic formatter support
-
-my $formatter_hash = {};
-
-sub register_formatter {
- my ($format, $func) = @_;
-
- die "formatter '$format' already defined" if $formatter_hash->{$format};
-
- $formatter_hash->{$format} = {
- func => $func,
- };
-}
-
-sub get_formatter {
- my ($format) = @_;
-
- return undef if !$format;
-
- my $info = $formatter_hash->{$format};
- return undef if !$info;
-
- return $info->{func};
-}
-
-my $login_formatter_hash = {};
-
-sub register_login_formatter {
- my ($format, $func) = @_;
-
- die "login formatter '$format' already defined" if $login_formatter_hash->{$format};
-
- $login_formatter_hash->{$format} = {
- func => $func,
- };
-}
-
-sub get_login_formatter {
- my ($format) = @_;
-
- return undef if !$format;
-
- my $info = $login_formatter_hash->{$format};
- return undef if !$info;
-
- return $info->{func};
-}
-
-
-# server implementation
-
sub log_request {
my ($self, $reqstate) = @_;
@@ -143,28 +92,6 @@ sub log_aborted_request {
$self->log_request($reqstate);
}
-sub extract_auth_cookie {
- my ($cookie, $cookie_name) = @_;
-
- return undef if !$cookie;
-
- my $ticket = ($cookie =~ /(?:^|\s)\Q$cookie_name\E=([^;]*)/)[0];
-
- if ($ticket && $ticket =~ m/^PVE%3A/) {
- $ticket = uri_unescape($ticket);
- }
-
- return $ticket;
-}
-
-sub create_auth_cookie {
- my ($ticket, $cookie_name) = @_;
-
- my $encticket = uri_escape($ticket);
-
- return "${cookie_name}=$encticket; path=/; secure;";
-}
-
sub cleanup_reqstate {
my ($reqstate) = @_;
@@ -606,7 +533,7 @@ sub proxy_request {
PVEClientIP => $clientip,
};
- $headers->{'cookie'} = create_auth_cookie($ticket, $self->{cookie_name}) if $ticket;
+ $headers->{'cookie'} = PVE::APIServer::Formatter::create_auth_cookie($ticket, $self->{cookie_name}) if $ticket;
$headers->{'CSRFPreventionToken'} = $token if $token;
$headers->{'Accept-Encoding'} = 'gzip' if $reqstate->{accept_gzip};
@@ -730,7 +657,7 @@ sub handle_api2_request {
my ($rel_uri, $format) = &$split_abs_uri($path, $self->{base_uri});
- my $formatter = get_formatter($format);
+ my $formatter = PVE::APIServer::Formatter::get_formatter($format);
if (!defined($formatter)) {
$self->error($reqstate, HTTP_NOT_IMPLEMENTED, "no such uri $rel_uri, $format");
@@ -1256,7 +1183,7 @@ sub unshift_read_header {
} elsif ($path =~ m/^\Q$base_uri\E/) {
my $token = $r->header('CSRFPreventionToken');
my $cookie = $r->header('Cookie');
- my $ticket = extract_auth_cookie($cookie, $self->{cookie_name});
+ my $ticket = PVE::APIServer::Formatter::extract_auth_cookie($cookie, $self->{cookie_name});
my ($rel_uri, $format) = &$split_abs_uri($path, $self->{base_uri});
if (!$format) {
@@ -1276,7 +1203,7 @@ sub unshift_read_header {
if (my $err = $@) {
# always delay unauthorized calls by 3 seconds
my $delay = 3;
- if (my $formatter = get_login_formatter($format)) {
+ if (my $formatter = PVE::APIServer::Formatter::get_login_formatter($format)) {
my ($raw, $ct, $nocomp) = &$formatter($path, $auth);
my $resp;
if (ref($raw) && (ref($raw) eq 'HTTP::Response')) {
diff --git a/PVE/APIServer/Formatter.pm b/PVE/APIServer/Formatter.pm
new file mode 100644
index 0000000..29f1898
--- /dev/null
+++ b/PVE/APIServer/Formatter.pm
@@ -0,0 +1,79 @@
+package PVE::APIServer::Formatter;
+
+use strict;
+use warnings;
+
+# generic formatter support
+# PVE::APIServer::Formatter::* classes should register themselves here
+
+my $formatter_hash = {};
+
+sub register_formatter {
+ my ($format, $func) = @_;
+
+ die "formatter '$format' already defined" if $formatter_hash->{$format};
+
+ $formatter_hash->{$format} = {
+ func => $func,
+ };
+}
+
+sub get_formatter {
+ my ($format) = @_;
+
+ return undef if !$format;
+
+ my $info = $formatter_hash->{$format};
+ return undef if !$info;
+
+ return $info->{func};
+}
+
+my $login_formatter_hash = {};
+
+sub register_login_formatter {
+ my ($format, $func) = @_;
+
+ die "login formatter '$format' already defined" if $login_formatter_hash->{$format};
+
+ $login_formatter_hash->{$format} = {
+ func => $func,
+ };
+}
+
+sub get_login_formatter {
+ my ($format) = @_;
+
+ return undef if !$format;
+
+ my $info = $login_formatter_hash->{$format};
+ return undef if !$info;
+
+ return $info->{func};
+}
+
+# some helper functions
+
+sub extract_auth_cookie {
+ my ($cookie, $cookie_name) = @_;
+
+ return undef if !$cookie;
+
+ my $ticket = ($cookie =~ /(?:^|\s)\Q$cookie_name\E=([^;]*)/)[0];
+
+ if ($ticket && $ticket =~ m/^PVE%3A/) {
+ $ticket = uri_unescape($ticket);
+ }
+
+ return $ticket;
+}
+
+sub create_auth_cookie {
+ my ($ticket, $cookie_name) = @_;
+
+ my $encticket = uri_escape($ticket);
+
+ return "${cookie_name}=$encticket; path=/; secure;";
+}
+
+1;
diff --git a/PVE/APIServer/Formatter/Bootstrap.pm b/PVE/APIServer/Formatter/Bootstrap.pm
new file mode 100644
index 0000000..be9d3d8
--- /dev/null
+++ b/PVE/APIServer/Formatter/Bootstrap.pm
@@ -0,0 +1,238 @@
+package PVE::APIServer::Formatter::Bootstrap;
+
+use strict;
+use warnings;
+use URI::Escape;
+use HTML::Entities;
+use JSON;
+
+use PVE::AccessControl; # to generate CSRF token
+
+# Helpers to generate simple html pages using Bootstrap markup.
+
+my $jssrc = <<_EOJS;
+PVE = {
+ delete_auth_cookie: function() {
+ document.cookie = "PVEAuthCookie=; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/; secure;";
+ },
+ open_vm_console: function(node, vmid) {
+ console.log("open vm " + vmid + " on node " + node);
+
+ var downloadWithName = function(uri, name) {
+ var link = jQuery('#pve_console_anchor');
+ link.attr("href", uri);
+
+ // Note: we need to tell android the correct file name extension
+ // but we do not set 'download' tag for other environments, because
+ // It can have strange side effects (additional user prompt on firefox)
+ var andriod = navigator.userAgent.match(/Android/i) ? true : false;
+ if (andriod) {
+ link.attr("download", name);
+ }
+
+ if (document.createEvent) {
+ var evt = document.createEvent("MouseEvents");
+ evt.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
+ link.get(0).dispatchEvent(evt);
+ } else {
+ link.get(0).fireEvent('onclick');
+ }
+ };
+
+ jQuery.ajax("/api2/json/console", {
+ data: { vmid: vmid, node: node },
+ headers: { CSRFPreventionToken: PVE.CSRFPreventionToken },
+ dataType: 'json',
+ type: 'POST',
+ error: function(jqXHR, textStatus, errorThrown) {
+ // fixme: howto view JS errors ?
+ console.log("ERROR " + textStatus + ": " + errorThrown);
+ },
+ success: function(data) {
+ var raw = "[virt-viewer]\\n";
+ jQuery.each(data.data, function(k, v) {
+ raw += k + "=" + v + "\\n";
+ });
+ var url = 'data:application/x-virt-viewer;charset=UTF-8,' +
+ encodeURIComponent(raw);
+
+ downloadWithName(url, "pve-spice.vv");
+ }
+ });
+ }
+};
+_EOJS
+
+sub new {
+ my ($class, $res, $url) = @_;
+
+ my $self = bless {
+ url => $url,
+ js => '',
+ };
+
+ if (my $username = $res->{auth}->{userid}) {
+ $self->{csrftoken} = PVE::AccessControl::assemble_csrf_prevention_token($username);
+ }
+
+ return $self;
+}
+
+sub body {
+ my ($self, $html) = @_;
+
+ my $jssetup = '';
+
+ if ($self->{csrftoken}) {
+ $jssetup .= "PVE.CSRFPreventionToken = '$self->{csrftoken}';\n";
+ }
+
+ return <<_EOD;
+
+
+
+
+
+
+ Proxmox VE API
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $html
+
+
+
+_EOD
+}
+
+my $comp_id_counter = 0;
+
+sub el {
+ my ($self, %param) = @_;
+
+ $param{tag} = 'div' if !$param{tag};
+
+ my $id;
+
+ my $html = "<$param{tag}";
+
+ if (wantarray) {
+ $comp_id_counter++;
+ $id = "pveid$comp_id_counter";
+ $html .= " id=$id";
+ }
+
+ my $skip = {
+ tag => 1,
+ cn => 1,
+ html => 1,
+ text => 1,
+ };
+
+ my $boolattr = {
+ required => 1,
+ autofocus => 1,
+ };
+
+ my $noescape = {
+ placeholder => 1,
+ };
+
+ foreach my $attr (keys %param) {
+ next if $skip->{$attr};
+ my $v = $noescape->{$attr} ? $param{$attr} : uri_escape_utf8($param{$attr},"[^\/\ A-Za-z0-9\-\._~]");
+ next if !defined($v);
+ if ($boolattr->{$attr}) {
+ $html .= " $attr" if $v;
+ } else {
+ $html .= " $attr=\"$v\"";
+ }
+ }
+
+ $html .= ">";
+
+
+ if (my $cn = $param{cn}) {
+ if(ref($cn) eq 'ARRAY'){
+ foreach my $rec (@$cn) {
+ $html .= $self->el(%$rec);
+ }
+ } else {
+ $html .= $self->el(%$cn);
+ }
+ } elsif ($param{html}) {
+ $html .= $param{html};
+ } elsif ($param{text}) {
+ $html .= encode_entities($param{text});
+ }
+
+ $html .= "$param{tag}>";
+
+ return wantarray ? ($html, $id) : $html;
+}
+
+sub alert {
+ my ($self, %param) = @_;
+
+ return $self->el(class => "alert alert-danger", %param);
+}
+
+sub add_js {
+ my ($self, $js) = @_;
+
+ $self->{js} .= $js . "\n";
+}
+
+my $format_event_callback = sub {
+ my ($info) = @_;
+
+ my $pstr = encode_json($info->{param});
+ return "function(e){$info->{fn}.apply(e, $pstr);}";
+};
+
+sub button {
+ my ($self, %param) = @_;
+
+ $param{tag} = 'button';
+ $param{class} = "btn btn-default btn-xs";
+
+ if (my $click = delete $param{click}) {
+ my ($html, $id) = $self->el(%param);
+ my $cb = &$format_event_callback($click);
+ $self->add_js("jQuery('#$id').on('click', $cb);");
+ return $html;
+ } else {
+ return $self->el(%param);
+ }
+}
+
+1;
diff --git a/PVE/APIServer/Formatter/HTML.pm b/PVE/APIServer/Formatter/HTML.pm
new file mode 100644
index 0000000..a27c88a
--- /dev/null
+++ b/PVE/APIServer/Formatter/HTML.pm
@@ -0,0 +1,288 @@
+package PVE::APIServer::Formatter::HTML;
+
+use strict;
+use warnings;
+
+use PVE::APIServer::Formatter;
+use HTTP::Status;
+use JSON;
+use HTML::Entities;
+use PVE::JSONSchema;
+use PVE::APIServer::Formatter::Bootstrap;
+use PVE::APIServer::Formatter::Standard;
+
+my $portal_format = 'html';
+my $portal_ct = 'text/html;charset=UTF-8';
+
+my $baseurl = "/api2/$portal_format";
+my $login_url = "$baseurl/access/ticket";
+
+sub render_page {
+ my ($doc, $html) = @_;
+
+ my $items = [];
+
+ push @$items, {
+ tag => 'li',
+ cn => {
+ tag => 'a',
+ href => $login_url,
+ onClick => "PVE.delete_auth_cookie();",
+ text => "Logout",
+ }};
+
+
+ my $title = "Proxmox VE";
+
+ my $nav = $doc->el(
+ class => "navbar navbar-inverse navbar-fixed-top",
+ role => "navigation", cn => {
+ class => "container", cn => [
+ {
+ class => "navbar-header", cn => [
+ {
+ tag => 'button',
+ type => 'button',
+ class => "navbar-toggle",
+ 'data-toggle' => "collapse",
+ 'data-target' => ".navbar-collapse",
+ cn => [
+ { tag => 'span', class => 'sr-only', text => "Toggle navigation" },
+ { tag => 'span', class => 'icon-bar' },
+ { tag => 'span', class => 'icon-bar' },
+ { tag => 'span', class => 'icon-bar' },
+ ],
+ },
+ {
+ tag => 'a',
+ class => "navbar-brand",
+ href => $baseurl,
+ text => $title,
+ },
+ ],
+ },
+ {
+ class => "collapse navbar-collapse",
+ cn => {
+ tag => 'ul',
+ class => "nav navbar-nav",
+ cn => $items,
+ },
+ },
+ ],
+ });
+
+ $items = [];
+ my @pcomp = split('/', $doc->{url});
+ shift @pcomp; # empty
+ shift @pcomp; # api2
+ shift @pcomp; # $format
+
+ my $href = $baseurl;
+ push @$items, { tag => 'li', cn => {
+ tag => 'a',
+ href => $href,
+ text => 'Home'}};
+
+ foreach my $comp (@pcomp) {
+ $href .= "/$comp";
+ push @$items, { tag => 'li', cn => {
+ tag => 'a',
+ href => $href,
+ text => $comp}};
+ }
+
+ my $breadcrumbs = $doc->el(tag => 'ol', class => 'breadcrumb container', cn => $items);
+
+ return $doc->body($nav . $breadcrumbs . $html);
+}
+
+my $login_form = sub {
+ my ($doc, $param, $errmsg) = @_;
+
+ $param = {} if !$param;
+
+ my $username = $param->{username} || '';
+ my $password = $param->{password} || '';
+
+ my $items = [
+ {
+ tag => 'label',
+ text => "Please sign in",
+ },
+ {
+ tag => 'input',
+ type => 'text',
+ class => 'form-control',
+ name => 'username',
+ value => $username,
+ placeholder => "Enter user name",
+ required => 1,
+ autofocus => 1,
+ },
+ {
+ tag => 'input',
+ type => 'password',
+ class => 'form-control',
+ name => 'password',
+ value => $password,
+ placeholder => 'Password',
+ required => 1,
+ },
+ ];
+
+ my $html = '';
+
+ $html .= $doc->alert(text => $errmsg) if ($errmsg);
+
+ $html .= $doc->el(
+ class => 'container',
+ cn => {
+ tag => 'form',
+ role => 'form',
+ method => 'POST',
+ action => $login_url,
+ cn => [
+ {
+ class => 'form-group',
+ cn => $items,
+ },
+ {
+ tag => 'button',
+ type => 'submit',
+ class => 'btn btn-lg btn-primary btn-block',
+ text => "Sign in",
+ },
+ ],
+ });
+
+ return $html;
+};
+
+PVE::APIServer::Formatter::register_login_formatter($portal_format, sub {
+ my ($path, $auth) = @_;
+
+ my $headers = HTTP::Headers->new(Location => $login_url);
+ return HTTP::Response->new(301, "Moved", $headers);
+});
+
+PVE::APIServer::Formatter::register_formatter($portal_format, sub {
+ my ($res, $data, $param, $path, $auth) = @_;
+
+ # fixme: clumsy!
+ PVE::API2::Formatter::Standard::prepare_response_data($portal_format, $res);
+ $data = $res->{data};
+
+ my $html = '';
+ my $doc = PVE::API2::Formatter::Bootstrap->new($res, $path);
+
+ if (!HTTP::Status::is_success($res->{status})) {
+ $html .= $doc->alert(text => "Error $res->{status}: $res->{message}");
+ }
+
+ my $info = $res->{info};
+
+ $html .= $doc->el(tag => 'h3', text => 'Description');
+ $html .= $doc->el(tag => 'p', text => $info->{description});
+
+ my $lnk = PVE::JSONSchema::method_get_child_link($info);
+
+ if ($lnk && $data && $data->{data} && HTTP::Status::is_success($res->{status})) {
+
+ my $href = $lnk->{href};
+ if ($href =~ m/^\{(\S+)\}$/) {
+
+ my $items = [];
+
+ my $prop = $1;
+ $path =~ s/\/+$//; # remove trailing slash
+
+ foreach my $elem (sort {$a->{$prop} cmp $b->{$prop}} @{$data->{data}}) {
+ next if !ref($elem);
+
+ if (defined(my $value = $elem->{$prop})) {
+ my $tv = to_json($elem, {pretty => 1, allow_nonref => 1, canonical => 1});
+
+ push @$items, {
+ tag => 'a',
+ class => 'list-group-item',
+ href => "$path/$value",
+ cn => [
+ {
+ tag => 'h4',
+ class => 'list-group-item-heading',
+ text => $value,
+ },
+ {
+ tag => 'pre',
+ class => 'list-group-item',
+ text => $tv,
+ },
+ ],
+ };
+ }
+ }
+
+ $html .= $doc->el(class => 'list-group', cn => $items);
+
+ } else {
+
+ my $json = to_json($data, {allow_nonref => 1, pretty => 1});
+ $html .= $doc->el(tag => 'pre', text => $json);
+ }
+
+ } else {
+
+ my $json = to_json($data, {allow_nonref => 1, pretty => 1});
+ $html .= $doc->el(tag => 'pre', text => $json);
+ }
+
+ $html = $doc->el(class => 'container', html => $html);
+
+ my $raw = render_page($doc, $html);
+ return ($raw, $portal_ct);
+});
+
+PVE::RESTHandler->register_page_formatter(
+ 'format' => $portal_format,
+ method => 'GET',
+ path => "/access/ticket",
+ code => sub {
+ my ($res, $data, $param, $path, $auth) = @_;
+
+ my $doc = PVE::API2::Formatter::Bootstrap->new($res, $path);
+
+ my $html = &$login_form($doc);
+
+ my $raw = render_page($doc, $html);
+ return ($raw, $portal_ct);
+ });
+
+PVE::RESTHandler->register_page_formatter(
+ 'format' => $portal_format,
+ method => 'POST',
+ path => "/access/ticket",
+ code => sub {
+ my ($res, $data, $param, $path, $auth) = @_;
+
+ if (HTTP::Status::is_success($res->{status})) {
+ my $cookie = PVE::APIServer::Formatter::create_auth_cookie(
+ $data->{ticket}, $auth->{cookie_name});
+
+ my $headers = HTTP::Headers->new(Location => $baseurl,
+ 'Set-Cookie' => $cookie);
+ return HTTP::Response->new(301, "Moved", $headers);
+ }
+
+ # Note: HTTP server redirects to 'GET /access/ticket', so below
+ # output is not really visible.
+
+ my $doc = PVE::APIServer::Formatter::Bootstrap->new($res, $path);
+
+ my $html = &$login_form($doc);
+
+ my $raw = render_page($doc, $html);
+ return ($raw, $portal_ct);
+ });
+
+1;
diff --git a/PVE/APIServer/Formatter/Standard.pm b/PVE/APIServer/Formatter/Standard.pm
new file mode 100644
index 0000000..cf5aac5
--- /dev/null
+++ b/PVE/APIServer/Formatter/Standard.pm
@@ -0,0 +1,141 @@
+package PVE::APIServer::Formatter::Standard;
+
+use strict;
+use warnings;
+
+use PVE::APIServer::Formatter;
+use HTTP::Status;
+use JSON;
+use HTML::Entities;
+use PVE::JSONSchema;
+
+# register result formatters
+
+sub prepare_response_data {
+ my ($format, $res) = @_;
+
+ my $success = 1;
+ my $new = {
+ data => $res->{data},
+ };
+ if (scalar(keys %{$res->{errors}})) {
+ $success = 0;
+ $new->{errors} = $res->{errors};
+ }
+
+ if ($format eq 'extjs' || $format eq 'htmljs') {
+ # HACK: extjs wants 'success' property instead of useful HTTP status codes
+ if (HTTP::Status::is_error($res->{status})) {
+ $success = 0;
+ $new->{message} = $res->{message} || status_message($res->{status});
+ $new->{status} = $res->{status} || 200;
+ $res->{message} = undef;
+ $res->{status} = 200;
+ }
+ $new->{success} = $success;
+ }
+
+ if ($success && $res->{total}) {
+ $new->{total} = $res->{total};
+ }
+
+ if ($success && $res->{changes}) {
+ $new->{changes} = $res->{changes};
+ }
+
+ $res->{data} = $new;
+}
+
+PVE::APIServer::Formatter::register_formatter('json', sub {
+ my ($res, $data, $param, $path, $auth) = @_;
+
+ my $nocomp = 0;
+
+ my $ct = 'application/json;charset=UTF-8';
+
+ prepare_response_data('json', $res);
+
+ my $raw = to_json($res->{data}, {utf8 => 1, allow_nonref => 1});
+
+ return ($raw, $ct, $nocomp);
+});
+
+
+PVE::APIServer::Formatter::register_formatter('extjs', sub {
+ my ($res, $data, $param, $path, $auth) = @_;
+
+ my $nocomp = 0;
+
+ my $ct = 'application/json;charset=UTF-8';
+
+ prepare_response_data('extjs', $res);
+
+ my $raw = to_json($res->{data}, {utf8 => 1, allow_nonref => 1});
+
+ return ($raw, $ct, $nocomp);
+});
+
+PVE::APIServer::Formatter::register_formatter('htmljs', sub {
+ my ($res, $data, $param, $path, $auth) = @_;
+
+ my $nocomp = 0;
+
+ # we use this for extjs file upload forms
+
+ my $ct = 'text/html;charset=UTF-8';
+
+ prepare_response_data('htmljs', $res);
+
+ my $raw = encode_entities(to_json($res->{data}, {allow_nonref => 1}));
+
+ return ($raw, $ct, $nocomp);
+});
+
+
+PVE::APIServer::Formatter::register_formatter('spiceconfig', sub {
+ my ($res, $data, $param, $path, $auth) = @_;
+
+ my $nocomp = 0;
+
+ my $ct = 'application/x-virt-viewer;charset=UTF-8';
+
+ prepare_response_data('spiceconfig', $res);
+
+ $data = $res->{data};
+
+ my $raw;
+
+ if ($data && ref($data) && ref($data->{data})) {
+ $raw = "[virt-viewer]\n";
+ while (my ($key, $value) = each %{$data->{data}}) {
+ $raw .= "$key=$value\n" if defined($value);
+ }
+ }
+
+ return ($raw, $ct, $nocomp);
+});
+
+PVE::APIServer::Formatter::register_formatter('png', sub {
+ my ($res, $data, $param, $path, $auth) = @_;
+
+ my $nocomp = 1;
+
+ my $ct = 'image/png';
+
+ prepare_response_data('png', $res);
+
+ $data = $res->{data};
+
+ # fixme: better to revove that whole png thing ?
+
+ my $filename;
+ my $raw = '';
+
+ if ($data && ref($data) && ref($data->{data}) &&
+ $data->{data}->{filename} && defined($data->{data}->{image})) {
+ $filename = $data->{data}->{filename};
+ $raw = $data->{data}->{image};
+ }
+
+ return ($raw, $ct, $nocomp);
+});
diff --git a/debian/control b/debian/control
index 5460c1e..66c66eb 100644
--- a/debian/control
+++ b/debian/control
@@ -8,6 +8,6 @@ Homepage: http://www.proxmox.com
Package: libpve-http-server-perl
Architecture: all
-Depends: ${misc:Depends}, libnet-ip-perl, libio-socket-ssl-perl, libjson-perl, libcrypt-ssleay-perl, libhttp-date-perl, libhttp-message-perl, liburi-perl, libanyevent-perl, libanyevent-http-perl, libpve-common-perl
+Depends: ${misc:Depends}, libnet-ip-perl, libio-socket-ssl-perl, libjson-perl, libcrypt-ssleay-perl, libhttp-date-perl, libhttp-message-perl, liburi-perl, libhtml-parser-perl, libanyevent-perl, libanyevent-http-perl, libpve-common-perl
Description: Proxmox Asynchrounous HTTP Server Implementation
This is used to implement the PVE REST API.
\ No newline at end of file