Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
CIRCLE
/
cloud
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
94
Merge Requests
10
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
A prog2-höz tartozó friss repo anyagok itt elérhetőek:
https://git.iit.bme.hu/
Commit
5cdd5412
authored
Jan 30, 2013
by
Őry Máté
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
store: basic code formatting
parent
5dbeb820
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
56 additions
and
60 deletions
+56
-60
miscellaneous/store-server/CloudStore.py
+56
-60
No files found.
miscellaneous/store-server/CloudStore.py
View file @
5cdd5412
#!/usr/bin/python
#!/usr/bin/python
#TODO File permission checks
#
TODO File permission checks
from
bottle
import
route
,
run
,
request
,
static_file
,
abort
,
redirect
,
app
from
bottle
import
route
,
run
,
request
,
static_file
,
abort
,
redirect
,
app
import
json
,
os
,
shutil
import
json
,
os
,
shutil
...
@@ -9,23 +9,19 @@ import subprocess
...
@@ -9,23 +9,19 @@ import subprocess
import
ConfigParser
import
ConfigParser
from
pwd
import
getpwnam
from
pwd
import
getpwnam
#Get configuration file
#
Get configuration file
config
=
ConfigParser
.
ConfigParser
()
config
=
ConfigParser
.
ConfigParser
()
config
.
read
(
'/opt/webadmin/cloud/miscellaneous/store-server/store.config'
)
config
.
read
(
'/opt/webadmin/cloud/miscellaneous/store-server/store.config'
)
#ROOT_WWW_FOLDER='/var/www'
ROOT_WWW_FOLDER
=
config
.
get
(
'store'
,
'root_www_folder'
)
ROOT_WWW_FOLDER
=
config
.
get
(
'store'
,
'root_www_folder'
)
#ROOT_BIN_FOLDER='/opt/store-server'
ROOT_BIN_FOLDER
=
config
.
get
(
'store'
,
'root_bin_folder'
)
ROOT_BIN_FOLDER
=
config
.
get
(
'store'
,
'root_bin_folder'
)
#SITE_URL='http://store.cloud.ik.bme.hu:8080'
SITE_URL
=
config
.
get
(
'store'
,
'site_url'
)
SITE_URL
=
config
.
get
(
'store'
,
'site_url'
)
#USER_MANAGER='UserManager.sh'
USER_MANAGER
=
config
.
get
(
'store'
,
'user_manager'
)
USER_MANAGER
=
config
.
get
(
'store'
,
'user_manager'
)
#Standalone server
#
Standalone server
SITE_HOST
=
config
.
get
(
'store'
,
'site_host'
)
SITE_HOST
=
config
.
get
(
'store'
,
'site_host'
)
SITE_PORT
=
config
.
get
(
'store'
,
'site_port'
)
SITE_PORT
=
config
.
get
(
'store'
,
'site_port'
)
#Temporary dir for tar.gz
#
Temporary dir for tar.gz
TEMP_DIR
=
config
.
get
(
'store'
,
'temp_dir'
)
TEMP_DIR
=
config
.
get
(
'store'
,
'temp_dir'
)
...
@@ -38,7 +34,7 @@ def index():
...
@@ -38,7 +34,7 @@ def index():
pass
pass
return
"It works! SSL: "
+
response
return
"It works! SSL: "
+
response
#@route('/<neptun:re:[a-zA-Z0-9]{6}>', method='GET')
#
@route('/<neptun:re:[a-zA-Z0-9]{6}>', method='GET')
@route
(
'/<neptun>'
,
method
=
'GET'
)
@route
(
'/<neptun>'
,
method
=
'GET'
)
def
neptun_GET
(
neptun
):
def
neptun_GET
(
neptun
):
home_path
=
'/home/'
+
neptun
+
'/home'
home_path
=
'/home/'
+
neptun
+
'/home'
...
@@ -50,20 +46,20 @@ def neptun_GET(neptun):
...
@@ -50,20 +46,20 @@ def neptun_GET(neptun):
@route
(
'/<neptun>'
,
method
=
'POST'
)
@route
(
'/<neptun>'
,
method
=
'POST'
)
def
neptun_POST
(
neptun
):
def
neptun_POST
(
neptun
):
#Check if user avaiable (home folder ready)
#
Check if user avaiable (home folder ready)
home_path
=
'/home/'
+
neptun
+
'/home'
home_path
=
'/home/'
+
neptun
+
'/home'
if
os
.
path
.
exists
(
home_path
)
!=
True
:
if
os
.
path
.
exists
(
home_path
)
!=
True
:
abort
(
401
,
'The requested user does not exist!'
)
abort
(
401
,
'The requested user does not exist!'
)
else
:
else
:
#Parse post
#
Parse post
#LISTING
#
LISTING
if
request
.
json
[
'CMD'
]
==
'LIST'
:
if
request
.
json
[
'CMD'
]
==
'LIST'
:
list_path
=
home_path
+
request
.
json
[
'PATH'
]
list_path
=
home_path
+
request
.
json
[
'PATH'
]
if
os
.
path
.
exists
(
list_path
)
!=
True
:
if
os
.
path
.
exists
(
list_path
)
!=
True
:
abort
(
404
,
"Path not found!"
)
abort
(
404
,
"Path not found!"
)
else
:
else
:
return
list_directory
(
home_path
,
list_path
)
return
list_directory
(
home_path
,
list_path
)
#DOWNLOAD LINK GENERATOR
#
DOWNLOAD LINK GENERATOR
elif
request
.
json
[
'CMD'
]
==
'DOWNLOAD'
:
elif
request
.
json
[
'CMD'
]
==
'DOWNLOAD'
:
dl_path
=
home_path
+
'/'
+
request
.
json
[
'PATH'
]
dl_path
=
home_path
+
'/'
+
request
.
json
[
'PATH'
]
dl_path
=
os
.
path
.
realpath
(
dl_path
)
dl_path
=
os
.
path
.
realpath
(
dl_path
)
...
@@ -72,8 +68,8 @@ def neptun_POST(neptun):
...
@@ -72,8 +68,8 @@ def neptun_POST(neptun):
dl_hash
=
str
(
uuid
.
uuid4
())
dl_hash
=
str
(
uuid
.
uuid4
())
if
(
os
.
path
.
isfile
(
dl_path
)
):
if
(
os
.
path
.
isfile
(
dl_path
)
):
os
.
symlink
(
dl_path
,
ROOT_WWW_FOLDER
+
'/'
+
dl_hash
)
os
.
symlink
(
dl_path
,
ROOT_WWW_FOLDER
+
'/'
+
dl_hash
)
#
Debug
#
Debug
#redirect('http://store.cloud.ik.bme.hu:8080/dl/'+dl_hash)
#
redirect('http://store.cloud.ik.bme.hu:8080/dl/'+dl_hash)
return
json
.
dumps
({
'LINK'
:
SITE_URL
+
'/dl/'
+
dl_hash
})
return
json
.
dumps
({
'LINK'
:
SITE_URL
+
'/dl/'
+
dl_hash
})
else
:
else
:
try
:
try
:
...
@@ -87,7 +83,7 @@ def neptun_POST(neptun):
...
@@ -87,7 +83,7 @@ def neptun_POST(neptun):
result
=
subprocess
.
call
([
'/usr/bin/zip'
,
'-rqDj'
,
temp_path
,
dl_path
],
stdout
=
fnull
,
stderr
=
fnull
)
result
=
subprocess
.
call
([
'/usr/bin/zip'
,
'-rqDj'
,
temp_path
,
dl_path
],
stdout
=
fnull
,
stderr
=
fnull
)
os
.
symlink
(
temp_path
,
ROOT_WWW_FOLDER
+
'/'
+
dl_hash
)
os
.
symlink
(
temp_path
,
ROOT_WWW_FOLDER
+
'/'
+
dl_hash
)
return
json
.
dumps
({
'LINK'
:
SITE_URL
+
'/dl/'
+
dl_hash
})
return
json
.
dumps
({
'LINK'
:
SITE_URL
+
'/dl/'
+
dl_hash
})
#UPLOAD
#
UPLOAD
elif
request
.
json
[
'CMD'
]
==
'UPLOAD'
:
elif
request
.
json
[
'CMD'
]
==
'UPLOAD'
:
up_path
=
home_path
+
'/'
+
request
.
json
[
'PATH'
]
up_path
=
home_path
+
'/'
+
request
.
json
[
'PATH'
]
up_path
=
os
.
path
.
realpath
(
up_path
)
up_path
=
os
.
path
.
realpath
(
up_path
)
...
@@ -99,7 +95,7 @@ def neptun_POST(neptun):
...
@@ -99,7 +95,7 @@ def neptun_POST(neptun):
return
json
.
dumps
({
'LINK'
:
SITE_URL
+
'/ul/'
+
up_hash
})
return
json
.
dumps
({
'LINK'
:
SITE_URL
+
'/ul/'
+
up_hash
})
else
:
else
:
abort
(
400
,
'Upload directory not exists!'
)
abort
(
400
,
'Upload directory not exists!'
)
#
MOVE
#
MOVE
elif
request
.
json
[
'CMD'
]
==
'MOVE'
:
elif
request
.
json
[
'CMD'
]
==
'MOVE'
:
src_path
=
home_path
+
'/'
+
request
.
json
[
'SOURCE'
]
src_path
=
home_path
+
'/'
+
request
.
json
[
'SOURCE'
]
dst_path
=
home_path
+
'/'
+
request
.
json
[
'DESTINATION'
]
dst_path
=
home_path
+
'/'
+
request
.
json
[
'DESTINATION'
]
...
@@ -113,9 +109,9 @@ def neptun_POST(neptun):
...
@@ -113,9 +109,9 @@ def neptun_POST(neptun):
shutil
.
move
(
src_path
,
dst_path
)
shutil
.
move
(
src_path
,
dst_path
)
return
return
else
:
else
:
#TODO
#
TODO
abort
(
400
,
"Can not move the file."
)
abort
(
400
,
"Can not move the file."
)
#RENAME
#
RENAME
elif
request
.
json
[
'CMD'
]
==
'RENAME'
:
elif
request
.
json
[
'CMD'
]
==
'RENAME'
:
src_path
=
home_path
+
'/'
+
request
.
json
[
'PATH'
]
src_path
=
home_path
+
'/'
+
request
.
json
[
'PATH'
]
src_path
=
os
.
path
.
realpath
(
src_path
)
src_path
=
os
.
path
.
realpath
(
src_path
)
...
@@ -127,7 +123,7 @@ def neptun_POST(neptun):
...
@@ -127,7 +123,7 @@ def neptun_POST(neptun):
else
:
else
:
abort
(
404
,
"File or Folder not found!"
)
abort
(
404
,
"File or Folder not found!"
)
return
return
#NEW FOLDER
#
NEW FOLDER
elif
request
.
json
[
'CMD'
]
==
'NEW_FOLDER'
:
elif
request
.
json
[
'CMD'
]
==
'NEW_FOLDER'
:
dir_path
=
home_path
+
'/'
+
request
.
json
[
'PATH'
]
dir_path
=
home_path
+
'/'
+
request
.
json
[
'PATH'
]
dir_path
=
os
.
path
.
realpath
(
dir_path
)
dir_path
=
os
.
path
.
realpath
(
dir_path
)
...
@@ -138,7 +134,7 @@ def neptun_POST(neptun):
...
@@ -138,7 +134,7 @@ def neptun_POST(neptun):
else
:
else
:
os
.
mkdir
(
dir_path
,
0755
)
os
.
mkdir
(
dir_path
,
0755
)
return
return
#REMOVE
#
REMOVE
elif
request
.
json
[
'CMD'
]
==
'REMOVE'
:
elif
request
.
json
[
'CMD'
]
==
'REMOVE'
:
remove_path
=
home_path
+
'/'
+
request
.
json
[
'PATH'
]
remove_path
=
home_path
+
'/'
+
request
.
json
[
'PATH'
]
remove_path
=
os
.
path
.
realpath
(
remove_path
)
remove_path
=
os
.
path
.
realpath
(
remove_path
)
...
@@ -166,9 +162,9 @@ def set_keys(neptun):
...
@@ -166,9 +162,9 @@ def set_keys(neptun):
key_list
.
append
(
key
)
key_list
.
append
(
key
)
except
:
except
:
abort
(
400
,
'Wrong syntax!'
)
abort
(
400
,
'Wrong syntax!'
)
result
=
subprocess
.
call
([
ROOT_BIN_FOLDER
+
'/'
+
USER_MANAGER
,
'set'
,
neptun
,
smbpasswd
])
result
=
subprocess
.
call
([
ROOT_BIN_FOLDER
+
'/'
+
USER_MANAGER
,
'set'
,
neptun
,
smbpasswd
])
if
result
==
0
:
if
result
==
0
:
updateSSHAuthorizedKeys
(
neptun
,
key_list
)
updateSSHAuthorizedKeys
(
neptun
,
key_list
)
return
return
elif
result
==
2
:
elif
result
==
2
:
abort
(
403
,
'User does not exist!'
)
abort
(
403
,
'User does not exist!'
)
...
@@ -182,15 +178,15 @@ def new_user(neptun):
...
@@ -182,15 +178,15 @@ def new_user(neptun):
smbpasswd
=
request
.
json
[
'SMBPASSWD'
]
smbpasswd
=
request
.
json
[
'SMBPASSWD'
]
except
:
except
:
abort
(
400
,
'Invalid syntax'
)
abort
(
400
,
'Invalid syntax'
)
#Call user creator script
#
Call user creator script
result
=
subprocess
.
call
([
ROOT_BIN_FOLDER
+
'/'
+
USER_MANAGER
,
'add'
,
neptun
,
smbpasswd
])
result
=
subprocess
.
call
([
ROOT_BIN_FOLDER
+
'/'
+
USER_MANAGER
,
'add'
,
neptun
,
smbpasswd
])
if
result
==
0
:
if
result
==
0
:
try
:
try
:
for
key
in
request
.
json
[
'KEYS'
]:
for
key
in
request
.
json
[
'KEYS'
]:
key_list
.
append
(
key
)
key_list
.
append
(
key
)
updateSSHAuthorizedKeys
(
neptun
,
key_list
)
updateSSHAuthorizedKeys
(
neptun
,
key_list
)
except
:
except
:
abort
(
400
,
'SSH'
)
abort
(
400
,
'SSH'
)
return
return
elif
result
==
2
:
elif
result
==
2
:
abort
(
403
,
'User already exist!'
)
abort
(
403
,
'User already exist!'
)
...
@@ -199,7 +195,7 @@ def new_user(neptun):
...
@@ -199,7 +195,7 @@ def new_user(neptun):
#Static file
#
Static file
@route
(
'/dl/<hash_num>'
,
method
=
'GET'
)
@route
(
'/dl/<hash_num>'
,
method
=
'GET'
)
def
dl_hash
(
hash_num
):
def
dl_hash
(
hash_num
):
hash_path
=
ROOT_WWW_FOLDER
hash_path
=
ROOT_WWW_FOLDER
...
@@ -207,11 +203,11 @@ def dl_hash(hash_num):
...
@@ -207,11 +203,11 @@ def dl_hash(hash_num):
abort
(
404
,
"File not found!"
)
abort
(
404
,
"File not found!"
)
else
:
else
:
filename
=
os
.
path
.
basename
(
os
.
path
.
realpath
(
hash_path
+
'/'
+
hash_num
))
filename
=
os
.
path
.
basename
(
os
.
path
.
realpath
(
hash_path
+
'/'
+
hash_num
))
return
static_file
(
hash_num
,
root
=
hash_path
,
download
=
filename
)
return
static_file
(
hash_num
,
root
=
hash_path
,
download
=
filename
)
@route
(
'/ul/<hash_num>'
,
method
=
'POST'
)
@route
(
'/ul/<hash_num>'
,
method
=
'POST'
)
def
upload
(
hash_num
):
def
upload
(
hash_num
):
if
not
os
.
path
.
exists
(
ROOT_WWW_FOLDER
+
'/'
+
hash_num
):
if
not
os
.
path
.
exists
(
ROOT_WWW_FOLDER
+
'/'
+
hash_num
):
abort
(
404
,
'Token not found!'
)
abort
(
404
,
'Token not found!'
)
try
:
try
:
file_data
=
request
.
files
.
data
file_data
=
request
.
files
.
data
file_name
=
file_data
.
filename
file_name
=
file_data
.
filename
...
@@ -222,79 +218,79 @@ def upload(hash_num):
...
@@ -222,79 +218,79 @@ def upload(hash_num):
up_path
=
os
.
path
.
realpath
(
ROOT_WWW_FOLDER
+
'/'
+
hash_num
+
'/'
+
file_name
)
up_path
=
os
.
path
.
realpath
(
ROOT_WWW_FOLDER
+
'/'
+
hash_num
+
'/'
+
file_name
)
if
os
.
path
.
exists
(
up_path
):
if
os
.
path
.
exists
(
up_path
):
abort
(
400
,
'File already exists'
)
abort
(
400
,
'File already exists'
)
#Check if upload path valid
#
Check if upload path valid
if
not
up_path
.
startswith
(
'/home'
):
if
not
up_path
.
startswith
(
'/home'
):
abort
(
400
,
'Invalid path.'
)
abort
(
400
,
'Invalid path.'
)
os
.
remove
(
ROOT_WWW_FOLDER
+
'/'
+
hash_num
)
os
.
remove
(
ROOT_WWW_FOLDER
+
'/'
+
hash_num
)
#Get the real upload path
#
Get the real upload path
#Delete the hash link
#
Delete the hash link
#Get the username from path for proper ownership
#
Get the username from path for proper ownership
username
=
up_path
.
split
(
'/'
,
3
)[
2
]
username
=
up_path
.
split
(
'/'
,
3
)[
2
]
#os.setegid(getpwnam(username).pw_gid)
#
os.setegid(getpwnam(username).pw_gid)
#os.seteuid(getpwnam(username).pw_uid)
#
os.seteuid(getpwnam(username).pw_uid)
#TODO setuid subcommand
#
TODO setuid subcommand
#Check if file exist (root can overwrite anything not safe)
#
Check if file exist (root can overwrite anything not safe)
f
=
open
(
up_path
,
'wb'
)
f
=
open
(
up_path
,
'wb'
)
datalength
=
0
datalength
=
0
for
chunk
in
fbuffer
(
file_data
.
file
):
for
chunk
in
fbuffer
(
file_data
.
file
):
f
.
write
(
chunk
)
f
.
write
(
chunk
)
datalength
+=
len
(
chunk
)
datalength
+=
len
(
chunk
)
f
.
close
()
f
.
close
()
os
.
chown
(
up_path
,
getpwnam
(
username
)
.
pw_uid
,
getpwnam
(
username
)
.
pw_gid
)
os
.
chown
(
up_path
,
getpwnam
(
username
)
.
pw_uid
,
getpwnam
(
username
)
.
pw_gid
)
os
.
chmod
(
up_path
,
0644
)
os
.
chmod
(
up_path
,
0644
)
return
'Upload finished: '
+
file_name
+
' - '
+
str
(
datalength
)
+
' Byte'
return
'Upload finished: '
+
file_name
+
' - '
+
str
(
datalength
)
+
' Byte'
#Define filebuffer for big uploads
#
Define filebuffer for big uploads
def
fbuffer
(
f
,
chunk_size
=
4096
):
def
fbuffer
(
f
,
chunk_size
=
4096
):
while
True
:
while
True
:
chunk
=
f
.
read
(
chunk_size
)
chunk
=
f
.
read
(
chunk_size
)
if
not
chunk
:
break
if
not
chunk
:
break
yield
chunk
yield
chunk
#Update users .ssh/authorized_keys
#
Update users .ssh/authorized_keys
def
updateSSHAuthorizedKeys
(
username
,
key_list
):
def
updateSSHAuthorizedKeys
(
username
,
key_list
):
user_home_ssh
=
'/home/'
+
username
+
'/home/.ssh'
user_home_ssh
=
'/home/'
+
username
+
'/home/.ssh'
user_uid
=
getpwnam
(
username
)
.
pw_uid
user_uid
=
getpwnam
(
username
)
.
pw_uid
user_gid
=
getpwnam
(
username
)
.
pw_gid
user_gid
=
getpwnam
(
username
)
.
pw_gid
if
not
os
.
path
.
exists
(
user_home_ssh
):
if
not
os
.
path
.
exists
(
user_home_ssh
):
os
.
mkdir
(
user_home_ssh
,
0700
)
os
.
mkdir
(
user_home_ssh
,
0700
)
os
.
chown
(
user_home_ssh
,
user_uid
,
user_gid
)
os
.
chown
(
user_home_ssh
,
user_uid
,
user_gid
)
auth_file_name
=
user_home_ssh
+
'/authorized_keys'
auth_file_name
=
user_home_ssh
+
'/authorized_keys'
auth_file
=
open
(
auth_file_name
,
'w'
)
auth_file
=
open
(
auth_file_name
,
'w'
)
for
key
in
key_list
:
for
key
in
key_list
:
auth_file
.
write
(
key
+
'
\n
'
)
auth_file
.
write
(
key
+
'
\n
'
)
auth_file
.
close
()
auth_file
.
close
()
os
.
chmod
(
auth_file_name
,
0600
)
os
.
chmod
(
auth_file_name
,
0600
)
os
.
chown
(
auth_file_name
,
user_uid
,
user_gid
)
os
.
chown
(
auth_file_name
,
user_uid
,
user_gid
)
return
return
#For debug purpose
#
For debug purpose
#@route('/ul/<hash_num>', method='GET')
#
@route('/ul/<hash_num>', method='GET')
#def upload_get(hash_num):
#
def upload_get(hash_num):
# return """<form method="POST" action="/ul/{hash}" enctype="multipart/form-data">
# return """<form method="POST" action="/ul/{hash}" enctype="multipart/form-data">
# <input name="data" type="file" />
# <input name="data" type="file" />
# <input type="submit" />
# <input type="submit" />
#</form>""".format(hash=hash_num)
#
</form>""".format(hash=hash_num)
def
list_directory
(
home
,
path
):
def
list_directory
(
home
,
path
):
#Check for path breakout
#
Check for path breakout
if
not
os
.
path
.
realpath
(
path
)
.
startswith
(
home
):
if
not
os
.
path
.
realpath
(
path
)
.
startswith
(
home
):
abort
(
400
,
'Invalid path.'
)
abort
(
400
,
'Invalid path.'
)
#Check if path exist
#
Check if path exist
if
os
.
path
.
exists
(
path
)
!=
True
:
if
os
.
path
.
exists
(
path
)
!=
True
:
abort
(
404
,
'No such file or directory'
)
abort
(
404
,
'No such file or directory'
)
else
:
else
:
#If it's a file return with list
#
If it's a file return with list
if
os
.
path
.
isdir
(
path
)
!=
True
:
if
os
.
path
.
isdir
(
path
)
!=
True
:
return
json
.
dumps
((
os
.
path
.
basename
(
path
),
'F'
,
os
.
path
.
getsize
(
path
),
os
.
path
.
getmtime
(
path
)))
return
json
.
dumps
((
os
.
path
.
basename
(
path
),
'F'
,
os
.
path
.
getsize
(
path
),
os
.
path
.
getmtime
(
path
)))
#List directory and return list
#
List directory and return list
else
:
else
:
tuplelist
=
[]
tuplelist
=
[]
filelist
=
os
.
listdir
(
path
)
filelist
=
os
.
listdir
(
path
)
#Add type support
#
Add type support
for
item
in
filelist
:
for
item
in
filelist
:
static_route
=
path
+
"/"
+
item
static_route
=
path
+
"/"
+
item
if
os
.
path
.
isdir
(
static_route
):
if
os
.
path
.
isdir
(
static_route
):
...
@@ -306,7 +302,7 @@ def list_directory(home,path):
...
@@ -306,7 +302,7 @@ def list_directory(home,path):
return
json
.
dumps
(
tuplelist
)
return
json
.
dumps
(
tuplelist
)
def
getQuotaStatus
(
neptun
):
def
getQuotaStatus
(
neptun
):
output
=
subprocess
.
check_output
([
ROOT_BIN_FOLDER
+
'/'
+
USER_MANAGER
,
'status'
,
neptun
],
stderr
=
subprocess
.
STDOUT
)
output
=
subprocess
.
check_output
([
ROOT_BIN_FOLDER
+
'/'
+
USER_MANAGER
,
'status'
,
neptun
],
stderr
=
subprocess
.
STDOUT
)
return
output
.
split
()
return
output
.
split
()
if
__name__
==
"__main__"
:
if
__name__
==
"__main__"
:
...
...
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