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
66824e77
Commit
66824e77
authored
Nov 23, 2013
by
Sergey Lyubka
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
moved CGI and IO into separate files
parent
7fe6b292
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
1258 additions
and
1254 deletions
+1258
-1254
Makefile
build/Makefile
+2
-1
cgi.c
build/src/cgi.c
+383
-0
directory.c
build/src/directory.c
+33
-0
io.c
build/src/io.c
+187
-0
mongoose.c
build/src/mongoose.c
+0
-710
parse_http.c
build/src/parse_http.c
+63
-0
string.c
build/src/string.c
+47
-0
mongoose.c
mongoose.c
+543
-543
No files found.
build/Makefile
View file @
66824e77
...
@@ -29,7 +29,8 @@ VERSION = $(shell perl -lne \
...
@@ -29,7 +29,8 @@ VERSION = $(shell perl -lne \
SOURCES
=
src/internal.h src/util.c src/string.c src/parse_date.c
\
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/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/mg_printf.c src/ssl.c src/http_client.c src/mime.c
\
src/directory.c src/log.c src/mongoose.c src/lua.c
src/directory.c src/log.c src/parse_http.c src/io.c src/cgi.c
\
src/mongoose.c src/lua.c
TINY_SOURCES
=
../mongoose.c main.c
TINY_SOURCES
=
../mongoose.c main.c
LUA_SOURCES
=
$(TINY_SOURCES)
sqlite3.c lsqlite3.c lua_5.2.1.c
LUA_SOURCES
=
$(TINY_SOURCES)
sqlite3.c lsqlite3.c lua_5.2.1.c
...
...
build/src/cgi.c
0 → 100644
View file @
66824e77
#include "internal.h"
static
int
forward_body_data
(
struct
mg_connection
*
conn
,
FILE
*
fp
,
SOCKET
sock
,
SSL
*
ssl
)
{
const
char
*
expect
,
*
body
;
char
buf
[
MG_BUF_LEN
];
int
nread
,
buffered_len
,
success
=
0
;
int64_t
left
;
expect
=
mg_get_header
(
conn
,
"Expect"
);
assert
(
fp
!=
NULL
);
if
(
conn
->
content_len
==
INT64_MAX
)
{
send_http_error
(
conn
,
411
,
"Length Required"
,
"%s"
,
""
);
}
else
if
(
expect
!=
NULL
&&
mg_strcasecmp
(
expect
,
"100-continue"
))
{
send_http_error
(
conn
,
417
,
"Expectation Failed"
,
"%s"
,
""
);
}
else
{
if
(
expect
!=
NULL
)
{
(
void
)
mg_printf
(
conn
,
"%s"
,
"HTTP/1.1 100 Continue
\r\n\r\n
"
);
}
buffered_len
=
conn
->
data_len
-
conn
->
request_len
;
body
=
conn
->
buf
+
conn
->
request_len
;
assert
(
buffered_len
>=
0
);
if
(
buffered_len
>
0
)
{
if
((
int64_t
)
buffered_len
>
conn
->
content_len
)
{
buffered_len
=
(
int
)
conn
->
content_len
;
}
push
(
fp
,
sock
,
ssl
,
body
,
(
int64_t
)
buffered_len
);
memmove
((
char
*
)
body
,
body
+
buffered_len
,
buffered_len
);
conn
->
data_len
-=
buffered_len
;
}
nread
=
0
;
while
(
conn
->
num_bytes_read
<
conn
->
content_len
+
conn
->
request_len
)
{
left
=
left_to_read
(
conn
);
if
(
left
>
(
int64_t
)
sizeof
(
buf
))
{
left
=
sizeof
(
buf
);
}
nread
=
pull
(
NULL
,
conn
,
buf
,
(
int
)
left
);
if
(
nread
<=
0
||
push
(
fp
,
sock
,
ssl
,
buf
,
nread
)
!=
nread
)
{
break
;
}
}
if
(
left_to_read
(
conn
)
==
0
)
{
success
=
nread
>=
0
;
}
// Each error code path in this function must send an error
if
(
!
success
)
{
send_http_error
(
conn
,
577
,
http_500_error
,
"%s"
,
""
);
}
}
return
success
;
}
#if !defined(NO_CGI)
// This structure helps to create an environment for the spawned CGI program.
// Environment is an array of "VARIABLE=VALUE\0" ASCIIZ strings,
// last element must be NULL.
// However, on Windows there is a requirement that all these VARIABLE=VALUE\0
// strings must reside in a contiguous buffer. The end of the buffer is
// marked by two '\0' characters.
// We satisfy both worlds: we create an envp array (which is vars), all
// entries are actually pointers inside buf.
struct
cgi_env_block
{
struct
mg_connection
*
conn
;
char
buf
[
CGI_ENVIRONMENT_SIZE
];
// Environment buffer
int
len
;
// Space taken
char
*
vars
[
MAX_CGI_ENVIR_VARS
];
// char **envp
int
nvars
;
// Number of variables
};
static
char
*
addenv
(
struct
cgi_env_block
*
block
,
PRINTF_FORMAT_STRING
(
const
char
*
fmt
),
...)
PRINTF_ARGS
(
2
,
3
);
// Append VARIABLE=VALUE\0 string to the buffer, and add a respective
// pointer into the vars array.
static
char
*
addenv
(
struct
cgi_env_block
*
block
,
const
char
*
fmt
,
...)
{
int
n
,
space
;
char
*
added
;
va_list
ap
;
// Calculate how much space is left in the buffer
space
=
sizeof
(
block
->
buf
)
-
block
->
len
-
2
;
assert
(
space
>=
0
);
// Make a pointer to the free space int the buffer
added
=
block
->
buf
+
block
->
len
;
// Copy VARIABLE=VALUE\0 string into the free space
va_start
(
ap
,
fmt
);
n
=
mg_vsnprintf
(
added
,
(
size_t
)
space
,
fmt
,
ap
);
va_end
(
ap
);
// Make sure we do not overflow buffer and the envp array
if
(
n
>
0
&&
n
+
1
<
space
&&
block
->
nvars
<
(
int
)
ARRAY_SIZE
(
block
->
vars
)
-
2
)
{
// Append a pointer to the added string into the envp array
block
->
vars
[
block
->
nvars
++
]
=
added
;
// Bump up used length counter. Include \0 terminator
block
->
len
+=
n
+
1
;
}
else
{
cry
(
block
->
conn
,
"%s: CGI env buffer truncated for [%s]"
,
__func__
,
fmt
);
}
return
added
;
}
static
void
prepare_cgi_environment
(
struct
mg_connection
*
conn
,
const
char
*
prog
,
struct
cgi_env_block
*
blk
)
{
const
struct
mg_request_info
*
ri
=
&
conn
->
request_info
;
const
char
*
s
,
*
slash
;
struct
vec
var_vec
;
char
*
p
,
src_addr
[
IP_ADDR_STR_LEN
];
int
i
;
blk
->
len
=
blk
->
nvars
=
0
;
blk
->
conn
=
conn
;
sockaddr_to_string
(
src_addr
,
sizeof
(
src_addr
),
&
conn
->
client
.
rsa
);
addenv
(
blk
,
"SERVER_NAME=%s"
,
conn
->
ctx
->
config
[
AUTHENTICATION_DOMAIN
]);
addenv
(
blk
,
"SERVER_ROOT=%s"
,
conn
->
ctx
->
config
[
DOCUMENT_ROOT
]);
addenv
(
blk
,
"DOCUMENT_ROOT=%s"
,
conn
->
ctx
->
config
[
DOCUMENT_ROOT
]);
addenv
(
blk
,
"SERVER_SOFTWARE=%s/%s"
,
"Mongoose"
,
mg_version
());
// Prepare the environment block
addenv
(
blk
,
"%s"
,
"GATEWAY_INTERFACE=CGI/1.1"
);
addenv
(
blk
,
"%s"
,
"SERVER_PROTOCOL=HTTP/1.1"
);
addenv
(
blk
,
"%s"
,
"REDIRECT_STATUS=200"
);
// For PHP
// TODO(lsm): fix this for IPv6 case
addenv
(
blk
,
"SERVER_PORT=%d"
,
ntohs
(
conn
->
client
.
lsa
.
sin
.
sin_port
));
addenv
(
blk
,
"REQUEST_METHOD=%s"
,
ri
->
request_method
);
addenv
(
blk
,
"REMOTE_ADDR=%s"
,
src_addr
);
addenv
(
blk
,
"REMOTE_PORT=%d"
,
ri
->
remote_port
);
addenv
(
blk
,
"REQUEST_URI=%s%s%s"
,
ri
->
uri
,
ri
->
query_string
==
NULL
?
""
:
"?"
,
ri
->
query_string
==
NULL
?
""
:
ri
->
query_string
);
// SCRIPT_NAME
if
(
conn
->
path_info
!=
NULL
)
{
addenv
(
blk
,
"SCRIPT_NAME=%.*s"
,
(
int
)
(
strlen
(
ri
->
uri
)
-
strlen
(
conn
->
path_info
)),
ri
->
uri
);
addenv
(
blk
,
"PATH_INFO=%s"
,
conn
->
path_info
);
}
else
{
s
=
strrchr
(
prog
,
'/'
);
slash
=
strrchr
(
ri
->
uri
,
'/'
);
addenv
(
blk
,
"SCRIPT_NAME=%.*s%s"
,
slash
==
NULL
?
0
:
(
int
)
(
slash
-
ri
->
uri
),
ri
->
uri
,
s
==
NULL
?
prog
:
s
);
}
addenv
(
blk
,
"SCRIPT_FILENAME=%s"
,
prog
);
addenv
(
blk
,
"PATH_TRANSLATED=%s"
,
prog
);
addenv
(
blk
,
"HTTPS=%s"
,
conn
->
ssl
==
NULL
?
"off"
:
"on"
);
if
((
s
=
mg_get_header
(
conn
,
"Content-Type"
))
!=
NULL
)
addenv
(
blk
,
"CONTENT_TYPE=%s"
,
s
);
if
(
ri
->
query_string
!=
NULL
)
{
addenv
(
blk
,
"QUERY_STRING=%s"
,
ri
->
query_string
);
}
if
((
s
=
mg_get_header
(
conn
,
"Content-Length"
))
!=
NULL
)
addenv
(
blk
,
"CONTENT_LENGTH=%s"
,
s
);
if
((
s
=
getenv
(
"PATH"
))
!=
NULL
)
addenv
(
blk
,
"PATH=%s"
,
s
);
#if defined(_WIN32)
if
((
s
=
getenv
(
"COMSPEC"
))
!=
NULL
)
{
addenv
(
blk
,
"COMSPEC=%s"
,
s
);
}
if
((
s
=
getenv
(
"SYSTEMROOT"
))
!=
NULL
)
{
addenv
(
blk
,
"SYSTEMROOT=%s"
,
s
);
}
if
((
s
=
getenv
(
"SystemDrive"
))
!=
NULL
)
{
addenv
(
blk
,
"SystemDrive=%s"
,
s
);
}
if
((
s
=
getenv
(
"ProgramFiles"
))
!=
NULL
)
{
addenv
(
blk
,
"ProgramFiles=%s"
,
s
);
}
if
((
s
=
getenv
(
"ProgramFiles(x86)"
))
!=
NULL
)
{
addenv
(
blk
,
"ProgramFiles(x86)=%s"
,
s
);
}
if
((
s
=
getenv
(
"CommonProgramFiles(x86)"
))
!=
NULL
)
{
addenv
(
blk
,
"CommonProgramFiles(x86)=%s"
,
s
);
}
#else
if
((
s
=
getenv
(
"LD_LIBRARY_PATH"
))
!=
NULL
)
addenv
(
blk
,
"LD_LIBRARY_PATH=%s"
,
s
);
#endif // _WIN32
if
((
s
=
getenv
(
"PERLLIB"
))
!=
NULL
)
addenv
(
blk
,
"PERLLIB=%s"
,
s
);
if
(
ri
->
remote_user
!=
NULL
)
{
addenv
(
blk
,
"REMOTE_USER=%s"
,
ri
->
remote_user
);
addenv
(
blk
,
"%s"
,
"AUTH_TYPE=Digest"
);
}
// Add all headers as HTTP_* variables
for
(
i
=
0
;
i
<
ri
->
num_headers
;
i
++
)
{
p
=
addenv
(
blk
,
"HTTP_%s=%s"
,
ri
->
http_headers
[
i
].
name
,
ri
->
http_headers
[
i
].
value
);
// Convert variable name into uppercase, and change - to _
for
(;
*
p
!=
'='
&&
*
p
!=
'\0'
;
p
++
)
{
if
(
*
p
==
'-'
)
*
p
=
'_'
;
*
p
=
(
char
)
toupper
(
*
(
unsigned
char
*
)
p
);
}
}
// Add user-specified variables
s
=
conn
->
ctx
->
config
[
CGI_ENVIRONMENT
];
while
((
s
=
next_option
(
s
,
&
var_vec
,
NULL
))
!=
NULL
)
{
addenv
(
blk
,
"%.*s"
,
(
int
)
var_vec
.
len
,
var_vec
.
ptr
);
}
blk
->
vars
[
blk
->
nvars
++
]
=
NULL
;
blk
->
buf
[
blk
->
len
++
]
=
'\0'
;
assert
(
blk
->
nvars
<
(
int
)
ARRAY_SIZE
(
blk
->
vars
));
assert
(
blk
->
len
>
0
);
assert
(
blk
->
len
<
(
int
)
sizeof
(
blk
->
buf
));
}
static
void
handle_cgi_request
(
struct
mg_connection
*
conn
,
const
char
*
prog
)
{
int
headers_len
,
data_len
,
i
,
fdin
[
2
],
fdout
[
2
];
const
char
*
status
,
*
status_text
;
char
buf
[
16384
],
*
pbuf
,
dir
[
PATH_MAX
],
*
p
;
struct
mg_request_info
ri
;
struct
cgi_env_block
blk
;
FILE
*
in
=
NULL
,
*
out
=
NULL
;
pid_t
pid
=
(
pid_t
)
-
1
;
prepare_cgi_environment
(
conn
,
prog
,
&
blk
);
// CGI must be executed in its own directory. 'dir' must point to the
// directory containing executable program, 'p' must point to the
// executable program name relative to 'dir'.
(
void
)
mg_snprintf
(
dir
,
sizeof
(
dir
),
"%s"
,
prog
);
if
((
p
=
strrchr
(
dir
,
'/'
))
!=
NULL
)
{
*
p
++
=
'\0'
;
}
else
{
dir
[
0
]
=
'.'
,
dir
[
1
]
=
'\0'
;
p
=
(
char
*
)
prog
;
}
if
(
pipe
(
fdin
)
!=
0
||
pipe
(
fdout
)
!=
0
)
{
send_http_error
(
conn
,
500
,
http_500_error
,
"Cannot create CGI pipe: %s"
,
strerror
(
ERRNO
));
goto
done
;
}
pid
=
spawn_process
(
conn
,
p
,
blk
.
buf
,
blk
.
vars
,
fdin
[
0
],
fdout
[
1
],
dir
);
if
(
pid
==
(
pid_t
)
-
1
)
{
send_http_error
(
conn
,
500
,
http_500_error
,
"Cannot spawn CGI process [%s]: %s"
,
prog
,
strerror
(
ERRNO
));
goto
done
;
}
// Make sure child closes all pipe descriptors. It must dup them to 0,1
set_close_on_exec
(
fdin
[
0
]);
set_close_on_exec
(
fdin
[
1
]);
set_close_on_exec
(
fdout
[
0
]);
set_close_on_exec
(
fdout
[
1
]);
// Parent closes only one side of the pipes.
// If we don't mark them as closed, close() attempt before
// return from this function throws an exception on Windows.
// Windows does not like when closed descriptor is closed again.
(
void
)
close
(
fdin
[
0
]);
(
void
)
close
(
fdout
[
1
]);
fdin
[
0
]
=
fdout
[
1
]
=
-
1
;
if
((
in
=
fdopen
(
fdin
[
1
],
"wb"
))
==
NULL
||
(
out
=
fdopen
(
fdout
[
0
],
"rb"
))
==
NULL
)
{
send_http_error
(
conn
,
500
,
http_500_error
,
"fopen: %s"
,
strerror
(
ERRNO
));
goto
done
;
}
setbuf
(
in
,
NULL
);
setbuf
(
out
,
NULL
);
// Send POST data to the CGI process if needed
if
(
!
strcmp
(
conn
->
request_info
.
request_method
,
"POST"
)
&&
!
forward_body_data
(
conn
,
in
,
INVALID_SOCKET
,
NULL
))
{
goto
done
;
}
// Close so child gets an EOF.
fclose
(
in
);
in
=
NULL
;
fdin
[
1
]
=
-
1
;
// Now read CGI reply into a buffer. We need to set correct
// status code, thus we need to see all HTTP headers first.
// Do not send anything back to client, until we buffer in all
// HTTP headers.
data_len
=
0
;
headers_len
=
read_request
(
out
,
conn
,
buf
,
sizeof
(
buf
),
&
data_len
);
if
(
headers_len
<=
0
)
{
send_http_error
(
conn
,
500
,
http_500_error
,
"CGI program sent malformed or too big (>%u bytes) "
"HTTP headers: [%.*s]"
,
(
unsigned
)
sizeof
(
buf
),
data_len
,
buf
);
goto
done
;
}
pbuf
=
buf
;
buf
[
headers_len
-
1
]
=
'\0'
;
parse_http_headers
(
&
pbuf
,
&
ri
);
// Make up and send the status line
status_text
=
"OK"
;
if
((
status
=
get_header
(
&
ri
,
"Status"
))
!=
NULL
)
{
conn
->
status_code
=
atoi
(
status
);
status_text
=
status
;
while
(
isdigit
(
*
(
unsigned
char
*
)
status_text
)
||
*
status_text
==
' '
)
{
status_text
++
;
}
}
else
if
(
get_header
(
&
ri
,
"Location"
)
!=
NULL
)
{
conn
->
status_code
=
302
;
}
else
{
conn
->
status_code
=
200
;
}
if
(
get_header
(
&
ri
,
"Connection"
)
!=
NULL
&&
!
mg_strcasecmp
(
get_header
(
&
ri
,
"Connection"
),
"keep-alive"
))
{
conn
->
must_close
=
1
;
}
(
void
)
mg_printf
(
conn
,
"HTTP/1.1 %d %s
\r\n
"
,
conn
->
status_code
,
status_text
);
// Send headers
for
(
i
=
0
;
i
<
ri
.
num_headers
;
i
++
)
{
mg_printf
(
conn
,
"%s: %s
\r\n
"
,
ri
.
http_headers
[
i
].
name
,
ri
.
http_headers
[
i
].
value
);
}
mg_write
(
conn
,
"
\r\n
"
,
2
);
// Send chunk of data that may have been read after the headers
conn
->
num_bytes_sent
+=
mg_write
(
conn
,
buf
+
headers_len
,
(
size_t
)(
data_len
-
headers_len
));
// Read the rest of CGI output and send to the client
send_file_data
(
conn
,
out
,
0
,
INT64_MAX
);
done:
if
(
pid
!=
(
pid_t
)
-
1
)
{
kill
(
pid
,
SIGKILL
);
}
if
(
fdin
[
0
]
!=
-
1
)
{
close
(
fdin
[
0
]);
}
if
(
fdout
[
1
]
!=
-
1
)
{
close
(
fdout
[
1
]);
}
if
(
in
!=
NULL
)
{
fclose
(
in
);
}
else
if
(
fdin
[
1
]
!=
-
1
)
{
close
(
fdin
[
1
]);
}
if
(
out
!=
NULL
)
{
fclose
(
out
);
}
else
if
(
fdout
[
0
]
!=
-
1
)
{
close
(
fdout
[
0
]);
}
}
#endif // !NO_CGI
build/src/directory.c
View file @
66824e77
...
@@ -236,3 +236,36 @@ static void handle_directory_request(struct mg_connection *conn,
...
@@ -236,3 +236,36 @@ static void handle_directory_request(struct mg_connection *conn,
conn
->
status_code
=
200
;
conn
->
status_code
=
200
;
}
}
// For a given PUT path, create all intermediate subdirectories
// for given path. Return 0 if the path itself is a directory,
// or -1 on error, 1 if OK.
static
int
put_dir
(
const
char
*
path
)
{
char
buf
[
PATH_MAX
];
const
char
*
s
,
*
p
;
struct
file
file
=
STRUCT_FILE_INITIALIZER
;
int
len
,
res
=
1
;
for
(
s
=
p
=
path
+
2
;
(
p
=
strchr
(
s
,
'/'
))
!=
NULL
;
s
=
++
p
)
{
len
=
p
-
path
;
if
(
len
>=
(
int
)
sizeof
(
buf
))
{
res
=
-
1
;
break
;
}
memcpy
(
buf
,
path
,
len
);
buf
[
len
]
=
'\0'
;
// Try to create intermediate directory
DEBUG_TRACE
((
"mkdir(%s)"
,
buf
));
if
(
!
mg_stat
(
buf
,
&
file
)
&&
mg_mkdir
(
buf
,
0755
)
!=
0
)
{
res
=
-
1
;
break
;
}
// Is path itself a directory?
if
(
p
[
1
]
==
'\0'
)
{
res
=
0
;
}
}
return
res
;
}
build/src/io.c
0 → 100644
View file @
66824e77
#include "internal.h"
// Return number of bytes left to read for this connection
static
int64_t
left_to_read
(
const
struct
mg_connection
*
conn
)
{
return
conn
->
content_len
+
conn
->
request_len
-
conn
->
num_bytes_read
;
}
// Write data to the IO channel - opened file descriptor, socket or SSL
// descriptor. Return number of bytes written.
static
int64_t
push
(
FILE
*
fp
,
SOCKET
sock
,
SSL
*
ssl
,
const
char
*
buf
,
int64_t
len
)
{
int64_t
sent
;
int
n
,
k
;
(
void
)
ssl
;
// Get rid of warning
sent
=
0
;
while
(
sent
<
len
)
{
// How many bytes we send in this iteration
k
=
len
-
sent
>
INT_MAX
?
INT_MAX
:
(
int
)
(
len
-
sent
);
#if !defined(NO_SSL)
if
(
ssl
!=
NULL
)
{
n
=
SSL_write
(
ssl
,
buf
+
sent
,
k
);
}
else
#endif
if
(
fp
!=
NULL
)
{
n
=
(
int
)
fwrite
(
buf
+
sent
,
1
,
(
size_t
)
k
,
fp
);
if
(
ferror
(
fp
))
n
=
-
1
;
}
else
{
n
=
send
(
sock
,
buf
+
sent
,
(
size_t
)
k
,
MSG_NOSIGNAL
);
}
if
(
n
<=
0
)
break
;
sent
+=
n
;
}
return
sent
;
}
// Read from IO channel - opened file descriptor, socket, or SSL descriptor.
// Return negative value on error, or number of bytes read on success.
static
int
pull
(
FILE
*
fp
,
struct
mg_connection
*
conn
,
char
*
buf
,
int
len
)
{
int
nread
;
if
(
len
<=
0
)
return
0
;
if
(
fp
!=
NULL
)
{
// Use read() instead of fread(), because if we're reading from the CGI
// pipe, fread() may block until IO buffer is filled up. We cannot afford
// to block and must pass all read bytes immediately to the client.
nread
=
read
(
fileno
(
fp
),
buf
,
(
size_t
)
len
);
#ifndef NO_SSL
}
else
if
(
conn
->
ssl
!=
NULL
)
{
nread
=
SSL_read
(
conn
->
ssl
,
buf
,
len
);
#endif
}
else
{
nread
=
recv
(
conn
->
client
.
sock
,
buf
,
(
size_t
)
len
,
0
);
}
if
(
nread
>
0
)
{
conn
->
num_bytes_read
+=
nread
;
}
return
conn
->
ctx
->
stop_flag
?
-
1
:
nread
;
}
static
int
pull_all
(
FILE
*
fp
,
struct
mg_connection
*
conn
,
char
*
buf
,
int
len
)
{
int
n
,
nread
=
0
;
while
(
len
>
0
&&
conn
->
ctx
->
stop_flag
==
0
)
{
n
=
pull
(
fp
,
conn
,
buf
+
nread
,
len
);
if
(
n
<
0
)
{
nread
=
n
;
// Propagate the error
break
;
}
else
if
(
n
==
0
)
{
break
;
// No more data to read
}
else
{
nread
+=
n
;
len
-=
n
;
}
}
return
nread
;
}
int
mg_read
(
struct
mg_connection
*
conn
,
void
*
buf
,
int
len
)
{
int
n
,
buffered_len
,
nread
=
0
;
int64_t
left
;
if
(
conn
->
content_len
<=
0
)
{
return
0
;
}
// conn->buf body
// |=================|==========|===============|
// |<--request_len-->| |
// |<-----------data_len------->| conn->buf + conn->buf_size
// First, check for data buffered in conn->buf by read_request().
if
(
len
>
0
&&
(
buffered_len
=
conn
->
data_len
-
conn
->
request_len
)
>
0
)
{
char
*
body
=
conn
->
buf
+
conn
->
request_len
;
if
(
buffered_len
>
len
)
buffered_len
=
len
;
if
(
buffered_len
>
conn
->
content_len
)
buffered_len
=
(
int
)
conn
->
content_len
;
memcpy
(
buf
,
body
,
(
size_t
)
buffered_len
);
memmove
(
body
,
body
+
buffered_len
,
&
conn
->
buf
[
conn
->
data_len
]
-
&
body
[
buffered_len
]);
len
-=
buffered_len
;
conn
->
data_len
-=
buffered_len
;
nread
+=
buffered_len
;
}
// Read data from the socket.
if
(
len
>
0
&&
(
left
=
left_to_read
(
conn
))
>
0
)
{
if
(
left
<
len
)
{
len
=
(
int
)
left
;
}
n
=
pull_all
(
NULL
,
conn
,
(
char
*
)
buf
+
nread
,
(
int
)
len
);
nread
=
n
>=
0
?
nread
+
n
:
n
;
}
return
nread
;
}
int
mg_write
(
struct
mg_connection
*
conn
,
const
void
*
buf
,
int
len
)
{
return
push
(
NULL
,
conn
->
client
.
sock
,
conn
->
ssl
,
(
const
char
*
)
buf
,
(
int64_t
)
len
);
}
// Keep reading the input (either opened file descriptor fd, or socket sock,
// or SSL descriptor ssl) into buffer buf, until \r\n\r\n appears in the
// buffer (which marks the end of HTTP request). Buffer buf may already
// have some data. The length of the data is stored in nread.
// Upon every read operation, increase nread by the number of bytes read.
static
int
read_request
(
FILE
*
fp
,
struct
mg_connection
*
conn
,
char
*
buf
,
int
bufsiz
,
int
*
nread
)
{
int
request_len
,
n
=
0
;
request_len
=
get_request_len
(
buf
,
*
nread
);
while
(
conn
->
ctx
->
stop_flag
==
0
&&
*
nread
<
bufsiz
&&
request_len
==
0
&&
(
n
=
pull
(
fp
,
conn
,
buf
+
*
nread
,
bufsiz
-
*
nread
))
>
0
)
{
*
nread
+=
n
;
assert
(
*
nread
<=
bufsiz
);
request_len
=
get_request_len
(
buf
,
*
nread
);
}
return
request_len
<=
0
&&
n
<=
0
?
-
1
:
request_len
;
}
// Send len bytes from the opened file to the client.
static
void
send_file_data
(
struct
mg_connection
*
conn
,
FILE
*
fp
,
int64_t
offset
,
int64_t
len
)
{
char
buf
[
MG_BUF_LEN
];
int
num_read
,
num_written
,
to_read
;
// If offset is beyond file boundaries, don't send anything
if
(
offset
>
0
&&
fseeko
(
fp
,
offset
,
SEEK_SET
)
!=
0
)
{
return
;
}
while
(
len
>
0
)
{
// Calculate how much to read from the file in the buffer
to_read
=
sizeof
(
buf
);
if
((
int64_t
)
to_read
>
len
)
{
to_read
=
(
int
)
len
;
}
// Read from file, exit the loop on error
if
((
num_read
=
fread
(
buf
,
1
,
(
size_t
)
to_read
,
fp
))
<=
0
)
{
break
;
}
// Send read bytes to the client, exit the loop on error
if
((
num_written
=
mg_write
(
conn
,
buf
,
(
size_t
)
num_read
))
!=
num_read
)
{
break
;
}
// Both read and were successful, adjust counters
conn
->
num_bytes_sent
+=
num_written
;
len
-=
num_written
;
}
}
build/src/mongoose.c
View file @
66824e77
#include "internal.h"
#include "internal.h"
// Return number of bytes left to read for this connection
static
int64_t
left_to_read
(
const
struct
mg_connection
*
conn
)
{
return
conn
->
content_len
+
conn
->
request_len
-
conn
->
num_bytes_read
;
}
static
int
call_user
(
int
type
,
struct
mg_connection
*
conn
,
void
*
p
)
{
static
int
call_user
(
int
type
,
struct
mg_connection
*
conn
,
void
*
p
)
{
if
(
conn
!=
NULL
&&
conn
->
ctx
!=
NULL
)
{
if
(
conn
!=
NULL
&&
conn
->
ctx
!=
NULL
)
{
conn
->
event
.
user_data
=
conn
->
ctx
->
user_data
;
conn
->
event
.
user_data
=
conn
->
ctx
->
user_data
;
...
@@ -120,177 +115,6 @@ static void send_http_error(struct mg_connection *conn, int status,
...
@@ -120,177 +115,6 @@ static void send_http_error(struct mg_connection *conn, int status,
}
}
}
}
// Write data to the IO channel - opened file descriptor, socket or SSL
// descriptor. Return number of bytes written.
static
int64_t
push
(
FILE
*
fp
,
SOCKET
sock
,
SSL
*
ssl
,
const
char
*
buf
,
int64_t
len
)
{
int64_t
sent
;
int
n
,
k
;
(
void
)
ssl
;
// Get rid of warning
sent
=
0
;
while
(
sent
<
len
)
{
// How many bytes we send in this iteration
k
=
len
-
sent
>
INT_MAX
?
INT_MAX
:
(
int
)
(
len
-
sent
);
#if !defined(NO_SSL)
if
(
ssl
!=
NULL
)
{
n
=
SSL_write
(
ssl
,
buf
+
sent
,
k
);
}
else
#endif
if
(
fp
!=
NULL
)
{
n
=
(
int
)
fwrite
(
buf
+
sent
,
1
,
(
size_t
)
k
,
fp
);
if
(
ferror
(
fp
))
n
=
-
1
;
}
else
{
n
=
send
(
sock
,
buf
+
sent
,
(
size_t
)
k
,
MSG_NOSIGNAL
);
}
if
(
n
<=
0
)
break
;
sent
+=
n
;
}
return
sent
;
}
// Read from IO channel - opened file descriptor, socket, or SSL descriptor.
// Return negative value on error, or number of bytes read on success.
static
int
pull
(
FILE
*
fp
,
struct
mg_connection
*
conn
,
char
*
buf
,
int
len
)
{
int
nread
;
if
(
len
<=
0
)
return
0
;
if
(
fp
!=
NULL
)
{
// Use read() instead of fread(), because if we're reading from the CGI
// pipe, fread() may block until IO buffer is filled up. We cannot afford
// to block and must pass all read bytes immediately to the client.
nread
=
read
(
fileno
(
fp
),
buf
,
(
size_t
)
len
);
#ifndef NO_SSL
}
else
if
(
conn
->
ssl
!=
NULL
)
{
nread
=
SSL_read
(
conn
->
ssl
,
buf
,
len
);
#endif
}
else
{
nread
=
recv
(
conn
->
client
.
sock
,
buf
,
(
size_t
)
len
,
0
);
}
if
(
nread
>
0
)
{
conn
->
num_bytes_read
+=
nread
;
}
return
conn
->
ctx
->
stop_flag
?
-
1
:
nread
;
}
static
int
pull_all
(
FILE
*
fp
,
struct
mg_connection
*
conn
,
char
*
buf
,
int
len
)
{
int
n
,
nread
=
0
;
while
(
len
>
0
&&
conn
->
ctx
->
stop_flag
==
0
)
{
n
=
pull
(
fp
,
conn
,
buf
+
nread
,
len
);
if
(
n
<
0
)
{
nread
=
n
;
// Propagate the error
break
;
}
else
if
(
n
==
0
)
{
break
;
// No more data to read
}
else
{
nread
+=
n
;
len
-=
n
;
}
}
return
nread
;
}
int
mg_read
(
struct
mg_connection
*
conn
,
void
*
buf
,
int
len
)
{
int
n
,
buffered_len
,
nread
=
0
;
int64_t
left
;
if
(
conn
->
content_len
<=
0
)
{
return
0
;
}
// conn->buf body
// |=================|==========|===============|
// |<--request_len-->| |
// |<-----------data_len------->| conn->buf + conn->buf_size
// First, check for data buffered in conn->buf by read_request().
if
(
len
>
0
&&
(
buffered_len
=
conn
->
data_len
-
conn
->
request_len
)
>
0
)
{
char
*
body
=
conn
->
buf
+
conn
->
request_len
;
if
(
buffered_len
>
len
)
buffered_len
=
len
;
if
(
buffered_len
>
conn
->
content_len
)
buffered_len
=
(
int
)
conn
->
content_len
;
memcpy
(
buf
,
body
,
(
size_t
)
buffered_len
);
memmove
(
body
,
body
+
buffered_len
,
&
conn
->
buf
[
conn
->
data_len
]
-
&
body
[
buffered_len
]);
len
-=
buffered_len
;
conn
->
data_len
-=
buffered_len
;
nread
+=
buffered_len
;
}
// Read data from the socket.
if
(
len
>
0
&&
(
left
=
left_to_read
(
conn
))
>
0
)
{
if
(
left
<
len
)
{
len
=
(
int
)
left
;
}
n
=
pull_all
(
NULL
,
conn
,
(
char
*
)
buf
+
nread
,
(
int
)
len
);
nread
=
n
>=
0
?
nread
+
n
:
n
;
}
return
nread
;
}
int
mg_write
(
struct
mg_connection
*
conn
,
const
void
*
buf
,
int
len
)
{
return
push
(
NULL
,
conn
->
client
.
sock
,
conn
->
ssl
,
(
const
char
*
)
buf
,
(
int64_t
)
len
);
}
int
mg_get_var
(
const
char
*
data
,
size_t
data_len
,
const
char
*
name
,
char
*
dst
,
size_t
dst_len
)
{
const
char
*
p
,
*
e
,
*
s
;
size_t
name_len
;
int
len
;
if
(
dst
==
NULL
||
dst_len
==
0
)
{
len
=
-
2
;
}
else
if
(
data
==
NULL
||
name
==
NULL
||
data_len
==
0
)
{
len
=
-
1
;
dst
[
0
]
=
'\0'
;
}
else
{
name_len
=
strlen
(
name
);
e
=
data
+
data_len
;
len
=
-
1
;
dst
[
0
]
=
'\0'
;
// data is "var1=val1&var2=val2...". Find variable first
for
(
p
=
data
;
p
+
name_len
<
e
;
p
++
)
{
if
((
p
==
data
||
p
[
-
1
]
==
'&'
)
&&
p
[
name_len
]
==
'='
&&
!
mg_strncasecmp
(
name
,
p
,
name_len
))
{
// Point p to variable value
p
+=
name_len
+
1
;
// Point s to the end of the value
s
=
(
const
char
*
)
memchr
(
p
,
'&'
,
(
size_t
)(
e
-
p
));
if
(
s
==
NULL
)
{
s
=
e
;
}
assert
(
s
>=
p
);
// Decode variable into destination buffer
len
=
mg_url_decode
(
p
,
(
size_t
)(
s
-
p
),
dst
,
dst_len
,
1
);
// Redirect error code from -1 to -2 (destination buffer too small).
if
(
len
==
-
1
)
{
len
=
-
2
;
}
break
;
}
}
}
return
len
;
}
// Return 1 if real file has been found, 0 otherwise
// Return 1 if real file has been found, 0 otherwise
static
int
convert_uri_to_file_name
(
struct
mg_connection
*
conn
,
char
*
buf
,
static
int
convert_uri_to_file_name
(
struct
mg_connection
*
conn
,
char
*
buf
,
size_t
buf_len
,
struct
file
*
filep
)
{
size_t
buf_len
,
struct
file
*
filep
)
{
...
@@ -366,44 +190,6 @@ static int convert_uri_to_file_name(struct mg_connection *conn, char *buf,
...
@@ -366,44 +190,6 @@ static int convert_uri_to_file_name(struct mg_connection *conn, char *buf,
return
0
;
return
0
;
}
}
// Send len bytes from the opened file to the client.
static
void
send_file_data
(
struct
mg_connection
*
conn
,
FILE
*
fp
,
int64_t
offset
,
int64_t
len
)
{
char
buf
[
MG_BUF_LEN
];
int
num_read
,
num_written
,
to_read
;
// If offset is beyond file boundaries, don't send anything
if
(
offset
>
0
&&
fseeko
(
fp
,
offset
,
SEEK_SET
)
!=
0
)
{
return
;
}
while
(
len
>
0
)
{
// Calculate how much to read from the file in the buffer
to_read
=
sizeof
(
buf
);
if
((
int64_t
)
to_read
>
len
)
{
to_read
=
(
int
)
len
;
}
// Read from file, exit the loop on error
if
((
num_read
=
fread
(
buf
,
1
,
(
size_t
)
to_read
,
fp
))
<=
0
)
{
break
;
}
// Send read bytes to the client, exit the loop on error
if
((
num_written
=
mg_write
(
conn
,
buf
,
(
size_t
)
num_read
))
!=
num_read
)
{
break
;
}
// Both read and were successful, adjust counters
conn
->
num_bytes_sent
+=
num_written
;
len
-=
num_written
;
}
}
static
int
parse_range_header
(
const
char
*
header
,
int64_t
*
a
,
int64_t
*
b
)
{
return
sscanf
(
header
,
"bytes=%"
INT64_FMT
"-%"
INT64_FMT
,
a
,
b
);
}
static
void
gmt_time_string
(
char
*
buf
,
size_t
buf_len
,
time_t
*
t
)
{
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
));
strftime
(
buf
,
buf_len
,
"%a, %d %b %Y %H:%M:%S GMT"
,
gmtime
(
t
));
}
}
...
@@ -513,88 +299,6 @@ void mg_send_file(struct mg_connection *conn, const char *path) {
...
@@ -513,88 +299,6 @@ void mg_send_file(struct mg_connection *conn, const char *path) {
}
}
}
}
// Parse HTTP headers from the given buffer, advance buffer to the point
// where parsing stopped.
static
void
parse_http_headers
(
char
**
buf
,
struct
mg_request_info
*
ri
)
{
int
i
;
for
(
i
=
0
;
i
<
(
int
)
ARRAY_SIZE
(
ri
->
http_headers
);
i
++
)
{
ri
->
http_headers
[
i
].
name
=
skip_quoted
(
buf
,
":"
,
" "
,
0
);
ri
->
http_headers
[
i
].
value
=
skip
(
buf
,
"
\r\n
"
);
if
(
ri
->
http_headers
[
i
].
name
[
0
]
==
'\0'
)
break
;
ri
->
num_headers
=
i
+
1
;
}
}
static
int
is_valid_http_method
(
const
char
*
method
)
{
return
!
strcmp
(
method
,
"GET"
)
||
!
strcmp
(
method
,
"POST"
)
||
!
strcmp
(
method
,
"HEAD"
)
||
!
strcmp
(
method
,
"CONNECT"
)
||
!
strcmp
(
method
,
"PUT"
)
||
!
strcmp
(
method
,
"DELETE"
)
||
!
strcmp
(
method
,
"OPTIONS"
)
||
!
strcmp
(
method
,
"PROPFIND"
)
||
!
strcmp
(
method
,
"MKCOL"
)
;
}
// Parse HTTP request, fill in mg_request_info structure.
// 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
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
;
ri
->
num_headers
=
0
;
buf
[
request_length
-
1
]
=
'\0'
;
// RFC says that all initial whitespaces should be ingored
while
(
*
buf
!=
'\0'
&&
isspace
(
*
(
unsigned
char
*
)
buf
))
{
buf
++
;
}
ri
->
request_method
=
skip
(
&
buf
,
" "
);
ri
->
uri
=
skip
(
&
buf
,
" "
);
ri
->
http_version
=
skip
(
&
buf
,
"
\r\n
"
);
// HTTP message could be either HTTP request or HTTP response, e.g.
// "GET / HTTP/1.0 ...." or "HTTP/1.0 200 OK ..."
is_request
=
is_valid_http_method
(
ri
->
request_method
);
if
((
is_request
&&
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
;
}
// Keep reading the input (either opened file descriptor fd, or socket sock,
// or SSL descriptor ssl) into buffer buf, until \r\n\r\n appears in the
// buffer (which marks the end of HTTP request). Buffer buf may already
// have some data. The length of the data is stored in nread.
// Upon every read operation, increase nread by the number of bytes read.
static
int
read_request
(
FILE
*
fp
,
struct
mg_connection
*
conn
,
char
*
buf
,
int
bufsiz
,
int
*
nread
)
{
int
request_len
,
n
=
0
;
request_len
=
get_request_len
(
buf
,
*
nread
);
while
(
conn
->
ctx
->
stop_flag
==
0
&&
*
nread
<
bufsiz
&&
request_len
==
0
&&
(
n
=
pull
(
fp
,
conn
,
buf
+
*
nread
,
bufsiz
-
*
nread
))
>
0
)
{
*
nread
+=
n
;
assert
(
*
nread
<=
bufsiz
);
request_len
=
get_request_len
(
buf
,
*
nread
);
}
return
request_len
<=
0
&&
n
<=
0
?
-
1
:
request_len
;
}
// For given directory path, substitute it to valid index file.
// For given directory path, substitute it to valid index file.
// Return 0 if index file has been found, -1 if not found.
// Return 0 if index file has been found, -1 if not found.
// If the file is found, it's stats is returned in stp.
// If the file is found, it's stats is returned in stp.
...
@@ -653,420 +357,6 @@ static int is_not_modified(const struct mg_connection *conn,
...
@@ -653,420 +357,6 @@ static int is_not_modified(const struct mg_connection *conn,
(
ims
!=
NULL
&&
filep
->
modification_time
<=
parse_date_string
(
ims
));
(
ims
!=
NULL
&&
filep
->
modification_time
<=
parse_date_string
(
ims
));
}
}
static
int
forward_body_data
(
struct
mg_connection
*
conn
,
FILE
*
fp
,
SOCKET
sock
,
SSL
*
ssl
)
{
const
char
*
expect
,
*
body
;
char
buf
[
MG_BUF_LEN
];
int
nread
,
buffered_len
,
success
=
0
;
int64_t
left
;
expect
=
mg_get_header
(
conn
,
"Expect"
);
assert
(
fp
!=
NULL
);
if
(
conn
->
content_len
==
INT64_MAX
)
{
send_http_error
(
conn
,
411
,
"Length Required"
,
"%s"
,
""
);
}
else
if
(
expect
!=
NULL
&&
mg_strcasecmp
(
expect
,
"100-continue"
))
{
send_http_error
(
conn
,
417
,
"Expectation Failed"
,
"%s"
,
""
);
}
else
{
if
(
expect
!=
NULL
)
{
(
void
)
mg_printf
(
conn
,
"%s"
,
"HTTP/1.1 100 Continue
\r\n\r\n
"
);
}
buffered_len
=
conn
->
data_len
-
conn
->
request_len
;
body
=
conn
->
buf
+
conn
->
request_len
;
assert
(
buffered_len
>=
0
);
if
(
buffered_len
>
0
)
{
if
((
int64_t
)
buffered_len
>
conn
->
content_len
)
{
buffered_len
=
(
int
)
conn
->
content_len
;
}
push
(
fp
,
sock
,
ssl
,
body
,
(
int64_t
)
buffered_len
);
memmove
((
char
*
)
body
,
body
+
buffered_len
,
buffered_len
);
conn
->
data_len
-=
buffered_len
;
}
nread
=
0
;
while
(
conn
->
num_bytes_read
<
conn
->
content_len
+
conn
->
request_len
)
{
left
=
left_to_read
(
conn
);
if
(
left
>
(
int64_t
)
sizeof
(
buf
))
{
left
=
sizeof
(
buf
);
}
nread
=
pull
(
NULL
,
conn
,
buf
,
(
int
)
left
);
if
(
nread
<=
0
||
push
(
fp
,
sock
,
ssl
,
buf
,
nread
)
!=
nread
)
{
break
;
}
}
if
(
left_to_read
(
conn
)
==
0
)
{
success
=
nread
>=
0
;
}
// Each error code path in this function must send an error
if
(
!
success
)
{
send_http_error
(
conn
,
577
,
http_500_error
,
"%s"
,
""
);
}
}
return
success
;
}
#if !defined(NO_CGI)
// This structure helps to create an environment for the spawned CGI program.
// Environment is an array of "VARIABLE=VALUE\0" ASCIIZ strings,
// last element must be NULL.
// However, on Windows there is a requirement that all these VARIABLE=VALUE\0
// strings must reside in a contiguous buffer. The end of the buffer is
// marked by two '\0' characters.
// We satisfy both worlds: we create an envp array (which is vars), all
// entries are actually pointers inside buf.
struct
cgi_env_block
{
struct
mg_connection
*
conn
;
char
buf
[
CGI_ENVIRONMENT_SIZE
];
// Environment buffer
int
len
;
// Space taken
char
*
vars
[
MAX_CGI_ENVIR_VARS
];
// char **envp
int
nvars
;
// Number of variables
};
static
char
*
addenv
(
struct
cgi_env_block
*
block
,
PRINTF_FORMAT_STRING
(
const
char
*
fmt
),
...)
PRINTF_ARGS
(
2
,
3
);
// Append VARIABLE=VALUE\0 string to the buffer, and add a respective
// pointer into the vars array.
static
char
*
addenv
(
struct
cgi_env_block
*
block
,
const
char
*
fmt
,
...)
{
int
n
,
space
;
char
*
added
;
va_list
ap
;
// Calculate how much space is left in the buffer
space
=
sizeof
(
block
->
buf
)
-
block
->
len
-
2
;
assert
(
space
>=
0
);
// Make a pointer to the free space int the buffer
added
=
block
->
buf
+
block
->
len
;
// Copy VARIABLE=VALUE\0 string into the free space
va_start
(
ap
,
fmt
);
n
=
mg_vsnprintf
(
added
,
(
size_t
)
space
,
fmt
,
ap
);
va_end
(
ap
);
// Make sure we do not overflow buffer and the envp array
if
(
n
>
0
&&
n
+
1
<
space
&&
block
->
nvars
<
(
int
)
ARRAY_SIZE
(
block
->
vars
)
-
2
)
{
// Append a pointer to the added string into the envp array
block
->
vars
[
block
->
nvars
++
]
=
added
;
// Bump up used length counter. Include \0 terminator
block
->
len
+=
n
+
1
;
}
else
{
cry
(
block
->
conn
,
"%s: CGI env buffer truncated for [%s]"
,
__func__
,
fmt
);
}
return
added
;
}
static
void
prepare_cgi_environment
(
struct
mg_connection
*
conn
,
const
char
*
prog
,
struct
cgi_env_block
*
blk
)
{
const
struct
mg_request_info
*
ri
=
&
conn
->
request_info
;
const
char
*
s
,
*
slash
;
struct
vec
var_vec
;
char
*
p
,
src_addr
[
IP_ADDR_STR_LEN
];
int
i
;
blk
->
len
=
blk
->
nvars
=
0
;
blk
->
conn
=
conn
;
sockaddr_to_string
(
src_addr
,
sizeof
(
src_addr
),
&
conn
->
client
.
rsa
);
addenv
(
blk
,
"SERVER_NAME=%s"
,
conn
->
ctx
->
config
[
AUTHENTICATION_DOMAIN
]);
addenv
(
blk
,
"SERVER_ROOT=%s"
,
conn
->
ctx
->
config
[
DOCUMENT_ROOT
]);
addenv
(
blk
,
"DOCUMENT_ROOT=%s"
,
conn
->
ctx
->
config
[
DOCUMENT_ROOT
]);
addenv
(
blk
,
"SERVER_SOFTWARE=%s/%s"
,
"Mongoose"
,
mg_version
());
// Prepare the environment block
addenv
(
blk
,
"%s"
,
"GATEWAY_INTERFACE=CGI/1.1"
);
addenv
(
blk
,
"%s"
,
"SERVER_PROTOCOL=HTTP/1.1"
);
addenv
(
blk
,
"%s"
,
"REDIRECT_STATUS=200"
);
// For PHP
// TODO(lsm): fix this for IPv6 case
addenv
(
blk
,
"SERVER_PORT=%d"
,
ntohs
(
conn
->
client
.
lsa
.
sin
.
sin_port
));
addenv
(
blk
,
"REQUEST_METHOD=%s"
,
ri
->
request_method
);
addenv
(
blk
,
"REMOTE_ADDR=%s"
,
src_addr
);
addenv
(
blk
,
"REMOTE_PORT=%d"
,
ri
->
remote_port
);
addenv
(
blk
,
"REQUEST_URI=%s%s%s"
,
ri
->
uri
,
ri
->
query_string
==
NULL
?
""
:
"?"
,
ri
->
query_string
==
NULL
?
""
:
ri
->
query_string
);
// SCRIPT_NAME
if
(
conn
->
path_info
!=
NULL
)
{
addenv
(
blk
,
"SCRIPT_NAME=%.*s"
,
(
int
)
(
strlen
(
ri
->
uri
)
-
strlen
(
conn
->
path_info
)),
ri
->
uri
);
addenv
(
blk
,
"PATH_INFO=%s"
,
conn
->
path_info
);
}
else
{
s
=
strrchr
(
prog
,
'/'
);
slash
=
strrchr
(
ri
->
uri
,
'/'
);
addenv
(
blk
,
"SCRIPT_NAME=%.*s%s"
,
slash
==
NULL
?
0
:
(
int
)
(
slash
-
ri
->
uri
),
ri
->
uri
,
s
==
NULL
?
prog
:
s
);
}
addenv
(
blk
,
"SCRIPT_FILENAME=%s"
,
prog
);
addenv
(
blk
,
"PATH_TRANSLATED=%s"
,
prog
);
addenv
(
blk
,
"HTTPS=%s"
,
conn
->
ssl
==
NULL
?
"off"
:
"on"
);
if
((
s
=
mg_get_header
(
conn
,
"Content-Type"
))
!=
NULL
)
addenv
(
blk
,
"CONTENT_TYPE=%s"
,
s
);
if
(
ri
->
query_string
!=
NULL
)
{
addenv
(
blk
,
"QUERY_STRING=%s"
,
ri
->
query_string
);
}
if
((
s
=
mg_get_header
(
conn
,
"Content-Length"
))
!=
NULL
)
addenv
(
blk
,
"CONTENT_LENGTH=%s"
,
s
);
if
((
s
=
getenv
(
"PATH"
))
!=
NULL
)
addenv
(
blk
,
"PATH=%s"
,
s
);
#if defined(_WIN32)
if
((
s
=
getenv
(
"COMSPEC"
))
!=
NULL
)
{
addenv
(
blk
,
"COMSPEC=%s"
,
s
);
}
if
((
s
=
getenv
(
"SYSTEMROOT"
))
!=
NULL
)
{
addenv
(
blk
,
"SYSTEMROOT=%s"
,
s
);
}
if
((
s
=
getenv
(
"SystemDrive"
))
!=
NULL
)
{
addenv
(
blk
,
"SystemDrive=%s"
,
s
);
}
if
((
s
=
getenv
(
"ProgramFiles"
))
!=
NULL
)
{
addenv
(
blk
,
"ProgramFiles=%s"
,
s
);
}
if
((
s
=
getenv
(
"ProgramFiles(x86)"
))
!=
NULL
)
{
addenv
(
blk
,
"ProgramFiles(x86)=%s"
,
s
);
}
if
((
s
=
getenv
(
"CommonProgramFiles(x86)"
))
!=
NULL
)
{
addenv
(
blk
,
"CommonProgramFiles(x86)=%s"
,
s
);
}
#else
if
((
s
=
getenv
(
"LD_LIBRARY_PATH"
))
!=
NULL
)
addenv
(
blk
,
"LD_LIBRARY_PATH=%s"
,
s
);
#endif // _WIN32
if
((
s
=
getenv
(
"PERLLIB"
))
!=
NULL
)
addenv
(
blk
,
"PERLLIB=%s"
,
s
);
if
(
ri
->
remote_user
!=
NULL
)
{
addenv
(
blk
,
"REMOTE_USER=%s"
,
ri
->
remote_user
);
addenv
(
blk
,
"%s"
,
"AUTH_TYPE=Digest"
);
}
// Add all headers as HTTP_* variables
for
(
i
=
0
;
i
<
ri
->
num_headers
;
i
++
)
{
p
=
addenv
(
blk
,
"HTTP_%s=%s"
,
ri
->
http_headers
[
i
].
name
,
ri
->
http_headers
[
i
].
value
);
// Convert variable name into uppercase, and change - to _
for
(;
*
p
!=
'='
&&
*
p
!=
'\0'
;
p
++
)
{
if
(
*
p
==
'-'
)
*
p
=
'_'
;
*
p
=
(
char
)
toupper
(
*
(
unsigned
char
*
)
p
);
}
}
// Add user-specified variables
s
=
conn
->
ctx
->
config
[
CGI_ENVIRONMENT
];
while
((
s
=
next_option
(
s
,
&
var_vec
,
NULL
))
!=
NULL
)
{
addenv
(
blk
,
"%.*s"
,
(
int
)
var_vec
.
len
,
var_vec
.
ptr
);
}
blk
->
vars
[
blk
->
nvars
++
]
=
NULL
;
blk
->
buf
[
blk
->
len
++
]
=
'\0'
;
assert
(
blk
->
nvars
<
(
int
)
ARRAY_SIZE
(
blk
->
vars
));
assert
(
blk
->
len
>
0
);
assert
(
blk
->
len
<
(
int
)
sizeof
(
blk
->
buf
));
}
static
void
handle_cgi_request
(
struct
mg_connection
*
conn
,
const
char
*
prog
)
{
int
headers_len
,
data_len
,
i
,
fdin
[
2
],
fdout
[
2
];
const
char
*
status
,
*
status_text
;
char
buf
[
16384
],
*
pbuf
,
dir
[
PATH_MAX
],
*
p
;
struct
mg_request_info
ri
;
struct
cgi_env_block
blk
;
FILE
*
in
=
NULL
,
*
out
=
NULL
;
pid_t
pid
=
(
pid_t
)
-
1
;
prepare_cgi_environment
(
conn
,
prog
,
&
blk
);
// CGI must be executed in its own directory. 'dir' must point to the
// directory containing executable program, 'p' must point to the
// executable program name relative to 'dir'.
(
void
)
mg_snprintf
(
dir
,
sizeof
(
dir
),
"%s"
,
prog
);
if
((
p
=
strrchr
(
dir
,
'/'
))
!=
NULL
)
{
*
p
++
=
'\0'
;
}
else
{
dir
[
0
]
=
'.'
,
dir
[
1
]
=
'\0'
;
p
=
(
char
*
)
prog
;
}
if
(
pipe
(
fdin
)
!=
0
||
pipe
(
fdout
)
!=
0
)
{
send_http_error
(
conn
,
500
,
http_500_error
,
"Cannot create CGI pipe: %s"
,
strerror
(
ERRNO
));
goto
done
;
}
pid
=
spawn_process
(
conn
,
p
,
blk
.
buf
,
blk
.
vars
,
fdin
[
0
],
fdout
[
1
],
dir
);
if
(
pid
==
(
pid_t
)
-
1
)
{
send_http_error
(
conn
,
500
,
http_500_error
,
"Cannot spawn CGI process [%s]: %s"
,
prog
,
strerror
(
ERRNO
));
goto
done
;
}
// Make sure child closes all pipe descriptors. It must dup them to 0,1
set_close_on_exec
(
fdin
[
0
]);
set_close_on_exec
(
fdin
[
1
]);
set_close_on_exec
(
fdout
[
0
]);
set_close_on_exec
(
fdout
[
1
]);
// Parent closes only one side of the pipes.
// If we don't mark them as closed, close() attempt before
// return from this function throws an exception on Windows.
// Windows does not like when closed descriptor is closed again.
(
void
)
close
(
fdin
[
0
]);
(
void
)
close
(
fdout
[
1
]);
fdin
[
0
]
=
fdout
[
1
]
=
-
1
;
if
((
in
=
fdopen
(
fdin
[
1
],
"wb"
))
==
NULL
||
(
out
=
fdopen
(
fdout
[
0
],
"rb"
))
==
NULL
)
{
send_http_error
(
conn
,
500
,
http_500_error
,
"fopen: %s"
,
strerror
(
ERRNO
));
goto
done
;
}
setbuf
(
in
,
NULL
);
setbuf
(
out
,
NULL
);
// Send POST data to the CGI process if needed
if
(
!
strcmp
(
conn
->
request_info
.
request_method
,
"POST"
)
&&
!
forward_body_data
(
conn
,
in
,
INVALID_SOCKET
,
NULL
))
{
goto
done
;
}
// Close so child gets an EOF.
fclose
(
in
);
in
=
NULL
;
fdin
[
1
]
=
-
1
;
// Now read CGI reply into a buffer. We need to set correct
// status code, thus we need to see all HTTP headers first.
// Do not send anything back to client, until we buffer in all
// HTTP headers.
data_len
=
0
;
headers_len
=
read_request
(
out
,
conn
,
buf
,
sizeof
(
buf
),
&
data_len
);
if
(
headers_len
<=
0
)
{
send_http_error
(
conn
,
500
,
http_500_error
,
"CGI program sent malformed or too big (>%u bytes) "
"HTTP headers: [%.*s]"
,
(
unsigned
)
sizeof
(
buf
),
data_len
,
buf
);
goto
done
;
}
pbuf
=
buf
;
buf
[
headers_len
-
1
]
=
'\0'
;
parse_http_headers
(
&
pbuf
,
&
ri
);
// Make up and send the status line
status_text
=
"OK"
;
if
((
status
=
get_header
(
&
ri
,
"Status"
))
!=
NULL
)
{
conn
->
status_code
=
atoi
(
status
);
status_text
=
status
;
while
(
isdigit
(
*
(
unsigned
char
*
)
status_text
)
||
*
status_text
==
' '
)
{
status_text
++
;
}
}
else
if
(
get_header
(
&
ri
,
"Location"
)
!=
NULL
)
{
conn
->
status_code
=
302
;
}
else
{
conn
->
status_code
=
200
;
}
if
(
get_header
(
&
ri
,
"Connection"
)
!=
NULL
&&
!
mg_strcasecmp
(
get_header
(
&
ri
,
"Connection"
),
"keep-alive"
))
{
conn
->
must_close
=
1
;
}
(
void
)
mg_printf
(
conn
,
"HTTP/1.1 %d %s
\r\n
"
,
conn
->
status_code
,
status_text
);
// Send headers
for
(
i
=
0
;
i
<
ri
.
num_headers
;
i
++
)
{
mg_printf
(
conn
,
"%s: %s
\r\n
"
,
ri
.
http_headers
[
i
].
name
,
ri
.
http_headers
[
i
].
value
);
}
mg_write
(
conn
,
"
\r\n
"
,
2
);
// Send chunk of data that may have been read after the headers
conn
->
num_bytes_sent
+=
mg_write
(
conn
,
buf
+
headers_len
,
(
size_t
)(
data_len
-
headers_len
));
// Read the rest of CGI output and send to the client
send_file_data
(
conn
,
out
,
0
,
INT64_MAX
);
done:
if
(
pid
!=
(
pid_t
)
-
1
)
{
kill
(
pid
,
SIGKILL
);
}
if
(
fdin
[
0
]
!=
-
1
)
{
close
(
fdin
[
0
]);
}
if
(
fdout
[
1
]
!=
-
1
)
{
close
(
fdout
[
1
]);
}
if
(
in
!=
NULL
)
{
fclose
(
in
);
}
else
if
(
fdin
[
1
]
!=
-
1
)
{
close
(
fdin
[
1
]);
}
if
(
out
!=
NULL
)
{
fclose
(
out
);
}
else
if
(
fdout
[
0
]
!=
-
1
)
{
close
(
fdout
[
0
]);
}
}
#endif // !NO_CGI
// For a given PUT path, create all intermediate subdirectories
// for given path. Return 0 if the path itself is a directory,
// or -1 on error, 1 if OK.
static
int
put_dir
(
const
char
*
path
)
{
char
buf
[
PATH_MAX
];
const
char
*
s
,
*
p
;
struct
file
file
=
STRUCT_FILE_INITIALIZER
;
int
len
,
res
=
1
;
for
(
s
=
p
=
path
+
2
;
(
p
=
strchr
(
s
,
'/'
))
!=
NULL
;
s
=
++
p
)
{
len
=
p
-
path
;
if
(
len
>=
(
int
)
sizeof
(
buf
))
{
res
=
-
1
;
break
;
}
memcpy
(
buf
,
path
,
len
);
buf
[
len
]
=
'\0'
;
// Try to create intermediate directory
DEBUG_TRACE
((
"mkdir(%s)"
,
buf
));
if
(
!
mg_stat
(
buf
,
&
file
)
&&
mg_mkdir
(
buf
,
0755
)
!=
0
)
{
res
=
-
1
;
break
;
}
// Is path itself a directory?
if
(
p
[
1
]
==
'\0'
)
{
res
=
0
;
}
}
return
res
;
}
static
void
mkcol
(
struct
mg_connection
*
conn
,
const
char
*
path
)
{
static
void
mkcol
(
struct
mg_connection
*
conn
,
const
char
*
path
)
{
int
rc
,
body_len
;
int
rc
,
body_len
;
struct
de
de
;
struct
de
de
;
...
...
build/src/parse_http.c
0 → 100644
View file @
66824e77
#include "internal.h"
// Parse HTTP headers from the given buffer, advance buffer to the point
// where parsing stopped.
static
void
parse_http_headers
(
char
**
buf
,
struct
mg_request_info
*
ri
)
{
int
i
;
for
(
i
=
0
;
i
<
(
int
)
ARRAY_SIZE
(
ri
->
http_headers
);
i
++
)
{
ri
->
http_headers
[
i
].
name
=
skip_quoted
(
buf
,
":"
,
" "
,
0
);
ri
->
http_headers
[
i
].
value
=
skip
(
buf
,
"
\r\n
"
);
if
(
ri
->
http_headers
[
i
].
name
[
0
]
==
'\0'
)
break
;
ri
->
num_headers
=
i
+
1
;
}
}
static
int
is_valid_http_method
(
const
char
*
method
)
{
return
!
strcmp
(
method
,
"GET"
)
||
!
strcmp
(
method
,
"POST"
)
||
!
strcmp
(
method
,
"HEAD"
)
||
!
strcmp
(
method
,
"CONNECT"
)
||
!
strcmp
(
method
,
"PUT"
)
||
!
strcmp
(
method
,
"DELETE"
)
||
!
strcmp
(
method
,
"OPTIONS"
)
||
!
strcmp
(
method
,
"PROPFIND"
)
||
!
strcmp
(
method
,
"MKCOL"
);
}
// Parse HTTP request, fill in mg_request_info structure.
// 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
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
;
ri
->
num_headers
=
0
;
buf
[
request_length
-
1
]
=
'\0'
;
// RFC says that all initial whitespaces should be ingored
while
(
*
buf
!=
'\0'
&&
isspace
(
*
(
unsigned
char
*
)
buf
))
{
buf
++
;
}
ri
->
request_method
=
skip
(
&
buf
,
" "
);
ri
->
uri
=
skip
(
&
buf
,
" "
);
ri
->
http_version
=
skip
(
&
buf
,
"
\r\n
"
);
// HTTP message could be either HTTP request or HTTP response, e.g.
// "GET / HTTP/1.0 ...." or "HTTP/1.0 200 OK ..."
is_request
=
is_valid_http_method
(
ri
->
request_method
);
if
((
is_request
&&
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_range_header
(
const
char
*
header
,
int64_t
*
a
,
int64_t
*
b
)
{
return
sscanf
(
header
,
"bytes=%"
INT64_FMT
"-%"
INT64_FMT
,
a
,
b
);
}
build/src/string.c
View file @
66824e77
...
@@ -374,3 +374,50 @@ int mg_get_cookie(const char *cookie_header, const char *var_name,
...
@@ -374,3 +374,50 @@ int mg_get_cookie(const char *cookie_header, const char *var_name,
}
}
return
len
;
return
len
;
}
}
int
mg_get_var
(
const
char
*
data
,
size_t
data_len
,
const
char
*
name
,
char
*
dst
,
size_t
dst_len
)
{
const
char
*
p
,
*
e
,
*
s
;
size_t
name_len
;
int
len
;
if
(
dst
==
NULL
||
dst_len
==
0
)
{
len
=
-
2
;
}
else
if
(
data
==
NULL
||
name
==
NULL
||
data_len
==
0
)
{
len
=
-
1
;
dst
[
0
]
=
'\0'
;
}
else
{
name_len
=
strlen
(
name
);
e
=
data
+
data_len
;
len
=
-
1
;
dst
[
0
]
=
'\0'
;
// data is "var1=val1&var2=val2...". Find variable first
for
(
p
=
data
;
p
+
name_len
<
e
;
p
++
)
{
if
((
p
==
data
||
p
[
-
1
]
==
'&'
)
&&
p
[
name_len
]
==
'='
&&
!
mg_strncasecmp
(
name
,
p
,
name_len
))
{
// Point p to variable value
p
+=
name_len
+
1
;
// Point s to the end of the value
s
=
(
const
char
*
)
memchr
(
p
,
'&'
,
(
size_t
)(
e
-
p
));
if
(
s
==
NULL
)
{
s
=
e
;
}
assert
(
s
>=
p
);
// Decode variable into destination buffer
len
=
mg_url_decode
(
p
,
(
size_t
)(
s
-
p
),
dst
,
dst_len
,
1
);
// Redirect error code from -1 to -2 (destination buffer too small).
if
(
len
==
-
1
)
{
len
=
-
2
;
}
break
;
}
}
}
return
len
;
}
mongoose.c
View file @
66824e77
...
@@ -870,6 +870,53 @@ int mg_get_cookie(const char *cookie_header, const char *var_name,
...
@@ -870,6 +870,53 @@ int mg_get_cookie(const char *cookie_header, const char *var_name,
return
len
;
return
len
;
}
}
int
mg_get_var
(
const
char
*
data
,
size_t
data_len
,
const
char
*
name
,
char
*
dst
,
size_t
dst_len
)
{
const
char
*
p
,
*
e
,
*
s
;
size_t
name_len
;
int
len
;
if
(
dst
==
NULL
||
dst_len
==
0
)
{
len
=
-
2
;
}
else
if
(
data
==
NULL
||
name
==
NULL
||
data_len
==
0
)
{
len
=
-
1
;
dst
[
0
]
=
'\0'
;
}
else
{
name_len
=
strlen
(
name
);
e
=
data
+
data_len
;
len
=
-
1
;
dst
[
0
]
=
'\0'
;
// data is "var1=val1&var2=val2...". Find variable first
for
(
p
=
data
;
p
+
name_len
<
e
;
p
++
)
{
if
((
p
==
data
||
p
[
-
1
]
==
'&'
)
&&
p
[
name_len
]
==
'='
&&
!
mg_strncasecmp
(
name
,
p
,
name_len
))
{
// Point p to variable value
p
+=
name_len
+
1
;
// Point s to the end of the value
s
=
(
const
char
*
)
memchr
(
p
,
'&'
,
(
size_t
)(
e
-
p
));
if
(
s
==
NULL
)
{
s
=
e
;
}
assert
(
s
>=
p
);
// Decode variable into destination buffer
len
=
mg_url_decode
(
p
,
(
size_t
)(
s
-
p
),
dst
,
dst_len
,
1
);
// Redirect error code from -1 to -2 (destination buffer too small).
if
(
len
==
-
1
)
{
len
=
-
2
;
}
break
;
}
}
}
return
len
;
}
static
const
char
*
month_names
[]
=
{
static
const
char
*
month_names
[]
=
{
"Jan"
,
"Feb"
,
"Mar"
,
"Apr"
,
"May"
,
"Jun"
,
"Jan"
,
"Feb"
,
"Mar"
,
"Apr"
,
"May"
,
"Jun"
,
"Jul"
,
"Aug"
,
"Sep"
,
"Oct"
,
"Nov"
,
"Dec"
"Jul"
,
"Aug"
,
"Sep"
,
"Oct"
,
"Nov"
,
"Dec"
...
@@ -2647,6 +2694,39 @@ static void handle_directory_request(struct mg_connection *conn,
...
@@ -2647,6 +2694,39 @@ static void handle_directory_request(struct mg_connection *conn,
conn
->
status_code
=
200
;
conn
->
status_code
=
200
;
}
}
// For a given PUT path, create all intermediate subdirectories
// for given path. Return 0 if the path itself is a directory,
// or -1 on error, 1 if OK.
static
int
put_dir
(
const
char
*
path
)
{
char
buf
[
PATH_MAX
];
const
char
*
s
,
*
p
;
struct
file
file
=
STRUCT_FILE_INITIALIZER
;
int
len
,
res
=
1
;
for
(
s
=
p
=
path
+
2
;
(
p
=
strchr
(
s
,
'/'
))
!=
NULL
;
s
=
++
p
)
{
len
=
p
-
path
;
if
(
len
>=
(
int
)
sizeof
(
buf
))
{
res
=
-
1
;
break
;
}
memcpy
(
buf
,
path
,
len
);
buf
[
len
]
=
'\0'
;
// Try to create intermediate directory
DEBUG_TRACE
((
"mkdir(%s)"
,
buf
));
if
(
!
mg_stat
(
buf
,
&
file
)
&&
mg_mkdir
(
buf
,
0755
)
!=
0
)
{
res
=
-
1
;
break
;
}
// Is path itself a directory?
if
(
p
[
1
]
==
'\0'
)
{
res
=
0
;
}
}
return
res
;
}
static
void
log_header
(
const
struct
mg_connection
*
conn
,
const
char
*
header
,
static
void
log_header
(
const
struct
mg_connection
*
conn
,
const
char
*
header
,
FILE
*
fp
)
{
FILE
*
fp
)
{
...
@@ -2691,124 +2771,71 @@ static void log_access(const struct mg_connection *conn) {
...
@@ -2691,124 +2771,71 @@ static void log_access(const struct mg_connection *conn) {
fclose
(
fp
);
fclose
(
fp
);
}
}
//
Return number of bytes left to read for this connection
//
Parse HTTP headers from the given buffer, advance buffer to the point
static
int64_t
left_to_read
(
const
struct
mg_connection
*
conn
)
{
// where parsing stopped.
return
conn
->
content_len
+
conn
->
request_len
-
conn
->
num_bytes_read
;
static
void
parse_http_headers
(
char
**
buf
,
struct
mg_request_info
*
ri
)
{
}
int
i
;
static
int
call_user
(
int
type
,
struct
mg_connection
*
conn
,
void
*
p
)
{
for
(
i
=
0
;
i
<
(
int
)
ARRAY_SIZE
(
ri
->
http_headers
);
i
++
)
{
if
(
conn
!=
NULL
&&
conn
->
ctx
!=
NULL
)
{
ri
->
http_headers
[
i
].
name
=
skip_quoted
(
buf
,
":"
,
" "
,
0
);
conn
->
event
.
user_data
=
conn
->
ctx
->
user_data
;
ri
->
http_headers
[
i
].
value
=
skip
(
buf
,
"
\r\n
"
);
conn
->
event
.
type
=
type
;
if
(
ri
->
http_headers
[
i
].
name
[
0
]
==
'\0'
)
conn
->
event
.
event_param
=
p
;
break
;
conn
->
event
.
request_info
=
&
conn
->
request_info
;
ri
->
num_headers
=
i
+
1
;
conn
->
event
.
conn
=
conn
;
}
}
return
conn
==
NULL
||
conn
->
ctx
==
NULL
||
conn
->
ctx
->
event_handler
==
NULL
?
0
:
conn
->
ctx
->
event_handler
(
&
conn
->
event
);
}
}
static
FILE
*
mg_fopen
(
const
char
*
path
,
const
char
*
mode
)
{
static
int
is_valid_http_method
(
const
char
*
method
)
{
#ifdef _WIN32
return
!
strcmp
(
method
,
"GET"
)
||
!
strcmp
(
method
,
"POST"
)
||
wchar_t
wbuf
[
PATH_MAX
],
wmode
[
20
];
!
strcmp
(
method
,
"HEAD"
)
||
!
strcmp
(
method
,
"CONNECT"
)
||
to_unicode
(
path
,
wbuf
,
ARRAY_SIZE
(
wbuf
));
!
strcmp
(
method
,
"PUT"
)
||
!
strcmp
(
method
,
"DELETE"
)
||
MultiByteToWideChar
(
CP_UTF8
,
0
,
mode
,
-
1
,
wmode
,
ARRAY_SIZE
(
wmode
));
!
strcmp
(
method
,
"OPTIONS"
)
||
!
strcmp
(
method
,
"PROPFIND"
)
return
_wfopen
(
wbuf
,
wmode
);
||
!
strcmp
(
method
,
"MKCOL"
);
#else
return
fopen
(
path
,
mode
);
#endif
}
}
// Print error message to the opened error log stream.
// Parse HTTP request, fill in mg_request_info structure.
static
void
cry
(
struct
mg_connection
*
conn
,
const
char
*
fmt
,
...)
{
// This function modifies the buffer by NUL-terminating
char
buf
[
MG_BUF_LEN
],
src_addr
[
IP_ADDR_STR_LEN
];
// HTTP request components, header names and header values.
va_list
ap
;
static
int
parse_http_message
(
char
*
buf
,
int
len
,
struct
mg_request_info
*
ri
)
{
FILE
*
fp
;
int
is_request
,
request_length
=
get_request_len
(
buf
,
len
);
time_t
timestamp
;
if
(
request_length
>
0
)
{
// Reset attributes. DO NOT TOUCH is_ssl, remote_ip, remote_port
va_start
(
ap
,
fmt
);
ri
->
remote_user
=
ri
->
request_method
=
ri
->
uri
=
ri
->
http_version
=
NULL
;
(
void
)
vsnprintf
(
buf
,
sizeof
(
buf
),
fmt
,
ap
);
ri
->
num_headers
=
0
;
va_end
(
ap
);
// 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
)
{
buf
[
request_length
-
1
]
=
'\0'
;
flockfile
(
fp
);
timestamp
=
time
(
NULL
);
sockaddr_to_string
(
src_addr
,
sizeof
(
src_addr
),
&
conn
->
client
.
rsa
);
// RFC says that all initial whitespaces should be ingored
fprintf
(
fp
,
"[%010lu] [error] [client %s] "
,
(
unsigned
long
)
timestamp
,
while
(
*
buf
!=
'\0'
&&
isspace
(
*
(
unsigned
char
*
)
buf
))
{
src_addr
);
buf
++
;
}
ri
->
request_method
=
skip
(
&
buf
,
" "
);
ri
->
uri
=
skip
(
&
buf
,
" "
);
ri
->
http_version
=
skip
(
&
buf
,
"
\r\n
"
);
if
(
conn
->
request_info
.
request_method
!=
NULL
)
{
// HTTP message could be either HTTP request or HTTP response, e.g.
fprintf
(
fp
,
"%s %s: "
,
conn
->
request_info
.
request_method
,
// "GET / HTTP/1.0 ...." or "HTTP/1.0 200 OK ..."
conn
->
request_info
.
uri
);
is_request
=
is_valid_http_method
(
ri
->
request_method
);
if
((
is_request
&&
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
);
fprintf
(
fp
,
"%s"
,
buf
);
fputc
(
'\n'
,
fp
);
funlockfile
(
fp
);
fclose
(
fp
);
}
}
}
}
return
request_length
;
}
}
const
char
*
mg_version
(
void
)
{
static
int
parse_range_header
(
const
char
*
header
,
int64_t
*
a
,
int64_t
*
b
)
{
return
MONGOOSE_VERSION
;
return
sscanf
(
header
,
"bytes=%"
INT64_FMT
"-%"
INT64_FMT
,
a
,
b
);
}
// 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
,
// Return number of bytes left to read for this connection
const
char
*
reason
,
const
char
*
fmt
,
...)
{
static
int64_t
left_to_read
(
const
struct
mg_connection
*
conn
)
{
char
buf
[
MG_BUF_LEN
];
return
conn
->
content_len
+
conn
->
request_len
-
conn
->
num_bytes_read
;
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
);
}
}
}
// Write data to the IO channel - opened file descriptor, socket or SSL
// Write data to the IO channel - opened file descriptor, socket or SSL
...
@@ -2935,126 +2962,26 @@ int mg_write(struct mg_connection *conn, const void *buf, int len) {
...
@@ -2935,126 +2962,26 @@ int mg_write(struct mg_connection *conn, const void *buf, int len) {
(
int64_t
)
len
);
(
int64_t
)
len
);
}
}
int
mg_get_var
(
const
char
*
data
,
size_t
data_len
,
const
char
*
name
,
// Keep reading the input (either opened file descriptor fd, or socket sock,
char
*
dst
,
size_t
dst_len
)
{
// or SSL descriptor ssl) into buffer buf, until \r\n\r\n appears in the
const
char
*
p
,
*
e
,
*
s
;
// buffer (which marks the end of HTTP request). Buffer buf may already
size_t
name_len
;
// have some data. The length of the data is stored in nread.
int
len
;
// Upon every read operation, increase nread by the number of bytes read.
static
int
read_request
(
FILE
*
fp
,
struct
mg_connection
*
conn
,
if
(
dst
==
NULL
||
dst_len
==
0
)
{
char
*
buf
,
int
bufsiz
,
int
*
nread
)
{
len
=
-
2
;
int
request_len
,
n
=
0
;
}
else
if
(
data
==
NULL
||
name
==
NULL
||
data_len
==
0
)
{
len
=
-
1
;
dst
[
0
]
=
'\0'
;
}
else
{
name_len
=
strlen
(
name
);
e
=
data
+
data_len
;
len
=
-
1
;
dst
[
0
]
=
'\0'
;
// data is "var1=val1&var2=val2...". Find variable first
request_len
=
get_request_len
(
buf
,
*
nread
);
for
(
p
=
data
;
p
+
name_len
<
e
;
p
++
)
{
while
(
conn
->
ctx
->
stop_flag
==
0
&&
if
((
p
==
data
||
p
[
-
1
]
==
'&'
)
&&
p
[
name_len
]
==
'='
&&
*
nread
<
bufsiz
&&
!
mg_strncasecmp
(
name
,
p
,
name_len
))
{
request_len
==
0
&&
(
n
=
pull
(
fp
,
conn
,
buf
+
*
nread
,
bufsiz
-
*
nread
))
>
0
)
{
// Point p to variable value
*
nread
+=
n
;
p
+=
name_len
+
1
;
assert
(
*
nread
<=
bufsiz
);
request_len
=
get_request_len
(
buf
,
*
nread
);
// Point s to the end of the value
s
=
(
const
char
*
)
memchr
(
p
,
'&'
,
(
size_t
)(
e
-
p
));
if
(
s
==
NULL
)
{
s
=
e
;
}
assert
(
s
>=
p
);
// Decode variable into destination buffer
len
=
mg_url_decode
(
p
,
(
size_t
)(
s
-
p
),
dst
,
dst_len
,
1
);
// Redirect error code from -1 to -2 (destination buffer too small).
if
(
len
==
-
1
)
{
len
=
-
2
;
}
break
;
}
}
}
return
len
;
}
// 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
;
return
request_len
<=
0
&&
n
<=
0
?
-
1
:
request_len
;
}
}
// Send len bytes from the opened file to the client.
// Send len bytes from the opened file to the client.
...
@@ -3091,316 +3018,65 @@ static void send_file_data(struct mg_connection *conn, FILE *fp,
...
@@ -3091,316 +3018,65 @@ static void send_file_data(struct mg_connection *conn, FILE *fp,
}
}
}
}
static
int
parse_range_header
(
const
char
*
header
,
int64_t
*
a
,
int64_t
*
b
)
{
return
sscanf
(
header
,
"bytes=%"
INT64_FMT
"-%"
INT64_FMT
,
a
,
b
);
}
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
);
static
int
forward_body_data
(
struct
mg_connection
*
conn
,
FILE
*
fp
,
cl
=
filep
->
size
;
SOCKET
sock
,
SSL
*
ssl
)
{
conn
->
status_code
=
200
;
const
char
*
expect
,
*
body
;
range
[
0
]
=
'\0'
;
char
buf
[
MG_BUF_LEN
];
int
nread
,
buffered_len
,
success
=
0
;
int64_t
left
;
// if this file is in fact a pre-gzipped file, rewrite its filename
expect
=
mg_get_header
(
conn
,
"Expect"
);
// it's important to rewrite the filename after resolving
assert
(
fp
!=
NULL
);
// 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
)
{
if
(
conn
->
content_len
==
INT64_MAX
)
{
send_http_error
(
conn
,
500
,
http_500_error
,
send_http_error
(
conn
,
411
,
"Length Required"
,
"%s"
,
""
);
"fopen(%s): %s"
,
path
,
strerror
(
ERRNO
));
}
else
if
(
expect
!=
NULL
&&
mg_strcasecmp
(
expect
,
"100-continue"
))
{
return
;
send_http_error
(
conn
,
417
,
"Expectation Failed"
,
"%s"
,
""
);
}
}
else
{
if
(
expect
!=
NULL
)
{
(
void
)
mg_printf
(
conn
,
"%s"
,
"HTTP/1.1 100 Continue
\r\n\r\n
"
);
}
fclose_on_exec
(
fp
);
buffered_len
=
conn
->
data_len
-
conn
->
request_len
;
body
=
conn
->
buf
+
conn
->
request_len
;
assert
(
buffered_len
>=
0
);
// If Range: header specified, act accordingly
if
(
buffered_len
>
0
)
{
r1
=
r2
=
0
;
if
((
int64_t
)
buffered_len
>
conn
->
content_len
)
{
hdr
=
mg_get_header
(
conn
,
"Range"
);
buffered_len
=
(
int
)
conn
->
content_len
;
if
(
hdr
!=
NULL
&&
(
n
=
parse_range_header
(
hdr
,
&
r1
,
&
r2
))
>
0
&&
}
r1
>=
0
&&
r2
>=
0
)
{
push
(
fp
,
sock
,
ssl
,
body
,
(
int64_t
)
buffered_len
);
// actually, range requests don't play well with a pre-gzipped
memmove
((
char
*
)
body
,
body
+
buffered_len
,
buffered_len
);
// file (since the range is specified in the uncmpressed space)
conn
->
data_len
-=
buffered_len
;
if
(
filep
->
gzipped
)
{
send_http_error
(
conn
,
501
,
"Not Implemented"
,
"range requests in gzipped files are not supported"
);
return
;
}
}
conn
->
status_code
=
206
;
cl
=
n
==
2
?
(
r2
>
cl
?
cl
:
r2
)
-
r1
+
1
:
cl
-
r1
;
mg_snprintf
(
range
,
sizeof
(
range
),
"Content-Range: bytes "
"%"
INT64_FMT
"-%"
INT64_FMT
"/%"
INT64_FMT
"
\r\n
"
,
r1
,
r1
+
cl
-
1
,
filep
->
size
);
msg
=
"Partial Content"
;
}
// Prepare Etag, Date, Last-Modified headers. Must be in UTC, according to
nread
=
0
;
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3
while
(
conn
->
num_bytes_read
<
conn
->
content_len
+
conn
->
request_len
)
{
gmt_time_string
(
date
,
sizeof
(
date
),
&
curtime
);
left
=
left_to_read
(
conn
);
gmt_time_string
(
lm
,
sizeof
(
lm
),
&
filep
->
modification_time
);
if
(
left
>
(
int64_t
)
sizeof
(
buf
))
{
construct_etag
(
etag
,
sizeof
(
etag
),
filep
);
left
=
sizeof
(
buf
);
}
nread
=
pull
(
NULL
,
conn
,
buf
,
(
int
)
left
);
if
(
nread
<=
0
||
push
(
fp
,
sock
,
ssl
,
buf
,
nread
)
!=
nread
)
{
break
;
}
}
(
void
)
mg_printf
(
conn
,
if
(
left_to_read
(
conn
)
==
0
)
{
"HTTP/1.1 %d %s
\r\n
"
success
=
nread
>=
0
;
"Date: %s
\r\n
"
}
"Last-Modified: %s
\r\n
"
"Etag: %s
\r\n
"
"Content-Type: %.*s
\r\n
"
"Content-Length: %"
INT64_FMT
"
\r\n
"
"Connection: %s
\r\n
"
"Accept-Ranges: bytes
\r\n
"
"%s%s%s
\r\n
"
,
conn
->
status_code
,
msg
,
date
,
lm
,
etag
,
(
int
)
mime_vec
.
len
,
mime_vec
.
ptr
,
cl
,
suggest_connection_header
(
conn
),
range
,
encoding
,
EXTRA_HTTP_HEADERS
);
if
(
strcmp
(
conn
->
request_info
.
request_method
,
"HEAD"
)
!=
0
)
{
// Each error code path in this function must send an error
send_file_data
(
conn
,
fp
,
r1
,
cl
);
if
(
!
success
)
{
send_http_error
(
conn
,
577
,
http_500_error
,
"%s"
,
""
);
}
}
}
fclose
(
fp
);
}
void
mg_send_file
(
struct
mg_connection
*
conn
,
const
char
*
path
)
{
return
success
;
struct
file
file
=
STRUCT_FILE_INITIALIZER
;
if
(
mg_stat
(
path
,
&
file
))
{
handle_file_request
(
conn
,
path
,
&
file
);
}
else
{
send_http_error
(
conn
,
404
,
"Not Found"
,
"%s"
,
"File not found"
);
}
}
}
// Parse HTTP headers from the given buffer, advance buffer to the point
// where parsing stopped.
static
void
parse_http_headers
(
char
**
buf
,
struct
mg_request_info
*
ri
)
{
int
i
;
for
(
i
=
0
;
i
<
(
int
)
ARRAY_SIZE
(
ri
->
http_headers
);
i
++
)
{
ri
->
http_headers
[
i
].
name
=
skip_quoted
(
buf
,
":"
,
" "
,
0
);
ri
->
http_headers
[
i
].
value
=
skip
(
buf
,
"
\r\n
"
);
if
(
ri
->
http_headers
[
i
].
name
[
0
]
==
'\0'
)
break
;
ri
->
num_headers
=
i
+
1
;
}
}
static
int
is_valid_http_method
(
const
char
*
method
)
{
return
!
strcmp
(
method
,
"GET"
)
||
!
strcmp
(
method
,
"POST"
)
||
!
strcmp
(
method
,
"HEAD"
)
||
!
strcmp
(
method
,
"CONNECT"
)
||
!
strcmp
(
method
,
"PUT"
)
||
!
strcmp
(
method
,
"DELETE"
)
||
!
strcmp
(
method
,
"OPTIONS"
)
||
!
strcmp
(
method
,
"PROPFIND"
)
||
!
strcmp
(
method
,
"MKCOL"
)
;
}
// Parse HTTP request, fill in mg_request_info structure.
// 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
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
;
ri
->
num_headers
=
0
;
buf
[
request_length
-
1
]
=
'\0'
;
// RFC says that all initial whitespaces should be ingored
while
(
*
buf
!=
'\0'
&&
isspace
(
*
(
unsigned
char
*
)
buf
))
{
buf
++
;
}
ri
->
request_method
=
skip
(
&
buf
,
" "
);
ri
->
uri
=
skip
(
&
buf
,
" "
);
ri
->
http_version
=
skip
(
&
buf
,
"
\r\n
"
);
// HTTP message could be either HTTP request or HTTP response, e.g.
// "GET / HTTP/1.0 ...." or "HTTP/1.0 200 OK ..."
is_request
=
is_valid_http_method
(
ri
->
request_method
);
if
((
is_request
&&
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
;
}
// Keep reading the input (either opened file descriptor fd, or socket sock,
// or SSL descriptor ssl) into buffer buf, until \r\n\r\n appears in the
// buffer (which marks the end of HTTP request). Buffer buf may already
// have some data. The length of the data is stored in nread.
// Upon every read operation, increase nread by the number of bytes read.
static
int
read_request
(
FILE
*
fp
,
struct
mg_connection
*
conn
,
char
*
buf
,
int
bufsiz
,
int
*
nread
)
{
int
request_len
,
n
=
0
;
request_len
=
get_request_len
(
buf
,
*
nread
);
while
(
conn
->
ctx
->
stop_flag
==
0
&&
*
nread
<
bufsiz
&&
request_len
==
0
&&
(
n
=
pull
(
fp
,
conn
,
buf
+
*
nread
,
bufsiz
-
*
nread
))
>
0
)
{
*
nread
+=
n
;
assert
(
*
nread
<=
bufsiz
);
request_len
=
get_request_len
(
buf
,
*
nread
);
}
return
request_len
<=
0
&&
n
<=
0
?
-
1
:
request_len
;
}
// For given directory path, substitute it to valid index file.
// Return 0 if index file has been found, -1 if not found.
// If the file is found, it's stats is returned in stp.
static
int
substitute_index_file
(
struct
mg_connection
*
conn
,
char
*
path
,
size_t
path_len
,
struct
file
*
filep
)
{
const
char
*
list
=
conn
->
ctx
->
config
[
INDEX_FILES
];
struct
file
file
=
STRUCT_FILE_INITIALIZER
;
struct
vec
filename_vec
;
size_t
n
=
strlen
(
path
);
int
found
=
0
;
// The 'path' given to us points to the directory. Remove all trailing
// directory separator characters from the end of the path, and
// then append single directory separator character.
while
(
n
>
0
&&
path
[
n
-
1
]
==
'/'
)
{
n
--
;
}
path
[
n
]
=
'/'
;
// Traverse index files list. For each entry, append it to the given
// path and see if the file exists. If it exists, break the loop
while
((
list
=
next_option
(
list
,
&
filename_vec
,
NULL
))
!=
NULL
)
{
// Ignore too long entries that may overflow path buffer
if
(
filename_vec
.
len
>
path_len
-
(
n
+
2
))
continue
;
// Prepare full path to the index file
mg_strlcpy
(
path
+
n
+
1
,
filename_vec
.
ptr
,
filename_vec
.
len
+
1
);
// Does it exist?
if
(
mg_stat
(
path
,
&
file
))
{
// Yes it does, break the loop
*
filep
=
file
;
found
=
1
;
break
;
}
}
// If no index file exists, restore directory path
if
(
!
found
)
{
path
[
n
]
=
'\0'
;
}
return
found
;
}
// Return True if we should reply 304 Not Modified.
static
int
is_not_modified
(
const
struct
mg_connection
*
conn
,
const
struct
file
*
filep
)
{
char
etag
[
64
];
const
char
*
ims
=
mg_get_header
(
conn
,
"If-Modified-Since"
);
const
char
*
inm
=
mg_get_header
(
conn
,
"If-None-Match"
);
construct_etag
(
etag
,
sizeof
(
etag
),
filep
);
return
(
inm
!=
NULL
&&
!
mg_strcasecmp
(
etag
,
inm
))
||
(
ims
!=
NULL
&&
filep
->
modification_time
<=
parse_date_string
(
ims
));
}
static
int
forward_body_data
(
struct
mg_connection
*
conn
,
FILE
*
fp
,
SOCKET
sock
,
SSL
*
ssl
)
{
const
char
*
expect
,
*
body
;
char
buf
[
MG_BUF_LEN
];
int
nread
,
buffered_len
,
success
=
0
;
int64_t
left
;
expect
=
mg_get_header
(
conn
,
"Expect"
);
assert
(
fp
!=
NULL
);
if
(
conn
->
content_len
==
INT64_MAX
)
{
send_http_error
(
conn
,
411
,
"Length Required"
,
"%s"
,
""
);
}
else
if
(
expect
!=
NULL
&&
mg_strcasecmp
(
expect
,
"100-continue"
))
{
send_http_error
(
conn
,
417
,
"Expectation Failed"
,
"%s"
,
""
);
}
else
{
if
(
expect
!=
NULL
)
{
(
void
)
mg_printf
(
conn
,
"%s"
,
"HTTP/1.1 100 Continue
\r\n\r\n
"
);
}
buffered_len
=
conn
->
data_len
-
conn
->
request_len
;
body
=
conn
->
buf
+
conn
->
request_len
;
assert
(
buffered_len
>=
0
);
if
(
buffered_len
>
0
)
{
if
((
int64_t
)
buffered_len
>
conn
->
content_len
)
{
buffered_len
=
(
int
)
conn
->
content_len
;
}
push
(
fp
,
sock
,
ssl
,
body
,
(
int64_t
)
buffered_len
);
memmove
((
char
*
)
body
,
body
+
buffered_len
,
buffered_len
);
conn
->
data_len
-=
buffered_len
;
}
nread
=
0
;
while
(
conn
->
num_bytes_read
<
conn
->
content_len
+
conn
->
request_len
)
{
left
=
left_to_read
(
conn
);
if
(
left
>
(
int64_t
)
sizeof
(
buf
))
{
left
=
sizeof
(
buf
);
}
nread
=
pull
(
NULL
,
conn
,
buf
,
(
int
)
left
);
if
(
nread
<=
0
||
push
(
fp
,
sock
,
ssl
,
buf
,
nread
)
!=
nread
)
{
break
;
}
}
if
(
left_to_read
(
conn
)
==
0
)
{
success
=
nread
>=
0
;
}
// Each error code path in this function must send an error
if
(
!
success
)
{
send_http_error
(
conn
,
577
,
http_500_error
,
"%s"
,
""
);
}
}
return
success
;
}
#if !defined(NO_CGI)
#if !defined(NO_CGI)
// This structure helps to create an environment for the spawned CGI program.
// This structure helps to create an environment for the spawned CGI program.
// Environment is an array of "VARIABLE=VALUE\0" ASCIIZ strings,
// Environment is an array of "VARIABLE=VALUE\0" ASCIIZ strings,
...
@@ -3724,38 +3400,362 @@ done:
...
@@ -3724,38 +3400,362 @@ done:
}
}
#endif // !NO_CGI
#endif // !NO_CGI
// For a given PUT path, create all intermediate subdirectories
// for given path. Return 0 if the path itself is a directory,
// or -1 on error, 1 if OK.
static
int
put_dir
(
const
char
*
path
)
{
char
buf
[
PATH_MAX
];
const
char
*
s
,
*
p
;
struct
file
file
=
STRUCT_FILE_INITIALIZER
;
int
len
,
res
=
1
;
for
(
s
=
p
=
path
+
2
;
(
p
=
strchr
(
s
,
'/'
))
!=
NULL
;
s
=
++
p
)
{
static
int
call_user
(
int
type
,
struct
mg_connection
*
conn
,
void
*
p
)
{
len
=
p
-
path
;
if
(
conn
!=
NULL
&&
conn
->
ctx
!=
NULL
)
{
if
(
len
>=
(
int
)
sizeof
(
buf
))
{
conn
->
event
.
user_data
=
conn
->
ctx
->
user_data
;
res
=
-
1
;
conn
->
event
.
type
=
type
;
break
;
conn
->
event
.
event_param
=
p
;
}
conn
->
event
.
request_info
=
&
conn
->
request_info
;
memcpy
(
buf
,
path
,
len
);
conn
->
event
.
conn
=
conn
;
buf
[
len
]
=
'\0'
;
}
return
conn
==
NULL
||
conn
->
ctx
==
NULL
||
conn
->
ctx
->
event_handler
==
NULL
?
0
:
conn
->
ctx
->
event_handler
(
&
conn
->
event
);
}
// Try to create intermediate directory
static
FILE
*
mg_fopen
(
const
char
*
path
,
const
char
*
mode
)
{
DEBUG_TRACE
((
"mkdir(%s)"
,
buf
));
#ifdef _WIN32
if
(
!
mg_stat
(
buf
,
&
file
)
&&
mg_mkdir
(
buf
,
0755
)
!=
0
)
{
wchar_t
wbuf
[
PATH_MAX
],
wmode
[
20
];
res
=
-
1
;
to_unicode
(
path
,
wbuf
,
ARRAY_SIZE
(
wbuf
));
break
;
MultiByteToWideChar
(
CP_UTF8
,
0
,
mode
,
-
1
,
wmode
,
ARRAY_SIZE
(
wmode
));
}
return
_wfopen
(
wbuf
,
wmode
);
#else
return
fopen
(
path
,
mode
);
#endif
}
// Is path itself a directory?
// Print error message to the opened error log stream.
if
(
p
[
1
]
==
'\0'
)
{
static
void
cry
(
struct
mg_connection
*
conn
,
const
char
*
fmt
,
...)
{
res
=
0
;
char
buf
[
MG_BUF_LEN
],
src_addr
[
IP_ADDR_STR_LEN
];
}
va_list
ap
;
}
FILE
*
fp
;
time_t
timestamp
;
return
res
;
va_start
(
ap
,
fmt
);
(
void
)
vsnprintf
(
buf
,
sizeof
(
buf
),
fmt
,
ap
);
va_end
(
ap
);
// 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
);
}
fprintf
(
fp
,
"%s"
,
buf
);
fputc
(
'\n'
,
fp
);
funlockfile
(
fp
);
fclose
(
fp
);
}
}
}
const
char
*
mg_version
(
void
)
{
return
MONGOOSE_VERSION
;
}
// 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)
if
(
filep
->
gzipped
)
{
send_http_error
(
conn
,
501
,
"Not Implemented"
,
"range requests in gzipped files are not supported"
);
return
;
}
conn
->
status_code
=
206
;
cl
=
n
==
2
?
(
r2
>
cl
?
cl
:
r2
)
-
r1
+
1
:
cl
-
r1
;
mg_snprintf
(
range
,
sizeof
(
range
),
"Content-Range: bytes "
"%"
INT64_FMT
"-%"
INT64_FMT
"/%"
INT64_FMT
"
\r\n
"
,
r1
,
r1
+
cl
-
1
,
filep
->
size
);
msg
=
"Partial Content"
;
}
// Prepare Etag, Date, Last-Modified headers. Must be in UTC, according to
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3
gmt_time_string
(
date
,
sizeof
(
date
),
&
curtime
);
gmt_time_string
(
lm
,
sizeof
(
lm
),
&
filep
->
modification_time
);
construct_etag
(
etag
,
sizeof
(
etag
),
filep
);
(
void
)
mg_printf
(
conn
,
"HTTP/1.1 %d %s
\r\n
"
"Date: %s
\r\n
"
"Last-Modified: %s
\r\n
"
"Etag: %s
\r\n
"
"Content-Type: %.*s
\r\n
"
"Content-Length: %"
INT64_FMT
"
\r\n
"
"Connection: %s
\r\n
"
"Accept-Ranges: bytes
\r\n
"
"%s%s%s
\r\n
"
,
conn
->
status_code
,
msg
,
date
,
lm
,
etag
,
(
int
)
mime_vec
.
len
,
mime_vec
.
ptr
,
cl
,
suggest_connection_header
(
conn
),
range
,
encoding
,
EXTRA_HTTP_HEADERS
);
if
(
strcmp
(
conn
->
request_info
.
request_method
,
"HEAD"
)
!=
0
)
{
send_file_data
(
conn
,
fp
,
r1
,
cl
);
}
fclose
(
fp
);
}
void
mg_send_file
(
struct
mg_connection
*
conn
,
const
char
*
path
)
{
struct
file
file
=
STRUCT_FILE_INITIALIZER
;
if
(
mg_stat
(
path
,
&
file
))
{
handle_file_request
(
conn
,
path
,
&
file
);
}
else
{
send_http_error
(
conn
,
404
,
"Not Found"
,
"%s"
,
"File not found"
);
}
}
// For given directory path, substitute it to valid index file.
// Return 0 if index file has been found, -1 if not found.
// If the file is found, it's stats is returned in stp.
static
int
substitute_index_file
(
struct
mg_connection
*
conn
,
char
*
path
,
size_t
path_len
,
struct
file
*
filep
)
{
const
char
*
list
=
conn
->
ctx
->
config
[
INDEX_FILES
];
struct
file
file
=
STRUCT_FILE_INITIALIZER
;
struct
vec
filename_vec
;
size_t
n
=
strlen
(
path
);
int
found
=
0
;
// The 'path' given to us points to the directory. Remove all trailing
// directory separator characters from the end of the path, and
// then append single directory separator character.
while
(
n
>
0
&&
path
[
n
-
1
]
==
'/'
)
{
n
--
;
}
path
[
n
]
=
'/'
;
// Traverse index files list. For each entry, append it to the given
// path and see if the file exists. If it exists, break the loop
while
((
list
=
next_option
(
list
,
&
filename_vec
,
NULL
))
!=
NULL
)
{
// Ignore too long entries that may overflow path buffer
if
(
filename_vec
.
len
>
path_len
-
(
n
+
2
))
continue
;
// Prepare full path to the index file
mg_strlcpy
(
path
+
n
+
1
,
filename_vec
.
ptr
,
filename_vec
.
len
+
1
);
// Does it exist?
if
(
mg_stat
(
path
,
&
file
))
{
// Yes it does, break the loop
*
filep
=
file
;
found
=
1
;
break
;
}
}
// If no index file exists, restore directory path
if
(
!
found
)
{
path
[
n
]
=
'\0'
;
}
return
found
;
}
// Return True if we should reply 304 Not Modified.
static
int
is_not_modified
(
const
struct
mg_connection
*
conn
,
const
struct
file
*
filep
)
{
char
etag
[
64
];
const
char
*
ims
=
mg_get_header
(
conn
,
"If-Modified-Since"
);
const
char
*
inm
=
mg_get_header
(
conn
,
"If-None-Match"
);
construct_etag
(
etag
,
sizeof
(
etag
),
filep
);
return
(
inm
!=
NULL
&&
!
mg_strcasecmp
(
etag
,
inm
))
||
(
ims
!=
NULL
&&
filep
->
modification_time
<=
parse_date_string
(
ims
));
}
}
static
void
mkcol
(
struct
mg_connection
*
conn
,
const
char
*
path
)
{
static
void
mkcol
(
struct
mg_connection
*
conn
,
const
char
*
path
)
{
...
...
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