Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
slapos
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Boxiang Sun
slapos
Commits
2a54030b
Commit
2a54030b
authored
Jan 24, 2025
by
Xavier Thompson
Browse files
Options
Browse Files
Download
Plain Diff
simplehttpserver: Allow disabling write access
See merge request
!1685
parents
5c1234dc
658becec
Changes
3
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
342 additions
and
135 deletions
+342
-135
slapos/recipe/simplehttpserver/__init__.py
slapos/recipe/simplehttpserver/__init__.py
+22
-31
slapos/recipe/simplehttpserver/simplehttpserver.py
slapos/recipe/simplehttpserver/simplehttpserver.py
+94
-62
slapos/test/recipe/test_simplehttpserver.py
slapos/test/recipe/test_simplehttpserver.py
+226
-42
No files found.
slapos/recipe/simplehttpserver/__init__.py
View file @
2a54030b
...
...
@@ -29,46 +29,37 @@ import string, random
import
os
from
six.moves
import
range
class
Recipe
(
GenericBaseRecipe
):
from
zc.buildout
import
UserError
from
zc.buildout.buildout
import
bool_option
def
__init__
(
self
,
buildout
,
name
,
options
):
base_path
=
options
[
'base-path'
]
if
options
.
get
(
'use-hash-url'
,
'True'
)
in
[
'true'
,
'True'
]:
pool
=
string
.
ascii_letters
+
string
.
digits
hash_string
=
''
.
join
(
random
.
choice
(
pool
)
for
i
in
range
(
64
))
path
=
os
.
path
.
join
(
base_path
,
hash_string
)
if
os
.
path
.
exists
(
base_path
):
path_list
=
os
.
listdir
(
base_path
)
if
len
(
path_list
)
==
1
:
hash_string
=
path_list
[
0
]
path
=
os
.
path
.
join
(
base_path
,
hash_string
)
elif
len
(
path_list
)
>
1
:
raise
ValueError
(
"Folder %s should contain 0 or 1 element."
%
base_path
)
options
[
'root-dir'
]
=
path
options
[
'path'
]
=
hash_string
else
:
options
[
'root-dir'
]
=
base_path
options
[
'path'
]
=
''
return
GenericBaseRecipe
.
__init__
(
self
,
buildout
,
name
,
options
)
def
issubpathof
(
subpath
,
path
):
subpath
=
os
.
path
.
abspath
(
subpath
)
path
=
os
.
path
.
abspath
(
path
)
relpath
=
os
.
path
.
relpath
(
subpath
,
start
=
path
)
return
not
relpath
.
startswith
(
os
.
pardir
)
def
install
(
self
):
class
Recipe
(
GenericBaseRecipe
):
def
__init__
(
self
,
buildout
,
name
,
options
):
host
,
port
,
socketpath
,
abstract
=
(
options
.
get
(
k
)
for
k
in
(
'host'
,
'port'
,
'socketpath'
,
'abstract'
))
oneof
=
host
,
socketpath
,
abstract
if
sum
(
bool
(
v
)
for
v
in
oneof
)
!=
1
or
bool
(
host
)
!=
bool
(
port
):
raise
UserError
(
"Specify one of (host, port) | socketpath | abstract"
)
address
=
(
host
,
int
(
port
))
if
host
else
socketpath
or
'
\
0
'
+
abstract
options
[
'address'
]
=
address
return
GenericBaseRecipe
.
__init__
(
self
,
buildout
,
name
,
options
)
if
not
os
.
path
.
exists
(
self
.
options
[
'root-dir'
]):
os
.
mkdir
(
self
.
options
[
'root-dir'
]
)
def
install
(
self
):
parameters
=
{
'host'
:
self
.
options
[
'host'
],
'port'
:
int
(
self
.
options
[
'port'
]),
'address'
:
self
.
options
[
'address'
],
'cwd'
:
self
.
options
[
'base-path'
],
'log-file'
:
self
.
options
[
'log-file'
],
'cert-file'
:
self
.
options
.
get
(
'cert-file'
,
''
),
'key-file'
:
self
.
options
.
get
(
'key-file'
,
''
),
'
root-dir'
:
self
.
options
[
'root-dir'
]
'cert-file'
:
self
.
options
.
get
(
'cert-file'
),
'key-file'
:
self
.
options
.
get
(
'key-file'
),
'
allow-write'
:
bool_option
(
self
.
options
,
'allow-write'
,
'false'
)
}
return
self
.
createPythonScript
(
self
.
options
[
'wrapper'
].
strip
(),
__name__
+
'.simplehttpserver.run'
,
...
...
slapos/recipe/simplehttpserver/simplehttpserver.py
View file @
2a54030b
# -*- coding: utf-8 -*-
from
six.moves.SimpleHTTPServer
import
SimpleHTTPRequestHandler
from
six.moves.BaseHTTPServer
import
HTTPServer
import
ssl
import
os
from
six.moves.socketserver
import
TCPServer
import
cgi
import
contextlib
import
errno
import
logging
from
netaddr
import
valid_ipv4
,
valid_ipv6
import
os
import
ssl
import
socket
import
cgi
,
errno
from
slapos.util
import
str2bytes
from
.
import
issubpathof
class
ServerHandler
(
SimpleHTTPRequestHandler
):
base_path
=
None
# set by run
restrict_write
=
True
# set by run
_additional_logs
=
None
@
contextlib
.
contextmanager
def
_log_extra
(
self
,
msg
):
self
.
_additional_logs
=
msg
try
:
yield
finally
:
self
.
_additional_logs
=
None
def
_log
(
self
,
level
,
msg
,
*
args
):
if
self
.
_additional_logs
:
msg
+=
self
.
_additional_logs
logging
.
log
(
level
,
'%s - - '
+
msg
,
self
.
client_address
[
0
],
*
args
)
def
log_message
(
self
,
msg
,
*
args
):
self
.
_log
(
logging
.
INFO
,
msg
,
*
args
)
def
log_error
(
self
,
msg
,
*
args
):
self
.
_log
(
logging
.
ERROR
,
msg
,
*
args
)
document_path
=
''
restrict_root_folder
=
True
def
log_request
(
self
,
*
args
):
with
self
.
_log_extra
(
'
\
n
'
+
str
(
self
.
headers
)):
SimpleHTTPRequestHandler
.
log_request
(
self
,
*
args
)
def
respond
(
self
,
code
=
200
,
type
=
'text/html'
):
self
.
send_response
(
code
)
self
.
send_header
(
"Content-type"
,
type
)
self
.
end_headers
()
def
restricted
Root
Access
(
self
):
if
self
.
restrict_
root_folder
and
self
.
path
and
self
.
path
==
'/'
:
# no
access to root path
def
restricted
Write
Access
(
self
):
if
self
.
restrict_
write
and
self
.
command
not
in
(
'GET'
,
'HEAD'
)
:
# no
write access
self
.
respond
(
403
)
self
.
wfile
.
write
(
b"Forbidden"
)
return
True
return
False
def
do_GET
(
self
):
logging
.
info
(
'%s - GET: %s
\
n
%s'
%
(
self
.
client_address
[
0
],
self
.
path
,
self
.
headers
))
if
self
.
restrictedRootAccess
():
return
SimpleHTTPRequestHandler
.
do_GET
(
self
)
def
do_POST
(
self
):
"""Write to a file on the server.
...
...
@@ -45,8 +66,7 @@ class ServerHandler(SimpleHTTPRequestHandler):
request can be encoded as application/x-www-form-urlencoded or multipart/form-data
"""
logging
.
info
(
'%s - POST: %s
\
n
%s'
%
(
self
.
client_address
[
0
],
self
.
path
,
self
.
headers
))
if
self
.
restrictedRootAccess
():
if
self
.
restrictedWriteAccess
():
return
form
=
cgi
.
FieldStorage
(
...
...
@@ -67,64 +87,76 @@ class ServerHandler(SimpleHTTPRequestHandler):
file_open_mode
=
'wb'
if
(
'clear'
in
form
and
form
[
'clear'
].
value
in
(
'1'
,
b'1'
))
else
'ab'
self
.
writeFile
(
file_path
,
file_content
,
file_open_mode
)
self
.
respond
(
200
,
type
=
self
.
headers
[
'Content-Type'
])
self
.
wfile
.
write
(
b"Content written to %s"
%
str2bytes
(
file_path
))
def
writeFile
(
self
,
filename
,
content
,
method
=
'ab'
):
file_path
=
os
.
path
.
abspath
(
os
.
path
.
join
(
self
.
document_path
,
filename
))
if
not
file_path
.
startswith
(
self
.
document_path
):
file_path
=
os
.
path
.
abspath
(
os
.
path
.
join
(
self
.
base_path
,
filename
))
# Check writing there is allowed
if
not
issubpathof
(
file_path
,
self
.
base_path
):
self
.
respond
(
403
,
'text/plain'
)
self
.
wfile
.
write
(
b"Forbidden"
)
return
# Create missing directories if needed
try
:
os
.
makedirs
(
os
.
path
.
dirname
(
file_path
))
except
OSError
as
exception
:
if
exception
.
errno
!=
errno
.
EEXIST
:
logging
.
error
(
'Failed to create file in %s. The error is
\
n
%s'
%
(
file_path
,
str
(
exception
))
)
logging
.
info
(
'Writing recieved content to file %s'
%
file_path
)
self
.
log_error
(
'Failed to create file in %s. The error is
\
n
%s'
,
file_path
,
exception
)
# Write content to file
self
.
log_message
(
'Writing received content to file %s'
,
file_path
)
try
:
with
open
(
file_path
,
method
)
as
myfile
:
myfile
.
write
(
content
)
logging
.
info
(
'Done.'
)
self
.
log_message
(
'Done.'
)
except
IOError
as
e
:
logging
.
error
(
'Something happened while processing
\
'
writeFile
\
'
. The message is %s'
%
str
(
e
))
class
HTTPServerV6
(
HTTPServer
):
address_family
=
socket
.
AF_INET6
self
.
log_error
(
'Something happened while processing
\
'
writeFile
\
'
. The message is %s'
,
e
)
self
.
respond
(
200
,
type
=
self
.
headers
[
'Content-Type'
])
self
.
wfile
.
write
(
b"Content written to %s"
%
str2bytes
(
filename
))
def
run
(
args
):
# minimal web server. serves files relative to the
# current directory.
logging
.
basicConfig
(
format
=
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
,
filename
=
args
[
'log-file'
]
,
level
=
logging
.
INFO
)
port
=
args
[
'port'
]
host
=
args
[
'host'
]
os
.
chdir
(
args
[
'cwd'
])
# minimal web server. serves files relative to the current directory.
logging
.
basicConfig
(
format
=
"%(asctime)s %(levelname)s - %(message)s"
,
filename
=
args
[
'log-file'
],
level
=
logging
.
INFO
)
address
=
args
[
'address'
]
cwd
=
args
[
'cwd'
]
os
.
chdir
(
cwd
)
Handler
=
ServerHandler
Handler
.
document_path
=
args
[
'root-dir'
]
Handler
.
restrict_root_folder
=
(
args
[
'root-dir'
]
!=
args
[
'cwd'
])
if
valid_ipv6
(
host
):
server
=
HTTPServerV6
else
:
server
=
HTTPServer
httpd
=
server
((
host
,
port
),
Handler
)
scheme
=
'http'
if
'cert-file'
in
args
and
'key-file'
in
args
and
\
os
.
path
.
exists
(
args
[
'cert-file'
])
and
os
.
path
.
exists
(
args
[
'key-file'
]):
scheme
=
'https'
httpd
.
socket
=
ssl
.
wrap_socket
(
httpd
.
socket
,
server_side
=
True
,
certfile
=
args
[
'cert-file'
],
keyfile
=
args
[
'key-file'
])
logging
.
info
(
"Starting simple http server at %s://%s:%s"
%
(
scheme
,
host
,
port
))
Handler
.
base_path
=
cwd
Handler
.
restrict_write
=
not
args
[
'allow-write'
]
try
:
host
,
port
=
address
family
,
_
,
_
,
_
,
_
=
socket
.
getaddrinfo
(
host
,
port
)[
0
]
except
ValueError
:
family
=
socket
.
AF_UNIX
class
Server
(
TCPServer
):
allow_reuse_address
=
1
# for tests, HTTPServer in stdlib sets it too
address_family
=
family
httpd
=
Server
(
address
,
Handler
)
certfile
=
args
[
'cert-file'
]
if
certfile
:
# keyfile == None signifies key is in certfile
PROTOCOL_TLS_SERVER
=
getattr
(
ssl
,
'PROTOCOL_TLS_SERVER'
,
None
)
if
PROTOCOL_TLS_SERVER
:
sslcontext
=
ssl
.
SSLContext
(
PROTOCOL_TLS_SERVER
)
sslcontext
.
load_cert_chain
(
certfile
,
args
[
'key-file'
])
httpd
.
socket
=
sslcontext
.
wrap_socket
(
httpd
.
socket
,
server_side
=
True
)
else
:
# BBB Py2, Py<3.6
httpd
.
socket
=
ssl
.
wrap_socket
(
httpd
.
socket
,
server_side
=
True
,
certfile
=
certfile
,
keyfile
=
args
[
'key-file'
])
logging
.
info
(
"Starting simple http server at %s"
,
address
)
httpd
.
serve_forever
()
slapos/test/recipe/test_simplehttpserver.py
View file @
2a54030b
This diff is collapsed.
Click to expand it.
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