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
b5fdd48d
Commit
b5fdd48d
authored
Nov 24, 2013
by
Sergey Lyubka
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Moved websocket functions into websocket.c
parent
f4f73a8d
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
499 additions
and
805 deletions
+499
-805
Makefile
build/Makefile
+1
-1
mongoose.c
build/src/mongoose.c
+0
-307
mongoose.c
mongoose.c
+498
-497
No files found.
build/Makefile
View file @
b5fdd48d
...
...
@@ -30,7 +30,7 @@ SOURCES = src/internal.h src/util.c src/string.c src/parse_date.c \
src/options.c src/crypto.c src/auth.c src/win32.c src/unix.c
\
src/mg_printf.c src/ssl.c src/http_client.c src/mime.c
\
src/directory.c src/log.c src/parse_http.c src/io.c src/cgi.c
\
src/upload.c src/mongoose.c src/lua.c
src/upload.c src/
websocket.c src/
mongoose.c src/lua.c
TINY_SOURCES
=
../mongoose.c main.c
LUA_SOURCES
=
$(TINY_SOURCES)
lua_5.2.1.c
...
...
build/src/mongoose.c
View file @
b5fdd48d
...
...
@@ -647,313 +647,6 @@ static void handle_propfind(struct mg_connection *conn, const char *path,
conn
->
num_bytes_sent
+=
mg_printf
(
conn
,
"%s
\n
"
,
"</d:multistatus>"
);
}
#if defined(USE_WEBSOCKET)
// START OF SHA-1 code
// Copyright(c) By Steve Reid <steve@edmweb.com>
#define SHA1HANDSOFF
#if defined(__sun)
#include "solarisfixes.h"
#endif
union
char64long16
{
unsigned
char
c
[
64
];
uint32_t
l
[
16
];
};
#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
static
uint32_t
blk0
(
union
char64long16
*
block
,
int
i
)
{
// Forrest: SHA expect BIG_ENDIAN, swap if LITTLE_ENDIAN
if
(
!
is_big_endian
())
{
block
->
l
[
i
]
=
(
rol
(
block
->
l
[
i
],
24
)
&
0xFF00FF00
)
|
(
rol
(
block
->
l
[
i
],
8
)
&
0x00FF00FF
);
}
return
block
->
l
[
i
];
}
#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \
^block->l[(i+2)&15]^block->l[i&15],1))
#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(block, i)+0x5A827999+rol(v,5);w=rol(w,30);
#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30);
#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30);
#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30);
#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30);
typedef
struct
{
uint32_t
state
[
5
];
uint32_t
count
[
2
];
unsigned
char
buffer
[
64
];
}
SHA1_CTX
;
static
void
SHA1Transform
(
uint32_t
state
[
5
],
const
unsigned
char
buffer
[
64
])
{
uint32_t
a
,
b
,
c
,
d
,
e
;
union
char64long16
block
[
1
];
memcpy
(
block
,
buffer
,
64
);
a
=
state
[
0
];
b
=
state
[
1
];
c
=
state
[
2
];
d
=
state
[
3
];
e
=
state
[
4
];
R0
(
a
,
b
,
c
,
d
,
e
,
0
);
R0
(
e
,
a
,
b
,
c
,
d
,
1
);
R0
(
d
,
e
,
a
,
b
,
c
,
2
);
R0
(
c
,
d
,
e
,
a
,
b
,
3
);
R0
(
b
,
c
,
d
,
e
,
a
,
4
);
R0
(
a
,
b
,
c
,
d
,
e
,
5
);
R0
(
e
,
a
,
b
,
c
,
d
,
6
);
R0
(
d
,
e
,
a
,
b
,
c
,
7
);
R0
(
c
,
d
,
e
,
a
,
b
,
8
);
R0
(
b
,
c
,
d
,
e
,
a
,
9
);
R0
(
a
,
b
,
c
,
d
,
e
,
10
);
R0
(
e
,
a
,
b
,
c
,
d
,
11
);
R0
(
d
,
e
,
a
,
b
,
c
,
12
);
R0
(
c
,
d
,
e
,
a
,
b
,
13
);
R0
(
b
,
c
,
d
,
e
,
a
,
14
);
R0
(
a
,
b
,
c
,
d
,
e
,
15
);
R1
(
e
,
a
,
b
,
c
,
d
,
16
);
R1
(
d
,
e
,
a
,
b
,
c
,
17
);
R1
(
c
,
d
,
e
,
a
,
b
,
18
);
R1
(
b
,
c
,
d
,
e
,
a
,
19
);
R2
(
a
,
b
,
c
,
d
,
e
,
20
);
R2
(
e
,
a
,
b
,
c
,
d
,
21
);
R2
(
d
,
e
,
a
,
b
,
c
,
22
);
R2
(
c
,
d
,
e
,
a
,
b
,
23
);
R2
(
b
,
c
,
d
,
e
,
a
,
24
);
R2
(
a
,
b
,
c
,
d
,
e
,
25
);
R2
(
e
,
a
,
b
,
c
,
d
,
26
);
R2
(
d
,
e
,
a
,
b
,
c
,
27
);
R2
(
c
,
d
,
e
,
a
,
b
,
28
);
R2
(
b
,
c
,
d
,
e
,
a
,
29
);
R2
(
a
,
b
,
c
,
d
,
e
,
30
);
R2
(
e
,
a
,
b
,
c
,
d
,
31
);
R2
(
d
,
e
,
a
,
b
,
c
,
32
);
R2
(
c
,
d
,
e
,
a
,
b
,
33
);
R2
(
b
,
c
,
d
,
e
,
a
,
34
);
R2
(
a
,
b
,
c
,
d
,
e
,
35
);
R2
(
e
,
a
,
b
,
c
,
d
,
36
);
R2
(
d
,
e
,
a
,
b
,
c
,
37
);
R2
(
c
,
d
,
e
,
a
,
b
,
38
);
R2
(
b
,
c
,
d
,
e
,
a
,
39
);
R3
(
a
,
b
,
c
,
d
,
e
,
40
);
R3
(
e
,
a
,
b
,
c
,
d
,
41
);
R3
(
d
,
e
,
a
,
b
,
c
,
42
);
R3
(
c
,
d
,
e
,
a
,
b
,
43
);
R3
(
b
,
c
,
d
,
e
,
a
,
44
);
R3
(
a
,
b
,
c
,
d
,
e
,
45
);
R3
(
e
,
a
,
b
,
c
,
d
,
46
);
R3
(
d
,
e
,
a
,
b
,
c
,
47
);
R3
(
c
,
d
,
e
,
a
,
b
,
48
);
R3
(
b
,
c
,
d
,
e
,
a
,
49
);
R3
(
a
,
b
,
c
,
d
,
e
,
50
);
R3
(
e
,
a
,
b
,
c
,
d
,
51
);
R3
(
d
,
e
,
a
,
b
,
c
,
52
);
R3
(
c
,
d
,
e
,
a
,
b
,
53
);
R3
(
b
,
c
,
d
,
e
,
a
,
54
);
R3
(
a
,
b
,
c
,
d
,
e
,
55
);
R3
(
e
,
a
,
b
,
c
,
d
,
56
);
R3
(
d
,
e
,
a
,
b
,
c
,
57
);
R3
(
c
,
d
,
e
,
a
,
b
,
58
);
R3
(
b
,
c
,
d
,
e
,
a
,
59
);
R4
(
a
,
b
,
c
,
d
,
e
,
60
);
R4
(
e
,
a
,
b
,
c
,
d
,
61
);
R4
(
d
,
e
,
a
,
b
,
c
,
62
);
R4
(
c
,
d
,
e
,
a
,
b
,
63
);
R4
(
b
,
c
,
d
,
e
,
a
,
64
);
R4
(
a
,
b
,
c
,
d
,
e
,
65
);
R4
(
e
,
a
,
b
,
c
,
d
,
66
);
R4
(
d
,
e
,
a
,
b
,
c
,
67
);
R4
(
c
,
d
,
e
,
a
,
b
,
68
);
R4
(
b
,
c
,
d
,
e
,
a
,
69
);
R4
(
a
,
b
,
c
,
d
,
e
,
70
);
R4
(
e
,
a
,
b
,
c
,
d
,
71
);
R4
(
d
,
e
,
a
,
b
,
c
,
72
);
R4
(
c
,
d
,
e
,
a
,
b
,
73
);
R4
(
b
,
c
,
d
,
e
,
a
,
74
);
R4
(
a
,
b
,
c
,
d
,
e
,
75
);
R4
(
e
,
a
,
b
,
c
,
d
,
76
);
R4
(
d
,
e
,
a
,
b
,
c
,
77
);
R4
(
c
,
d
,
e
,
a
,
b
,
78
);
R4
(
b
,
c
,
d
,
e
,
a
,
79
);
state
[
0
]
+=
a
;
state
[
1
]
+=
b
;
state
[
2
]
+=
c
;
state
[
3
]
+=
d
;
state
[
4
]
+=
e
;
a
=
b
=
c
=
d
=
e
=
0
;
memset
(
block
,
'\0'
,
sizeof
(
block
));
}
static
void
SHA1Init
(
SHA1_CTX
*
context
)
{
context
->
state
[
0
]
=
0x67452301
;
context
->
state
[
1
]
=
0xEFCDAB89
;
context
->
state
[
2
]
=
0x98BADCFE
;
context
->
state
[
3
]
=
0x10325476
;
context
->
state
[
4
]
=
0xC3D2E1F0
;
context
->
count
[
0
]
=
context
->
count
[
1
]
=
0
;
}
static
void
SHA1Update
(
SHA1_CTX
*
context
,
const
unsigned
char
*
data
,
uint32_t
len
)
{
uint32_t
i
,
j
;
j
=
context
->
count
[
0
];
if
((
context
->
count
[
0
]
+=
len
<<
3
)
<
j
)
context
->
count
[
1
]
++
;
context
->
count
[
1
]
+=
(
len
>>
29
);
j
=
(
j
>>
3
)
&
63
;
if
((
j
+
len
)
>
63
)
{
memcpy
(
&
context
->
buffer
[
j
],
data
,
(
i
=
64
-
j
));
SHA1Transform
(
context
->
state
,
context
->
buffer
);
for
(
;
i
+
63
<
len
;
i
+=
64
)
{
SHA1Transform
(
context
->
state
,
&
data
[
i
]);
}
j
=
0
;
}
else
i
=
0
;
memcpy
(
&
context
->
buffer
[
j
],
&
data
[
i
],
len
-
i
);
}
static
void
SHA1Final
(
unsigned
char
digest
[
20
],
SHA1_CTX
*
context
)
{
unsigned
i
;
unsigned
char
finalcount
[
8
],
c
;
for
(
i
=
0
;
i
<
8
;
i
++
)
{
finalcount
[
i
]
=
(
unsigned
char
)((
context
->
count
[(
i
>=
4
?
0
:
1
)]
>>
((
3
-
(
i
&
3
))
*
8
)
)
&
255
);
}
c
=
0200
;
SHA1Update
(
context
,
&
c
,
1
);
while
((
context
->
count
[
0
]
&
504
)
!=
448
)
{
c
=
0000
;
SHA1Update
(
context
,
&
c
,
1
);
}
SHA1Update
(
context
,
finalcount
,
8
);
for
(
i
=
0
;
i
<
20
;
i
++
)
{
digest
[
i
]
=
(
unsigned
char
)
((
context
->
state
[
i
>>
2
]
>>
((
3
-
(
i
&
3
))
*
8
)
)
&
255
);
}
memset
(
context
,
'\0'
,
sizeof
(
*
context
));
memset
(
&
finalcount
,
'\0'
,
sizeof
(
finalcount
));
}
// END OF SHA1 CODE
static
void
base64_encode
(
const
unsigned
char
*
src
,
int
src_len
,
char
*
dst
)
{
static
const
char
*
b64
=
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
;
int
i
,
j
,
a
,
b
,
c
;
for
(
i
=
j
=
0
;
i
<
src_len
;
i
+=
3
)
{
a
=
src
[
i
];
b
=
i
+
1
>=
src_len
?
0
:
src
[
i
+
1
];
c
=
i
+
2
>=
src_len
?
0
:
src
[
i
+
2
];
dst
[
j
++
]
=
b64
[
a
>>
2
];
dst
[
j
++
]
=
b64
[((
a
&
3
)
<<
4
)
|
(
b
>>
4
)];
if
(
i
+
1
<
src_len
)
{
dst
[
j
++
]
=
b64
[(
b
&
15
)
<<
2
|
(
c
>>
6
)];
}
if
(
i
+
2
<
src_len
)
{
dst
[
j
++
]
=
b64
[
c
&
63
];
}
}
while
(
j
%
4
!=
0
)
{
dst
[
j
++
]
=
'='
;
}
dst
[
j
++
]
=
'\0'
;
}
void
mg_websocket_handshake
(
struct
mg_connection
*
conn
)
{
static
const
char
*
magic
=
"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
;
char
buf
[
100
],
sha
[
20
],
b64_sha
[
sizeof
(
sha
)
*
2
];
SHA1_CTX
sha_ctx
;
mg_snprintf
(
buf
,
sizeof
(
buf
),
"%s%s"
,
mg_get_header
(
conn
,
"Sec-WebSocket-Key"
),
magic
);
SHA1Init
(
&
sha_ctx
);
SHA1Update
(
&
sha_ctx
,
(
unsigned
char
*
)
buf
,
strlen
(
buf
));
SHA1Final
((
unsigned
char
*
)
sha
,
&
sha_ctx
);
base64_encode
((
unsigned
char
*
)
sha
,
sizeof
(
sha
),
b64_sha
);
mg_printf
(
conn
,
"%s%s%s"
,
"HTTP/1.1 101 Switching Protocols
\r\n
"
"Upgrade: websocket
\r\n
"
"Connection: Upgrade
\r\n
"
"Sec-WebSocket-Accept: "
,
b64_sha
,
"
\r\n\r\n
"
);
}
int
mg_websocket_read
(
struct
mg_connection
*
conn
,
int
*
bits
,
char
**
data
)
{
// Pointer to the beginning of the portion of the incoming websocket message
// queue. The original websocket upgrade request is never removed,
// so the queue begins after it.
unsigned
char
*
buf
=
(
unsigned
char
*
)
conn
->
buf
+
conn
->
request_len
;
int
n
,
stop
=
0
;
size_t
i
,
len
,
mask_len
,
data_len
,
header_len
,
body_len
;
char
mask
[
4
];
assert
(
conn
->
content_len
==
0
);
// Loop continuously, reading messages from the socket, invoking the callback,
// and waiting repeatedly until an error occurs.
while
(
!
stop
)
{
header_len
=
0
;
// body_len is the length of the entire queue in bytes
// len is the length of the current message
// data_len is the length of the current message's data payload
// header_len is the length of the current message's header
if
((
body_len
=
conn
->
data_len
-
conn
->
request_len
)
>=
2
)
{
len
=
buf
[
1
]
&
127
;
mask_len
=
buf
[
1
]
&
128
?
4
:
0
;
if
(
len
<
126
&&
body_len
>=
mask_len
)
{
data_len
=
len
;
header_len
=
2
+
mask_len
;
}
else
if
(
len
==
126
&&
body_len
>=
4
+
mask_len
)
{
header_len
=
4
+
mask_len
;
data_len
=
((((
int
)
buf
[
2
])
<<
8
)
+
buf
[
3
]);
}
else
if
(
body_len
>=
10
+
mask_len
)
{
header_len
=
10
+
mask_len
;
data_len
=
(((
uint64_t
)
htonl
(
*
(
uint32_t
*
)
&
buf
[
2
]))
<<
32
)
+
htonl
(
*
(
uint32_t
*
)
&
buf
[
6
]);
}
}
// Data layout is as follows:
// conn->buf buf
// v v frame1 | frame2
// |---------------------|----------------|--------------|-------
// | |<--header_len-->|<--data_len-->|
// |<-conn->request_len->|<-----body_len----------->|
// |<-------------------conn->data_len------------->|
if
(
header_len
>
0
)
{
// Allocate space to hold websocket payload
if
((
*
data
=
malloc
(
data_len
))
==
NULL
)
{
// Allocation failed, exit the loop and then close the connection
// TODO: notify user about the failure
data_len
=
0
;
break
;
}
// Save mask and bits, otherwise it may be clobbered by memmove below
*
bits
=
buf
[
0
];
memcpy
(
mask
,
buf
+
header_len
-
mask_len
,
mask_len
);
// Read frame payload into the allocated buffer.
assert
(
body_len
>=
header_len
);
if
(
data_len
+
header_len
>
body_len
)
{
len
=
body_len
-
header_len
;
memcpy
(
*
data
,
buf
+
header_len
,
len
);
// TODO: handle pull error
pull_all
(
NULL
,
conn
,
*
data
+
len
,
data_len
-
len
);
conn
->
data_len
=
conn
->
request_len
;
}
else
{
len
=
data_len
+
header_len
;
memcpy
(
*
data
,
buf
+
header_len
,
data_len
);
memmove
(
buf
,
buf
+
len
,
body_len
-
len
);
conn
->
data_len
-=
len
;
}
// Apply mask if necessary
if
(
mask_len
>
0
)
{
for
(
i
=
0
;
i
<
data_len
;
i
++
)
{
(
*
data
)[
i
]
^=
mask
[
i
%
4
];
}
}
return
data_len
;
}
else
{
// Buffering websocket request
if
((
n
=
pull
(
NULL
,
conn
,
conn
->
buf
+
conn
->
data_len
,
conn
->
buf_size
-
conn
->
data_len
))
<=
0
)
{
break
;
}
conn
->
data_len
+=
n
;
}
}
return
0
;
}
int
mg_websocket_write
(
struct
mg_connection
*
conn
,
int
opcode
,
const
char
*
data
,
size_t
data_len
)
{
unsigned
char
*
copy
;
size_t
copy_len
=
0
;
int
retval
=
-
1
;
if
((
copy
=
(
unsigned
char
*
)
malloc
(
data_len
+
10
))
==
NULL
)
{
return
-
1
;
}
copy
[
0
]
=
0x80
+
(
opcode
&
0x0f
);
// Frame format: http://tools.ietf.org/html/rfc6455#section-5.2
if
(
data_len
<
126
)
{
// Inline 7-bit length field
copy
[
1
]
=
data_len
;
memcpy
(
copy
+
2
,
data
,
data_len
);
copy_len
=
2
+
data_len
;
}
else
if
(
data_len
<=
0xFFFF
)
{
// 16-bit length field
copy
[
1
]
=
126
;
*
(
uint16_t
*
)
(
copy
+
2
)
=
htons
(
data_len
);
memcpy
(
copy
+
4
,
data
,
data_len
);
copy_len
=
4
+
data_len
;
}
else
{
// 64-bit length field
copy
[
1
]
=
127
;
*
(
uint32_t
*
)
(
copy
+
2
)
=
htonl
((
uint64_t
)
data_len
>>
32
);
*
(
uint32_t
*
)
(
copy
+
6
)
=
htonl
(
data_len
&
0xffffffff
);
memcpy
(
copy
+
10
,
data
,
data_len
);
copy_len
=
10
+
data_len
;
}
// Not thread safe
if
(
copy_len
>
0
)
{
retval
=
mg_write
(
conn
,
copy
,
copy_len
);
}
free
(
copy
);
return
retval
;
}
#endif // !USE_WEBSOCKET
static
int
isbyte
(
int
n
)
{
return
n
>=
0
&&
n
<=
255
;
}
...
...
mongoose.c
View file @
b5fdd48d
...
...
@@ -3541,252 +3541,560 @@ FILE *mg_upload(struct mg_connection *conn, const char *destination_dir,
static
int
call_user
(
int
type
,
struct
mg_connection
*
conn
,
void
*
p
)
{
if
(
conn
!=
NULL
&&
conn
->
ctx
!=
NULL
)
{
conn
->
event
.
user_data
=
conn
->
ctx
->
user_data
;
conn
->
event
.
type
=
type
;
conn
->
event
.
event_param
=
p
;
conn
->
event
.
request_info
=
&
conn
->
request_info
;
conn
->
event
.
conn
=
conn
;
}
return
conn
==
NULL
||
conn
->
ctx
==
NULL
||
conn
->
ctx
->
event_handler
==
NULL
?
0
:
conn
->
ctx
->
event_handler
(
&
conn
->
event
);
}
#if defined(USE_WEBSOCKET)
static
FILE
*
mg_fopen
(
const
char
*
path
,
const
char
*
mode
)
{
#ifdef _WIN32
wchar_t
wbuf
[
PATH_MAX
],
wmode
[
20
];
to_unicode
(
path
,
wbuf
,
ARRAY_SIZE
(
wbuf
));
MultiByteToWideChar
(
CP_UTF8
,
0
,
mode
,
-
1
,
wmode
,
ARRAY_SIZE
(
wmode
));
return
_wfopen
(
wbuf
,
wmode
);
#else
return
fopen
(
path
,
mode
);
// START OF SHA-1 code
// Copyright(c) By Steve Reid <steve@edmweb.com>
#define SHA1HANDSOFF
#if defined(__sun)
#include "solarisfixes.h"
#endif
}
// Print error message to the opened error log stream.
static
void
cry
(
struct
mg_connection
*
conn
,
const
char
*
fmt
,
...)
{
char
buf
[
MG_BUF_LEN
],
src_addr
[
IP_ADDR_STR_LEN
];
va_list
ap
;
FILE
*
fp
;
time_t
timestamp
;
union
char64long16
{
unsigned
char
c
[
64
];
uint32_t
l
[
16
];
};
va_start
(
ap
,
fmt
);
(
void
)
vsnprintf
(
buf
,
sizeof
(
buf
),
fmt
,
ap
);
va_end
(
ap
);
#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
// Do not lock when getting the callback value, here and below.
// I suppose this is fine, since function cannot disappear in the
// same way string option can.
if
(
call_user
(
MG_EVENT_LOG
,
conn
,
buf
)
==
0
)
{
fp
=
conn
->
ctx
==
NULL
||
conn
->
ctx
->
config
[
ERROR_LOG_FILE
]
==
NULL
?
NULL
:
fopen
(
conn
->
ctx
->
config
[
ERROR_LOG_FILE
],
"a+"
);
static
uint32_t
blk0
(
union
char64long16
*
block
,
int
i
)
{
// Forrest: SHA expect BIG_ENDIAN, swap if LITTLE_ENDIAN
if
(
!
is_big_endian
())
{
block
->
l
[
i
]
=
(
rol
(
block
->
l
[
i
],
24
)
&
0xFF00FF00
)
|
(
rol
(
block
->
l
[
i
],
8
)
&
0x00FF00FF
);
}
return
block
->
l
[
i
];
}
if
(
fp
!=
NULL
)
{
flockfile
(
fp
);
timestamp
=
time
(
NULL
);
#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \
^block->l[(i+2)&15]^block->l[i&15],1))
#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(block, i)+0x5A827999+rol(v,5);w=rol(w,30);
#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30);
#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30);
#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30);
#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30);
sockaddr_to_string
(
src_addr
,
sizeof
(
src_addr
),
&
conn
->
client
.
rsa
);
fprintf
(
fp
,
"[%010lu] [error] [client %s] "
,
(
unsigned
long
)
timestamp
,
src_addr
);
typedef
struct
{
uint32_t
state
[
5
];
uint32_t
count
[
2
];
unsigned
char
buffer
[
64
];
}
SHA1_CTX
;
if
(
conn
->
request_info
.
request_method
!=
NULL
)
{
fprintf
(
fp
,
"%s %s: "
,
conn
->
request_info
.
request_method
,
conn
->
request_info
.
uri
);
}
static
void
SHA1Transform
(
uint32_t
state
[
5
],
const
unsigned
char
buffer
[
64
])
{
uint32_t
a
,
b
,
c
,
d
,
e
;
union
char64long16
block
[
1
];
fprintf
(
fp
,
"%s"
,
buf
);
fputc
(
'\n'
,
fp
);
funlockfile
(
fp
);
fclose
(
fp
);
}
}
memcpy
(
block
,
buffer
,
64
);
a
=
state
[
0
];
b
=
state
[
1
];
c
=
state
[
2
];
d
=
state
[
3
];
e
=
state
[
4
];
R0
(
a
,
b
,
c
,
d
,
e
,
0
);
R0
(
e
,
a
,
b
,
c
,
d
,
1
);
R0
(
d
,
e
,
a
,
b
,
c
,
2
);
R0
(
c
,
d
,
e
,
a
,
b
,
3
);
R0
(
b
,
c
,
d
,
e
,
a
,
4
);
R0
(
a
,
b
,
c
,
d
,
e
,
5
);
R0
(
e
,
a
,
b
,
c
,
d
,
6
);
R0
(
d
,
e
,
a
,
b
,
c
,
7
);
R0
(
c
,
d
,
e
,
a
,
b
,
8
);
R0
(
b
,
c
,
d
,
e
,
a
,
9
);
R0
(
a
,
b
,
c
,
d
,
e
,
10
);
R0
(
e
,
a
,
b
,
c
,
d
,
11
);
R0
(
d
,
e
,
a
,
b
,
c
,
12
);
R0
(
c
,
d
,
e
,
a
,
b
,
13
);
R0
(
b
,
c
,
d
,
e
,
a
,
14
);
R0
(
a
,
b
,
c
,
d
,
e
,
15
);
R1
(
e
,
a
,
b
,
c
,
d
,
16
);
R1
(
d
,
e
,
a
,
b
,
c
,
17
);
R1
(
c
,
d
,
e
,
a
,
b
,
18
);
R1
(
b
,
c
,
d
,
e
,
a
,
19
);
R2
(
a
,
b
,
c
,
d
,
e
,
20
);
R2
(
e
,
a
,
b
,
c
,
d
,
21
);
R2
(
d
,
e
,
a
,
b
,
c
,
22
);
R2
(
c
,
d
,
e
,
a
,
b
,
23
);
R2
(
b
,
c
,
d
,
e
,
a
,
24
);
R2
(
a
,
b
,
c
,
d
,
e
,
25
);
R2
(
e
,
a
,
b
,
c
,
d
,
26
);
R2
(
d
,
e
,
a
,
b
,
c
,
27
);
R2
(
c
,
d
,
e
,
a
,
b
,
28
);
R2
(
b
,
c
,
d
,
e
,
a
,
29
);
R2
(
a
,
b
,
c
,
d
,
e
,
30
);
R2
(
e
,
a
,
b
,
c
,
d
,
31
);
R2
(
d
,
e
,
a
,
b
,
c
,
32
);
R2
(
c
,
d
,
e
,
a
,
b
,
33
);
R2
(
b
,
c
,
d
,
e
,
a
,
34
);
R2
(
a
,
b
,
c
,
d
,
e
,
35
);
R2
(
e
,
a
,
b
,
c
,
d
,
36
);
R2
(
d
,
e
,
a
,
b
,
c
,
37
);
R2
(
c
,
d
,
e
,
a
,
b
,
38
);
R2
(
b
,
c
,
d
,
e
,
a
,
39
);
R3
(
a
,
b
,
c
,
d
,
e
,
40
);
R3
(
e
,
a
,
b
,
c
,
d
,
41
);
R3
(
d
,
e
,
a
,
b
,
c
,
42
);
R3
(
c
,
d
,
e
,
a
,
b
,
43
);
R3
(
b
,
c
,
d
,
e
,
a
,
44
);
R3
(
a
,
b
,
c
,
d
,
e
,
45
);
R3
(
e
,
a
,
b
,
c
,
d
,
46
);
R3
(
d
,
e
,
a
,
b
,
c
,
47
);
R3
(
c
,
d
,
e
,
a
,
b
,
48
);
R3
(
b
,
c
,
d
,
e
,
a
,
49
);
R3
(
a
,
b
,
c
,
d
,
e
,
50
);
R3
(
e
,
a
,
b
,
c
,
d
,
51
);
R3
(
d
,
e
,
a
,
b
,
c
,
52
);
R3
(
c
,
d
,
e
,
a
,
b
,
53
);
R3
(
b
,
c
,
d
,
e
,
a
,
54
);
R3
(
a
,
b
,
c
,
d
,
e
,
55
);
R3
(
e
,
a
,
b
,
c
,
d
,
56
);
R3
(
d
,
e
,
a
,
b
,
c
,
57
);
R3
(
c
,
d
,
e
,
a
,
b
,
58
);
R3
(
b
,
c
,
d
,
e
,
a
,
59
);
R4
(
a
,
b
,
c
,
d
,
e
,
60
);
R4
(
e
,
a
,
b
,
c
,
d
,
61
);
R4
(
d
,
e
,
a
,
b
,
c
,
62
);
R4
(
c
,
d
,
e
,
a
,
b
,
63
);
R4
(
b
,
c
,
d
,
e
,
a
,
64
);
R4
(
a
,
b
,
c
,
d
,
e
,
65
);
R4
(
e
,
a
,
b
,
c
,
d
,
66
);
R4
(
d
,
e
,
a
,
b
,
c
,
67
);
R4
(
c
,
d
,
e
,
a
,
b
,
68
);
R4
(
b
,
c
,
d
,
e
,
a
,
69
);
R4
(
a
,
b
,
c
,
d
,
e
,
70
);
R4
(
e
,
a
,
b
,
c
,
d
,
71
);
R4
(
d
,
e
,
a
,
b
,
c
,
72
);
R4
(
c
,
d
,
e
,
a
,
b
,
73
);
R4
(
b
,
c
,
d
,
e
,
a
,
74
);
R4
(
a
,
b
,
c
,
d
,
e
,
75
);
R4
(
e
,
a
,
b
,
c
,
d
,
76
);
R4
(
d
,
e
,
a
,
b
,
c
,
77
);
R4
(
c
,
d
,
e
,
a
,
b
,
78
);
R4
(
b
,
c
,
d
,
e
,
a
,
79
);
state
[
0
]
+=
a
;
state
[
1
]
+=
b
;
state
[
2
]
+=
c
;
state
[
3
]
+=
d
;
state
[
4
]
+=
e
;
a
=
b
=
c
=
d
=
e
=
0
;
memset
(
block
,
'\0'
,
sizeof
(
block
));
}
const
char
*
mg_version
(
void
)
{
return
MONGOOSE_VERSION
;
static
void
SHA1Init
(
SHA1_CTX
*
context
)
{
context
->
state
[
0
]
=
0x67452301
;
context
->
state
[
1
]
=
0xEFCDAB89
;
context
->
state
[
2
]
=
0x98BADCFE
;
context
->
state
[
3
]
=
0x10325476
;
context
->
state
[
4
]
=
0xC3D2E1F0
;
context
->
count
[
0
]
=
context
->
count
[
1
]
=
0
;
}
// HTTP 1.1 assumes keep alive if "Connection:" header is not set
// This function must tolerate situations when connection info is not
// set up, for example if request parsing failed.
static
int
should_keep_alive
(
const
struct
mg_connection
*
conn
)
{
const
char
*
http_version
=
conn
->
request_info
.
http_version
;
const
char
*
header
=
mg_get_header
(
conn
,
"Connection"
);
if
(
conn
->
must_close
||
conn
->
status_code
==
401
||
mg_strcasecmp
(
conn
->
ctx
->
config
[
ENABLE_KEEP_ALIVE
],
"yes"
)
!=
0
||
(
header
!=
NULL
&&
mg_strcasecmp
(
header
,
"keep-alive"
)
!=
0
)
||
(
header
==
NULL
&&
http_version
&&
strcmp
(
http_version
,
"1.1"
)))
{
return
0
;
}
return
1
;
}
static
void
SHA1Update
(
SHA1_CTX
*
context
,
const
unsigned
char
*
data
,
uint32_t
len
)
{
uint32_t
i
,
j
;
static
const
char
*
suggest_connection_header
(
const
struct
mg_connection
*
conn
)
{
return
should_keep_alive
(
conn
)
?
"keep-alive"
:
"close"
;
j
=
context
->
count
[
0
];
if
((
context
->
count
[
0
]
+=
len
<<
3
)
<
j
)
context
->
count
[
1
]
++
;
context
->
count
[
1
]
+=
(
len
>>
29
);
j
=
(
j
>>
3
)
&
63
;
if
((
j
+
len
)
>
63
)
{
memcpy
(
&
context
->
buffer
[
j
],
data
,
(
i
=
64
-
j
));
SHA1Transform
(
context
->
state
,
context
->
buffer
);
for
(
;
i
+
63
<
len
;
i
+=
64
)
{
SHA1Transform
(
context
->
state
,
&
data
[
i
]);
}
j
=
0
;
}
else
i
=
0
;
memcpy
(
&
context
->
buffer
[
j
],
&
data
[
i
],
len
-
i
);
}
static
void
send_http_error
(
struct
mg_connection
*
conn
,
int
status
,
const
char
*
reason
,
const
char
*
fmt
,
...)
{
char
buf
[
MG_BUF_LEN
];
va_list
ap
;
int
len
=
0
;
conn
->
status_code
=
status
;
buf
[
0
]
=
'\0'
;
// Errors 1xx, 204 and 304 MUST NOT send a body
if
(
status
>
199
&&
status
!=
204
&&
status
!=
304
)
{
len
=
mg_snprintf
(
buf
,
sizeof
(
buf
),
"Error %d: %s"
,
status
,
reason
);
buf
[
len
++
]
=
'\n'
;
static
void
SHA1Final
(
unsigned
char
digest
[
20
],
SHA1_CTX
*
context
)
{
unsigned
i
;
unsigned
char
finalcount
[
8
],
c
;
va_start
(
ap
,
fmt
);
len
+=
mg_vsnprintf
(
buf
+
len
,
sizeof
(
buf
)
-
len
,
fmt
,
ap
);
va_end
(
ap
);
for
(
i
=
0
;
i
<
8
;
i
++
)
{
finalcount
[
i
]
=
(
unsigned
char
)((
context
->
count
[(
i
>=
4
?
0
:
1
)]
>>
((
3
-
(
i
&
3
))
*
8
)
)
&
255
);
}
DEBUG_TRACE
((
"[%s]"
,
buf
));
if
(
call_user
(
MG_HTTP_ERROR
,
conn
,
(
void
*
)
(
long
)
status
)
==
0
)
{
mg_printf
(
conn
,
"HTTP/1.1 %d %s
\r\n
"
"Content-Length: %d
\r\n
"
"Connection: %s
\r\n\r\n
"
,
status
,
reason
,
len
,
suggest_connection_header
(
conn
));
conn
->
num_bytes_sent
+=
mg_printf
(
conn
,
"%s"
,
buf
);
c
=
0200
;
SHA1Update
(
context
,
&
c
,
1
);
while
((
context
->
count
[
0
]
&
504
)
!=
448
)
{
c
=
0000
;
SHA1Update
(
context
,
&
c
,
1
);
}
SHA1Update
(
context
,
finalcount
,
8
);
for
(
i
=
0
;
i
<
20
;
i
++
)
{
digest
[
i
]
=
(
unsigned
char
)
((
context
->
state
[
i
>>
2
]
>>
((
3
-
(
i
&
3
))
*
8
)
)
&
255
);
}
memset
(
context
,
'\0'
,
sizeof
(
*
context
));
memset
(
&
finalcount
,
'\0'
,
sizeof
(
finalcount
));
}
// END OF SHA1 CODE
// Return 1 if real file has been found, 0 otherwise
static
int
convert_uri_to_file_name
(
struct
mg_connection
*
conn
,
char
*
buf
,
size_t
buf_len
,
struct
file
*
filep
)
{
struct
vec
a
,
b
;
const
char
*
rewrite
,
*
uri
=
conn
->
request_info
.
uri
,
*
root
=
conn
->
ctx
->
config
[
DOCUMENT_ROOT
];
char
*
p
;
int
match_len
;
char
gz_path
[
PATH_MAX
];
char
const
*
accept_encoding
;
// No filesystem access
if
(
root
==
NULL
)
{
return
0
;
}
static
void
base64_encode
(
const
unsigned
char
*
src
,
int
src_len
,
char
*
dst
)
{
static
const
char
*
b64
=
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
;
int
i
,
j
,
a
,
b
,
c
;
// Using buf_len - 1 because memmove() for PATH_INFO may shift part
// of the path one byte on the right.
// If document_root is NULL, leave the file empty.
mg_snprintf
(
buf
,
buf_len
-
1
,
"%s%s"
,
root
,
uri
)
;
for
(
i
=
j
=
0
;
i
<
src_len
;
i
+=
3
)
{
a
=
src
[
i
];
b
=
i
+
1
>=
src_len
?
0
:
src
[
i
+
1
];
c
=
i
+
2
>=
src_len
?
0
:
src
[
i
+
2
]
;
rewrite
=
conn
->
ctx
->
config
[
REWRITE
];
while
((
rewrite
=
next_option
(
rewrite
,
&
a
,
&
b
))
!=
NULL
)
{
if
((
match_len
=
match_prefix
(
a
.
ptr
,
a
.
len
,
uri
))
>
0
)
{
mg_snprintf
(
buf
,
buf_len
-
1
,
"%.*s%s"
,
(
int
)
b
.
len
,
b
.
ptr
,
uri
+
match_len
);
break
;
dst
[
j
++
]
=
b64
[
a
>>
2
];
dst
[
j
++
]
=
b64
[((
a
&
3
)
<<
4
)
|
(
b
>>
4
)];
if
(
i
+
1
<
src_len
)
{
dst
[
j
++
]
=
b64
[(
b
&
15
)
<<
2
|
(
c
>>
6
)];
}
if
(
i
+
2
<
src_len
)
{
dst
[
j
++
]
=
b64
[
c
&
63
];
}
}
if
(
mg_stat
(
buf
,
filep
))
{
return
1
;
while
(
j
%
4
!=
0
)
{
dst
[
j
++
]
=
'='
;
}
dst
[
j
++
]
=
'\0'
;
}
// if we can't find the actual file, look for the file
// with the same name but a .gz extension. If we find it,
// use that and set the gzipped flag in the file struct
// to indicate that the response need to have the content-
// encoding: gzip header
// we can only do this if the browser declares support
if
((
accept_encoding
=
mg_get_header
(
conn
,
"Accept-Encoding"
))
!=
NULL
)
{
if
(
strstr
(
accept_encoding
,
"gzip"
)
!=
NULL
)
{
snprintf
(
gz_path
,
sizeof
(
gz_path
),
"%s.gz"
,
buf
);
if
(
mg_stat
(
gz_path
,
filep
))
{
filep
->
gzipped
=
1
;
return
1
;
void
mg_websocket_handshake
(
struct
mg_connection
*
conn
)
{
static
const
char
*
magic
=
"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
;
char
buf
[
100
],
sha
[
20
],
b64_sha
[
sizeof
(
sha
)
*
2
];
SHA1_CTX
sha_ctx
;
mg_snprintf
(
buf
,
sizeof
(
buf
),
"%s%s"
,
mg_get_header
(
conn
,
"Sec-WebSocket-Key"
),
magic
);
SHA1Init
(
&
sha_ctx
);
SHA1Update
(
&
sha_ctx
,
(
unsigned
char
*
)
buf
,
strlen
(
buf
));
SHA1Final
((
unsigned
char
*
)
sha
,
&
sha_ctx
);
base64_encode
((
unsigned
char
*
)
sha
,
sizeof
(
sha
),
b64_sha
);
mg_printf
(
conn
,
"%s%s%s"
,
"HTTP/1.1 101 Switching Protocols
\r\n
"
"Upgrade: websocket
\r\n
"
"Connection: Upgrade
\r\n
"
"Sec-WebSocket-Accept: "
,
b64_sha
,
"
\r\n\r\n
"
);
}
int
mg_websocket_read
(
struct
mg_connection
*
conn
,
int
*
bits
,
char
**
data
)
{
// Pointer to the beginning of the portion of the incoming websocket message
// queue. The original websocket upgrade request is never removed,
// so the queue begins after it.
unsigned
char
*
buf
=
(
unsigned
char
*
)
conn
->
buf
+
conn
->
request_len
;
int
n
,
stop
=
0
;
size_t
i
,
len
,
mask_len
,
data_len
,
header_len
,
body_len
;
char
mask
[
4
];
assert
(
conn
->
content_len
==
0
);
// Loop continuously, reading messages from the socket, invoking the callback,
// and waiting repeatedly until an error occurs.
while
(
!
stop
)
{
header_len
=
0
;
// body_len is the length of the entire queue in bytes
// len is the length of the current message
// data_len is the length of the current message's data payload
// header_len is the length of the current message's header
if
((
body_len
=
conn
->
data_len
-
conn
->
request_len
)
>=
2
)
{
len
=
buf
[
1
]
&
127
;
mask_len
=
buf
[
1
]
&
128
?
4
:
0
;
if
(
len
<
126
&&
body_len
>=
mask_len
)
{
data_len
=
len
;
header_len
=
2
+
mask_len
;
}
else
if
(
len
==
126
&&
body_len
>=
4
+
mask_len
)
{
header_len
=
4
+
mask_len
;
data_len
=
((((
int
)
buf
[
2
])
<<
8
)
+
buf
[
3
]);
}
else
if
(
body_len
>=
10
+
mask_len
)
{
header_len
=
10
+
mask_len
;
data_len
=
(((
uint64_t
)
htonl
(
*
(
uint32_t
*
)
&
buf
[
2
]))
<<
32
)
+
htonl
(
*
(
uint32_t
*
)
&
buf
[
6
]);
}
}
// Data layout is as follows:
// conn->buf buf
// v v frame1 | frame2
// |---------------------|----------------|--------------|-------
// | |<--header_len-->|<--data_len-->|
// |<-conn->request_len->|<-----body_len----------->|
// |<-------------------conn->data_len------------->|
if
(
header_len
>
0
)
{
// Allocate space to hold websocket payload
if
((
*
data
=
malloc
(
data_len
))
==
NULL
)
{
// Allocation failed, exit the loop and then close the connection
// TODO: notify user about the failure
data_len
=
0
;
break
;
}
// Save mask and bits, otherwise it may be clobbered by memmove below
*
bits
=
buf
[
0
];
memcpy
(
mask
,
buf
+
header_len
-
mask_len
,
mask_len
);
// Read frame payload into the allocated buffer.
assert
(
body_len
>=
header_len
);
if
(
data_len
+
header_len
>
body_len
)
{
len
=
body_len
-
header_len
;
memcpy
(
*
data
,
buf
+
header_len
,
len
);
// TODO: handle pull error
pull_all
(
NULL
,
conn
,
*
data
+
len
,
data_len
-
len
);
conn
->
data_len
=
conn
->
request_len
;
}
else
{
len
=
data_len
+
header_len
;
memcpy
(
*
data
,
buf
+
header_len
,
data_len
);
memmove
(
buf
,
buf
+
len
,
body_len
-
len
);
conn
->
data_len
-=
len
;
}
// Apply mask if necessary
if
(
mask_len
>
0
)
{
for
(
i
=
0
;
i
<
data_len
;
i
++
)
{
(
*
data
)[
i
]
^=
mask
[
i
%
4
];
}
}
// Support PATH_INFO for CGI scripts.
for
(
p
=
buf
+
strlen
(
root
==
NULL
?
""
:
root
);
*
p
!=
'\0'
;
p
++
)
{
if
(
*
p
==
'/'
)
{
*
p
=
'\0'
;
if
(
match_prefix
(
conn
->
ctx
->
config
[
CGI_EXTENSIONS
],
strlen
(
conn
->
ctx
->
config
[
CGI_EXTENSIONS
]),
buf
)
>
0
&&
mg_stat
(
buf
,
filep
))
{
// Shift PATH_INFO block one character right, e.g.
// "/x.cgi/foo/bar\x00" => "/x.cgi\x00/foo/bar\x00"
// conn->path_info is pointing to the local variable "path" declared
// in handle_request(), so PATH_INFO is not valid after
// handle_request returns.
conn
->
path_info
=
p
+
1
;
memmove
(
p
+
2
,
p
+
1
,
strlen
(
p
+
1
)
+
1
);
// +1 is for trailing \0
p
[
1
]
=
'/'
;
return
1
;
return
data_len
;
}
else
{
*
p
=
'/'
;
// Buffering websocket request
if
((
n
=
pull
(
NULL
,
conn
,
conn
->
buf
+
conn
->
data_len
,
conn
->
buf_size
-
conn
->
data_len
))
<=
0
)
{
break
;
}
conn
->
data_len
+=
n
;
}
}
return
0
;
}
static
void
gmt_time_string
(
char
*
buf
,
size_t
buf_len
,
time_t
*
t
)
{
strftime
(
buf
,
buf_len
,
"%a, %d %b %Y %H:%M:%S GMT"
,
gmtime
(
t
));
int
mg_websocket_write
(
struct
mg_connection
*
conn
,
int
opcode
,
const
char
*
data
,
size_t
data_len
)
{
unsigned
char
*
copy
;
size_t
copy_len
=
0
;
int
retval
=
-
1
;
if
((
copy
=
(
unsigned
char
*
)
malloc
(
data_len
+
10
))
==
NULL
)
{
return
-
1
;
}
copy
[
0
]
=
0x80
+
(
opcode
&
0x0f
);
// Frame format: http://tools.ietf.org/html/rfc6455#section-5.2
if
(
data_len
<
126
)
{
// Inline 7-bit length field
copy
[
1
]
=
data_len
;
memcpy
(
copy
+
2
,
data
,
data_len
);
copy_len
=
2
+
data_len
;
}
else
if
(
data_len
<=
0xFFFF
)
{
// 16-bit length field
copy
[
1
]
=
126
;
*
(
uint16_t
*
)
(
copy
+
2
)
=
htons
(
data_len
);
memcpy
(
copy
+
4
,
data
,
data_len
);
copy_len
=
4
+
data_len
;
}
else
{
// 64-bit length field
copy
[
1
]
=
127
;
*
(
uint32_t
*
)
(
copy
+
2
)
=
htonl
((
uint64_t
)
data_len
>>
32
);
*
(
uint32_t
*
)
(
copy
+
6
)
=
htonl
(
data_len
&
0xffffffff
);
memcpy
(
copy
+
10
,
data
,
data_len
);
copy_len
=
10
+
data_len
;
}
// Not thread safe
if
(
copy_len
>
0
)
{
retval
=
mg_write
(
conn
,
copy
,
copy_len
);
}
free
(
copy
);
return
retval
;
}
#endif // !USE_WEBSOCKET
static
void
construct_etag
(
char
*
buf
,
size_t
buf_len
,
const
struct
file
*
filep
)
{
snprintf
(
buf
,
buf_len
,
"
\"
%lx.%"
INT64_FMT
"
\"
"
,
(
unsigned
long
)
filep
->
modification_time
,
filep
->
size
);
static
int
call_user
(
int
type
,
struct
mg_connection
*
conn
,
void
*
p
)
{
if
(
conn
!=
NULL
&&
conn
->
ctx
!=
NULL
)
{
conn
->
event
.
user_data
=
conn
->
ctx
->
user_data
;
conn
->
event
.
type
=
type
;
conn
->
event
.
event_param
=
p
;
conn
->
event
.
request_info
=
&
conn
->
request_info
;
conn
->
event
.
conn
=
conn
;
}
return
conn
==
NULL
||
conn
->
ctx
==
NULL
||
conn
->
ctx
->
event_handler
==
NULL
?
0
:
conn
->
ctx
->
event_handler
(
&
conn
->
event
);
}
static
void
fclose_on_exec
(
FILE
*
fp
)
{
if
(
fp
!=
NULL
)
{
#ifndef _WIN32
fcntl
(
fileno
(
fp
),
F_SETFD
,
FD_CLOEXEC
);
static
FILE
*
mg_fopen
(
const
char
*
path
,
const
char
*
mode
)
{
#ifdef _WIN32
wchar_t
wbuf
[
PATH_MAX
],
wmode
[
20
];
to_unicode
(
path
,
wbuf
,
ARRAY_SIZE
(
wbuf
));
MultiByteToWideChar
(
CP_UTF8
,
0
,
mode
,
-
1
,
wmode
,
ARRAY_SIZE
(
wmode
));
return
_wfopen
(
wbuf
,
wmode
);
#else
return
fopen
(
path
,
mode
);
#endif
}
}
static
void
handle_file_request
(
struct
mg_connection
*
conn
,
const
char
*
path
,
struct
file
*
filep
)
{
char
date
[
64
],
lm
[
64
],
etag
[
64
],
range
[
64
];
const
char
*
msg
=
"OK"
,
*
hdr
;
time_t
curtime
=
time
(
NULL
);
int64_t
cl
,
r1
,
r2
;
struct
vec
mime_vec
;
int
n
;
char
gz_path
[
PATH_MAX
];
char
const
*
encoding
=
""
;
// Print error message to the opened error log stream.
static
void
cry
(
struct
mg_connection
*
conn
,
const
char
*
fmt
,
...)
{
char
buf
[
MG_BUF_LEN
],
src_addr
[
IP_ADDR_STR_LEN
];
va_list
ap
;
FILE
*
fp
;
time_t
timestamp
;
get_mime_type
(
conn
->
ctx
,
path
,
&
mime_vec
);
cl
=
filep
->
size
;
conn
->
status_code
=
200
;
range
[
0
]
=
'\0'
;
va_start
(
ap
,
fmt
);
(
void
)
vsnprintf
(
buf
,
sizeof
(
buf
),
fmt
,
ap
);
va_end
(
ap
);
// if this file is in fact a pre-gzipped file, rewrite its filename
// it's important to rewrite the filename after resolving
// the mime type from it, to preserve the actual file's type
if
(
filep
->
gzipped
)
{
snprintf
(
gz_path
,
sizeof
(
gz_path
),
"%s.gz"
,
path
);
path
=
gz_path
;
encoding
=
"Content-Encoding: gzip
\r\n
"
;
// Do not lock when getting the callback value, here and below.
// I suppose this is fine, since function cannot disappear in the
// same way string option can.
if
(
call_user
(
MG_EVENT_LOG
,
conn
,
buf
)
==
0
)
{
fp
=
conn
->
ctx
==
NULL
||
conn
->
ctx
->
config
[
ERROR_LOG_FILE
]
==
NULL
?
NULL
:
fopen
(
conn
->
ctx
->
config
[
ERROR_LOG_FILE
],
"a+"
);
if
(
fp
!=
NULL
)
{
flockfile
(
fp
);
timestamp
=
time
(
NULL
);
sockaddr_to_string
(
src_addr
,
sizeof
(
src_addr
),
&
conn
->
client
.
rsa
);
fprintf
(
fp
,
"[%010lu] [error] [client %s] "
,
(
unsigned
long
)
timestamp
,
src_addr
);
if
(
conn
->
request_info
.
request_method
!=
NULL
)
{
fprintf
(
fp
,
"%s %s: "
,
conn
->
request_info
.
request_method
,
conn
->
request_info
.
uri
);
}
if
((
fp
=
mg_fopen
(
path
,
"rb"
))
==
NULL
)
{
send_http_error
(
conn
,
500
,
http_500_error
,
"fopen(%s): %s"
,
path
,
strerror
(
ERRNO
)
);
return
;
fprintf
(
fp
,
"%s"
,
buf
);
fputc
(
'\n'
,
fp
);
funlockfile
(
fp
);
fclose
(
fp
)
;
}
}
}
fclose_on_exec
(
fp
);
const
char
*
mg_version
(
void
)
{
return
MONGOOSE_VERSION
;
}
// If Range: header specified, act accordingly
r1
=
r2
=
0
;
hdr
=
mg_get_header
(
conn
,
"Range"
);
if
(
hdr
!=
NULL
&&
(
n
=
parse_range_header
(
hdr
,
&
r1
,
&
r2
))
>
0
&&
// HTTP 1.1 assumes keep alive if "Connection:" header is not set
// This function must tolerate situations when connection info is not
// set up, for example if request parsing failed.
static
int
should_keep_alive
(
const
struct
mg_connection
*
conn
)
{
const
char
*
http_version
=
conn
->
request_info
.
http_version
;
const
char
*
header
=
mg_get_header
(
conn
,
"Connection"
);
if
(
conn
->
must_close
||
conn
->
status_code
==
401
||
mg_strcasecmp
(
conn
->
ctx
->
config
[
ENABLE_KEEP_ALIVE
],
"yes"
)
!=
0
||
(
header
!=
NULL
&&
mg_strcasecmp
(
header
,
"keep-alive"
)
!=
0
)
||
(
header
==
NULL
&&
http_version
&&
strcmp
(
http_version
,
"1.1"
)))
{
return
0
;
}
return
1
;
}
static
const
char
*
suggest_connection_header
(
const
struct
mg_connection
*
conn
)
{
return
should_keep_alive
(
conn
)
?
"keep-alive"
:
"close"
;
}
static
void
send_http_error
(
struct
mg_connection
*
conn
,
int
status
,
const
char
*
reason
,
const
char
*
fmt
,
...)
{
char
buf
[
MG_BUF_LEN
];
va_list
ap
;
int
len
=
0
;
conn
->
status_code
=
status
;
buf
[
0
]
=
'\0'
;
// Errors 1xx, 204 and 304 MUST NOT send a body
if
(
status
>
199
&&
status
!=
204
&&
status
!=
304
)
{
len
=
mg_snprintf
(
buf
,
sizeof
(
buf
),
"Error %d: %s"
,
status
,
reason
);
buf
[
len
++
]
=
'\n'
;
va_start
(
ap
,
fmt
);
len
+=
mg_vsnprintf
(
buf
+
len
,
sizeof
(
buf
)
-
len
,
fmt
,
ap
);
va_end
(
ap
);
}
DEBUG_TRACE
((
"[%s]"
,
buf
));
if
(
call_user
(
MG_HTTP_ERROR
,
conn
,
(
void
*
)
(
long
)
status
)
==
0
)
{
mg_printf
(
conn
,
"HTTP/1.1 %d %s
\r\n
"
"Content-Length: %d
\r\n
"
"Connection: %s
\r\n\r\n
"
,
status
,
reason
,
len
,
suggest_connection_header
(
conn
));
conn
->
num_bytes_sent
+=
mg_printf
(
conn
,
"%s"
,
buf
);
}
}
// Return 1 if real file has been found, 0 otherwise
static
int
convert_uri_to_file_name
(
struct
mg_connection
*
conn
,
char
*
buf
,
size_t
buf_len
,
struct
file
*
filep
)
{
struct
vec
a
,
b
;
const
char
*
rewrite
,
*
uri
=
conn
->
request_info
.
uri
,
*
root
=
conn
->
ctx
->
config
[
DOCUMENT_ROOT
];
char
*
p
;
int
match_len
;
char
gz_path
[
PATH_MAX
];
char
const
*
accept_encoding
;
// No filesystem access
if
(
root
==
NULL
)
{
return
0
;
}
// Using buf_len - 1 because memmove() for PATH_INFO may shift part
// of the path one byte on the right.
// If document_root is NULL, leave the file empty.
mg_snprintf
(
buf
,
buf_len
-
1
,
"%s%s"
,
root
,
uri
);
rewrite
=
conn
->
ctx
->
config
[
REWRITE
];
while
((
rewrite
=
next_option
(
rewrite
,
&
a
,
&
b
))
!=
NULL
)
{
if
((
match_len
=
match_prefix
(
a
.
ptr
,
a
.
len
,
uri
))
>
0
)
{
mg_snprintf
(
buf
,
buf_len
-
1
,
"%.*s%s"
,
(
int
)
b
.
len
,
b
.
ptr
,
uri
+
match_len
);
break
;
}
}
if
(
mg_stat
(
buf
,
filep
))
{
return
1
;
}
// if we can't find the actual file, look for the file
// with the same name but a .gz extension. If we find it,
// use that and set the gzipped flag in the file struct
// to indicate that the response need to have the content-
// encoding: gzip header
// we can only do this if the browser declares support
if
((
accept_encoding
=
mg_get_header
(
conn
,
"Accept-Encoding"
))
!=
NULL
)
{
if
(
strstr
(
accept_encoding
,
"gzip"
)
!=
NULL
)
{
snprintf
(
gz_path
,
sizeof
(
gz_path
),
"%s.gz"
,
buf
);
if
(
mg_stat
(
gz_path
,
filep
))
{
filep
->
gzipped
=
1
;
return
1
;
}
}
}
// Support PATH_INFO for CGI scripts.
for
(
p
=
buf
+
strlen
(
root
==
NULL
?
""
:
root
);
*
p
!=
'\0'
;
p
++
)
{
if
(
*
p
==
'/'
)
{
*
p
=
'\0'
;
if
(
match_prefix
(
conn
->
ctx
->
config
[
CGI_EXTENSIONS
],
strlen
(
conn
->
ctx
->
config
[
CGI_EXTENSIONS
]),
buf
)
>
0
&&
mg_stat
(
buf
,
filep
))
{
// Shift PATH_INFO block one character right, e.g.
// "/x.cgi/foo/bar\x00" => "/x.cgi\x00/foo/bar\x00"
// conn->path_info is pointing to the local variable "path" declared
// in handle_request(), so PATH_INFO is not valid after
// handle_request returns.
conn
->
path_info
=
p
+
1
;
memmove
(
p
+
2
,
p
+
1
,
strlen
(
p
+
1
)
+
1
);
// +1 is for trailing \0
p
[
1
]
=
'/'
;
return
1
;
}
else
{
*
p
=
'/'
;
}
}
}
return
0
;
}
static
void
gmt_time_string
(
char
*
buf
,
size_t
buf_len
,
time_t
*
t
)
{
strftime
(
buf
,
buf_len
,
"%a, %d %b %Y %H:%M:%S GMT"
,
gmtime
(
t
));
}
static
void
construct_etag
(
char
*
buf
,
size_t
buf_len
,
const
struct
file
*
filep
)
{
snprintf
(
buf
,
buf_len
,
"
\"
%lx.%"
INT64_FMT
"
\"
"
,
(
unsigned
long
)
filep
->
modification_time
,
filep
->
size
);
}
static
void
fclose_on_exec
(
FILE
*
fp
)
{
if
(
fp
!=
NULL
)
{
#ifndef _WIN32
fcntl
(
fileno
(
fp
),
F_SETFD
,
FD_CLOEXEC
);
#endif
}
}
static
void
handle_file_request
(
struct
mg_connection
*
conn
,
const
char
*
path
,
struct
file
*
filep
)
{
char
date
[
64
],
lm
[
64
],
etag
[
64
],
range
[
64
];
const
char
*
msg
=
"OK"
,
*
hdr
;
time_t
curtime
=
time
(
NULL
);
int64_t
cl
,
r1
,
r2
;
struct
vec
mime_vec
;
int
n
;
char
gz_path
[
PATH_MAX
];
char
const
*
encoding
=
""
;
FILE
*
fp
;
get_mime_type
(
conn
->
ctx
,
path
,
&
mime_vec
);
cl
=
filep
->
size
;
conn
->
status_code
=
200
;
range
[
0
]
=
'\0'
;
// if this file is in fact a pre-gzipped file, rewrite its filename
// it's important to rewrite the filename after resolving
// the mime type from it, to preserve the actual file's type
if
(
filep
->
gzipped
)
{
snprintf
(
gz_path
,
sizeof
(
gz_path
),
"%s.gz"
,
path
);
path
=
gz_path
;
encoding
=
"Content-Encoding: gzip
\r\n
"
;
}
if
((
fp
=
mg_fopen
(
path
,
"rb"
))
==
NULL
)
{
send_http_error
(
conn
,
500
,
http_500_error
,
"fopen(%s): %s"
,
path
,
strerror
(
ERRNO
));
return
;
}
fclose_on_exec
(
fp
);
// If Range: header specified, act accordingly
r1
=
r2
=
0
;
hdr
=
mg_get_header
(
conn
,
"Range"
);
if
(
hdr
!=
NULL
&&
(
n
=
parse_range_header
(
hdr
,
&
r1
,
&
r2
))
>
0
&&
r1
>=
0
&&
r2
>=
0
)
{
// actually, range requests don't play well with a pre-gzipped
// file (since the range is specified in the uncmpressed space)
...
...
@@ -4001,499 +4309,192 @@ static void do_ssi_include(struct mg_connection *conn, const char *ssi,
sizeof
(
path
)
-
strlen
(
path
),
"%s"
,
file_name
);
}
else
{
cry
(
conn
,
"Bad SSI #include: [%s]"
,
tag
);
return
;
}
if
((
fp
=
mg_fopen
(
path
,
"rb"
))
==
NULL
)
{
cry
(
conn
,
"Cannot open SSI #include: [%s]: fopen(%s): %s"
,
tag
,
path
,
strerror
(
ERRNO
));
}
else
{
fclose_on_exec
(
fp
);
if
(
match_prefix
(
conn
->
ctx
->
config
[
SSI_EXTENSIONS
],
strlen
(
conn
->
ctx
->
config
[
SSI_EXTENSIONS
]),
path
)
>
0
)
{
send_ssi_file
(
conn
,
path
,
fp
,
include_level
+
1
);
}
else
{
send_file_data
(
conn
,
fp
,
0
,
INT64_MAX
);
}
fclose
(
fp
);
}
}
#if !defined(NO_POPEN)
static
void
do_ssi_exec
(
struct
mg_connection
*
conn
,
char
*
tag
)
{
char
cmd
[
MG_BUF_LEN
];
FILE
*
fp
;
if
(
sscanf
(
tag
,
"
\"
%[^
\"
]
\"
"
,
cmd
)
!=
1
)
{
cry
(
conn
,
"Bad SSI #exec: [%s]"
,
tag
);
}
else
if
((
fp
=
popen
(
cmd
,
"r"
))
==
NULL
)
{
cry
(
conn
,
"Cannot SSI #exec: [%s]: %s"
,
cmd
,
strerror
(
ERRNO
));
}
else
{
send_file_data
(
conn
,
fp
,
0
,
INT64_MAX
);
pclose
(
fp
);
}
}
#endif // !NO_POPEN
static
void
send_ssi_file
(
struct
mg_connection
*
conn
,
const
char
*
path
,
FILE
*
fp
,
int
include_level
)
{
char
buf
[
MG_BUF_LEN
];
int
ch
,
offset
,
len
,
in_ssi_tag
;
if
(
include_level
>
10
)
{
cry
(
conn
,
"SSI #include level is too deep (%s)"
,
path
);
return
;
}
in_ssi_tag
=
len
=
offset
=
0
;
while
((
ch
=
fgetc
(
fp
))
!=
EOF
)
{
if
(
in_ssi_tag
&&
ch
==
'>'
)
{
in_ssi_tag
=
0
;
buf
[
len
++
]
=
(
char
)
ch
;
buf
[
len
]
=
'\0'
;
assert
(
len
<=
(
int
)
sizeof
(
buf
));
if
(
len
<
6
||
memcmp
(
buf
,
"<!--#"
,
5
)
!=
0
)
{
// Not an SSI tag, pass it
(
void
)
mg_write
(
conn
,
buf
,
(
size_t
)
len
);
}
else
{
if
(
!
memcmp
(
buf
+
5
,
"include"
,
7
))
{
do_ssi_include
(
conn
,
path
,
buf
+
12
,
include_level
);
#if !defined(NO_POPEN)
}
else
if
(
!
memcmp
(
buf
+
5
,
"exec"
,
4
))
{
do_ssi_exec
(
conn
,
buf
+
9
);
#endif // !NO_POPEN
}
else
{
cry
(
conn
,
"%s: unknown SSI "
"command:
\"
%s
\"
"
,
path
,
buf
);
}
}
len
=
0
;
}
else
if
(
in_ssi_tag
)
{
if
(
len
==
5
&&
memcmp
(
buf
,
"<!--#"
,
5
)
!=
0
)
{
// Not an SSI tag
in_ssi_tag
=
0
;
}
else
if
(
len
==
(
int
)
sizeof
(
buf
)
-
2
)
{
cry
(
conn
,
"%s: SSI tag is too large"
,
path
);
len
=
0
;
}
buf
[
len
++
]
=
ch
&
0xff
;
}
else
if
(
ch
==
'<'
)
{
in_ssi_tag
=
1
;
if
(
len
>
0
)
{
mg_write
(
conn
,
buf
,
(
size_t
)
len
);
}
len
=
0
;
buf
[
len
++
]
=
ch
&
0xff
;
}
else
{
buf
[
len
++
]
=
ch
&
0xff
;
if
(
len
==
(
int
)
sizeof
(
buf
))
{
mg_write
(
conn
,
buf
,
(
size_t
)
len
);
len
=
0
;
}
}
}
// Send the rest of buffered data
if
(
len
>
0
)
{
mg_write
(
conn
,
buf
,
(
size_t
)
len
);
}
}
static
void
handle_ssi_file_request
(
struct
mg_connection
*
conn
,
const
char
*
path
)
{
struct
vec
mime_vec
;
FILE
*
fp
;
if
((
fp
=
mg_fopen
(
path
,
"rb"
))
==
NULL
)
{
send_http_error
(
conn
,
500
,
http_500_error
,
"fopen(%s): %s"
,
path
,
strerror
(
ERRNO
));
}
else
{
conn
->
must_close
=
1
;
fclose_on_exec
(
fp
);
get_mime_type
(
conn
->
ctx
,
path
,
&
mime_vec
);
mg_printf
(
conn
,
"HTTP/1.1 200 OK
\r\n
"
"Content-Type: %.*s
\r\n
"
"Connection: close
\r\n\r\n
"
,
(
int
)
mime_vec
.
len
,
mime_vec
.
ptr
);
send_ssi_file
(
conn
,
path
,
fp
,
0
);
fclose
(
fp
);
}
}
static
void
handle_options_request
(
struct
mg_connection
*
conn
)
{
static
const
char
reply
[]
=
"HTTP/1.1 200 OK
\r\n
"
"Allow: GET, POST, HEAD, CONNECT, PUT, DELETE, OPTIONS, PROPFIND, MKCOL
\r\n
"
"DAV: 1
\r\n\r\n
"
;
conn
->
status_code
=
200
;
mg_write
(
conn
,
reply
,
sizeof
(
reply
)
-
1
);
}
// Writes PROPFIND properties for a collection element
static
void
print_props
(
struct
mg_connection
*
conn
,
const
char
*
uri
,
struct
file
*
filep
)
{
char
mtime
[
64
];
gmt_time_string
(
mtime
,
sizeof
(
mtime
),
&
filep
->
modification_time
);
conn
->
num_bytes_sent
+=
mg_printf
(
conn
,
"<d:response>"
"<d:href>%s</d:href>"
"<d:propstat>"
"<d:prop>"
"<d:resourcetype>%s</d:resourcetype>"
"<d:getcontentlength>%"
INT64_FMT
"</d:getcontentlength>"
"<d:getlastmodified>%s</d:getlastmodified>"
"</d:prop>"
"<d:status>HTTP/1.1 200 OK</d:status>"
"</d:propstat>"
"</d:response>
\n
"
,
uri
,
filep
->
is_directory
?
"<d:collection/>"
:
""
,
filep
->
size
,
mtime
);
}
static
void
print_dav_dir_entry
(
struct
de
*
de
,
void
*
data
)
{
char
href
[
PATH_MAX
];
char
href_encoded
[
PATH_MAX
];
struct
mg_connection
*
conn
=
(
struct
mg_connection
*
)
data
;
mg_snprintf
(
href
,
sizeof
(
href
),
"%s%s"
,
conn
->
request_info
.
uri
,
de
->
file_name
);
mg_url_encode
(
href
,
href_encoded
,
PATH_MAX
-
1
);
print_props
(
conn
,
href_encoded
,
&
de
->
file
);
}
static
void
handle_propfind
(
struct
mg_connection
*
conn
,
const
char
*
path
,
struct
file
*
filep
)
{
const
char
*
depth
=
mg_get_header
(
conn
,
"Depth"
);
conn
->
must_close
=
1
;
conn
->
status_code
=
207
;
mg_printf
(
conn
,
"HTTP/1.1 207 Multi-Status
\r\n
"
"Connection: close
\r\n
"
"Content-Type: text/xml; charset=utf-8
\r\n\r\n
"
);
conn
->
num_bytes_sent
+=
mg_printf
(
conn
,
"<?xml version=
\"
1.0
\"
encoding=
\"
utf-8
\"
?>"
"<d:multistatus xmlns:d='DAV:'>
\n
"
);
// Print properties for the requested resource itself
print_props
(
conn
,
conn
->
request_info
.
uri
,
filep
);
// If it is a directory, print directory entries too if Depth is not 0
if
(
filep
->
is_directory
&&
!
mg_strcasecmp
(
conn
->
ctx
->
config
[
ENABLE_DIRECTORY_LISTING
],
"yes"
)
&&
(
depth
==
NULL
||
strcmp
(
depth
,
"0"
)
!=
0
))
{
scan_directory
(
conn
,
path
,
conn
,
&
print_dav_dir_entry
);
}
conn
->
num_bytes_sent
+=
mg_printf
(
conn
,
"%s
\n
"
,
"</d:multistatus>"
);
}
#if defined(USE_WEBSOCKET)
// START OF SHA-1 code
// Copyright(c) By Steve Reid <steve@edmweb.com>
#define SHA1HANDSOFF
#if defined(__sun)
#include "solarisfixes.h"
#endif
union
char64long16
{
unsigned
char
c
[
64
];
uint32_t
l
[
16
];
};
#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
static
uint32_t
blk0
(
union
char64long16
*
block
,
int
i
)
{
// Forrest: SHA expect BIG_ENDIAN, swap if LITTLE_ENDIAN
if
(
!
is_big_endian
())
{
block
->
l
[
i
]
=
(
rol
(
block
->
l
[
i
],
24
)
&
0xFF00FF00
)
|
(
rol
(
block
->
l
[
i
],
8
)
&
0x00FF00FF
);
}
return
block
->
l
[
i
];
}
#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \
^block->l[(i+2)&15]^block->l[i&15],1))
#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(block, i)+0x5A827999+rol(v,5);w=rol(w,30);
#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30);
#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30);
#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30);
#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30);
typedef
struct
{
uint32_t
state
[
5
];
uint32_t
count
[
2
];
unsigned
char
buffer
[
64
];
}
SHA1_CTX
;
static
void
SHA1Transform
(
uint32_t
state
[
5
],
const
unsigned
char
buffer
[
64
])
{
uint32_t
a
,
b
,
c
,
d
,
e
;
union
char64long16
block
[
1
];
memcpy
(
block
,
buffer
,
64
);
a
=
state
[
0
];
b
=
state
[
1
];
c
=
state
[
2
];
d
=
state
[
3
];
e
=
state
[
4
];
R0
(
a
,
b
,
c
,
d
,
e
,
0
);
R0
(
e
,
a
,
b
,
c
,
d
,
1
);
R0
(
d
,
e
,
a
,
b
,
c
,
2
);
R0
(
c
,
d
,
e
,
a
,
b
,
3
);
R0
(
b
,
c
,
d
,
e
,
a
,
4
);
R0
(
a
,
b
,
c
,
d
,
e
,
5
);
R0
(
e
,
a
,
b
,
c
,
d
,
6
);
R0
(
d
,
e
,
a
,
b
,
c
,
7
);
R0
(
c
,
d
,
e
,
a
,
b
,
8
);
R0
(
b
,
c
,
d
,
e
,
a
,
9
);
R0
(
a
,
b
,
c
,
d
,
e
,
10
);
R0
(
e
,
a
,
b
,
c
,
d
,
11
);
R0
(
d
,
e
,
a
,
b
,
c
,
12
);
R0
(
c
,
d
,
e
,
a
,
b
,
13
);
R0
(
b
,
c
,
d
,
e
,
a
,
14
);
R0
(
a
,
b
,
c
,
d
,
e
,
15
);
R1
(
e
,
a
,
b
,
c
,
d
,
16
);
R1
(
d
,
e
,
a
,
b
,
c
,
17
);
R1
(
c
,
d
,
e
,
a
,
b
,
18
);
R1
(
b
,
c
,
d
,
e
,
a
,
19
);
R2
(
a
,
b
,
c
,
d
,
e
,
20
);
R2
(
e
,
a
,
b
,
c
,
d
,
21
);
R2
(
d
,
e
,
a
,
b
,
c
,
22
);
R2
(
c
,
d
,
e
,
a
,
b
,
23
);
R2
(
b
,
c
,
d
,
e
,
a
,
24
);
R2
(
a
,
b
,
c
,
d
,
e
,
25
);
R2
(
e
,
a
,
b
,
c
,
d
,
26
);
R2
(
d
,
e
,
a
,
b
,
c
,
27
);
R2
(
c
,
d
,
e
,
a
,
b
,
28
);
R2
(
b
,
c
,
d
,
e
,
a
,
29
);
R2
(
a
,
b
,
c
,
d
,
e
,
30
);
R2
(
e
,
a
,
b
,
c
,
d
,
31
);
R2
(
d
,
e
,
a
,
b
,
c
,
32
);
R2
(
c
,
d
,
e
,
a
,
b
,
33
);
R2
(
b
,
c
,
d
,
e
,
a
,
34
);
R2
(
a
,
b
,
c
,
d
,
e
,
35
);
R2
(
e
,
a
,
b
,
c
,
d
,
36
);
R2
(
d
,
e
,
a
,
b
,
c
,
37
);
R2
(
c
,
d
,
e
,
a
,
b
,
38
);
R2
(
b
,
c
,
d
,
e
,
a
,
39
);
R3
(
a
,
b
,
c
,
d
,
e
,
40
);
R3
(
e
,
a
,
b
,
c
,
d
,
41
);
R3
(
d
,
e
,
a
,
b
,
c
,
42
);
R3
(
c
,
d
,
e
,
a
,
b
,
43
);
R3
(
b
,
c
,
d
,
e
,
a
,
44
);
R3
(
a
,
b
,
c
,
d
,
e
,
45
);
R3
(
e
,
a
,
b
,
c
,
d
,
46
);
R3
(
d
,
e
,
a
,
b
,
c
,
47
);
R3
(
c
,
d
,
e
,
a
,
b
,
48
);
R3
(
b
,
c
,
d
,
e
,
a
,
49
);
R3
(
a
,
b
,
c
,
d
,
e
,
50
);
R3
(
e
,
a
,
b
,
c
,
d
,
51
);
R3
(
d
,
e
,
a
,
b
,
c
,
52
);
R3
(
c
,
d
,
e
,
a
,
b
,
53
);
R3
(
b
,
c
,
d
,
e
,
a
,
54
);
R3
(
a
,
b
,
c
,
d
,
e
,
55
);
R3
(
e
,
a
,
b
,
c
,
d
,
56
);
R3
(
d
,
e
,
a
,
b
,
c
,
57
);
R3
(
c
,
d
,
e
,
a
,
b
,
58
);
R3
(
b
,
c
,
d
,
e
,
a
,
59
);
R4
(
a
,
b
,
c
,
d
,
e
,
60
);
R4
(
e
,
a
,
b
,
c
,
d
,
61
);
R4
(
d
,
e
,
a
,
b
,
c
,
62
);
R4
(
c
,
d
,
e
,
a
,
b
,
63
);
R4
(
b
,
c
,
d
,
e
,
a
,
64
);
R4
(
a
,
b
,
c
,
d
,
e
,
65
);
R4
(
e
,
a
,
b
,
c
,
d
,
66
);
R4
(
d
,
e
,
a
,
b
,
c
,
67
);
R4
(
c
,
d
,
e
,
a
,
b
,
68
);
R4
(
b
,
c
,
d
,
e
,
a
,
69
);
R4
(
a
,
b
,
c
,
d
,
e
,
70
);
R4
(
e
,
a
,
b
,
c
,
d
,
71
);
R4
(
d
,
e
,
a
,
b
,
c
,
72
);
R4
(
c
,
d
,
e
,
a
,
b
,
73
);
R4
(
b
,
c
,
d
,
e
,
a
,
74
);
R4
(
a
,
b
,
c
,
d
,
e
,
75
);
R4
(
e
,
a
,
b
,
c
,
d
,
76
);
R4
(
d
,
e
,
a
,
b
,
c
,
77
);
R4
(
c
,
d
,
e
,
a
,
b
,
78
);
R4
(
b
,
c
,
d
,
e
,
a
,
79
);
state
[
0
]
+=
a
;
state
[
1
]
+=
b
;
state
[
2
]
+=
c
;
state
[
3
]
+=
d
;
state
[
4
]
+=
e
;
a
=
b
=
c
=
d
=
e
=
0
;
memset
(
block
,
'\0'
,
sizeof
(
block
));
}
static
void
SHA1Init
(
SHA1_CTX
*
context
)
{
context
->
state
[
0
]
=
0x67452301
;
context
->
state
[
1
]
=
0xEFCDAB89
;
context
->
state
[
2
]
=
0x98BADCFE
;
context
->
state
[
3
]
=
0x10325476
;
context
->
state
[
4
]
=
0xC3D2E1F0
;
context
->
count
[
0
]
=
context
->
count
[
1
]
=
0
;
}
static
void
SHA1Update
(
SHA1_CTX
*
context
,
const
unsigned
char
*
data
,
uint32_t
len
)
{
uint32_t
i
,
j
;
j
=
context
->
count
[
0
];
if
((
context
->
count
[
0
]
+=
len
<<
3
)
<
j
)
context
->
count
[
1
]
++
;
context
->
count
[
1
]
+=
(
len
>>
29
);
j
=
(
j
>>
3
)
&
63
;
if
((
j
+
len
)
>
63
)
{
memcpy
(
&
context
->
buffer
[
j
],
data
,
(
i
=
64
-
j
));
SHA1Transform
(
context
->
state
,
context
->
buffer
);
for
(
;
i
+
63
<
len
;
i
+=
64
)
{
SHA1Transform
(
context
->
state
,
&
data
[
i
]);
}
j
=
0
;
}
else
i
=
0
;
memcpy
(
&
context
->
buffer
[
j
],
&
data
[
i
],
len
-
i
);
}
static
void
SHA1Final
(
unsigned
char
digest
[
20
],
SHA1_CTX
*
context
)
{
unsigned
i
;
unsigned
char
finalcount
[
8
],
c
;
for
(
i
=
0
;
i
<
8
;
i
++
)
{
finalcount
[
i
]
=
(
unsigned
char
)((
context
->
count
[(
i
>=
4
?
0
:
1
)]
>>
((
3
-
(
i
&
3
))
*
8
)
)
&
255
);
}
c
=
0200
;
SHA1Update
(
context
,
&
c
,
1
);
while
((
context
->
count
[
0
]
&
504
)
!=
448
)
{
c
=
0000
;
SHA1Update
(
context
,
&
c
,
1
);
}
SHA1Update
(
context
,
finalcount
,
8
);
for
(
i
=
0
;
i
<
20
;
i
++
)
{
digest
[
i
]
=
(
unsigned
char
)
((
context
->
state
[
i
>>
2
]
>>
((
3
-
(
i
&
3
))
*
8
)
)
&
255
);
}
memset
(
context
,
'\0'
,
sizeof
(
*
context
));
memset
(
&
finalcount
,
'\0'
,
sizeof
(
finalcount
));
}
// END OF SHA1 CODE
static
void
base64_encode
(
const
unsigned
char
*
src
,
int
src_len
,
char
*
dst
)
{
static
const
char
*
b64
=
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
;
int
i
,
j
,
a
,
b
,
c
;
for
(
i
=
j
=
0
;
i
<
src_len
;
i
+=
3
)
{
a
=
src
[
i
];
b
=
i
+
1
>=
src_len
?
0
:
src
[
i
+
1
];
c
=
i
+
2
>=
src_len
?
0
:
src
[
i
+
2
];
dst
[
j
++
]
=
b64
[
a
>>
2
];
dst
[
j
++
]
=
b64
[((
a
&
3
)
<<
4
)
|
(
b
>>
4
)];
if
(
i
+
1
<
src_len
)
{
dst
[
j
++
]
=
b64
[(
b
&
15
)
<<
2
|
(
c
>>
6
)];
}
if
(
i
+
2
<
src_len
)
{
dst
[
j
++
]
=
b64
[
c
&
63
];
}
}
while
(
j
%
4
!=
0
)
{
dst
[
j
++
]
=
'='
;
}
dst
[
j
++
]
=
'\0'
;
}
void
mg_websocket_handshake
(
struct
mg_connection
*
conn
)
{
static
const
char
*
magic
=
"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
;
char
buf
[
100
],
sha
[
20
],
b64_sha
[
sizeof
(
sha
)
*
2
];
SHA1_CTX
sha_ctx
;
mg_snprintf
(
buf
,
sizeof
(
buf
),
"%s%s"
,
mg_get_header
(
conn
,
"Sec-WebSocket-Key"
),
magic
);
SHA1Init
(
&
sha_ctx
);
SHA1Update
(
&
sha_ctx
,
(
unsigned
char
*
)
buf
,
strlen
(
buf
));
SHA1Final
((
unsigned
char
*
)
sha
,
&
sha_ctx
);
base64_encode
((
unsigned
char
*
)
sha
,
sizeof
(
sha
),
b64_sha
);
mg_printf
(
conn
,
"%s%s%s"
,
"HTTP/1.1 101 Switching Protocols
\r\n
"
"Upgrade: websocket
\r\n
"
"Connection: Upgrade
\r\n
"
"Sec-WebSocket-Accept: "
,
b64_sha
,
"
\r\n\r\n
"
);
}
int
mg_websocket_read
(
struct
mg_connection
*
conn
,
int
*
bits
,
char
**
data
)
{
// Pointer to the beginning of the portion of the incoming websocket message
// queue. The original websocket upgrade request is never removed,
// so the queue begins after it.
unsigned
char
*
buf
=
(
unsigned
char
*
)
conn
->
buf
+
conn
->
request_len
;
int
n
,
stop
=
0
;
size_t
i
,
len
,
mask_len
,
data_len
,
header_len
,
body_len
;
char
mask
[
4
];
assert
(
conn
->
content_len
==
0
);
return
;
}
// Loop continuously, reading messages from the socket, invoking the callback,
// and waiting repeatedly until an error occurs.
while
(
!
stop
)
{
header_len
=
0
;
// body_len is the length of the entire queue in bytes
// len is the length of the current message
// data_len is the length of the current message's data payload
// header_len is the length of the current message's header
if
((
body_len
=
conn
->
data_len
-
conn
->
request_len
)
>=
2
)
{
len
=
buf
[
1
]
&
127
;
mask_len
=
buf
[
1
]
&
128
?
4
:
0
;
if
(
len
<
126
&&
body_len
>=
mask_len
)
{
data_len
=
len
;
header_len
=
2
+
mask_len
;
}
else
if
(
len
==
126
&&
body_len
>=
4
+
mask_len
)
{
header_len
=
4
+
mask_len
;
data_len
=
((((
int
)
buf
[
2
])
<<
8
)
+
buf
[
3
]);
}
else
if
(
body_len
>=
10
+
mask_len
)
{
header_len
=
10
+
mask_len
;
data_len
=
(((
uint64_t
)
htonl
(
*
(
uint32_t
*
)
&
buf
[
2
]))
<<
32
)
+
htonl
(
*
(
uint32_t
*
)
&
buf
[
6
]);
if
((
fp
=
mg_fopen
(
path
,
"rb"
))
==
NULL
)
{
cry
(
conn
,
"Cannot open SSI #include: [%s]: fopen(%s): %s"
,
tag
,
path
,
strerror
(
ERRNO
));
}
else
{
fclose_on_exec
(
fp
);
if
(
match_prefix
(
conn
->
ctx
->
config
[
SSI_EXTENSIONS
],
strlen
(
conn
->
ctx
->
config
[
SSI_EXTENSIONS
]),
path
)
>
0
)
{
send_ssi_file
(
conn
,
path
,
fp
,
include_level
+
1
);
}
else
{
send_file_data
(
conn
,
fp
,
0
,
INT64_MAX
);
}
fclose
(
fp
);
}
}
// Data layout is as follows:
// conn->buf buf
// v v frame1 | frame2
// |---------------------|----------------|--------------|-------
// | |<--header_len-->|<--data_len-->|
// |<-conn->request_len->|<-----body_len----------->|
// |<-------------------conn->data_len------------->|
#if !defined(NO_POPEN)
static
void
do_ssi_exec
(
struct
mg_connection
*
conn
,
char
*
tag
)
{
char
cmd
[
MG_BUF_LEN
];
FILE
*
fp
;
if
(
header_len
>
0
)
{
// Allocate space to hold websocket payload
if
((
*
data
=
malloc
(
data_len
))
==
NULL
)
{
// Allocation failed, exit the loop and then close the connection
// TODO: notify user about the failure
data_len
=
0
;
break
;
if
(
sscanf
(
tag
,
"
\"
%[^
\"
]
\"
"
,
cmd
)
!=
1
)
{
cry
(
conn
,
"Bad SSI #exec: [%s]"
,
tag
);
}
else
if
((
fp
=
popen
(
cmd
,
"r"
))
==
NULL
)
{
cry
(
conn
,
"Cannot SSI #exec: [%s]: %s"
,
cmd
,
strerror
(
ERRNO
));
}
else
{
send_file_data
(
conn
,
fp
,
0
,
INT64_MAX
)
;
pclose
(
fp
)
;
}
}
#endif // !NO_POPEN
// Save mask and bits, otherwise it may be clobbered by memmove below
*
bits
=
buf
[
0
];
memcpy
(
mask
,
buf
+
header_len
-
mask_len
,
mask_len
);
static
void
send_ssi_file
(
struct
mg_connection
*
conn
,
const
char
*
path
,
FILE
*
fp
,
int
include_level
)
{
char
buf
[
MG_BUF_LEN
];
int
ch
,
offset
,
len
,
in_ssi_tag
;
// Read frame payload into the allocated buffer.
assert
(
body_len
>=
header_len
);
if
(
data_len
+
header_len
>
body_len
)
{
len
=
body_len
-
header_len
;
memcpy
(
*
data
,
buf
+
header_len
,
len
);
// TODO: handle pull error
pull_all
(
NULL
,
conn
,
*
data
+
len
,
data_len
-
len
);
conn
->
data_len
=
conn
->
request_len
;
}
else
{
len
=
data_len
+
header_len
;
memcpy
(
*
data
,
buf
+
header_len
,
data_len
);
memmove
(
buf
,
buf
+
len
,
body_len
-
len
);
conn
->
data_len
-=
len
;
if
(
include_level
>
10
)
{
cry
(
conn
,
"SSI #include level is too deep (%s)"
,
path
);
return
;
}
// Apply mask if necessary
if
(
mask_len
>
0
)
{
for
(
i
=
0
;
i
<
data_len
;
i
++
)
{
(
*
data
)[
i
]
^=
mask
[
i
%
4
];
in_ssi_tag
=
len
=
offset
=
0
;
while
((
ch
=
fgetc
(
fp
))
!=
EOF
)
{
if
(
in_ssi_tag
&&
ch
==
'>'
)
{
in_ssi_tag
=
0
;
buf
[
len
++
]
=
(
char
)
ch
;
buf
[
len
]
=
'\0'
;
assert
(
len
<=
(
int
)
sizeof
(
buf
));
if
(
len
<
6
||
memcmp
(
buf
,
"<!--#"
,
5
)
!=
0
)
{
// Not an SSI tag, pass it
(
void
)
mg_write
(
conn
,
buf
,
(
size_t
)
len
);
}
else
{
if
(
!
memcmp
(
buf
+
5
,
"include"
,
7
))
{
do_ssi_include
(
conn
,
path
,
buf
+
12
,
include_level
);
#if !defined(NO_POPEN)
}
else
if
(
!
memcmp
(
buf
+
5
,
"exec"
,
4
))
{
do_ssi_exec
(
conn
,
buf
+
9
);
#endif // !NO_POPEN
}
else
{
cry
(
conn
,
"%s: unknown SSI "
"command:
\"
%s
\"
"
,
path
,
buf
);
}
}
return
data_len
;
len
=
0
;
}
else
if
(
in_ssi_tag
)
{
if
(
len
==
5
&&
memcmp
(
buf
,
"<!--#"
,
5
)
!=
0
)
{
// Not an SSI tag
in_ssi_tag
=
0
;
}
else
if
(
len
==
(
int
)
sizeof
(
buf
)
-
2
)
{
cry
(
conn
,
"%s: SSI tag is too large"
,
path
);
len
=
0
;
}
buf
[
len
++
]
=
ch
&
0xff
;
}
else
if
(
ch
==
'<'
)
{
in_ssi_tag
=
1
;
if
(
len
>
0
)
{
mg_write
(
conn
,
buf
,
(
size_t
)
len
);
}
len
=
0
;
buf
[
len
++
]
=
ch
&
0xff
;
}
else
{
// Buffering websocket request
if
(
(
n
=
pull
(
NULL
,
conn
,
conn
->
buf
+
conn
->
data_len
,
conn
->
buf_size
-
conn
->
data_len
))
<=
0
)
{
break
;
buf
[
len
++
]
=
ch
&
0xff
;
if
(
len
==
(
int
)
sizeof
(
buf
))
{
mg_write
(
conn
,
buf
,
(
size_t
)
len
);
len
=
0
;
}
conn
->
data_len
+=
n
;
}
}
return
0
;
// Send the rest of buffered data
if
(
len
>
0
)
{
mg_write
(
conn
,
buf
,
(
size_t
)
len
);
}
}
int
mg_websocket_write
(
struct
mg_connection
*
conn
,
int
opcode
,
const
char
*
data
,
size_t
data_len
)
{
unsigned
char
*
copy
;
size_t
copy_len
=
0
;
int
retval
=
-
1
;
static
void
handle_ssi_file_request
(
struct
mg_connection
*
conn
,
const
char
*
path
)
{
struct
vec
mime_vec
;
FILE
*
fp
;
if
((
copy
=
(
unsigned
char
*
)
malloc
(
data_len
+
10
))
==
NULL
)
{
return
-
1
;
if
((
fp
=
mg_fopen
(
path
,
"rb"
))
==
NULL
)
{
send_http_error
(
conn
,
500
,
http_500_error
,
"fopen(%s): %s"
,
path
,
strerror
(
ERRNO
));
}
else
{
conn
->
must_close
=
1
;
fclose_on_exec
(
fp
);
get_mime_type
(
conn
->
ctx
,
path
,
&
mime_vec
);
mg_printf
(
conn
,
"HTTP/1.1 200 OK
\r\n
"
"Content-Type: %.*s
\r\n
"
"Connection: close
\r\n\r\n
"
,
(
int
)
mime_vec
.
len
,
mime_vec
.
ptr
);
send_ssi_file
(
conn
,
path
,
fp
,
0
);
fclose
(
fp
);
}
}
copy
[
0
]
=
0x80
+
(
opcode
&
0x0f
);
static
void
handle_options_request
(
struct
mg_connection
*
conn
)
{
static
const
char
reply
[]
=
"HTTP/1.1 200 OK
\r\n
"
"Allow: GET, POST, HEAD, CONNECT, PUT, DELETE, OPTIONS, PROPFIND, MKCOL
\r\n
"
"DAV: 1
\r\n\r\n
"
;
// Frame format: http://tools.ietf.org/html/rfc6455#section-5.2
if
(
data_len
<
126
)
{
// Inline 7-bit length field
copy
[
1
]
=
data_len
;
memcpy
(
copy
+
2
,
data
,
data_len
);
copy_len
=
2
+
data_len
;
}
else
if
(
data_len
<=
0xFFFF
)
{
// 16-bit length field
copy
[
1
]
=
126
;
*
(
uint16_t
*
)
(
copy
+
2
)
=
htons
(
data_len
);
memcpy
(
copy
+
4
,
data
,
data_len
);
copy_len
=
4
+
data_len
;
}
else
{
// 64-bit length field
copy
[
1
]
=
127
;
*
(
uint32_t
*
)
(
copy
+
2
)
=
htonl
((
uint64_t
)
data_len
>>
32
);
*
(
uint32_t
*
)
(
copy
+
6
)
=
htonl
(
data_len
&
0xffffffff
);
memcpy
(
copy
+
10
,
data
,
data_len
);
copy_len
=
10
+
data_len
;
}
conn
->
status_code
=
200
;
mg_write
(
conn
,
reply
,
sizeof
(
reply
)
-
1
);
}
// Not thread safe
if
(
copy_len
>
0
)
{
retval
=
mg_write
(
conn
,
copy
,
copy_len
);
// Writes PROPFIND properties for a collection element
static
void
print_props
(
struct
mg_connection
*
conn
,
const
char
*
uri
,
struct
file
*
filep
)
{
char
mtime
[
64
];
gmt_time_string
(
mtime
,
sizeof
(
mtime
),
&
filep
->
modification_time
);
conn
->
num_bytes_sent
+=
mg_printf
(
conn
,
"<d:response>"
"<d:href>%s</d:href>"
"<d:propstat>"
"<d:prop>"
"<d:resourcetype>%s</d:resourcetype>"
"<d:getcontentlength>%"
INT64_FMT
"</d:getcontentlength>"
"<d:getlastmodified>%s</d:getlastmodified>"
"</d:prop>"
"<d:status>HTTP/1.1 200 OK</d:status>"
"</d:propstat>"
"</d:response>
\n
"
,
uri
,
filep
->
is_directory
?
"<d:collection/>"
:
""
,
filep
->
size
,
mtime
);
}
static
void
print_dav_dir_entry
(
struct
de
*
de
,
void
*
data
)
{
char
href
[
PATH_MAX
];
char
href_encoded
[
PATH_MAX
];
struct
mg_connection
*
conn
=
(
struct
mg_connection
*
)
data
;
mg_snprintf
(
href
,
sizeof
(
href
),
"%s%s"
,
conn
->
request_info
.
uri
,
de
->
file_name
);
mg_url_encode
(
href
,
href_encoded
,
PATH_MAX
-
1
);
print_props
(
conn
,
href_encoded
,
&
de
->
file
);
}
static
void
handle_propfind
(
struct
mg_connection
*
conn
,
const
char
*
path
,
struct
file
*
filep
)
{
const
char
*
depth
=
mg_get_header
(
conn
,
"Depth"
);
conn
->
must_close
=
1
;
conn
->
status_code
=
207
;
mg_printf
(
conn
,
"HTTP/1.1 207 Multi-Status
\r\n
"
"Connection: close
\r\n
"
"Content-Type: text/xml; charset=utf-8
\r\n\r\n
"
);
conn
->
num_bytes_sent
+=
mg_printf
(
conn
,
"<?xml version=
\"
1.0
\"
encoding=
\"
utf-8
\"
?>"
"<d:multistatus xmlns:d='DAV:'>
\n
"
);
// Print properties for the requested resource itself
print_props
(
conn
,
conn
->
request_info
.
uri
,
filep
);
// If it is a directory, print directory entries too if Depth is not 0
if
(
filep
->
is_directory
&&
!
mg_strcasecmp
(
conn
->
ctx
->
config
[
ENABLE_DIRECTORY_LISTING
],
"yes"
)
&&
(
depth
==
NULL
||
strcmp
(
depth
,
"0"
)
!=
0
))
{
scan_directory
(
conn
,
path
,
conn
,
&
print_dav_dir_entry
);
}
free
(
copy
);
return
retval
;
conn
->
num_bytes_sent
+=
mg_printf
(
conn
,
"%s
\n
"
,
"</d:multistatus>"
)
;
}
#endif // !USE_WEBSOCKET
static
int
isbyte
(
int
n
)
{
return
n
>=
0
&&
n
<=
255
;
...
...
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