Skip to content
Permalink
Browse files

Feature custom response candidate3 (#120)

Customizable HTTP responses
  • Loading branch information...
strictlymike committed Apr 1, 2019
1 parent bf64bc1 commit e1f863cf058d30d04ed709b88c57705fd0b53bcf
@@ -1,3 +1,7 @@
Version 1.4.9
-------------
* Fully customizable HTTP responses without building a full listener

Version 1.4.8
-------------
* FakeNet terminates upon listener exceptions, closing #117
@@ -0,0 +1,61 @@
# HTTP Listener Custom Responses

The `HTTPListener` can accept a setting named `Custom` that enables
customizable responses beyond what can be achieved by emplacing files in the
web root (e.g. under `defaultFiles/`).

The `HTTPListener` `Custom` setting indicates the name of an HTTP custom
response configuration file in the same location and format as the active
FakeNet-NG configuration file. An example HTTP custom response configuration
file is supplied in `fakenet/configs/sample_http_custom.ini`.

The sections of the HTTP custom response configuration file can define a series
of named rules regarding how to match requests and what to return.

Valid matching specifications are:
* `MatchHosts`: a comma-separated list of hostnames that will match against
host headers.
* `MatchURIs`: a comma-separated list of URIs that will match against request
URIs.

If both matching specifications are present in a single section, they will be
evaluated conjunctively (logical and).

Valid response specifications are:
* `RawFile`: Returns the raw contents of the specified file located under
the web root, with the exception of date replacement.
* `StaticString`: Wraps the specified string with server headers and a 200 OK
response code, replacing `\r\n` tokens with actual CRLFs and performing date
replacement as necessary.
* `ContentType`: Optionally, you accompany the `StaticString` setting with
an HTTP `Content-Type` header value to send. It is an error to specify
this setting with any other kind of response specification.
* `Dynamic`: Loads the specified Python file located under the web root
and invokes its `HandleRequest` function as described below.

Date replacement applies to both `RawFile` and `StaticString`, and replaces any
occurrences of `<RAW-DATE>` with a server-formatted date.

## Implementing a Dynamic Response Handler

The `HandleRequest` method must conform to the following prototype:

```
def HandleRequest(req, method, post_data=None):
"""Handle an HTTP request.
Parameters
----------
req : BaseHTTPServer.BaseHTTPRequestHandler
The BaseHTTPRequestHandler that recevied the request
method: str
The HTTP method, either 'HEAD', 'GET', 'POST' as of this writing
post_data: str
The HTTP post data received by calling `rfile.read()` against the
BaseHTTPRequestHandler that received the request.
"""
pass
```

An example dynamic response handler is supplied in
`defaultFiles/HTTPCustomProviderExample.py`
@@ -276,6 +276,8 @@ Timeout: 10
DumpHTTPPosts: Yes
DumpHTTPPostsFilePrefix: http
Hidden: False
# To read about customizing HTTP responses, see docs/CustomResponse.md
# Custom: sample_http_custom.ini

[HTTPListener443]
Enabled: True
@@ -0,0 +1,13 @@
# To read about customizing HTTP responses, see docs/CustomResponse.md
[Example0]
MatchURIs: /test.txt
StaticString: Wraps this with normal FakeNet HTTP headers (<RAW-DATE>)\r\n

[Example1]
MatchHosts: some.random.c2.com, other.c2.com
RawFile: sample_raw_response.txt

[Example2]
MatchHosts: both_host.com
MatchURIs: and_uri.txt
Dynamic: HTTPCustomProviderExample.py
@@ -0,0 +1,31 @@
# To read about customizing HTTP responses, see docs/CustomResponse.md
def HandleRequest(req, method, post_data=None):
"""Sample dynamic HTTP response handler.
Parameters
----------
req : BaseHTTPServer.BaseHTTPRequestHandler
The BaseHTTPRequestHandler that recevied the request
method: str
The HTTP method, either 'HEAD', 'GET', 'POST' as of this writing
post_data: str
The HTTP post data received by calling `rfile.read()` against the
BaseHTTPRequestHandler that received the request.
"""
response = 'Ahoy\r\n'

if method == 'GET':
req.send_response(200)
req.send_header('Content-Length', len(response))
req.end_headers()
req.wfile.write(response)

elif method == 'POST':
req.send_response(200)
req.send_header('Content-Length', len(response))
req.end_headers()
req.wfile.write(response)

elif method == 'HEAD':
req.send_response(200)
req.end_headers()
@@ -0,0 +1,13 @@
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 8
Last-Modified: Mon, 15 May 2017 18:04:40 GMT
ETag: "ae780585f49b94ce1444eb7d28906123"
Accept-Ranges: bytes
Server: AmazonS3
X-Amz-Cf-Id: DOMFBGDxBc9xK7seJjwbgRcfGUMjatIzbXRAY4nKJan2KJgKx6nM9g==
Cache-Control: no-cache, no-store, must-revalidate
Date: <RAW-DATE>
Connection: close

success
@@ -47,6 +47,7 @@ def __init__(self, logging_level = logging.INFO):
self.diverter = None

# FakeNet options and parameters
self.fakenet_config_dir = ''
self.fakenet_config = dict()

# Diverter options and parameters
@@ -74,6 +75,7 @@ def parse_config(self, config_filename):
config_filename)
sys.exit(1)

self.fakenet_config_dir = os.path.dirname(config_filename)
config = ConfigParser()
config.read(config_filename)

@@ -202,6 +204,7 @@ def start(self):

listener_config = self.listeners_config[listener_name]
listener_config['ipaddr'] = fn_addr
listener_config['configdir'] = self.fakenet_config_dir
# Anonymous listener
if not 'listener' in listener_config:
self.logger.debug('Anonymous %s listener on %s port %s...',
@@ -328,7 +331,7 @@ def main():
| | / ____ \| . \| |____| |\ | |____ | | | |\ | |__| |
|_|/_/ \_\_|\_\______|_| \_|______| |_| |_| \_|\_____|
Version 1.4.8
Version 1.4.9
_____________________________________________________________
Developed by FLARE Team
_____________________________________________________________
Oops, something went wrong.

0 comments on commit e1f863c

Please sign in to comment.
You can’t perform that action at this time.