Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
P
pve-manager
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
pve-manager
Commits
519ea5b0
Commit
519ea5b0
authored
Apr 12, 2013
by
Dietmar Maurer
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
implement file upload
And remove CGI.pm dependency (because we want nonblocking upload).
parent
bc8f141b
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
375 additions
and
143 deletions
+375
-143
HTTPServer.pm
PVE/HTTPServer.pm
+338
-68
REST.pm
PVE/REST.pm
+36
-72
pveproxy
bin/pveproxy
+1
-3
No files found.
PVE/HTTPServer.pm
View file @
519ea5b0
...
...
@@ -2,10 +2,13 @@ package PVE::HTTPServer;
use
strict
;
use
warnings
;
use
Time::
HiRes
qw(usleep ualarm gettimeofday tv_interval)
;
use
Socket
qw(IPPROTO_TCP TCP_NODELAY SOMAXCONN)
;
use
POSIX
qw(strftime EINTR EAGAIN)
;
use
Fcntl
;
use
IO::
File
;
use
File::
stat
qw()
;
use
Digest::
MD5
;
use
AnyEvent::
Strict
;
use
AnyEvent::
Util
qw(guard fh_nonblocking WSAEWOULDBLOCK WSAEINPROGRESS)
;
use
AnyEvent::
Handle
;
...
...
@@ -24,11 +27,8 @@ use HTTP::Status qw(:constants);
use
HTTP::
Headers
;
use
HTTP::
Response
;
use
CGI
;
# fixme: remove this!
# DOS attack prevention
# fixme: remove CGI.pm
$
CGI::
DISABLE_UPLOADS
=
1
;
# no uploads
$
CGI::
POST_MAX
=
1024
*
10
;
# max 10K posts
# fixme
# POST_MAX = 1024 * 10; # max 10K posts
use
Data::
Dumper
;
# fixme: remove
...
...
@@ -39,6 +39,17 @@ my $known_methods = {
DELETE
=>
1
,
};
my
$baseuri
=
"
/api2
";
sub
split_abs_uri
{
my
(
$abs_uri
)
=
@_
;
my
(
$format
,
$rel_uri
)
=
$abs_uri
=~
m/^\Q$baseuri\E\/+(html|text|json|extjs|png|htmljs)(\/.*)?$/
;
$rel_uri
=
'
/
'
if
!
$rel_uri
;
return
wantarray
?
(
$rel_uri
,
$format
)
:
$rel_uri
;
}
sub
log_request
{
my
(
$self
,
$reqstate
)
=
@_
;
...
...
@@ -75,6 +86,11 @@ sub log_aborted_request {
sub
client_do_disconnect
{
my
(
$self
,
$reqstate
)
=
@_
;
if
(
$reqstate
->
{
tmpfilename
})
{
unlink
$reqstate
->
{
tmpfilename
};
delete
$reqstate
->
{
tmpfilename
};
}
my
$hdl
=
delete
$reqstate
->
{
hdl
};
if
(
!
$hdl
)
{
...
...
@@ -103,8 +119,13 @@ sub finish_response {
delete
$reqstate
->
{
request
};
delete
$reqstate
->
{
proto
};
if
(
$reqstate
->
{
tmpfilename
})
{
unlink
$reqstate
->
{
tmpfilename
};
delete
$reqstate
->
{
tmpfilename
};
}
if
(
!
$self
->
{
end_loop
}
&&
$reqstate
->
{
keep_alive
}
>
0
)
{
# print "KEEPALIVE $reqstate->{keep_alive}\n";
# print "KEEPALIVE $reqstate->{keep_alive}\n"
if $self->{debug}
;
$hdl
->
on_read
(
sub
{
eval
{
$self
->
push_request_header
(
$reqstate
);
};
warn
$@
if
$@
;
...
...
@@ -244,17 +265,17 @@ sub send_file_start {
}
sub
proxy_request
{
my
(
$self
,
$reqstate
,
$
r
,
$clientip
,
$host
,
$method
,
$abs_
uri
,
$ticket
,
$token
,
$params
)
=
@_
;
my
(
$self
,
$reqstate
,
$
clientip
,
$host
,
$method
,
$
uri
,
$ticket
,
$token
,
$params
)
=
@_
;
eval
{
my
$target
;
my
$keep_alive
=
1
;
if
(
$host
eq
'
localhost
')
{
$target
=
"
http://
$host
:85
$
abs_
uri
";
$target
=
"
http://
$host
:85
$uri
";
# keep alive for localhost is not worth (connection setup is about 0.2ms)
$keep_alive
=
0
;
}
else
{
$target
=
"
https://
$host
:8006
$
abs_
uri
";
$target
=
"
https://
$host
:8006
$uri
";
}
my
$headers
=
{
...
...
@@ -271,8 +292,7 @@ sub proxy_request {
if
(
$method
eq
'
POST
'
||
$method
eq
'
PUT
')
{
$headers
->
{'
Content-Type
'}
=
'
application/x-www-form-urlencoded
';
# We use a temporary URI object to format
# the application/x-www-form-urlencoded content.
# use URI object to format application/x-www-form-urlencoded content.
my
$url
=
URI
->
new
('
http:
');
$url
->
query_form
(
%
$params
);
$content
=
$url
->
query
;
...
...
@@ -309,21 +329,40 @@ sub proxy_request {
warn
$@
if
$@
;
}
# return arrays as \0 separated strings (like CGI.pm)
sub
decode_urlencoded
{
my
(
$data
)
=
@_
;
my
$res
=
{};
return
$res
if
!
$data
;
foreach
my
$kv
(
split
(
/[\&\;]/
,
$data
))
{
my
(
$k
,
$v
)
=
split
(
/=/
,
$kv
);
$k
=~
s/\+/ /g
;
$k
=~
s/%([0-9a-fA-F][0-9a-fA-F])/chr(hex($1))/
eg
;
$v
=~
s/\+/ /g
;
$v
=~
s/%([0-9a-fA-F][0-9a-fA-F])/chr(hex($1))/
eg
;
if
(
defined
(
my
$old
=
$res
->
{
$k
}))
{
$res
->
{
$k
}
=
"
$old
\
0
$v
";
}
else
{
$res
->
{
$k
}
=
$v
;
}
}
return
$res
;
}
my
$extract_params
=
sub
{
my
(
$r
,
$method
)
=
@_
;
# NOTE: HTTP::Request::Params return undef instead of ''
#my $parser = HTTP::Request::Params->new({req => $r});
#my $params = $parser->params;
my
$post_params
=
{};
my
$params
=
{};
if
(
$method
eq
'
PUT
'
||
$method
eq
'
POST
')
{
$p
ost_params
=
CGI
->
new
(
$r
->
content
())
->
Vars
;
$p
arams
=
decode_urlencoded
(
$r
->
content
)
;
}
my
$query_params
=
CGI
->
new
(
$r
->
url
->
query
)
->
Vars
;
my
$params
=
$post_params
||
{};
my
$query_params
=
decode_urlencoded
(
$r
->
url
->
query
());
foreach
my
$k
(
keys
%
{
$query_params
})
{
$params
->
{
$k
}
=
$query_params
->
{
$k
};
...
...
@@ -333,42 +372,41 @@ my $extract_params = sub {
};
sub
handle_api2_request
{
my
(
$self
,
$reqstate
)
=
@_
;
my
(
$self
,
$reqstate
,
$auth
,
$upload_state
)
=
@_
;
eval
{
my
$r
=
$reqstate
->
{
request
};
my
$method
=
$r
->
method
();
my
$path
=
$r
->
uri
->
path
();
my
(
$rel_uri
,
$format
)
=
PVE::REST::
split_abs_uri
(
$path
);
my
(
$rel_uri
,
$format
)
=
split_abs_uri
(
$path
);
if
(
!
$format
)
{
$self
->
error
(
$reqstate
,
HTTP_NOT_IMPLEMENTED
,
"
no such uri
");
return
;
}
my
$rpcenv
=
$self
->
{
rpcenv
};
my
$headers
=
$r
->
headers
;
print
Dumper
(
$upload_state
)
if
$upload_state
;
my
$
token
=
$headers
->
header
('
CSRFPreventionToken
')
;
my
$
rpcenv
=
$self
->
{
rpcenv
}
;
my
$
cookie
=
$headers
->
header
('
Cookie
')
;
my
$
params
;
my
$ticket
=
PVE::REST::
extract_auth_cookie
(
$cookie
);
if
(
$upload_state
)
{
$params
=
$upload_state
->
{
params
};
}
else
{
$params
=
&
$extract_params
(
$r
,
$method
);
# fixme
}
my
$params
=
&
$extract_params
(
$r
,
$method
);
delete
$params
->
{
_dc
};
# remove disable cache parameter
my
$clientip
=
$
headers
->
header
('
PVEClientIP
')
;
my
$clientip
=
$
reqstate
->
{
peer_host
}
;
$rpcenv
->
init_request
(
params
=>
$params
);
$rpcenv
->
init_request
();
my
$res
=
PVE::REST::
rest_handler
(
$rpcenv
,
$clientip
,
$method
,
$
path
,
$rel_uri
,
$ticket
,
$token
);
my
$res
=
PVE::REST::
rest_handler
(
$rpcenv
,
$clientip
,
$method
,
$
rel_uri
,
$auth
,
$params
);
# todo: eval { $userid = $rpcenv->get_user(); };
my
$userid
=
$rpcenv
->
{
user
};
# this is faster
$rpcenv
->
set_user
(
undef
);
# clear after request
$reqstate
->
{
log
}
->
{
userid
}
=
$userid
;
if
(
$res
->
{
proxy
})
{
if
(
$self
->
{
trusted_env
})
{
...
...
@@ -376,8 +414,11 @@ sub handle_api2_request {
return
;
}
$self
->
proxy_request
(
$reqstate
,
$r
,
$clientip
,
$res
->
{
proxy
},
$method
,
$r
->
uri
,
$ticket
,
$token
,
$res
->
{
proxy_params
});
$res
->
{
proxy_params
}
->
{
tmpfilename
}
=
$reqstate
->
{
tmpfilename
}
if
$upload_state
;
# fixme: cleanup parameter list
$self
->
proxy_request
(
$reqstate
,
$clientip
,
$res
->
{
proxy
},
$method
,
$r
->
uri
,
$auth
->
{
ticket
},
$auth
->
{
token
},
$res
->
{
proxy_params
});
return
;
}
...
...
@@ -389,38 +430,29 @@ sub handle_api2_request {
$resp
->
header
("
Content-Type
"
=>
$ct
);
$resp
->
content
(
$raw
);
$self
->
response
(
$reqstate
,
$resp
);
return
;
};
warn
$@
if
$@
;
if
(
my
$err
=
$@
)
{
$self
->
error
(
$reqstate
,
501
,
$err
);
}
}
sub
handle_request
{
my
(
$self
,
$reqstate
)
=
@_
;
#print "REQUEST" . Dumper($reqstate->{request});
my
(
$self
,
$reqstate
,
$auth
)
=
@_
;
eval
{
my
$r
=
$reqstate
->
{
request
};
my
$method
=
$r
->
method
();
my
$path
=
$r
->
uri
->
path
();
# print "REQUEST $path\n";
if
(
!
$known_methods
->
{
$method
})
{
my
$resp
=
HTTP::
Response
->
new
(
HTTP_NOT_IMPLEMENTED
,
"
method '
$method
' not available
");
$self
->
response
(
$reqstate
,
$resp
);
return
;
}
if
(
$path
=~
m!/api2!
)
{
$self
->
handle_api2_request
(
$reqstate
);
if
(
$path
=~
m!$baseuri!
)
{
$self
->
handle_api2_request
(
$reqstate
,
$auth
);
return
;
}
if
(
$self
->
{
pages
}
&&
(
$method
eq
'
GET
')
&&
(
my
$handler
=
$self
->
{
pages
}
->
{
$path
}))
{
if
(
ref
(
$handler
)
eq
'
CODE
')
{
my
(
$resp
,
$userid
)
=
&
$handler
(
$self
,
$reqstate
->
{
request
});
my
$params
=
decode_urlencoded
(
$r
->
url
->
query
());
my
(
$resp
,
$userid
)
=
&
$handler
(
$self
,
$reqstate
->
{
request
},
$params
);
$self
->
response
(
$reqstate
,
$resp
);
}
elsif
(
ref
(
$handler
)
eq
'
HASH
')
{
if
(
my
$filename
=
$handler
->
{
file
})
{
...
...
@@ -457,6 +489,157 @@ sub handle_request {
}
}
sub
file_upload_multipart
{
my
(
$self
,
$reqstate
,
$auth
,
$rstate
)
=
@_
;
eval
{
my
$boundary
=
$rstate
->
{
boundary
};
my
$hdl
=
$reqstate
->
{
hdl
};
my
$startlen
=
length
(
$hdl
->
{
rbuf
});
if
(
$rstate
->
{
phase
}
==
0
)
{
# skip everything until start
if
(
$hdl
->
{
rbuf
}
=~
s/^.*?--\Q$boundary\E \015?\012
((?:[^\015]+\015\012)* ) \015?\012//xs
)
{
my
$header
=
$1
;
my
(
$ct
,
$disp
,
$name
,
$filename
);
foreach
my
$line
(
split
(
/\015?\012/
,
$header
))
{
# assume we have single line headers
if
(
$line
=~
m/^Content-Type\s*:\s*(.*)/i
)
{
$ct
=
parse_content_type
(
$1
);
}
elsif
(
$line
=~
m/^Content-Disposition\s*:\s*(.*)/i
)
{
(
$disp
,
$name
,
$filename
)
=
parse_content_disposition
(
$1
);
}
}
if
(
!
(
$disp
&&
$disp
eq
'
form-data
'
&&
$name
))
{
syslog
('
err
',
"
wrong content disposition im multipart - abort upload
");
$rstate
->
{
phase
}
=
-
1
;
}
else
{
$rstate
->
{
fieldname
}
=
$name
;
if
(
!
$ct
)
{
# found form data for field $name
$rstate
->
{
phase
}
=
2
;
}
elsif
(
$ct
&&
$ct
eq
'
application/octet-stream
'
&&
$name
eq
'
filename
'
&&
$filename
)
{
# found file upload data
$rstate
->
{
phase
}
=
1
;
$rstate
->
{
filename
}
=
$filename
;
}
else
{
syslog
('
err
',
"
wrong content type '
$ct
' im multipart - abort upload
");
$rstate
->
{
phase
}
=
-
1
;
}
}
}
else
{
my
$len
=
length
(
$hdl
->
{
rbuf
});
substr
(
$hdl
->
{
rbuf
},
0
,
$len
-
$rstate
->
{
maxheader
},
'')
if
$len
>
$rstate
->
{
maxheader
};
# skip garbage
}
}
elsif
(
$rstate
->
{
phase
}
==
1
)
{
# inside file - dump until end marker
if
(
$hdl
->
{
rbuf
}
=~
s/^(.*?)\015?\012(--\Q$boundary\E(--)? \015?\012(.*))$/$2/xs
)
{
my
(
$rest
,
$eof
)
=
(
$1
,
$3
);
my
$len
=
length
(
$rest
);
die
"
write to temporary file failed - $!
"
if
syswrite
(
$rstate
->
{
outfh
},
$rest
)
!=
$len
;
$rstate
->
{
ctx
}
->
add
(
$rest
);
$rstate
->
{
params
}
->
{
filename
}
=
$rstate
->
{
filename
};
$rstate
->
{
md5sum
}
=
$rstate
->
{
ctx
}
->
hexdigest
;
$rstate
->
{
bytes
}
+=
$len
;
$rstate
->
{
phase
}
=
$eof
?
100
:
0
;
}
else
{
my
$len
=
length
(
$hdl
->
{
rbuf
});
my
$wlen
=
$len
-
$rstate
->
{
boundlen
};
if
(
$wlen
>
0
)
{
my
$data
=
substr
(
$hdl
->
{
rbuf
},
0
,
$wlen
,
'');
die
"
write to temporary file failed - $!
"
if
syswrite
(
$rstate
->
{
outfh
},
$data
)
!=
$wlen
;
$rstate
->
{
bytes
}
+=
$wlen
;
$rstate
->
{
ctx
}
->
add
(
$data
);
}
}
}
elsif
(
$rstate
->
{
phase
}
==
2
)
{
# inside normal field
if
(
$hdl
->
{
rbuf
}
=~
s/^(.*?)\015?\012(--\Q$boundary\E(--)? \015?\012(.*))$/$2/xs
)
{
my
(
$rest
,
$eof
)
=
(
$1
,
$3
);
my
$len
=
length
(
$rest
);
if
(
$len
<
1024
)
{
# fixme: max data size
$rstate
->
{
params
}
->
{
$rstate
->
{
fieldname
}}
=
$rest
;
$rstate
->
{
phase
}
=
$eof
?
100
:
0
;
}
else
{
syslog
('
err
',
"
for data to large - abort upload
");
$rstate
->
{
phase
}
=
-
1
;
# skip
}
}
}
else
{
# skip
my
$len
=
length
(
$hdl
->
{
rbuf
});
substr
(
$hdl
->
{
rbuf
},
0
,
$len
,
'');
# empty rbuf
}
$rstate
->
{
read
}
+=
(
$startlen
-
length
(
$hdl
->
{
rbuf
}));
if
(
!
$rstate
->
{
done
}
&&
(
$rstate
->
{
read
}
+
length
(
$hdl
->
{
rbuf
}))
>=
$rstate
->
{
size
})
{
$rstate
->
{
done
}
=
1
;
# make sure we dont get called twice
if
(
$rstate
->
{
phase
}
<
0
||
!
$rstate
->
{
md5sum
})
{
$self
->
error
(
$reqstate
,
501
,
"
upload failed
");
# fixme: better msg
}
else
{
my
$elapsed
=
tv_interval
(
$rstate
->
{
starttime
});
my
$rate
=
int
(
$rstate
->
{
bytes
}
/
(
$elapsed
*
1024
*
1024
));
syslog
('
info
',
"
multipart upload complete
"
.
"
(size: %d time: %ds rate: %.2fMiB/s)
",
$rstate
->
{
size
},
$elapsed
,
$rate
);
$self
->
handle_api2_request
(
$reqstate
,
$auth
,
$rstate
);
}
}
};
if
(
my
$err
=
$@
)
{
$self
->
error
(
$reqstate
,
501
,
$err
);
}
}
sub
parse_content_type
{
my
(
$ctype
)
=
@_
;
my
(
$ct
,
@params
)
=
split
(
/\s*[;,]\s*/o
,
$ctype
);
foreach
my
$v
(
@params
)
{
if
(
$v
=~
m/^\s*boundary\s*=\s*(\S+?)\s*$/o
)
{
return
wantarray
?
(
$ct
,
$1
)
:
$ct
;
}
}
return
wantarray
?
(
$ct
)
:
$ct
;
}
sub
parse_content_disposition
{
my
(
$line
)
=
@_
;
my
(
$disp
,
@params
)
=
split
(
/\s*[;,]\s*/o
,
$line
);
my
$name
;
my
$filename
;
foreach
my
$v
(
@params
)
{
if
(
$v
=~
m/^\s*name\s*=\s*(\S+?)\s*$/o
)
{
$name
=
$1
;
$name
=~
s/^"(.*)"$/$1/
;
}
elsif
(
$v
=~
m/^\s*filename\s*=\s*(\S+?)\s*$/o
)
{
$filename
=
$1
;
$filename
=~
s/^"(.*)"$/$1/
;
}
}
return
wantarray
?
(
$disp
,
$name
,
$filename
)
:
$disp
;
}
my
$tmpfile_seq_no
=
0
;
sub
get_upload_filename
{
# choose unpredictable tmpfile name
$tmpfile_seq_no
++
;
return
"
/var/tmp/pveupload-
"
.
Digest::MD5::
md5_hex
(
$tmpfile_seq_no
.
time
()
.
$$
);
}
sub
unshift_read_header
{
my
(
$self
,
$reqstate
,
$state
)
=
@_
;
...
...
@@ -471,9 +654,18 @@ sub unshift_read_header {
my
$r
=
$reqstate
->
{
request
};
if
(
$line
eq
'')
{
my
$path
=
$r
->
uri
->
path
();
my
$method
=
$r
->
method
();
$r
->
push_header
(
$state
->
{
key
},
$state
->
{
val
})
if
$state
->
{
key
};
if
(
!
$known_methods
->
{
$method
})
{
my
$resp
=
HTTP::
Response
->
new
(
HTTP_NOT_IMPLEMENTED
,
"
method '
$method
' not available
");
$self
->
response
(
$reqstate
,
$resp
);
return
;
}
my
$conn
=
$r
->
header
('
Connection
');
if
(
$conn
)
{
...
...
@@ -484,9 +676,16 @@ sub unshift_read_header {
}
}
# how much content to read?
my
$te
=
$r
->
header
('
Transfer-Encoding
');
my
$len
=
$r
->
header
('
Content-Length
');
if
(
$te
&&
lc
(
$te
)
eq
'
chunked
')
{
# Handle chunked transfer encoding
$self
->
error
(
$reqstate
,
501
,
"
chunked transfer encoding not supported
");
return
;
}
elsif
(
$te
)
{
$self
->
error
(
$reqstate
,
501
,
"
Unknown transfer encoding '
$te
'
");
return
;
}
my
$pveclientip
=
$r
->
header
('
PVEClientIP
');
# fixme: how can we make PVEClientIP header trusted?
...
...
@@ -496,19 +695,90 @@ sub unshift_read_header {
$r
->
header
('
PVEClientIP
',
$reqstate
->
{
peer_host
});
}
if
(
$te
&&
lc
(
$te
)
eq
'
chunked
')
{
# Handle chunked transfer encoding
$self
->
error
(
$reqstate
,
501
,
"
chunked transfer encoding not supported
");
}
elsif
(
$te
)
{
$self
->
error
(
$reqstate
,
501
,
"
Unknown transfer encoding '
$te
'
");
}
elsif
(
defined
(
$len
))
{
$reqstate
->
{
hdl
}
->
unshift_read
(
chunk
=>
$len
,
sub
{
my
(
$hdl
,
$data
)
=
@_
;
$r
->
content
(
$data
);
$self
->
handle_request
(
$reqstate
);
});
my
$len
=
$r
->
header
('
Content-Length
');
# header processing complete - authenticate now
my
$auth
=
{};
if
(
$path
=~
m!$baseuri!
)
{
my
$token
=
$r
->
header
('
CSRFPreventionToken
');
my
$cookie
=
$r
->
header
('
Cookie
');
my
$ticket
=
PVE::REST::
extract_auth_cookie
(
$cookie
);
my
(
$rel_uri
,
$format
)
=
split_abs_uri
(
$path
);
if
(
!
$format
)
{
$self
->
error
(
$reqstate
,
HTTP_NOT_IMPLEMENTED
,
"
no such uri
");
return
;
}
eval
{
$auth
=
PVE::REST::
auth_handler
(
$self
->
{
rpcenv
},
$reqstate
->
{
peer_host
},
$method
,
$rel_uri
,
$ticket
,
$token
);
};
if
(
my
$err
=
$@
)
{
$self
->
error
(
$reqstate
,
HTTP_UNAUTHORIZED
,
$err
);
return
;
}
}
$reqstate
->
{
log
}
->
{
userid
}
=
$auth
->
{
userid
};
if
(
defined
(
$len
))
{
if
(
!
(
$method
eq
'
PUT
'
||
$method
eq
'
POST
'))
{
$self
->
error
(
$reqstate
,
501
,
"
Unexpected content for method '
$method
'
");
return
;
}
my
$ctype
=
$r
->
header
('
Content-Type
');
my
(
$ct
,
$boundary
)
=
parse_content_type
(
$ctype
);
if
(
$auth
->
{
isUpload
}
&&
!
$self
->
{
trusted_env
})
{
die
"
upload 'Content-Type '
$ctype
' not implemented
\n
"
if
!
(
$boundary
&&
(
$ct
eq
'
multipart/form-data
'));
die
"
upload without content length header not supported
"
if
!
$len
;
die
"
upload without content length header not supported
"
if
!
$len
;
print
"
start upload
$path
$ct
$boundary
\n
"
if
$self
->
{
debug
};
my
$tmpfilename
=
get_upload_filename
();
my
$outfh
=
IO::
File
->
new
(
$tmpfilename
,
O_RDWR
|
O_CREAT
|
O_EXCL
,
0600
)
||
die
"
unable to create temporary upload file '
$tmpfilename
'
";
$reqstate
->
{
keep_alive
}
=
0
;
my
$boundlen
=
length
(
$boundary
)
+
8
;
# \015?\012--$boundary--\015?\012
my
$state
=
{
size
=>
$len
,
boundary
=>
$boundary
,
ctx
=>
Digest::
MD5
->
new
,
boundlen
=>
$boundlen
,
maxheader
=>
2048
+
$boundlen
,
# should be large enough
params
=>
decode_urlencoded
(
$r
->
url
->
query
()),
phase
=>
0
,
read
=>
0
,
starttime
=>
[
gettimeofday
],
outfh
=>
$outfh
,
};
$reqstate
->
{
tmpfilename
}
=
$tmpfilename
;
$reqstate
->
{
hdl
}
->
on_read
(
sub
{
$self
->
file_upload_multipart
(
$reqstate
,
$auth
,
$state
);
});
return
;
}
if
(
!
$ct
||
$ct
eq
'
application/x-www-form-urlencoded
')
{
$reqstate
->
{
hdl
}
->
unshift_read
(
chunk
=>
$len
,
sub
{
my
(
$hdl
,
$data
)
=
@_
;
$r
->
content
(
$data
);
$self
->
handle_request
(
$reqstate
,
$auth
);
});
}
else
{
$self
->
error
(
$reqstate
,
506
,
"
upload 'Content-Type '
$ctype
' not implemented
");
}
}
else
{
$self
->
handle_request
(
$reqstate
);
$self
->
handle_request
(
$reqstate
,
$auth
);
}
}
elsif
(
$line
=~
/^([^:\s]+)\s*:\s*(.*)/
)
{
$r
->
push_header
(
$state
->
{
key
},
$state
->
{
val
})
if
$state
->
{
key
};
...
...
@@ -655,7 +925,7 @@ sub accept_connections {
$reqstate
->
{
hdl
}
=
AnyEvent::
Handle
->
new
(
fh
=>
$clientfh
,
rbuf_max
=>
32768
,
# fixme: set smaller max read buffer ?
rbuf_max
=>
64
*
1024
,
timeout
=>
$self
->
{
timeout
},
linger
=>
0
,
# avoid problems with ssh - really needed ?
on_eof
=>
sub
{
...
...
PVE/REST.pm
View file @
519ea5b0
...
...
@@ -2,6 +2,7 @@ package PVE::REST;
use
warnings
;
use
strict
;
use
English
;
use
PVE::
Cluster
;
use
PVE::
SafeSyslog
;
use
PVE::
Tools
;
...
...
@@ -19,22 +20,8 @@ use URI::Escape;
use
Data::
Dumper
;
# fixme: remove
# my $MaxRequestsPerChild = 200;
my
$cookie_name
=
'
PVEAuthCookie
';
my
$baseuri
=
"
/api2
";
my
$debug_enabled
;
sub
enable_debug
{
$debug_enabled
=
1
;
}
sub
debug_msg
{
return
if
!
$debug_enabled
;
syslog
('
info
',
@_
);
}
sub
extract_auth_cookie
{
my
(
$cookie
)
=
@_
;
...
...
@@ -184,15 +171,14 @@ my $exc_to_res = sub {
return
$resp
;
};
sub
rest
_handler
{
my
(
$rpcenv
,
$clientip
,
$method
,
$
abs_uri
,
$
rel_uri
,
$ticket
,
$token
)
=
@_
;
sub
auth
_handler
{
my
(
$rpcenv
,
$clientip
,
$method
,
$rel_uri
,
$ticket
,
$token
)
=
@_
;
# set environment variables
$rpcenv
->
set_user
(
undef
);
$rpcenv
->
set_language
('
C
');
# fixme:
$rpcenv
->
set_client_ip
(
$clientip
);
my
$euid
=
$>
;
my
$require_auth
=
1
;
# explicitly allow some calls without auth
...
...
@@ -207,58 +193,49 @@ sub rest_handler {
if
(
$require_auth
)
{
eval
{
die
"
No ticket
\n
"
if
!
$ticket
;
die
"
No ticket
\n
"
if
!
$ticket
;
(
$username
,
$age
)
=
PVE::AccessControl::
verify_ticket
(
$ticket
);
(
$username
,
$age
)
=
PVE::AccessControl::
verify_ticket
(
$ticket
);
$rpcenv
->
set_user
(
$username
);
$rpcenv
->
set_user
(
$username
);
if
(
$method
eq
'
POST
'
&&
$rel_uri
=~
m|^/nodes/([^/]+)/storage/([^/]+)/upload$|
)
{
my
(
$node
,
$storeid
)
=
(
$1
,
$2
);
# we disable CSRF checks if $isUpload is set,
# to improve security we check user upload permission here
my
$perm
=
{
check
=>
['
perm
',
"
/storage/
$storeid
",
['
Datastore.AllocateTemplate
']]
};
$rpcenv
->
check_api2_permissions
(
$perm
,
$username
,
{});
$isUpload
=
1
;
}
# we skip CSRF check for file upload, because it is
# difficult to pass CSRF HTTP headers with native html forms,
# and it should not be necessary at all.
PVE::AccessControl::
verify_csrf_prevention_token
(
$username
,
$token
)
if
!
$isUpload
&&
(
$euid
!=
0
)
&&
(
$method
ne
'
GET
');
};
if
(
my
$err
=
$@
)
{
return
&
$exc_to_res
(
$err
,
HTTP_UNAUTHORIZED
);
if
(
$method
eq
'
POST
'
&&
$rel_uri
=~
m|^/nodes/([^/]+)/storage/([^/]+)/upload$|
)
{
my
(
$node
,
$storeid
)
=
(
$1
,
$2
);
# we disable CSRF checks if $isUpload is set,
# to improve security we check user upload permission here
my
$perm
=
{
check
=>
['
perm
',
"
/storage/
$storeid
",
['
Datastore.AllocateTemplate
']]
};
$rpcenv
->
check_api2_permissions
(
$perm
,
$username
,
{});
$isUpload
=
1
;
}
# we skip CSRF check for file upload, because it is
# difficult to pass CSRF HTTP headers with native html forms,
# and it should not be necessary at all.
PVE::AccessControl::
verify_csrf_prevention_token
(
$username
,
$token
)
if
!
$isUpload
&&
(
$EUID
!=
0
)
&&
(
$method
ne
'
GET
');
}
# we are authenticated now
return
{
ticket
=>
$ticket
,
token
=>
$token
,
userid
=>
$username
,
age
=>
$age
,
isUpload
=>
$isUpload
,
};
}
sub
rest_handler
{
my
(
$rpcenv
,
$clientip
,
$method
,
$rel_uri
,
$auth
,
$params
)
=
@_
;
my
$uri_param
=
{};
my
(
$handler
,
$info
)
=
PVE::
API2
->
find_handler
(
$method
,
$rel_uri
,
$uri_param
);
if
(
!
$handler
||
!
$info
)
{
return
{
status
=>
HTTP_NOT_IMPLEMENTED
,
message
=>
"
Method '
$method
$
abs
_uri
' not implemented
",
message
=>
"
Method '
$method
$
rel
_uri
' not implemented
",
};
}
# Note: we need to delay CGI parameter parsing until
# we are authenticated (avoid DOS (file upload) attacs)
my
$params
;
eval
{
$params
=
$rpcenv
->
parse_params
(
$isUpload
);
};
if
(
my
$err
=
$@
)
{
return
{
status
=>
HTTP_BAD_REQUEST
,
message
=>
"
parameter parser failed:
$err
",
};
}
delete
$params
->
{
_dc
};
# remove disable cache parameter
foreach
my
$p
(
keys
%
{
$params
})
{
if
(
defined
(
$uri_param
->
{
$p
}))
{
return
{
...
...
@@ -270,7 +247,7 @@ sub rest_handler {
}
# check access permissions
eval
{
$rpcenv
->
check_api2_permissions
(
$info
->
{
permissions
},
$
username
,
$uri_param
);
};
eval
{
$rpcenv
->
check_api2_permissions
(
$info
->
{
permissions
},
$
auth
->
{
userid
}
,
$uri_param
);
};
if
(
my
$err
=
$@
)
{
return
&
$exc_to_res
(
$err
,
HTTP_FORBIDDEN
);
}
...
...
@@ -283,7 +260,7 @@ sub rest_handler {
die
"
proxy parameter '
$pn
' does not exists
"
if
!
$node
;
if
(
$node
ne
'
localhost
'
&&
$node
ne
PVE::INotify::
nodename
())
{
die
"
unable to proxy file uploads
"
if
$
isUpload
;
die
"
unable to proxy file uploads
"
if
$
auth
->
{
isUpload
}
;
$remip
=
PVE::Cluster::
remote_node_ip
(
$node
);
}
};
...
...
@@ -295,11 +272,7 @@ sub rest_handler {
}
}
if
(
$info
->
{
protected
}
&&
(
$euid
!=
0
))
{
if
(
$isUpload
)
{
my
$uinfo
=
$rpcenv
->
get_upload_info
('
filename
');
$params
->
{
tmpfilename
}
=
$uinfo
->
{
tmpfilename
};
}
if
(
$info
->
{
protected
}
&&
(
$EUID
!=
0
))
{
return
{
proxy
=>
'
localhost
'
,
proxy_params
=>
$params
}
}
...
...
@@ -325,13 +298,4 @@ sub rest_handler {
return
$resp
;
}
sub
split_abs_uri
{
my
(
$abs_uri
)
=
@_
;
my
(
$format
,
$rel_uri
)
=
$abs_uri
=~
m/^\Q$baseuri\E\/+(html|text|json|extjs|png|htmljs)(\/.*)?$/
;
$rel_uri
=
'
/
'
if
!
$rel_uri
;
return
wantarray
?
(
$rel_uri
,
$format
)
:
$rel_uri
;
}
1
;
bin/pveproxy
View file @
519ea5b0
...
...
@@ -165,7 +165,7 @@ exit (0);
# so we must be very careful here
sub get_index {
my ($server, $r, $
param
s) = @_;
my ($server, $r, $
arg
s) = @_;
my $lang = 'en';
my $username;
...
...
@@ -183,8 +183,6 @@ sub get_index {
}
}
my $args = $r->url->query_form_hash();
my $workspace = defined($args->{console}) ?
"PVE.ConsoleWorkspace" : "PVE.StdWorkspace";
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment