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
75d9a6c8
Commit
75d9a6c8
authored
Jan 23, 2013
by
Sergey Lyubka
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Removed mg_connect() and mg_fetch(). Added mg_download()
parent
179761fd
Changes
4
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
257 additions
and
222 deletions
+257
-222
mongoose.c
mongoose.c
+115
-110
mongoose.h
mongoose.h
+22
-26
test.pl
test/test.pl
+7
-8
unit_test.c
test/unit_test.c
+113
-78
No files found.
mongoose.c
View file @
75d9a6c8
...
...
@@ -1627,16 +1627,13 @@ int mg_write(struct mg_connection *conn, const void *buf, size_t len) {
return
(
int
)
total
;
}
int
mg_
printf
(
struct
mg_connection
*
conn
,
const
char
*
fmt
,
...
)
{
int
mg_
vprintf
(
struct
mg_connection
*
conn
,
const
char
*
fmt
,
va_list
ap
)
{
char
mem
[
MG_BUF_LEN
],
*
buf
=
mem
;
int
len
;
va_list
ap
;
// Print in a local buffer first, hoping that it is large enough to
// hold the whole message
va_start
(
ap
,
fmt
);
len
=
vsnprintf
(
mem
,
sizeof
(
mem
),
fmt
,
ap
);
va_end
(
ap
);
if
(
len
==
0
)
{
// Do nothing. mg_printf(conn, "%s", "") was called.
...
...
@@ -1647,9 +1644,7 @@ int mg_printf(struct mg_connection *conn, const char *fmt, ...) {
}
else
if
(
len
>
(
int
)
sizeof
(
mem
)
&&
(
buf
=
(
char
*
)
malloc
(
len
+
1
))
!=
NULL
)
{
// Local buffer is not large enough, allocate big buffer on heap
va_start
(
ap
,
fmt
);
vsnprintf
(
buf
,
len
+
1
,
fmt
,
ap
);
va_end
(
ap
);
len
=
mg_write
(
conn
,
buf
,
(
size_t
)
len
);
free
(
buf
);
}
else
if
(
len
>
(
int
)
sizeof
(
mem
))
{
...
...
@@ -1665,6 +1660,12 @@ int mg_printf(struct mg_connection *conn, const char *fmt, ...) {
return
len
;
}
int
mg_printf
(
struct
mg_connection
*
conn
,
const
char
*
fmt
,
...)
{
va_list
ap
;
va_start
(
ap
,
fmt
);
return
mg_vprintf
(
conn
,
fmt
,
ap
);
}
// URL-decode input buffer into destination buffer.
// 0-terminate the destination buffer. Return the length of decoded data.
// form-url-encoded data differs from URI encoding in a way that it
...
...
@@ -2891,7 +2892,7 @@ static int is_valid_http_method(const char *method) {
// This function modifies the buffer by NUL-terminating
// HTTP request components, header names and header values.
static
int
parse_http_message
(
char
*
buf
,
int
len
,
struct
mg_request_info
*
ri
)
{
int
request_length
=
get_request_len
(
buf
,
len
);
int
is_request
,
request_length
=
get_request_len
(
buf
,
len
);
if
(
request_length
>
0
)
{
// Reset attributes. DO NOT TOUCH is_ssl, remote_ip, remote_port
ri
->
remote_user
=
ri
->
request_method
=
ri
->
uri
=
ri
->
http_version
=
NULL
;
...
...
@@ -2906,26 +2907,18 @@ static int parse_http_message(char *buf, int len, struct mg_request_info *ri) {
ri
->
request_method
=
skip
(
&
buf
,
" "
);
ri
->
uri
=
skip
(
&
buf
,
" "
);
ri
->
http_version
=
skip
(
&
buf
,
"
\r\n
"
);
if
(((
is_request
=
is_valid_http_method
(
ri
->
request_method
))
&&
memcmp
(
ri
->
http_version
,
"HTTP/"
,
5
)
!=
0
)
||
(
!
is_request
&&
memcmp
(
ri
->
request_method
,
"HTTP/"
,
5
))
!=
0
)
{
request_length
=
-
1
;
}
else
{
if
(
is_request
)
{
ri
->
http_version
+=
5
;
}
parse_http_headers
(
&
buf
,
ri
);
}
return
request_length
;
}
static
int
parse_http_request
(
char
*
buf
,
int
len
,
struct
mg_request_info
*
ri
)
{
int
result
=
parse_http_message
(
buf
,
len
,
ri
);
if
(
result
>
0
&&
is_valid_http_method
(
ri
->
request_method
)
&&
!
strncmp
(
ri
->
http_version
,
"HTTP/"
,
5
))
{
ri
->
http_version
+=
5
;
// Skip "HTTP/"
}
else
{
result
=
-
1
;
}
return
result
;
}
static
int
parse_http_response
(
char
*
buf
,
int
len
,
struct
mg_request_info
*
ri
)
{
int
result
=
parse_http_message
(
buf
,
len
,
ri
);
return
result
>
0
&&
!
strncmp
(
ri
->
request_method
,
"HTTP/"
,
5
)
?
result
:
-
1
;
return
request_length
;
}
// Keep reading the input (either opened file descriptor fd, or socket sock,
...
...
@@ -4734,74 +4727,109 @@ void mg_close_connection(struct mg_connection *conn) {
free
(
conn
);
}
struct
mg_connection
*
mg_connect
(
struct
mg_context
*
ctx
,
const
char
*
host
,
int
port
,
int
use_ssl
)
{
struct
mg_connection
*
newconn
=
NULL
;
struct
mg_connection
*
mg_connect
(
const
char
*
host
,
int
port
,
int
use_ssl
,
char
*
ebuf
,
size_t
ebuf_len
)
{
static
struct
mg_context
fake_ctx
;
struct
mg_connection
*
conn
=
NULL
;
struct
sockaddr_in
sin
;
struct
hostent
*
he
;
SSL_CTX
*
ssl
=
NULL
;
int
sock
;
if
(
use_ssl
&&
(
ctx
==
NULL
||
ctx
->
client_ssl_ctx
==
NULL
))
{
cry
(
fc
(
ctx
),
"%s: SSL is not initialized"
,
__func__
);
if
(
host
==
NULL
)
{
snprintf
(
ebuf
,
ebuf_len
,
"%s"
,
"NULL host"
);
}
else
if
(
use_ssl
&&
SSLv23_client_method
==
NULL
)
{
snprintf
(
ebuf
,
ebuf_len
,
"%s"
,
"SSL is not initialized"
);
#ifndef NO_SSL
}
else
if
(
use_ssl
&&
(
ssl
=
SSL_CTX_new
(
SSLv23_client_method
()))
==
NULL
)
{
snprintf
(
ebuf
,
ebuf_len
,
"SSL_CTX_new: %s"
,
ssl_error
());
#endif // NO_SSL
}
else
if
((
he
=
gethostbyname
(
host
))
==
NULL
)
{
cry
(
fc
(
ctx
),
"%s: gethostbyname(%s): %s"
,
__func__
,
host
,
strerror
(
ERRNO
));
snprintf
(
ebuf
,
ebuf_len
,
"gethostbyname(%s): %s"
,
host
,
strerror
(
ERRNO
));
}
else
if
((
sock
=
socket
(
PF_INET
,
SOCK_STREAM
,
0
))
==
INVALID_SOCKET
)
{
cry
(
fc
(
ctx
),
"%s: socket: %s"
,
__func__
,
strerror
(
ERRNO
));
snprintf
(
ebuf
,
ebuf_len
,
"socket(): %s"
,
strerror
(
ERRNO
));
}
else
{
sin
.
sin_family
=
AF_INET
;
sin
.
sin_port
=
htons
((
uint16_t
)
port
);
sin
.
sin_addr
=
*
(
struct
in_addr
*
)
he
->
h_addr_list
[
0
];
if
(
connect
(
sock
,
(
struct
sockaddr
*
)
&
sin
,
sizeof
(
sin
))
!=
0
)
{
cry
(
fc
(
ctx
),
"%s: connect(%s:%d): %s"
,
__func__
,
host
,
port
,
strerror
(
ERRNO
));
snprintf
(
ebuf
,
ebuf_len
,
"connect(%s:%d): %s"
,
host
,
port
,
strerror
(
ERRNO
));
closesocket
(
sock
);
}
else
if
((
new
conn
=
(
struct
mg_connection
*
)
calloc
(
1
,
sizeof
(
*
newconn
)
))
==
NULL
)
{
cry
(
fc
(
ctx
),
"%s: calloc: %s"
,
__func__
,
strerror
(
ERRNO
));
}
else
if
((
conn
=
(
struct
mg_connection
*
)
calloc
(
1
,
sizeof
(
*
conn
)
+
MAX_REQUEST_SIZE
))
==
NULL
)
{
snprintf
(
ebuf
,
ebuf_len
,
"calloc(): %s"
,
strerror
(
ERRNO
));
closesocket
(
sock
);
}
else
{
newconn
->
ctx
=
ctx
;
newconn
->
client
.
sock
=
sock
;
newconn
->
client
.
rsa
.
sin
=
sin
;
newconn
->
client
.
is_ssl
=
use_ssl
;
conn
->
buf_size
=
MAX_REQUEST_SIZE
;
conn
->
buf
=
(
char
*
)
(
conn
+
1
);
conn
->
ctx
=
&
fake_ctx
;
conn
->
client
.
sock
=
sock
;
conn
->
client
.
rsa
.
sin
=
sin
;
conn
->
client
.
is_ssl
=
use_ssl
;
if
(
use_ssl
)
{
sslize
(
newconn
,
ctx
->
client_ssl_ctx
,
SSL_connect
);
sslize
(
conn
,
ssl
,
SSL_connect
);
}
}
}
if
(
ssl
!=
NULL
)
{
SSL_CTX_free
(
ssl
);
}
return
newconn
;
return
conn
;
}
static
int
is_valid_uri
(
const
char
*
uri
)
{
// Conform to http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
// URI can be an asterisk (*) or should start with slash.
return
uri
[
0
]
==
'/'
||
(
uri
[
0
]
==
'*'
&&
uri
[
1
]
==
'\0'
);
}
FILE
*
mg_fetch
(
struct
mg_context
*
ctx
,
const
char
*
url
,
const
char
*
path
,
char
*
buf
,
size_t
buf_len
,
struct
mg_request_info
*
ri
)
{
struct
mg_connection
*
newconn
;
int
n
,
req_length
,
data_length
,
port
;
char
host
[
1025
],
proto
[
10
],
buf2
[
MG_BUF_LEN
];
FILE
*
fp
=
NULL
;
static
int
getreq
(
struct
mg_connection
*
conn
,
char
*
ebuf
,
size_t
ebuf_len
)
{
const
char
*
cl
;
ebuf
[
0
]
=
'\0'
;
reset_per_request_attributes
(
conn
);
conn
->
request_len
=
read_request
(
NULL
,
conn
,
conn
->
buf
,
conn
->
buf_size
,
&
conn
->
data_len
);
assert
(
conn
->
request_len
<
0
||
conn
->
data_len
>=
conn
->
request_len
);
if
(
sscanf
(
url
,
"%9[htps]://%1024[^:]:%d/%n"
,
proto
,
host
,
&
port
,
&
n
)
==
3
)
{
}
else
if
(
sscanf
(
url
,
"%9[htps]://%1024[^/]/%n"
,
proto
,
host
,
&
n
)
==
2
)
{
port
=
mg_strcasecmp
(
proto
,
"https"
)
==
0
?
443
:
80
;
if
(
conn
->
request_len
==
0
&&
conn
->
data_len
==
conn
->
buf_size
)
{
snprintf
(
ebuf
,
ebuf_len
,
"%s"
,
"Request Too Large"
);
}
if
(
conn
->
request_len
<=
0
)
{
snprintf
(
ebuf
,
ebuf_len
,
"%s"
,
"Client closed connection"
);
}
else
if
(
parse_http_message
(
conn
->
buf
,
conn
->
buf_size
,
&
conn
->
request_info
)
<=
0
)
{
snprintf
(
ebuf
,
ebuf_len
,
"Bad request: [%.*s]"
,
conn
->
data_len
,
conn
->
buf
);
}
else
{
cry
(
fc
(
ctx
),
"%s: invalid URL: [%s]"
,
__func__
,
url
);
return
NULL
;
// Request is valid
if
((
cl
=
get_header
(
&
conn
->
request_info
,
"Content-Length"
))
!=
NULL
)
{
conn
->
content_len
=
strtoll
(
cl
,
NULL
,
10
);
}
else
if
(
!
mg_strcasecmp
(
conn
->
request_info
.
request_method
,
"POST"
)
||
!
mg_strcasecmp
(
conn
->
request_info
.
request_method
,
"PUT"
))
{
conn
->
content_len
=
-
1
;
}
else
{
conn
->
content_len
=
0
;
}
conn
->
birth_time
=
time
(
NULL
);
}
return
ebuf
[
0
]
==
'\0'
;
}
if
((
newconn
=
mg_connect
(
ctx
,
host
,
port
,
!
strcmp
(
proto
,
"https"
)))
==
NULL
)
{
cry
(
fc
(
ctx
),
"%s: mg_connect(%s): %s"
,
__func__
,
url
,
strerror
(
ERRNO
));
}
else
{
mg_printf
(
newconn
,
"GET /%s HTTP/1.0
\r\n
Host: %s
\r\n\r\n
"
,
url
+
n
,
host
);
data_length
=
0
;
req_length
=
read_request
(
NULL
,
newconn
,
buf
,
buf_len
,
&
data_length
);
if
(
req_length
<=
0
)
{
cry
(
fc
(
ctx
),
"%s(%s): invalid HTTP reply"
,
__func__
,
url
);
}
else
if
(
parse_http_response
(
buf
,
req_length
,
ri
)
<=
0
)
{
cry
(
fc
(
ctx
),
"%s(%s): cannot parse HTTP headers"
,
__func__
,
url
);
}
else
if
((
fp
=
fopen
(
path
,
"w+b"
))
==
NULL
)
{
cry
(
fc
(
ctx
),
"%s: fopen(%s): %s"
,
__func__
,
path
,
strerror
(
ERRNO
));
struct
mg_connection
*
mg_download
(
const
char
*
host
,
int
port
,
int
use_ssl
,
char
*
ebuf
,
size_t
ebuf_len
,
const
char
*
fmt
,
...)
{
struct
mg_connection
*
conn
;
va_list
ap
;
va_start
(
ap
,
fmt
);
ebuf
[
0
]
=
'\0'
;
if
((
conn
=
mg_connect
(
host
,
port
,
use_ssl
,
ebuf
,
ebuf_len
))
==
NULL
)
{
}
else
if
(
mg_vprintf
(
conn
,
fmt
,
ap
)
<=
0
)
{
snprintf
(
ebuf
,
ebuf_len
,
"%s"
,
"Error sending request"
);
}
else
if
(
!
getreq
(
conn
,
ebuf
,
ebuf_len
))
{
}
else
{
#if 0
// Write chunk of data that may be in the user's buffer
data_length -= req_length;
if (data_length > 0 &&
...
...
@@ -4811,8 +4839,8 @@ FILE *mg_fetch(struct mg_context *ctx, const char *url, const char *path,
fp = NULL;
}
// Read the rest of the response and write it to the file. Do not use
// mg_read() cause we didn't set
new
conn->content_len properly.
while
(
fp
&&
(
data_length
=
pull
(
0
,
new
conn
,
buf2
,
sizeof
(
buf2
)))
>
0
)
{
// mg_read() cause we didn't set conn->content_len properly.
while (fp && (data_length = pull(0, conn, buf2, sizeof(buf2))) > 0) {
if (fwrite(buf2, 1, data_length, fp) != (size_t) data_length) {
cry(fc(ctx), "%s: fwrite(%s): %s", __func__, path, strerror(ERRNO));
fclose(fp);
...
...
@@ -4820,23 +4848,20 @@ FILE *mg_fetch(struct mg_context *ctx, const char *url, const char *path,
break;
}
}
#endif
}
mg_close_connection
(
newconn
);
if
(
ebuf
[
0
]
!=
'\0'
&&
conn
!=
NULL
)
{
mg_close_connection
(
conn
);
conn
=
NULL
;
}
return
fp
;
}
static
int
is_valid_uri
(
const
char
*
uri
)
{
// Conform to http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
// URI can be an asterisk (*) or should start with slash.
return
uri
[
0
]
==
'/'
||
(
uri
[
0
]
==
'*'
&&
uri
[
1
]
==
'\0'
);
return
conn
;
}
static
void
process_new_connection
(
struct
mg_connection
*
conn
)
{
struct
mg_request_info
*
ri
=
&
conn
->
request_info
;
int
keep_alive_enabled
,
keep_alive
,
discard_len
;
c
onst
char
*
cl
;
c
har
ebuf
[
100
]
;
keep_alive_enabled
=
!
strcmp
(
conn
->
ctx
->
config
[
ENABLE_KEEP_ALIVE
],
"yes"
);
keep_alive
=
0
;
...
...
@@ -4845,38 +4870,18 @@ static void process_new_connection(struct mg_connection *conn) {
// to crule42.
conn
->
data_len
=
0
;
do
{
reset_per_request_attributes
(
conn
);
conn
->
request_len
=
read_request
(
NULL
,
conn
,
conn
->
buf
,
conn
->
buf_size
,
&
conn
->
data_len
);
assert
(
conn
->
request_len
<
0
||
conn
->
data_len
>=
conn
->
request_len
);
if
(
conn
->
request_len
==
0
&&
conn
->
data_len
==
conn
->
buf_size
)
{
send_http_error
(
conn
,
413
,
"Request Too Large"
,
"%s"
,
""
);
return
;
}
if
(
conn
->
request_len
<=
0
)
{
return
;
// Remote end closed the connection
}
if
(
parse_http_request
(
conn
->
buf
,
conn
->
buf_size
,
ri
)
<=
0
||
!
is_valid_uri
(
ri
->
uri
))
{
// Do not put garbage in the access log, just send it back to the client
send_http_error
(
conn
,
400
,
"Bad Request"
,
"Cannot parse HTTP request: [%.*s]"
,
conn
->
data_len
,
conn
->
buf
);
conn
->
must_close
=
1
;
if
(
!
getreq
(
conn
,
ebuf
,
sizeof
(
ebuf
)))
{
send_http_error
(
conn
,
500
,
"Server Error"
,
"%s"
,
ebuf
);
}
else
if
(
!
is_valid_uri
(
conn
->
request_info
.
uri
))
{
snprintf
(
ebuf
,
sizeof
(
ebuf
),
"Invalid URI: [%s]"
,
ri
->
uri
);
send_http_error
(
conn
,
400
,
"Bad Request"
,
"%s"
,
ebuf
);
}
else
if
(
strcmp
(
ri
->
http_version
,
"1.0"
)
&&
strcmp
(
ri
->
http_version
,
"1.1"
))
{
// Request seems valid, but HTTP version is strange
send_http_error
(
conn
,
505
,
"HTTP version not supported"
,
"%s"
,
""
);
log_access
(
conn
);
}
else
{
// Request is valid, handle it
if
((
cl
=
get_header
(
ri
,
"Content-Length"
))
!=
NULL
)
{
conn
->
content_len
=
strtoll
(
cl
,
NULL
,
10
);
}
else
if
(
!
mg_strcasecmp
(
ri
->
request_method
,
"POST"
)
||
!
mg_strcasecmp
(
ri
->
request_method
,
"PUT"
))
{
conn
->
content_len
=
-
1
;
}
else
{
conn
->
content_len
=
0
;
snprintf
(
ebuf
,
sizeof
(
ebuf
),
"Bad HTTP version: [%s]"
,
ri
->
http_version
);
send_http_error
(
conn
,
505
,
"Bad HTTP version"
,
"%s"
,
ebuf
);
}
conn
->
birth_time
=
time
(
NULL
);
if
(
ebuf
[
0
]
==
'\0'
)
{
handle_request
(
conn
);
conn
->
request_info
.
ev_data
=
(
void
*
)
(
long
)
conn
->
status_code
;
call_user
(
conn
,
MG_REQUEST_COMPLETE
);
...
...
mongoose.h
View file @
75d9a6c8
...
...
@@ -236,12 +236,6 @@ struct mg_request_info *mg_get_request_info(struct mg_connection *);
int
mg_write
(
struct
mg_connection
*
,
const
void
*
buf
,
size_t
len
);
// Send data to the browser using printf() semantics.
//
// Works exactly like mg_write(), but allows to do message formatting.
// Below are the macros for enabling compiler-specific checks for
// printf-like arguments.
#undef PRINTF_FORMAT_STRING
#if _MSC_VER >= 1400
#include <sal.h>
...
...
@@ -260,6 +254,11 @@ int mg_write(struct mg_connection *, const void *buf, size_t len);
#define PRINTF_ARGS(x, y)
#endif
// Send data to the browser using printf() semantics.
//
// Works exactly like mg_write(), but allows to do message formatting.
// Below are the macros for enabling compiler-specific checks for
// printf-like arguments.
int
mg_printf
(
struct
mg_connection
*
,
PRINTF_FORMAT_STRING
(
const
char
*
fmt
),
...)
PRINTF_ARGS
(
2
,
3
);
...
...
@@ -316,32 +315,29 @@ int mg_get_cookie(const struct mg_connection *,
const
char
*
cookie_name
,
char
*
buf
,
size_t
buf_len
);
// Connect to the remote web server.
// Download data from the remote web server.
// host: host name to connect to, e.g. "foo.com", or "10.12.40.1".
// port: port number, e.g. 80.
// use_ssl: wether to use SSL connection.
// error_buffer, error_buffer_size: error message placeholder.
// request_fmt,...: HTTP request.
// Return:
// On success, valid pointer to the new connection
// On error, NULL
struct
mg_connection
*
mg_connect
(
struct
mg_context
*
ctx
,
const
char
*
host
,
int
port
,
int
use_ssl
);
// On success, valid pointer to the new connection, suitable for mg_read().
// On error, NULL.
// Example:
// char ebuf[100];
// struct mg_connection *conn;
// conn = mg_download("google.com", 80, 0, ebuf, sizeof(ebuf),
// "%s", "GET / HTTP/1.0\r\n\r\nHost: google.com\r\n\r\n");
struct
mg_connection
*
mg_download
(
const
char
*
host
,
int
port
,
int
use_ssl
,
char
*
error_buffer
,
size_t
error_buffer_size
,
const
char
*
request_fmt
,
...);
// Close the connection opened by mg_
connect
().
// Close the connection opened by mg_
download
().
void
mg_close_connection
(
struct
mg_connection
*
conn
);
// Download given URL to a given file.
// url: URL to download
// path: file name where to save the data
// request_info: pointer to a structure that will hold parsed reply headers
// buf, bul_len: a buffer for the reply headers
// Return:
// On error, NULL
// On success, opened file stream to the downloaded contents. The stream
// is positioned to the end of the file. It is the user's responsibility
// to fclose() the opened file stream.
FILE
*
mg_fetch
(
struct
mg_context
*
ctx
,
const
char
*
url
,
const
char
*
path
,
char
*
buf
,
size_t
buf_len
,
struct
mg_request_info
*
request_info
);
// File upload functionality. Each uploaded file gets saved into a temporary
// file and MG_UPLOAD event is sent.
// Return number of uploaded files.
...
...
test/test.pl
View file @
75d9a6c8
...
...
@@ -167,7 +167,7 @@ kill_spawned_child();
# Spawn the server on port $port
my
$cmd
=
"$exe "
.
"-listening_ports $port "
.
"-listening_ports
127.0.0.1:
$port "
.
"-access_log_file access.log "
.
"-error_log_file debug.log "
.
"-cgi_environment CGI_FOO=foo,CGI_BAR=bar,CGI_BAZ=baz "
.
...
...
@@ -220,11 +220,10 @@ write_file("$root/a+.txt", '');
o
(
"GET /a+.txt HTTP/1.0\n\n"
,
'HTTP/1.1 200 OK'
,
'URL-decoding, + in URI'
);
# Test HTTP version parsing
o
(
"GET / HTTPX/1.0\r\n\r\n"
,
'400 Bad Request'
,
'Bad HTTP Version'
,
0
);
o
(
"GET / HTTP/x.1\r\n\r\n"
,
'505 HTTP'
,
'Bad HTTP maj Version'
);
o
(
"GET / HTTP/1.1z\r\n\r\n"
,
'505 HTTP'
,
'Bad HTTP min Version'
);
o
(
"GET / HTTP/02.0\r\n\r\n"
,
'505 HTTP version not supported'
,
'HTTP Version >1.1'
);
o
(
"GET / HTTPX/1.0\r\n\r\n"
,
'^HTTP/1.1 500'
,
'Bad HTTP Version'
,
0
);
o
(
"GET / HTTP/x.1\r\n\r\n"
,
'^HTTP/1.1 505'
,
'Bad HTTP maj Version'
,
0
);
o
(
"GET / HTTP/1.1z\r\n\r\n"
,
'^HTTP/1.1 505'
,
'Bad HTTP min Version'
,
0
);
o
(
"GET / HTTP/02.0\r\n\r\n"
,
'^HTTP/1.1 505'
,
'HTTP Version >1.1'
,
0
);
# File with leading single dot
o
(
"GET /.leading.dot.txt HTTP/1.0\n\n"
,
'abc123'
,
'Leading dot 1'
);
...
...
@@ -463,7 +462,7 @@ sub do_unit_test {
sub
do_embedded_test
{
my
$cmd
=
"cc -W -Wall -o $embed_exe $root/embed.c mongoose.c -I. "
.
"-pthread -DNO_SSL -DLISTENING_PORT=\\\"$port\\\""
;
"-pthread -DNO_SSL -DLISTENING_PORT=\\\"
127.0.0.1:
$port\\\""
;
if
(
on_windows
())
{
$cmd
=
"cl $root/embed.c mongoose.c /I. /nologo /DNO_SSL "
.
"/DLISTENING_PORT=\\\"$port\\\" /link /out:$embed_exe.exe ws2_32.lib "
;
...
...
@@ -514,7 +513,7 @@ sub do_embedded_test {
'Remote user: \[\]'
,
'request_info'
,
0
);
o
(
"GET /not_exist HTTP/1.0\n\n"
,
'Error: \[404\]'
,
'404 handler'
,
0
);
o
(
"bad request\n\n"
,
'Error: \[
4
00\]'
,
'* error handler'
,
0
);
o
(
"bad request\n\n"
,
'Error: \[
5
00\]'
,
'* error handler'
,
0
);
# o("GET /foo/secret HTTP/1.0\n\n",
# '401 Unauthorized', 'mg_protect_uri', 0);
# o("GET /foo/secret HTTP/1.0\nAuthorization: Digest username=bill\n\n",
...
...
test/unit_test.c
View file @
75d9a6c8
...
...
@@ -21,6 +21,7 @@
// Unit test for the mongoose web server. Tests embedded API.
#define USE_WEBSOCKET
#define USE_LUA
#include "mongoose.c"
#define FATAL(str, line) do { \
...
...
@@ -29,24 +30,35 @@
} while (0)
#define ASSERT(expr) do { if (!(expr)) FATAL(#expr, __LINE__); } while (0)
#define LISTENING_ADDR "127.0.0.1:56789"
#define HTTP_PORT "56789"
#define HTTPS_PORT "56790"
#define LISTENING_ADDR "127.0.0.1:" HTTP_PORT "r,127.0.0.1:" HTTPS_PORT "s"
static
void
test_parse_http_
request
()
{
static
void
test_parse_http_
message
()
{
struct
mg_request_info
ri
;
char
req1
[]
=
"GET / HTTP/1.1
\r\n\r\n
"
;
char
req2
[]
=
"BLAH / HTTP/1.1
\r\n\r\n
"
;
char
req3
[]
=
"GET / HTTP/1.1
\r\n
Bah
\r\n
"
;
char
req4
[]
=
"GET / HTTP/1.1
\r\n
A: foo bar
\r\n
B: bar
\r\n
baz
\r\n\r\n
"
;
char
req5
[]
=
"GET / HTTP/1.1
\r\n\r\n
"
;
char
req6
[]
=
"G"
;
char
req7
[]
=
" blah "
;
char
req8
[]
=
" HTTP/1.1 200 OK
\n\n
"
;
ASSERT
(
parse_http_
request
(
req1
,
sizeof
(
req1
),
&
ri
)
==
sizeof
(
req1
)
-
1
);
ASSERT
(
parse_http_
message
(
req1
,
sizeof
(
req1
),
&
ri
)
==
sizeof
(
req1
)
-
1
);
ASSERT
(
strcmp
(
ri
.
http_version
,
"1.1"
)
==
0
);
ASSERT
(
ri
.
num_headers
==
0
);
ASSERT
(
parse_http_request
(
req2
,
sizeof
(
req2
),
&
ri
)
==
-
1
);
ASSERT
(
parse_http_request
(
req3
,
sizeof
(
req3
),
&
ri
)
==
-
1
);
ASSERT
(
parse_http_message
(
req2
,
sizeof
(
req2
),
&
ri
)
==
-
1
);
ASSERT
(
parse_http_message
(
req3
,
sizeof
(
req3
),
&
ri
)
==
0
);
ASSERT
(
parse_http_message
(
req6
,
sizeof
(
req6
),
&
ri
)
==
0
);
ASSERT
(
parse_http_message
(
req7
,
sizeof
(
req7
),
&
ri
)
==
0
);
ASSERT
(
parse_http_message
(
""
,
0
,
&
ri
)
==
0
);
ASSERT
(
parse_http_message
(
req8
,
sizeof
(
req8
),
&
ri
)
==
sizeof
(
req8
)
-
1
);
// TODO(lsm): Fix this. Header value may span multiple lines.
ASSERT
(
parse_http_request
(
req4
,
sizeof
(
req4
),
&
ri
)
==
sizeof
(
req4
)
-
1
);
ASSERT
(
parse_http_message
(
req4
,
sizeof
(
req4
),
&
ri
)
==
sizeof
(
req4
)
-
1
);
ASSERT
(
strcmp
(
ri
.
http_version
,
"1.1"
)
==
0
);
ASSERT
(
ri
.
num_headers
==
3
);
ASSERT
(
strcmp
(
ri
.
http_headers
[
0
].
name
,
"A"
)
==
0
);
ASSERT
(
strcmp
(
ri
.
http_headers
[
0
].
value
,
"foo bar"
)
==
0
);
...
...
@@ -55,7 +67,9 @@ static void test_parse_http_request() {
ASSERT
(
strcmp
(
ri
.
http_headers
[
2
].
name
,
"baz
\r\n\r
"
)
==
0
);
ASSERT
(
strcmp
(
ri
.
http_headers
[
2
].
value
,
""
)
==
0
);
// TODO(lsm): add more tests.
ASSERT
(
parse_http_message
(
req5
,
sizeof
(
req5
),
&
ri
)
==
sizeof
(
req5
)
-
1
);
ASSERT
(
strcmp
(
ri
.
request_method
,
"GET"
)
==
0
);
ASSERT
(
strcmp
(
ri
.
http_version
,
"1.1"
)
==
0
);
}
static
void
test_should_keep_alive
(
void
)
{
...
...
@@ -68,7 +82,8 @@ static void test_should_keep_alive(void) {
memset
(
&
conn
,
0
,
sizeof
(
conn
));
conn
.
ctx
=
&
ctx
;
parse_http_request
(
req1
,
sizeof
(
req1
),
&
conn
.
request_info
);
ASSERT
(
parse_http_message
(
req1
,
sizeof
(
req1
),
&
conn
.
request_info
)
==
sizeof
(
req1
)
-
1
);
ctx
.
config
[
ENABLE_KEEP_ALIVE
]
=
"no"
;
ASSERT
(
should_keep_alive
(
&
conn
)
==
0
);
...
...
@@ -80,13 +95,13 @@ static void test_should_keep_alive(void) {
ASSERT
(
should_keep_alive
(
&
conn
)
==
0
);
conn
.
must_close
=
0
;
parse_http_
request
(
req2
,
sizeof
(
req2
),
&
conn
.
request_info
);
parse_http_
message
(
req2
,
sizeof
(
req2
),
&
conn
.
request_info
);
ASSERT
(
should_keep_alive
(
&
conn
)
==
0
);
parse_http_
request
(
req3
,
sizeof
(
req3
),
&
conn
.
request_info
);
parse_http_
message
(
req3
,
sizeof
(
req3
),
&
conn
.
request_info
);
ASSERT
(
should_keep_alive
(
&
conn
)
==
0
);
parse_http_
request
(
req4
,
sizeof
(
req4
),
&
conn
.
request_info
);
parse_http_
message
(
req4
,
sizeof
(
req4
),
&
conn
.
request_info
);
ASSERT
(
should_keep_alive
(
&
conn
)
==
1
);
conn
.
status_code
=
401
;
...
...
@@ -143,7 +158,9 @@ static void test_remove_double_dots() {
size_t
i
;
for
(
i
=
0
;
i
<
ARRAY_SIZE
(
data
);
i
++
)
{
#if 0
printf("[%s] -> [%s]\n", data[i].before, data[i].after);
#endif
remove_double_dots_and_double_slashes
(
data
[
i
].
before
);
ASSERT
(
strcmp
(
data
[
i
].
before
,
data
[
i
].
after
)
==
0
);
}
...
...
@@ -162,14 +179,12 @@ static void *event_handler(enum mg_event event, struct mg_connection *conn) {
return
""
;
}
else
if
(
event
==
MG_OPEN_FILE
)
{
const
char
*
path
=
request_info
->
ev_data
;
printf
(
"%s: [%s]
\n
"
,
__func__
,
path
);
if
(
strcmp
(
path
,
"./blah"
)
==
0
)
{
mg_get_request_info
(
conn
)
->
ev_data
=
(
void
*
)
(
int
)
strlen
(
inmemory_file_data
);
return
(
void
*
)
inmemory_file_data
;
}
}
else
if
(
event
==
MG_EVENT_LOG
)
{
printf
(
"%s
\n
"
,
(
const
char
*
)
mg_get_request_info
(
conn
)
->
ev_data
);
}
return
NULL
;
...
...
@@ -178,6 +193,7 @@ static void *event_handler(enum mg_event event, struct mg_connection *conn) {
static
const
char
*
OPTIONS
[]
=
{
"document_root"
,
"."
,
"listening_ports"
,
LISTENING_ADDR
,
"ssl_certificate"
,
"build/ssl_cert.pem"
,
NULL
,
};
...
...
@@ -187,69 +203,87 @@ static void test_mg_upload(void) {
mg_stop
(
ctx
);
}
static
void
test_mg_fetch
(
void
)
{
char
buf
[
2000
],
buf2
[
2000
];
int
n
,
length
;
struct
mg_context
*
ctx
;
struct
mg_request_info
ri
;
const
char
*
tmp_file
=
"temporary_file_name_for_unit_test.txt"
;
struct
file
file
;
static
char
*
read_file
(
const
char
*
path
,
int
*
size
)
{
FILE
*
fp
;
struct
stat
st
;
char
*
data
=
NULL
;
if
((
fp
=
fopen
(
path
,
"r"
))
!=
NULL
&&
!
fstat
(
fileno
(
fp
),
&
st
))
{
*
size
=
st
.
st_size
;
ASSERT
((
data
=
malloc
(
*
size
))
!=
NULL
);
ASSERT
(
fread
(
data
,
1
,
*
size
,
fp
)
==
(
size_t
)
*
size
);
fclose
(
fp
);
}
return
data
;
}
ASSERT
((
ctx
=
mg_start
(
event_handler
,
NULL
,
OPTIONS
))
!=
NULL
);
static
char
*
read_conn
(
struct
mg_connection
*
conn
,
int
*
size
)
{
char
buf
[
100
],
*
data
=
NULL
;
int
len
;
*
size
=
0
;
while
((
len
=
mg_read
(
conn
,
buf
,
sizeof
(
buf
)))
>
0
)
{
*
size
+=
len
;
ASSERT
((
data
=
realloc
(
data
,
*
size
))
!=
NULL
);
memcpy
(
data
+
*
size
-
len
,
buf
,
len
);
}
return
data
;
}
// Failed fetch, pass invalid URL
ASSERT
(
mg_fetch
(
ctx
,
"localhost"
,
tmp_file
,
buf
,
sizeof
(
buf
),
&
ri
)
==
NULL
);
ASSERT
(
mg_fetch
(
ctx
,
LISTENING_ADDR
,
tmp_file
,
buf
,
sizeof
(
buf
),
&
ri
)
==
NULL
);
ASSERT
(
mg_fetch
(
ctx
,
"http://$$$.$$$"
,
tmp_file
,
buf
,
sizeof
(
buf
),
&
ri
)
==
NULL
);
// Failed fetch, pass invalid file name
ASSERT
(
mg_fetch
(
ctx
,
"http://"
LISTENING_ADDR
"/data"
,
"/this/file/must/not/exist/ever"
,
buf
,
sizeof
(
buf
),
&
ri
)
==
NULL
);
// Successful fetch
ASSERT
((
fp
=
mg_fetch
(
ctx
,
"http://"
LISTENING_ADDR
"/data"
,
tmp_file
,
buf
,
sizeof
(
buf
),
&
ri
))
!=
NULL
);
ASSERT
(
ri
.
num_headers
==
2
);
ASSERT
(
!
strcmp
(
ri
.
request_method
,
"HTTP/1.1"
));
ASSERT
(
!
strcmp
(
ri
.
uri
,
"200"
));
ASSERT
(
!
strcmp
(
ri
.
http_version
,
"OK"
));
ASSERT
((
length
=
ftell
(
fp
))
==
(
int
)
strlen
(
fetch_data
));
fseek
(
fp
,
0
,
SEEK_SET
);
ASSERT
(
fread
(
buf2
,
1
,
length
,
fp
)
==
(
size_t
)
length
);
ASSERT
(
memcmp
(
buf2
,
fetch_data
,
length
)
==
0
);
fclose
(
fp
);
static
void
test_mg_download
(
void
)
{
char
*
p1
,
*
p2
,
ebuf
[
100
];
int
len1
,
len2
,
port
=
atoi
(
HTTPS_PORT
);
struct
mg_connection
*
conn
;
struct
mg_context
*
ctx
;
// Fetch big file, mongoose.c
ASSERT
((
fp
=
mg_fetch
(
ctx
,
"http://"
LISTENING_ADDR
"/mongoose.c"
,
tmp_file
,
buf
,
sizeof
(
buf
),
&
ri
))
!=
NULL
);
ASSERT
(
mg_stat
(
fc
(
ctx
),
"mongoose.c"
,
&
file
));
ASSERT
(
file
.
size
==
ftell
(
fp
));
ASSERT
(
!
strcmp
(
ri
.
request_method
,
"HTTP/1.1"
));
// Fetch nonexistent file, /blah
ASSERT
((
fp
=
mg_fetch
(
ctx
,
"http://"
LISTENING_ADDR
"/boo"
,
tmp_file
,
buf
,
sizeof
(
buf
),
&
ri
))
!=
NULL
);
ASSERT
(
!
mg_strcasecmp
(
ri
.
uri
,
"404"
));
fclose
(
fp
);
ASSERT
((
ctx
=
mg_start
(
event_handler
,
NULL
,
OPTIONS
))
!=
NULL
);
// Fetch existing inmemory file
ASSERT
((
fp
=
mg_fetch
(
ctx
,
"http://"
LISTENING_ADDR
"/blah"
,
tmp_file
,
buf
,
sizeof
(
buf
),
&
ri
))
!=
NULL
);
ASSERT
(
!
mg_strcasecmp
(
ri
.
uri
,
"200"
));
n
=
ftell
(
fp
);
fseek
(
fp
,
0
,
SEEK_SET
);
printf
(
"%s %d %d [%.*s]
\n
"
,
__func__
,
n
,
(
int
)
feof
(
fp
),
n
,
buf2
);
n
=
fread
(
buf2
,
1
,
n
,
fp
);
printf
(
"%s %d %d [%.*s]
\n
"
,
__func__
,
n
,
(
int
)
feof
(
fp
),
n
,
buf2
);
ASSERT
((
size_t
)
ftell
(
fp
)
==
(
size_t
)
strlen
(
inmemory_file_data
));
ASSERT
(
!
memcmp
(
inmemory_file_data
,
buf2
,
ftell
(
fp
)));
fclose
(
fp
);
ASSERT
(
mg_download
(
NULL
,
port
,
0
,
ebuf
,
sizeof
(
ebuf
),
""
)
==
NULL
);
ASSERT
(
mg_download
(
"localhost"
,
0
,
0
,
ebuf
,
sizeof
(
ebuf
),
""
)
==
NULL
);
ASSERT
(
mg_download
(
"localhost"
,
port
,
1
,
ebuf
,
sizeof
(
ebuf
),
""
)
==
NULL
);
// Fetch nonexistent file, should see 404
ASSERT
((
conn
=
mg_download
(
"localhost"
,
port
,
1
,
ebuf
,
sizeof
(
ebuf
),
"%s"
,
"GET /gimbec HTTP/1.0
\r\n\r\n
"
))
!=
NULL
);
ASSERT
(
strcmp
(
conn
->
request_info
.
uri
,
"404"
)
==
0
);
mg_close_connection
(
conn
);
// Fetch mongoose.c, should succeed
ASSERT
((
conn
=
mg_download
(
"localhost"
,
port
,
1
,
ebuf
,
sizeof
(
ebuf
),
"%s"
,
"GET /mongoose.c HTTP/1.0
\r\n\r\n
"
))
!=
NULL
);
ASSERT
(
!
strcmp
(
conn
->
request_info
.
uri
,
"200"
));
ASSERT
((
p1
=
read_conn
(
conn
,
&
len1
))
!=
NULL
);
ASSERT
((
p2
=
read_file
(
"mongoose.c"
,
&
len2
))
!=
NULL
);
ASSERT
(
len1
==
len2
);
ASSERT
(
memcmp
(
p1
,
p2
,
len1
)
==
0
);
free
(
p1
),
free
(
p2
);
mg_close_connection
(
conn
);
// Fetch in-memory file, should succeed.
ASSERT
((
conn
=
mg_download
(
"localhost"
,
port
,
1
,
ebuf
,
sizeof
(
ebuf
),
"%s"
,
"GET /blah HTTP/1.1
\r\n\r\n
"
))
!=
NULL
);
ASSERT
((
p1
=
read_conn
(
conn
,
&
len1
))
!=
NULL
);
ASSERT
(
len1
==
(
int
)
strlen
(
inmemory_file_data
));
ASSERT
(
memcmp
(
p1
,
inmemory_file_data
,
len1
)
==
0
);
free
(
p1
);
mg_close_connection
(
conn
);
// Test SSL redirect, IP address
ASSERT
((
conn
=
mg_download
(
"localhost"
,
atoi
(
HTTP_PORT
),
0
,
ebuf
,
sizeof
(
ebuf
),
"%s"
,
"GET /foo HTTP/1.1
\r\n\r\n
"
))
!=
NULL
);
ASSERT
(
strcmp
(
conn
->
request_info
.
uri
,
"302"
)
==
0
);
ASSERT
(
strcmp
(
mg_get_header
(
conn
,
"Location"
),
"https://127.0.0.1:"
HTTPS_PORT
"/foo"
)
==
0
);
mg_close_connection
(
conn
);
// Test SSL redirect, Host:
ASSERT
((
conn
=
mg_download
(
"localhost"
,
atoi
(
HTTP_PORT
),
0
,
ebuf
,
sizeof
(
ebuf
),
"%s"
,
"GET /foo HTTP/1.1
\r\n
Host: a.b:77
\n\n
"
))
!=
NULL
);
ASSERT
(
strcmp
(
conn
->
request_info
.
uri
,
"302"
)
==
0
);
ASSERT
(
strcmp
(
mg_get_header
(
conn
,
"Location"
),
"https://a.b:"
HTTPS_PORT
"/foo"
)
==
0
);
mg_close_connection
(
conn
);
remove
(
tmp_file
);
mg_stop
(
ctx
);
}
...
...
@@ -261,7 +295,6 @@ static void test_base64_encode(void) {
for
(
i
=
0
;
in
[
i
]
!=
NULL
;
i
++
)
{
base64_encode
((
unsigned
char
*
)
in
[
i
],
strlen
(
in
[
i
]),
buf
);
printf
(
"[%s] [%s]
\n
"
,
out
[
i
],
buf
);
ASSERT
(
!
strcmp
(
buf
,
out
[
i
]));
}
}
...
...
@@ -329,7 +362,9 @@ static void check_lua_expr(lua_State *L, const char *expr, const char *value) {
luaL_dostring
(
L
,
buf
);
lua_getglobal
(
L
,
var_name
);
v
=
lua_tostring
(
L
,
-
1
);
#if 0
printf("%s: %s: [%s] [%s]\n", __func__, expr, v == NULL ? "null" : v, value);
#endif
ASSERT
((
value
==
NULL
&&
v
==
NULL
)
||
(
value
!=
NULL
&&
v
!=
NULL
&&
!
strcmp
(
value
,
v
)));
}
...
...
@@ -341,13 +376,12 @@ static void test_lua(void) {
char
http_request
[]
=
"POST /foo/bar HTTP/1.1
\r\n
"
"Content-Length: 12
\r\n
"
"Connection: close
\r\n\r\n
hello world!"
;
const
char
*
page
=
"<? print('hi') ?>"
;
lua_State
*
L
=
luaL_newstate
();
conn
.
ctx
=
&
ctx
;
conn
.
buf
=
http_request
;
conn
.
buf_size
=
conn
.
data_len
=
strlen
(
http_request
);
conn
.
request_len
=
parse_http_
request
(
conn
.
buf
,
conn
.
data_len
,
conn
.
request_len
=
parse_http_
message
(
conn
.
buf
,
conn
.
data_len
,
&
conn
.
request_info
);
conn
.
content_len
=
conn
.
data_len
-
conn
.
request_len
;
...
...
@@ -371,7 +405,7 @@ static void test_lua(void) {
static
void
*
user_data_tester
(
enum
mg_event
event
,
struct
mg_connection
*
conn
)
{
struct
mg_request_info
*
ri
=
mg_get_request_info
(
conn
);
ASSERT
(
ri
->
user_data
==
(
void
*
)
123
);
ASSERT
(
event
==
MG_NEW_REQUEST
);
ASSERT
(
event
==
MG_NEW_REQUEST
||
event
==
MG_INIT_SSL
);
return
NULL
;
}
...
...
@@ -413,8 +447,8 @@ int __cdecl main(void) {
test_match_prefix
();
test_remove_double_dots
();
test_should_keep_alive
();
test_parse_http_
request
();
test_mg_
fetch
();
test_parse_http_
message
();
test_mg_
download
();
test_mg_get_var
();
test_set_throttle
();
test_next_option
();
...
...
@@ -425,5 +459,6 @@ int __cdecl main(void) {
test_lua
();
#endif
test_skip_quoted
();
printf
(
"%s
\n
"
,
"PASSED"
);
return
0
;
}
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