Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
M
mongoose
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
esp
mongoose
Commits
70154f6c
Commit
70154f6c
authored
Oct 01, 2013
by
Sergey Lyubka
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Moved mod_lua.c to src/lua.c
parent
d3577b78
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
409 additions
and
15 deletions
+409
-15
Makefile
build/Makefile
+3
-3
internal.h
build/src/internal.h
+6
-0
lua.c
build/src/lua.c
+3
-2
mongoose.c
build/src/mongoose.c
+0
-4
mongoose.c
mongoose.c
+395
-4
unit_test.c
test/unit_test.c
+2
-2
No files found.
build/Makefile
View file @
70154f6c
...
...
@@ -21,13 +21,13 @@
PROG
=
mongoose
EXE_SUFFIX
=
CFLAGS
=
-std
=
c99
-O2
-W
-Wall
-pedantic
-pthread
-pipe
-I
..
$(CFLAGS_EXTRA)
CFLAGS
=
-std
=
c99
-O2
-W
-Wall
-pedantic
-pthread
-pipe
-I
.
-I
.
.
$(CFLAGS_EXTRA)
VERSION
=
$(
shell
perl
-lne
\
'print $$1 if /define\s+MONGOOSE_VERSION\s+"(\S+
)
"/'
../mongoose.c
)
# The order in which files are listed is important
SOURCES
=
src/internal.h src/string.c src/parse_date.c src/options.c
\
src/crypto.c src/auth.c src/mongoose.c
src/crypto.c src/auth.c src/mongoose.c
src/lua.c
TINY_SOURCES
=
../mongoose.c main.c
LUA_SOURCES
=
$(TINY_SOURCES)
sqlite3.c lsqlite3.c lua_5.2.1.c
...
...
@@ -60,7 +60,7 @@ endif
all
:
@
echo
"make (unix|windows|macos)"
../mongoose.c
:
mod_lua.c
../mongoose.h Makefile $(SOURCES)
../mongoose.c
:
../mongoose.h Makefile $(SOURCES)
cat
$(SOURCES)
|
sed
'/#include "internal.h"/d'
>
$@
unix_unit_test
:
$(LUA_SOURCES) Makefile ../test/unit_test.c
...
...
build/src/internal.h
View file @
70154f6c
...
...
@@ -460,3 +460,9 @@ struct de {
static
FILE
*
mg_fopen
(
const
char
*
path
,
const
char
*
mode
);
static
int
mg_stat
(
const
char
*
path
,
struct
file
*
filep
);
#ifdef USE_LUA
#include "lua_5.2.1.h"
static
int
handle_lsp_request
(
struct
mg_connection
*
,
const
char
*
,
struct
file
*
,
struct
lua_State
*
);
#endif
build/
mod_
lua.c
→
build/
src/
lua.c
View file @
70154f6c
#include "
lua_5.2.1
.h"
#include "
internal
.h"
#ifdef USE_LUA
#ifdef _WIN32
static
void
*
mmap
(
void
*
addr
,
int64_t
len
,
int
prot
,
int
flags
,
int
fd
,
int
offset
)
{
...
...
@@ -20,7 +21,6 @@ static void *mmap(void *addr, int64_t len, int prot, int flags, int fd,
static
const
char
*
LUASOCKET
=
"luasocket"
;
// Forward declarations
static
void
handle_request
(
struct
mg_connection
*
);
static
int
handle_lsp_request
(
struct
mg_connection
*
,
const
char
*
,
struct
file
*
,
struct
lua_State
*
);
...
...
@@ -387,3 +387,4 @@ static int handle_lsp_request(struct mg_connection *conn, const char *path,
return
error
;
}
#endif // USE_LUA
build/src/mongoose.c
View file @
70154f6c
...
...
@@ -2842,10 +2842,6 @@ static uint32_t get_remote_ip(const struct mg_connection *conn) {
return
ntohl
(
*
(
uint32_t
*
)
&
conn
->
client
.
rsa
.
sin
.
sin_addr
);
}
#ifdef USE_LUA
#include "build/mod_lua.c"
#endif // USE_LUA
FILE
*
mg_upload
(
struct
mg_connection
*
conn
,
const
char
*
destination_dir
,
char
*
path
,
int
path_len
)
{
const
char
*
content_type_header
,
*
boundary_start
;
...
...
mongoose.c
View file @
70154f6c
...
...
@@ -461,6 +461,12 @@ struct de {
static
FILE
*
mg_fopen
(
const
char
*
path
,
const
char
*
mode
);
static
int
mg_stat
(
const
char
*
path
,
struct
file
*
filep
);
#ifdef USE_LUA
#include "lua_5.2.1.h"
static
int
handle_lsp_request
(
struct
mg_connection
*
,
const
char
*
,
struct
file
*
,
struct
lua_State
*
);
#endif
static
void
mg_strlcpy
(
register
char
*
dst
,
register
const
char
*
src
,
size_t
n
)
{
for
(;
*
src
!=
'\0'
&&
n
>
1
;
n
--
)
{
*
dst
++
=
*
src
++
;
...
...
@@ -4147,10 +4153,6 @@ static uint32_t get_remote_ip(const struct mg_connection *conn) {
return
ntohl
(
*
(
uint32_t
*
)
&
conn
->
client
.
rsa
.
sin
.
sin_addr
);
}
#ifdef USE_LUA
#include "build/mod_lua.c"
#endif // USE_LUA
FILE
*
mg_upload
(
struct
mg_connection
*
conn
,
const
char
*
destination_dir
,
char
*
path
,
int
path_len
)
{
const
char
*
content_type_header
,
*
boundary_start
;
...
...
@@ -5390,3 +5392,392 @@ struct mg_context *mg_start(const char **options,
return
ctx
;
}
#ifdef USE_LUA
#ifdef _WIN32
static
void
*
mmap
(
void
*
addr
,
int64_t
len
,
int
prot
,
int
flags
,
int
fd
,
int
offset
)
{
HANDLE
fh
=
(
HANDLE
)
_get_osfhandle
(
fd
);
HANDLE
mh
=
CreateFileMapping
(
fh
,
0
,
PAGE_READONLY
,
0
,
0
,
0
);
void
*
p
=
MapViewOfFile
(
mh
,
FILE_MAP_READ
,
0
,
0
,
(
size_t
)
len
);
CloseHandle
(
mh
);
return
p
;
}
#define munmap(x, y) UnmapViewOfFile(x)
#define MAP_FAILED NULL
#define MAP_PRIVATE 0
#define PROT_READ 0
#else
#include <sys/mman.h>
#endif
static
const
char
*
LUASOCKET
=
"luasocket"
;
// Forward declarations
static
int
handle_lsp_request
(
struct
mg_connection
*
,
const
char
*
,
struct
file
*
,
struct
lua_State
*
);
static
void
reg_string
(
struct
lua_State
*
L
,
const
char
*
name
,
const
char
*
val
)
{
lua_pushstring
(
L
,
name
);
lua_pushstring
(
L
,
val
);
lua_rawset
(
L
,
-
3
);
}
static
void
reg_int
(
struct
lua_State
*
L
,
const
char
*
name
,
int
val
)
{
lua_pushstring
(
L
,
name
);
lua_pushinteger
(
L
,
val
);
lua_rawset
(
L
,
-
3
);
}
static
void
reg_function
(
struct
lua_State
*
L
,
const
char
*
name
,
lua_CFunction
func
,
struct
mg_connection
*
conn
)
{
lua_pushstring
(
L
,
name
);
lua_pushlightuserdata
(
L
,
conn
);
lua_pushcclosure
(
L
,
func
,
1
);
lua_rawset
(
L
,
-
3
);
}
static
int
lsp_sock_close
(
lua_State
*
L
)
{
if
(
lua_gettop
(
L
)
>
0
&&
lua_istable
(
L
,
-
1
))
{
lua_getfield
(
L
,
-
1
,
"sock"
);
closesocket
((
SOCKET
)
lua_tonumber
(
L
,
-
1
));
}
else
{
return
luaL_error
(
L
,
"invalid :close() call"
);
}
return
1
;
}
static
int
lsp_sock_recv
(
lua_State
*
L
)
{
char
buf
[
2000
];
int
n
;
if
(
lua_gettop
(
L
)
>
0
&&
lua_istable
(
L
,
-
1
))
{
lua_getfield
(
L
,
-
1
,
"sock"
);
n
=
recv
((
SOCKET
)
lua_tonumber
(
L
,
-
1
),
buf
,
sizeof
(
buf
),
0
);
if
(
n
<=
0
)
{
lua_pushnil
(
L
);
}
else
{
lua_pushlstring
(
L
,
buf
,
n
);
}
}
else
{
return
luaL_error
(
L
,
"invalid :close() call"
);
}
return
1
;
}
static
int
lsp_sock_send
(
lua_State
*
L
)
{
const
char
*
buf
;
size_t
len
,
sent
=
0
;
int
n
,
sock
;
if
(
lua_gettop
(
L
)
>
1
&&
lua_istable
(
L
,
-
2
)
&&
lua_isstring
(
L
,
-
1
))
{
buf
=
lua_tolstring
(
L
,
-
1
,
&
len
);
lua_getfield
(
L
,
-
2
,
"sock"
);
sock
=
(
int
)
lua_tonumber
(
L
,
-
1
);
while
(
sent
<
len
)
{
if
((
n
=
send
(
sock
,
buf
+
sent
,
len
-
sent
,
0
))
<=
0
)
{
break
;
}
sent
+=
n
;
}
lua_pushnumber
(
L
,
n
);
}
else
{
return
luaL_error
(
L
,
"invalid :close() call"
);
}
return
1
;
}
static
const
struct
luaL_Reg
luasocket_methods
[]
=
{
{
"close"
,
lsp_sock_close
},
{
"send"
,
lsp_sock_send
},
{
"recv"
,
lsp_sock_recv
},
{
NULL
,
NULL
}
};
static
int
lsp_connect
(
lua_State
*
L
)
{
char
ebuf
[
100
];
SOCKET
sock
;
if
(
lua_isstring
(
L
,
-
3
)
&&
lua_isnumber
(
L
,
-
2
)
&&
lua_isnumber
(
L
,
-
1
))
{
sock
=
conn2
(
lua_tostring
(
L
,
-
3
),
(
int
)
lua_tonumber
(
L
,
-
2
),
(
int
)
lua_tonumber
(
L
,
-
1
),
ebuf
,
sizeof
(
ebuf
));
if
(
sock
==
INVALID_SOCKET
)
{
return
luaL_error
(
L
,
ebuf
);
}
else
{
lua_newtable
(
L
);
reg_int
(
L
,
"sock"
,
sock
);
reg_string
(
L
,
"host"
,
lua_tostring
(
L
,
-
4
));
luaL_getmetatable
(
L
,
LUASOCKET
);
lua_setmetatable
(
L
,
-
2
);
}
}
else
{
return
luaL_error
(
L
,
"connect(host,port,is_ssl): invalid parameter given."
);
}
return
1
;
}
static
int
lsp_error
(
lua_State
*
L
)
{
lua_getglobal
(
L
,
"mg"
);
lua_getfield
(
L
,
-
1
,
"onerror"
);
lua_pushvalue
(
L
,
-
3
);
lua_pcall
(
L
,
1
,
0
,
0
);
return
0
;
}
// Silently stop processing chunks.
static
void
lsp_abort
(
lua_State
*
L
)
{
int
top
=
lua_gettop
(
L
);
lua_getglobal
(
L
,
"mg"
);
lua_pushnil
(
L
);
lua_setfield
(
L
,
-
2
,
"onerror"
);
lua_settop
(
L
,
top
);
lua_pushstring
(
L
,
"aborting"
);
lua_error
(
L
);
}
static
int
lsp
(
struct
mg_connection
*
conn
,
const
char
*
path
,
const
char
*
p
,
int64_t
len
,
lua_State
*
L
)
{
int
i
,
j
,
pos
=
0
,
lines
=
1
,
lualines
=
0
;
char
chunkname
[
MG_BUF_LEN
];
for
(
i
=
0
;
i
<
len
;
i
++
)
{
if
(
p
[
i
]
==
'\n'
)
lines
++
;
if
(
p
[
i
]
==
'<'
&&
p
[
i
+
1
]
==
'?'
)
{
for
(
j
=
i
+
1
;
j
<
len
;
j
++
)
{
if
(
p
[
j
]
==
'\n'
)
lualines
++
;
if
(
p
[
j
]
==
'?'
&&
p
[
j
+
1
]
==
'>'
)
{
mg_write
(
conn
,
p
+
pos
,
i
-
pos
);
snprintf
(
chunkname
,
sizeof
(
chunkname
),
"@%s+%i"
,
path
,
lines
);
lua_pushlightuserdata
(
L
,
conn
);
lua_pushcclosure
(
L
,
lsp_error
,
1
);
if
(
luaL_loadbuffer
(
L
,
p
+
(
i
+
2
),
j
-
(
i
+
2
),
chunkname
))
{
// Syntax error or OOM. Error message is pushed on stack.
lua_pcall
(
L
,
1
,
0
,
0
);
}
else
{
// Success loading chunk. Call it.
lua_pcall
(
L
,
0
,
0
,
1
);
}
pos
=
j
+
2
;
i
=
pos
-
1
;
break
;
}
}
if
(
lualines
>
0
)
{
lines
+=
lualines
;
lualines
=
0
;
}
}
}
if
(
i
>
pos
)
{
mg_write
(
conn
,
p
+
pos
,
i
-
pos
);
}
return
0
;
}
static
int
lsp_write
(
lua_State
*
L
)
{
int
i
,
num_args
;
const
char
*
str
;
size_t
size
;
struct
mg_connection
*
conn
=
lua_touserdata
(
L
,
lua_upvalueindex
(
1
));
num_args
=
lua_gettop
(
L
);
for
(
i
=
1
;
i
<=
num_args
;
i
++
)
{
if
(
lua_isstring
(
L
,
i
))
{
str
=
lua_tolstring
(
L
,
i
,
&
size
);
mg_write
(
conn
,
str
,
size
);
}
}
return
0
;
}
static
int
lsp_read
(
lua_State
*
L
)
{
struct
mg_connection
*
conn
=
lua_touserdata
(
L
,
lua_upvalueindex
(
1
));
char
buf
[
1024
];
int
len
=
mg_read
(
conn
,
buf
,
sizeof
(
buf
));
if
(
len
<=
0
)
return
0
;
lua_pushlstring
(
L
,
buf
,
len
);
return
1
;
}
// mg.include: Include another .lp file
static
int
lsp_include
(
lua_State
*
L
)
{
struct
mg_connection
*
conn
=
lua_touserdata
(
L
,
lua_upvalueindex
(
1
));
struct
file
file
=
STRUCT_FILE_INITIALIZER
;
if
(
handle_lsp_request
(
conn
,
lua_tostring
(
L
,
-
1
),
&
file
,
L
))
{
// handle_lsp_request returned an error code, meaning an error occured in
// the included page and mg.onerror returned non-zero. Stop processing.
lsp_abort
(
L
);
}
return
0
;
}
// mg.cry: Log an error. Default value for mg.onerror.
static
int
lsp_cry
(
lua_State
*
L
){
struct
mg_connection
*
conn
=
lua_touserdata
(
L
,
lua_upvalueindex
(
1
));
cry
(
conn
,
"%s"
,
lua_tostring
(
L
,
-
1
));
return
0
;
}
// mg.redirect: Redirect the request (internally).
static
int
lsp_redirect
(
lua_State
*
L
)
{
struct
mg_connection
*
conn
=
lua_touserdata
(
L
,
lua_upvalueindex
(
1
));
conn
->
request_info
.
uri
=
lua_tostring
(
L
,
-
1
);
handle_request
(
conn
);
lsp_abort
(
L
);
return
0
;
}
static
void
prepare_lua_environment
(
struct
mg_connection
*
conn
,
lua_State
*
L
)
{
const
struct
mg_request_info
*
ri
=
&
conn
->
request_info
;
extern
void
luaL_openlibs
(
lua_State
*
);
int
i
;
luaL_openlibs
(
L
);
#ifdef USE_LUA_SQLITE3
{
extern
int
luaopen_lsqlite3
(
lua_State
*
);
luaopen_lsqlite3
(
L
);
}
#endif
luaL_newmetatable
(
L
,
LUASOCKET
);
lua_pushliteral
(
L
,
"__index"
);
luaL_newlib
(
L
,
luasocket_methods
);
lua_rawset
(
L
,
-
3
);
lua_pop
(
L
,
1
);
lua_register
(
L
,
"connect"
,
lsp_connect
);
if
(
conn
==
NULL
)
return
;
// Register mg module
lua_newtable
(
L
);
reg_function
(
L
,
"read"
,
lsp_read
,
conn
);
reg_function
(
L
,
"write"
,
lsp_write
,
conn
);
reg_function
(
L
,
"cry"
,
lsp_cry
,
conn
);
reg_function
(
L
,
"include"
,
lsp_include
,
conn
);
reg_function
(
L
,
"redirect"
,
lsp_redirect
,
conn
);
reg_string
(
L
,
"version"
,
MONGOOSE_VERSION
);
// Export request_info
lua_pushstring
(
L
,
"request_info"
);
lua_newtable
(
L
);
reg_string
(
L
,
"request_method"
,
ri
->
request_method
);
reg_string
(
L
,
"uri"
,
ri
->
uri
);
reg_string
(
L
,
"http_version"
,
ri
->
http_version
);
reg_string
(
L
,
"query_string"
,
ri
->
query_string
);
reg_int
(
L
,
"remote_ip"
,
ri
->
remote_ip
);
reg_int
(
L
,
"remote_port"
,
ri
->
remote_port
);
reg_int
(
L
,
"num_headers"
,
ri
->
num_headers
);
lua_pushstring
(
L
,
"http_headers"
);
lua_newtable
(
L
);
for
(
i
=
0
;
i
<
ri
->
num_headers
;
i
++
)
{
reg_string
(
L
,
ri
->
http_headers
[
i
].
name
,
ri
->
http_headers
[
i
].
value
);
}
lua_rawset
(
L
,
-
3
);
lua_rawset
(
L
,
-
3
);
lua_setglobal
(
L
,
"mg"
);
// Register default mg.onerror function
luaL_dostring
(
L
,
"mg.onerror = function(e) mg.write('
\\
nLua error:
\\
n', "
"debug.traceback(e, 1)) end"
);
}
static
int
lua_error_handler
(
lua_State
*
L
)
{
const
char
*
error_msg
=
lua_isstring
(
L
,
-
1
)
?
lua_tostring
(
L
,
-
1
)
:
"?
\n
"
;
lua_getglobal
(
L
,
"mg"
);
if
(
!
lua_isnil
(
L
,
-
1
))
{
lua_getfield
(
L
,
-
1
,
"write"
);
// call mg.write()
lua_pushstring
(
L
,
error_msg
);
lua_pushliteral
(
L
,
"
\n
"
);
lua_call
(
L
,
2
,
0
);
luaL_dostring
(
L
,
"mg.write(debug.traceback(), '
\\
n')"
);
}
else
{
printf
(
"Lua error: [%s]
\n
"
,
error_msg
);
luaL_dostring
(
L
,
"print(debug.traceback(), '
\\
n')"
);
}
// TODO(lsm): leave the stack balanced
return
0
;
}
void
mg_exec_lua_script
(
struct
mg_connection
*
conn
,
const
char
*
path
,
const
void
**
exports
)
{
int
i
;
lua_State
*
L
;
if
(
path
!=
NULL
&&
(
L
=
luaL_newstate
())
!=
NULL
)
{
prepare_lua_environment
(
conn
,
L
);
lua_pushcclosure
(
L
,
&
lua_error_handler
,
0
);
lua_pushglobaltable
(
L
);
if
(
exports
!=
NULL
)
{
for
(
i
=
0
;
exports
[
i
]
!=
NULL
&&
exports
[
i
+
1
]
!=
NULL
;
i
+=
2
)
{
lua_pushstring
(
L
,
exports
[
i
]);
lua_pushcclosure
(
L
,
(
lua_CFunction
)
exports
[
i
+
1
],
0
);
lua_rawset
(
L
,
-
3
);
}
}
if
(
luaL_loadfile
(
L
,
path
)
!=
0
)
{
lua_error_handler
(
L
);
}
lua_pcall
(
L
,
0
,
0
,
-
2
);
lua_close
(
L
);
}
}
static
void
lsp_send_err
(
struct
mg_connection
*
conn
,
struct
lua_State
*
L
,
const
char
*
fmt
,
...)
{
char
buf
[
MG_BUF_LEN
];
va_list
ap
;
va_start
(
ap
,
fmt
);
vsnprintf
(
buf
,
sizeof
(
buf
),
fmt
,
ap
);
va_end
(
ap
);
if
(
L
==
NULL
)
{
send_http_error
(
conn
,
500
,
http_500_error
,
"%s"
,
buf
);
}
else
{
lua_pushstring
(
L
,
buf
);
lua_error
(
L
);
}
}
static
int
handle_lsp_request
(
struct
mg_connection
*
conn
,
const
char
*
path
,
struct
file
*
filep
,
struct
lua_State
*
ls
)
{
void
*
p
=
NULL
;
lua_State
*
L
=
NULL
;
FILE
*
fp
=
NULL
;
int
error
=
1
;
// We need both mg_stat to get file size, and mg_fopen to get fd
if
(
!
mg_stat
(
path
,
filep
)
||
(
fp
=
mg_fopen
(
path
,
"r"
))
==
NULL
)
{
lsp_send_err
(
conn
,
ls
,
"File [%s] not found"
,
path
);
}
else
if
((
p
=
mmap
(
NULL
,
(
size_t
)
filep
->
size
,
PROT_READ
,
MAP_PRIVATE
,
fileno
(
fp
),
0
))
==
MAP_FAILED
)
{
lsp_send_err
(
conn
,
ls
,
"mmap(%s, %zu, %d): %s"
,
path
,
(
size_t
)
filep
->
size
,
fileno
(
fp
),
strerror
(
errno
));
}
else
if
((
L
=
ls
!=
NULL
?
ls
:
luaL_newstate
())
==
NULL
)
{
send_http_error
(
conn
,
500
,
http_500_error
,
"%s"
,
"luaL_newstate failed"
);
}
else
{
// We're not sending HTTP headers here, Lua page must do it.
if
(
ls
==
NULL
)
{
prepare_lua_environment
(
conn
,
L
);
}
error
=
lsp
(
conn
,
path
,
p
,
filep
->
size
,
L
);
}
if
(
L
!=
NULL
&&
ls
==
NULL
)
lua_close
(
L
);
if
(
p
!=
NULL
)
munmap
(
p
,
filep
->
size
);
fclose
(
fp
);
return
error
;
}
#endif // USE_LUA
test/unit_test.c
View file @
70154f6c
...
...
@@ -222,7 +222,7 @@ static int event_handler(struct mg_event *event) {
if
(
!
strcmp
(
ri
->
uri
,
"/upload"
))
{
test_upload
(
event
->
conn
,
"lua_5.2.1.h"
,
"./f1.txt"
);
test_upload
(
event
->
conn
,
"
mod_lua
.c"
,
"./f2.txt"
);
test_upload
(
event
->
conn
,
"
lsqlite3
.c"
,
"./f2.txt"
);
ASSERT
(
mg_upload
(
event
->
conn
,
"."
,
NULL
,
0
)
==
NULL
);
mg_printf
(
event
->
conn
,
"HTTP/1.0 200 OK
\r\n
"
...
...
@@ -330,7 +330,7 @@ static void test_mg_upload(void) {
// Upload two files
ASSERT
((
file_data
=
read_file
(
"lua_5.2.1.h"
,
&
file_len
))
!=
NULL
);
ASSERT
((
file2_data
=
read_file
(
"
mod_lua
.c"
,
&
file2_len
))
!=
NULL
);
ASSERT
((
file2_data
=
read_file
(
"
lsqlite3
.c"
,
&
file2_len
))
!=
NULL
);
post_data
=
NULL
;
post_data_len
=
alloc_printf
(
&
post_data
,
0
,
// First file
...
...
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