#define __http_init
// ***
// This function forms part of Faucet HTTP v1.0
// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension
// 
// Copyright (c) 2013-2014, Andrea Faulds <ajf@ajf.me>
// 
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
// 
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// ***

// Creates global.__HttpClient
// real __http_init()

global.__HttpClient = object_add();
object_set_persistent(global.__HttpClient, true);

#define __http_split
// ***
// This function forms part of Faucet HTTP v1.0
// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension
// 
// Copyright (c) 2013-2014, Andrea Faulds <ajf@ajf.me>
// 
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
// 
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// ***

// real __http_split(string text, delimeter delimeter, real limit)
// Splits string into items

// text - string comma-separated values
// delimeter - delimeter to split by
// limit  if non-zero, maximum times to split text
// When limited, the remaining text will be left as the last item.
// E.g. splitting "1,2,3,4,5" with delimeter "," and limit 2 yields this list:
// "1", "2", "3,4,5"

// return value - ds_list containing strings of values

var text, delimeter, limit;
text = argument0;
delimeter = argument1;
limit = argument2;

var list, count;
list = ds_list_create();
count = 0;

while (string_pos(delimeter, text) != 0)
{
    ds_list_add(list, string_copy(text, 1, string_pos(delimeter,text) - 1));
    text = string_copy(text, string_pos(delimeter, text) + string_length(delimeter), string_length(text) - string_pos(delimeter, text));

    count += 1;
    if (limit and count == limit)
        break;
}
ds_list_add(list, text);

return list;

#define __http_parse_url
// ***
// This function forms part of Faucet HTTP v1.0
// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension
// 
// Copyright (c) 2013-2014, Andrea Faulds <ajf@ajf.me>
// 
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
// 
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// ***

// Parses a URL into its components
// real __http_parse_url(string url)

// Return value is a ds_map containing keys for the different URL parts: (or -1 on failure)
// "url" - the URL which was passed in
// "scheme" - the URL scheme (e.g. "http")
// "host" - the hostname (e.g. "example.com" or "127.0.0.1")
// "port" - the port (e.g. 8000) - this is a real, unlike the others
// "abs_path" - the absolute path (e.g. "/" or "/index.html")
// "query" - the query string (e.g. "a=b&c=3")
// Parts which are not included will not be in the map
// e.g. http://example.com will not have the "port", "path" or "query" keys

// This will *only* work properly for URLs of format:
// scheme ":" "//" host [ ":" port ] [ abs_path [ "?" query ]]"
// where [] denotes an optional component
// file: URLs will *not* work as they lack the authority (host:port) component
// It will not work correctly for IPv6 host values

var url;
url = argument0;

var map;
map = ds_map_create();
ds_map_add(map, 'url', url);

// before scheme
var colonPos;
// Find colon for end of scheme
colonPos = string_pos(':', url);
// No colon - bad URL
if (colonPos == 0)
    return -1;
ds_map_add(map, 'scheme', string_copy(url, 1, colonPos - 1));
url = string_copy(url, colonPos + 1, string_length(url) - colonPos);

// before double slash
// remove slashes (yes this will screw up file:// but who cares)
while (string_char_at(url, 1) == '/')
    url = string_copy(url, 2, string_length(url) - 1);

// before hostname
var slashPos, colonPos;
// Find slash for beginning of path
slashPos = string_pos('/', url);
// No slash ahead - http://host format with no ending slash
if (slashPos == 0)
{
    // Find : for beginning of port
    colonPos = string_pos(':', url);
}
else
{
    // Find : for beginning of port prior to /
    colonPos = string_pos(':', string_copy(url, 1, slashPos - 1));
}
// No colon - no port
if (colonPos == 0)
{
    // There was no slash
    if (slashPos == 0)
    {
        ds_map_add(map, 'host', url);
        return map;
    }
    // There was a slash
    else
    {
        ds_map_add(map, 'host', string_copy(url, 1, slashPos - 1));
        url = string_copy(url, slashPos, string_length(url) - slashPos + 1);
    }
}
// There's a colon - port specified
else
{
    // There was no slash
    if (slashPos == 0)
    {
        ds_map_add(map, 'host', string_copy(url, 1, colonPos - 1));
        ds_map_add(map, 'port', real(string_copy(url, colonPos + 1, string_length(url) - colonPos)));
        return map;
    }
    // There was a slash
    else
    {
        ds_map_add(map, 'host', string_copy(url, 1, colonPos - 1));
        url = string_copy(url, colonPos + 1, string_length(url) - colonPos);
        slashPos = string_pos('/', url);
        ds_map_add(map, 'port', real(string_copy(url, 1, slashPos - 1)));
        url = string_copy(url, slashPos, string_length(url) - slashPos + 1); 
    }
}

// before path
var queryPos;
queryPos = string_pos('?', url);
// There's no ? - no query
if (queryPos == 0)
{
    ds_map_add(map, 'abs_path', url);
    return map;
}
else
{
    ds_map_add(map, 'abs_path', string_copy(url, 1, queryPos - 1));
    ds_map_add(map, 'query', string_copy(url, queryPos + 1, string_length(url) - queryPos));
    return map;
}

// Return -1 upon unlikely error
ds_map_destroy(map);
return -1;

#define __http_resolve_url
// ***
// This function forms part of Faucet HTTP v1.0
// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension
// 
// Copyright (c) 2013-2014, Andrea Faulds <ajf@ajf.me>
// 
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
// 
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// ***

// Takes a base URL and a URL reference and applies it to the base URL
// Returns resulting absolute URL
// string __http_resolve_url(string baseUrl, string refUrl)

// Return value is a string containing the new absolute URL, or "" on failure

// Works only for restricted URL syntax as understood by by http_resolve_url
// The sole restriction of which is that only scheme://authority/path URLs work
// This notably excludes file: URLs which lack the authority component

// As described by RFC3986:
//      URI-reference = URI / relative-ref
//      relative-ref  = relative-part [ "?" query ] [ "#" fragment ]
//      relative-part = "//" authority path-abempty
//                    / path-absolute
//                    / path-noscheme
//                    / path-empty
// However http_resolve_url does *not* deal with fragments

// Algorithm based on that of section 5.2.2 of RFC 3986

var baseUrl, refUrl;
baseUrl = argument0;
refUrl = argument1;

// Parse base URL
var urlParts;
urlParts = __http_parse_url(baseUrl);
if (urlParts == -1)
    return '';

// Try to parse reference URL
var refUrlParts, canParseRefUrl;
refUrlParts = __http_parse_url(refUrl);
canParseRefUrl = (refUrlParts != -1);
if (refUrlParts != -1)
    ds_map_destroy(refUrlParts);

var result;
result = '';

// Parsing of reference URL succeeded - it's absolute and we're done
if (canParseRefUrl)
{
    result = refUrl;
}
// Begins with '//' - scheme-relative URL
else if (string_copy(refUrl, 1, 2) == '//' and string_length(refUrl) > 2)
{
    result = ds_map_find_value(urlParts, 'scheme') + ':' + refUrl;
}
// Is or begins with '/' - absolute path relative URL
else if (((string_char_at(refUrl, 1) == '/' and string_length(refUrl) > 1) or refUrl == '/')
// Doesn't begin with ':' and is not blank - relative path relative URL
    or (string_char_at(refUrl, 1) != ':' and string_length(refUrl) > 0)) 
{
    // Find '?' for query
    var queryPos;
    queryPos = string_pos('?', refUrl);
    // No query
    if (queryPos == 0)
    {
        refUrl = __http_resolve_path(ds_map_find_value(urlParts, 'abs_path'), refUrl);
        ds_map_replace(urlParts, 'abs_path', refUrl);
        if (ds_map_exists(urlParts, 'query'))
            ds_map_delete(urlParts, 'query');
    }
    // Query exists, split
    else
    {
        var path, query;
        path = string_copy(refUrl, 1, queryPos - 1);
        query = string_copy(refUrl, queryPos + 1, string_length(relUrl) - queryPos);
        path = __http_resolve_path(ds_map_find_value(urlParts, 'abs_path'), path);
        ds_map_replace(urlParts, 'abs_path', path);
        if (ds_map_exists(urlParts, 'query'))
            ds_map_replace(urlParts, 'query', query);
        else
            ds_map_add(urlParts, 'query', query);
    }
    result = __http_construct_url(urlParts);
}

ds_map_destroy(urlParts);
return result;

#define __http_resolve_path
// ***
// This function forms part of Faucet HTTP v1.0
// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension
// 
// Copyright (c) 2013-2014, Andrea Faulds <ajf@ajf.me>
// 
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
// 
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// ***

// Takes a base path and a path reference and applies it to the base path
// Returns resulting absolute path
// string __http_resolve_path(string basePath, string refPath)

// Return value is a string containing the new absolute path

// Deals with UNIX-style / paths, not Windows-style \ paths
// Can be used to clean up .. and . in non-absolute paths too ('' as basePath)

var basePath, refPath;
basePath = argument0;
refPath = argument1;

// refPath begins with '/' (is absolute), we can ignore all of basePath
if (string_char_at(refPath, 1) == '/')
{
    basePath = refPath;
    refPath = '';
}

var parts, refParts;
parts = __http_split(basePath, '/', 0);
refParts = __http_split(refPath, '/', 0);

if (refPath != '')
{
    // Find last part of base path
    var lastPart;
    lastPart = ds_list_find_value(parts, ds_list_size(parts) - 1);

    // If it's not blank (points to a file), remove it
    if (lastPart != '')
    {
        ds_list_delete(parts, ds_list_size(parts) - 1);
    }
    
    // Concatenate refParts to end of parts
    var i;
    for (i = 0; i < ds_list_size(refParts); i += 1)
        ds_list_add(parts, ds_list_find_value(refParts, i));
}

// We now don't need refParts any more
ds_list_destroy(refParts);

// Deal with '..' and '.'
for (i = 0; i < ds_list_size(parts); i += 1)
{
    var part;
    part = ds_list_find_value(parts, i);

    if (part == '.')
    {
        if (i == 1 or i == ds_list_size(parts) - 1)
            ds_list_replace(parts, i, '');
        else
            ds_list_delete(parts, i);
        i -= 1;
        continue;
    }
    else if (part == '..')
    {
        if (i > 1)
        {
            ds_list_delete(parts, i - 1);
            ds_list_delete(part, i);
            i -= 2;
        }
        else
        {
            ds_list_replace(parts, i, '');
            i -= 1;
        }
        continue;
    }
    else if (part == '' and i != 0 and i != ds_list_size(parts) - 1)
    {
        ds_list_delete(parts, i);
        i -= 1;
        continue;
    }
}

// Reconstruct path from parts
var path;
path = '';
for (i = 0; i < ds_list_size(parts); i += 1)
{
    if (i != 0)
        path += '/';
    path += ds_list_find_value(parts, i);
}

ds_map_destroy(parts);
return path;

#define __http_parse_hex
// ***
// This function forms part of Faucet HTTP v1.0
// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension
// 
// Copyright (c) 2013-2014, Andrea Faulds <ajf@ajf.me>
// 
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
// 
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// ***

// Takes a lowercase hexadecimal string and returns its integer value
// real __http_parse_hex(string hexString)

// Return value is the whole number value (or -1 if invalid)
// Only works for whole numbers (non-fractional numbers >= 0) and lowercase hex

var hexString;
hexString = argument0;

var result, hexValues;
result = 0;
hexValues = "0123456789abcdef";

var i;
for (i = 1; i <= string_length(hexString); i += 1) {
    result *= 16;
    var digit;
    digit = string_pos(string_char_at(hexString, i), hexValues) - 1;
    if (digit == -1)
        return -1;
    result += digit;
}

return result;

#define __http_construct_url
// ***
// This function forms part of Faucet HTTP v1.0
// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension
// 
// Copyright (c) 2013-2014, Andrea Faulds <ajf@ajf.me>
// 
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
// 
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// ***

// Constructs an URL from its components (as http_parse_url would return)
// string __http_construct_url(real parts)

// Return value is the string of the constructed URL
// Keys of parts map:
// "scheme" - the URL scheme (e.g. "http")
// "host" - the hostname (e.g. "example.com" or "127.0.0.1")
// "port" - the port (e.g. 8000) - this is a real, unlike the others
// "abs_path" - the absolute path (e.g. "/" or "/index.html")
// "query" - the query string (e.g. "a=b&c=3")
// Parts which are omitted will be omitted in the URL
// e.g. http://example.com lacks "port", "path" or "query" keys

// This will *only* work properly for URLs of format:
// scheme ":" "//" host [ ":" port ] [ abs_path [ "?" query ]]"
// where [] denotes an optional component
// file: URLs will *not* work as they lack the authority (host:port) component
// Should work correctly for IPv6 host values, but bare in mind parse_url won't

var parts;
parts = argument0;

var url;
url = '';

url += ds_map_find_value(parts, 'scheme');
url += '://';
url += ds_map_find_value(parts, 'host');
if (ds_map_exists(parts, 'port'))
    url += ':' + string(ds_map_find_value(parts, 'port'));
if (ds_map_exists(parts, 'abs_path'))
{
    url += ds_map_find_value(parts, 'abs_path');
    if (ds_map_exists(parts, 'query'))
        url += '?' + ds_map_find_value(parts, 'query');
}

return url;

#define __http_prepare_request
// ***
// This function forms part of Faucet HTTP v1.0
// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension
// 
// Copyright (c) 2013-2014, Andrea Faulds <ajf@ajf.me>
// 
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
// 
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// ***

// Internal function - prepares request
// void __http_prepare_request(real client, string url, real headers)

// client - HttpClient object to prepare
// url - URL to send GET request to
// headers - ds_map of extra headers to send, -1 if none

var client, url, headers;
client = argument0;
url = argument1;
headers = argument2;

var parsed;
parsed = __http_parse_url(url);

if (parsed == -1)
    show_error("Error when making HTTP GET request - can't parse URL: " + url, true);

if (!ds_map_exists(parsed, 'port'))
    ds_map_add(parsed, 'port', 80);
if (!ds_map_exists(parsed, 'abs_path'))
    ds_map_add(parsed, 'abs_path', '/');

with (client)
{
    destroyed = false;
    CR = chr(13);
    LF = chr(10);
    CRLF = CR + LF;
    socket = tcp_connect(ds_map_find_value(parsed, 'host'), ds_map_find_value(parsed, 'port'));
    state = 0;
    errored = false;
    error = '';
    linebuf = '';
    line = 0;
    statusCode = -1;
    reasonPhrase = '';
    responseBody = buffer_create();
    responseBodySize = -1;
    responseBodyProgress = -1;
    responseHeaders = ds_map_create();
    requestUrl = url;
    requestHeaders = headers;

    //  Request       = Request-Line              ; Section 5.1
    //                  *(( general-header        ; Section 4.5
    //                   | request-header         ; Section 5.3
    //                   | entity-header ) CRLF)  ; Section 7.1
    //                  CRLF
    //                  [ message-body ]          ; Section 4.3

    // "The Request-Line begins with a method token, followed by the
    // Request-URI and the protocol version, and ending with CRLF. The
    // elements are separated by SP characters. No CR or LF is allowed
    // except in the final CRLF sequence."
    if (ds_map_exists(parsed, 'query'))
        write_string(socket, 'GET ' + ds_map_find_value(parsed, 'abs_path') + '?' + ds_map_find_value(parsed, 'query') + ' HTTP/1.1' + CRLF);
    else
        write_string(socket, 'GET ' + ds_map_find_value(parsed, 'abs_path') + ' HTTP/1.1' + CRLF);

    // "A client MUST include a Host header field in all HTTP/1.1 request
    // messages."
    // "A "host" without any trailing port information implies the default
    // port for the service requested (e.g., "80" for an HTTP URL)."
    if (ds_map_find_value(parsed, 'port') == 80)
        write_string(socket, 'Host: ' + ds_map_find_value(parsed, 'host') + CRLF);
    else
        write_string(socket, 'Host: ' + ds_map_find_value(parsed, 'host')
            + ':' + string(ds_map_find_value(parsed, 'port')) + CRLF);

    // "An HTTP/1.1 server MAY assume that a HTTP/1.1 client intends to
    // maintain a persistent connection unless a Connection header including
    // the connection-token "close" was sent in the request."
    write_string(socket, 'Connection: close' + CRLF);

    // "If no Accept-Encoding field is present in a request, the server MAY
    // assume that the client will accept any content coding."
    write_string(socket, 'Accept-Encoding:' + CRLF);
    
    // If headers specified
    if (headers != -1)
    {
        var key;
        // Iterate over headers map
        for (key = ds_map_find_first(headers); is_string(key); key = ds_map_find_next(headers, key))
        {
            write_string(socket, key + ': ' + ds_map_find_value(headers, key) + CRLF);
        }
    }
    
    // Send extra CRLF to terminate request
    write_string(socket, CRLF);
    
    socket_send(socket);

    ds_map_destroy(parsed);
}

#define __http_parse_header
// ***
// This function forms part of Faucet HTTP v1.0
// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension
// 
// Copyright (c) 2013-2014, Andrea Faulds <ajf@ajf.me>
// 
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
// 
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// ***

// Internal function - parses header
// real __http_parse_header(string linebuf, real line)
// Returns false if it errored (caller should return and destroy)

var linebuf, line;
linebuf = argument0;
line = argument1;

// "HTTP/1.1 header field values can be folded onto multiple lines if the
// continuation line begins with a space or horizontal tab."
if ((string_char_at(linebuf, 1) == ' ' or ord(string_char_at(linebuf, 1)) == 9))
{
    if (line == 1)
    {
        errored = true;
        error = "First header line of response can't be a continuation, right?";
        return false;
    }
    headerValue = ds_map_find_value(responseHeaders, string_lower(headerName))
        + string_copy(linebuf, 2, string_length(linebuf) - 1);
}
// "Each header field consists
// of a name followed by a colon (":") and the field value. Field names
// are case-insensitive. The field value MAY be preceded by any amount
// of LWS, though a single SP is preferred."
else
{
    var colonPos;
    colonPos = string_pos(':', linebuf);
    if (colonPos == 0)
    {
        errored = true;
        error = "No colon in a header line of response";
        return false;
    }
    headerName = string_copy(linebuf, 1, colonPos - 1);
    headerValue = string_copy(linebuf, colonPos + 1, string_length(linebuf) - colonPos);
    // "The field-content does not include any leading or trailing LWS:
    // linear white space occurring before the first non-whitespace
    // character of the field-value or after the last non-whitespace
    // character of the field-value. Such leading or trailing LWS MAY be
    // removed without changing the semantics of the field value."
    while (string_char_at(headerValue, 1) == ' ' or ord(string_char_at(headerValue, 1)) == 9)
        headerValue = string_copy(headerValue, 2, string_length(headerValue) - 1);
}

ds_map_add(responseHeaders, string_lower(headerName), headerValue);

if (string_lower(headerName) == 'content-length')
{
    responseBodySize = real(headerValue);
    responseBodyProgress = 0;
}

return true;

#define __http_client_step
// ***
// This function forms part of Faucet HTTP v1.0
// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension
// 
// Copyright (c) 2013-2014, Andrea Faulds <ajf@ajf.me>
// 
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
// 
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// ***

// Steps the HTTP client (needs to be called each step or so)

var client;
client = argument0;

with (client)
{
    if (errored)
        exit;
    
    // Socket error
    if (socket_has_error(socket))
    {
        errored = true;
        error = "Socket error: " + socket_error(socket);
        return __http_client_destroy();
    }
    
    var available;
    available = tcp_receive_available(socket);
    
    switch (state)
    {
    // Receiving lines
    case 0:
        if (!available && tcp_eof(socket))
        {
            errored = true;
            error = "Unexpected EOF when receiving headers/status code";
            return __http_client_destroy();
        }
    
        var bytesRead, c;
        for (bytesRead = 1; bytesRead <= available; bytesRead += 1)
        {
            c = read_string(socket, 1);
            // Reached end of line
            // "HTTP/1.1 defines the sequence CR LF as the end-of-line marker for all
            // protocol elements except the entity-body (see appendix 19.3 for
            // tolerant applications)."
            if (c == LF and string_char_at(linebuf, string_length(linebuf)) == CR)
            {
                // Strip trailing CR
                linebuf = string_copy(linebuf, 1, string_length(linebuf) - 1);
                // First line - status code
                if (line == 0)
                {
                    // "The first line of a Response message is the Status-Line, consisting
                    // of the protocol version followed by a numeric status code and its
                    // associated textual phrase, with each element separated by SP
                    // characters. No CR or LF is allowed except in the final CRLF sequence."
                    var httpVer, spacePos;
                    spacePos = string_pos(' ', linebuf);
                    if (spacePos == 0)
                    {
                        errored = true;
                        error = "No space in first line of response";
                        return __http_client_destroy();
                    }
                    httpVer = string_copy(linebuf, 1, spacePos);
                    linebuf = string_copy(linebuf, spacePos + 1, string_length(linebuf) - spacePos);
    
                    spacePos = string_pos(' ', linebuf);
                    if (spacePos == 0)
                    {
                        errored = true;
                        error = "No second space in first line of response";
                        return __http_client_destroy();
                    }
                    statusCode = real(string_copy(linebuf, 1, spacePos));
                    reasonPhrase = string_copy(linebuf, spacePos + 1, string_length(linebuf) - spacePos);
                }
                // Other line
                else
                {
                    // Blank line, end of response headers
                    if (linebuf == '')
                    {
                        state = 1;
                        // write remainder
                        write_buffer_part(responseBody, socket, available - bytesRead);
                        responseBodyProgress = available - bytesRead;
                        break;
                    }
                    // Header
                    else
                    {
                        if (!__http_parse_header(linebuf, line))
                            return __http_client_destroy();
                    }
                }
    
                linebuf = '';
                line += 1;
            }
            else
                linebuf += c;
        }
        break;
    // Receiving response body
    case 1:
        write_buffer(responseBody, socket);
        responseBodyProgress += available;
        if (tcp_eof(socket))
        {
            if (ds_map_exists(responseHeaders, 'transfer-encoding'))
            {
                if (ds_map_find_value(responseHeaders, 'transfer-encoding') == 'chunked')
                {
                    // Chunked transfer, let's decode it
                    var actualResponseBody, actualResponseSize;
                    actualResponseBody = buffer_create();
                    actualResponseBodySize = 0;

                    // Parse chunks
                    // chunk          = chunk-size [ chunk-extension ] CRLF
                    //                  chunk-data CRLF
                    // chunk-size     = 1*HEX
                    while (buffer_bytes_left(responseBody))
                    {
                        var chunkSize, c;
                        chunkSize = '';
                        
                        // Read chunk size byte by byte 
                        while (buffer_bytes_left(responseBody))
                        {
                            c = read_string(responseBody, 1);
                            if (c == CR or c == ';')
                                break;
                            else
                                chunkSize += c;
                        }
                        
                        // We found a semicolon - beginning of chunk-extension
                        if (c == ';')
                        {
                            // skip all extension stuff
                            while (buffer_bytes_left(responseBody) && c != CR)
                            {
                                c = read_string(responseBody, 1);
                            }
                        }
                        // Reached end of header
                        if (c == CR)
                        {
                            c += read_string(responseBody, 1);
                            // Doesn't end in CRLF
                            if (c != CRLF)
                            {
                                errored = true;
                                error = 'header of chunk in chunked transfer did not end in CRLF';
                                buffer_destroy(actualResponseBody);
                                return __http_client_destroy();
                            }
                            // chunk-size is empty - something's up!
                            if (chunkSize == '')
                            {
                                errored = true;
                                error = 'empty chunk-size in a chunked transfer';
                                buffer_destroy(actualResponseBody);
                                return __http_client_destroy();
                            }
                            chunkSize = __http_parse_hex(chunkSize);
                            // Parsing of size failed - not hex?
                            if (chunkSize == -1)
                            {
                                errored = true;
                                error = 'chunk-size was not hexadecimal in a chunked transfer';
                                buffer_destroy(actualResponseBody);
                                return __http_client_destroy();
                            }
                            // Is the chunk bigger than the remaining response?
                            if (chunkSize + 2 > buffer_bytes_left(responseBody))
                            {
                                errored = true;
                                error = 'chunk-size was greater than remaining data in a chunked transfer';
                                buffer_destroy(actualResponseBody);
                                return __http_client_destroy();
                            }
                            // OK, everything's good, read the chunk
                            write_buffer_part(actualResponseBody, responseBody, chunkSize);
                            actualResponseBodySize += chunkSize;
                            // Check for CRLF
                            if (read_string(responseBody, 2) != CRLF)
                            {
                                errored = true;
                                error = 'chunk did not end in CRLF in a chunked transfer';
                                return __http_client_destroy();
                            }
                        }
                        else
                        {
                            errored = true;
                            error = 'did not find CR after reading chunk header in a chunked transfer, Faucet HTTP bug?';
                            return __http_client_destroy();
                        }
                        // if the chunk size is zero, then it was the last chunk
                        if (chunkSize == 0
                            // trailer headers will be present
                            and ds_map_exists(responseHeaders, 'trailer'))
                        {
                            // Parse header lines
                            var line;
                            line = 1;
                            while (buffer_bytes_left(responseBody))
                            {
                                var linebuf;
                                linebuf = '';
                                while (buffer_bytes_left(responseBody))
                                {
                                    c = read_string(responseBody, 1);
                                    if (c != CR)
                                        linebuf += c;
                                    else
                                        break;
                                }
                                c += read_string(responseBody, 1);
                                if (c != CRLF)
                                {
                                    errored = true;
                                    error = 'trailer header did not end in CRLF in a chunked transfer';
                                    return __http_client_destroy();
                                }
                                if (!__http_parse_header(linebuf, line))
                                    return __http_client_destroy();
                                line += 1;
                            }
                        }
                    }
                    responseBodySize = actualResponseBodySize;
                    buffer_destroy(responseBody);
                    responseBody = actualResponseBody;
                }
                else
                {
                    errored = true;
                    error = 'Unsupported Transfer-Encoding: "' + ds_map_find_value(responseHaders, 'transfer-encoding') + '"';
                    return __http_client_destroy();
                }
            }
            else if (responseBodySize != -1)
            {
                if (responseBodyProgress < responseBodySize)
                {
                    errored = true;
                    error = "Unexpected EOF, response body size is less than expected";
                    return __http_client_destroy();
                }
            }
            // 301 Moved Permanently/302 Found/303 See Other/307 Moved Temporarily
            if (statusCode == 301 or statusCode == 302 or statusCode == 303 or statusCode == 307)
            {
                if (ds_map_exists(responseHeaders, 'location'))
                {
                    var location, resolved;
                    location = ds_map_find_value(responseHeaders, 'location');
                    resolved = __http_resolve_url(requestUrl, location);
                    // Resolving URL didn't fail and it's http://
                    if (resolved != '' and string_copy(resolved, 1, 7) == 'http://')
                    {
                        // Restart request
                        __http_client_destroy();
                        __http_prepare_request(client, resolved, requestHeaders);
                    }
                    else
                    {
                        errored = true;
                        error = "301, 302, 303 or 307 response with invalid or unsupported Location URL ('" + location +  "') - can't redirect";
                        return __http_client_destroy();
                    }
                    exit;
                }
                else
                {
                    errored = true;
                    error = "301, 302, 303 or 307 response without Location header - can't redirect";
                    return __http_client_destroy();
                }
            }
            else
                state = 2;
        }
        break;
    // Done.
    case 2:
        break;
    }
}

#define __http_client_destroy
// ***
// This function forms part of Faucet HTTP v1.0
// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension
// 
// Copyright (c) 2013-2014, Andrea Faulds <ajf@ajf.me>
// 
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
// 
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// ***

// Clears up contents of an httpClient prior to destruction or after error

if (!destroyed) {
    socket_destroy(socket);
    buffer_destroy(responseBody);
    ds_map_destroy(responseHeaders);
}
destroyed = true;

#define http_new_get
// ***
// This function forms part of Faucet HTTP v1.0
// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension
// 
// Copyright (c) 2013-2014, Andrea Faulds <ajf@ajf.me>
// 
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
// 
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// ***

// Makes a GET HTTP request
// real http_new_get(string url)

// url - URL to send GET request to

// Return value is an HttpClient instance that can be passed to
// fct_http_request_status etc.
// (errors on failure to parse URL)

var url, client;

url = argument0;

if (!variable_global_exists('__HttpClient'))
    __http_init();

client = instance_create(0, 0, global.__HttpClient);
__http_prepare_request(client, url, -1);
return client;

#define http_new_get_ex
// ***
// This function forms part of Faucet HTTP v1.0
// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension
// 
// Copyright (c) 2013-2014, Andrea Faulds <ajf@ajf.me>
// 
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
// 
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// ***

// Makes a GET HTTP request with custom headers
// real http_new_get_ex(string url, real headers)

// url - URL to send GET request to
// headers - ds_map of extra headers to send

// Return value is an HttpClient instance that can be passed to
// fct_http_request_status etc.
// (errors on failure to parse URL)

var url, headers, client;

url = argument0;
headers = argument1;

if (!variable_global_exists('__HttpClient'))
    __http_init();

client = instance_create(0, 0, global.__HttpClient);
__http_prepare_request(client, url, headers);
return client;

#define http_step
// ***
// This function forms part of Faucet HTTP v1.0
// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension
// 
// Copyright (c) 2013-2014, Andrea Faulds <ajf@ajf.me>
// 
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
// 
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// ***

// Steps the HTTP client. This is what makes everything actually happen.
// Call it each step. Returns whether or not the request has finished.
// real http_step(real client)

// client - HttpClient object

// Return value is either:
// 0 - In progress
// 1 - Done or Errored

// Example usage:
// req = http_new_get("http://example.com/x.txt");
// while (http_step(req)) {}
// if (http_status_code(req) != 200) {
//     // Errored!
// } else {
//     // Hasn't errored, do stuff here.
// }

var client;
client = argument0;

__http_client_step(client);

if (client.errored || client.state == 2)
    return 1;
else
    return 0;

#define http_status_code
// ***
// This function forms part of Faucet HTTP v1.0
// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension
// 
// Copyright (c) 2013-2014, Andrea Faulds <ajf@ajf.me>
// 
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
// 
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// ***

// Gets the status code
// real http_status_code(real client)

// client - HttpClient object

// Return value is either:
// * 0, if the request has not yet finished
// * a negative value, if there was an internal Faucet HTTP error
// * a positive value, the status code of the HTTP request

// "The Status-Code element is a 3-digit integer result code of the
// attempt to understand and satisfy the request. These codes are fully
// defined in section 10. The Reason-Phrase is intended to give a short
// textual description of the Status-Code. The Status-Code is intended
// for use by automata and the Reason-Phrase is intended for the human
// user. The client is not required to examine or display the Reason-
// Phrase."

// See also: http_reason_phrase, gets the Reason-Phrase

var client;
client = argument0;

if (client.errored)
    return -1;
else if (client.state == 2)
    return client.statusCode;
else
    return 0;

#define http_reason_phrase
// ***
// This function forms part of Faucet HTTP v1.0
// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension
// 
// Copyright (c) 2013-2014, Andrea Faulds <ajf@ajf.me>
// 
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
// 
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// ***

// Gets the reason phrase
// string http_reason_phrase(real client)

// client - HttpClient object
// Return value is either:
// * "", if the request has not yet finished
// * an internal Faucet HTTP error message, if there was one
// * the reason phrase of the HTTP request

// "The Status-Code element is a 3-digit integer result code of the
// attempt to understand and satisfy the request. These codes are fully
// defined in section 10. The Reason-Phrase is intended to give a short
// textual description of the Status-Code. The Status-Code is intended
// for use by automata and the Reason-Phrase is intended for the human
// user. The client is not required to examine or display the Reason-
// Phrase."

// See also: http_status_code, gets the Status-Code

var client;
client = argument0;

if (client.errored)
    return client.error;
else if (client.state == 2)
    return client.reasonPhrase;
else
    return "";

#define http_response_body
// ***
// This function forms part of Faucet HTTP v1.0
// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension
// 
// Copyright (c) 2013-2014, Andrea Faulds <ajf@ajf.me>
// 
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
// 
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// ***

// Gets the response body returned by an HTTP request as a buffer
// real http_response_body(real client)

// client - HttpClient object

// Return value is a buffer if client hasn't errored and is finished

var client;
client = argument0;

return client.responseBody;

#define http_response_body_size
// ***
// This function forms part of Faucet HTTP v1.0
// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension
// 
// Copyright (c) 2013-2014, Andrea Faulds <ajf@ajf.me>
// 
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
// 
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// ***

// Gets the size of response body returned by an HTTP request
// real http_response_body_size(real client)

// client - HttpClient object

// Return value is the size in bytes, or -1 if we don't know or don't know yet

// Call this each time you use the size - it may have changed in the case of redirect

var client;
client = argument0;

return client.responseBodySize;

#define http_response_body_progress
// ***
// This function forms part of Faucet HTTP v1.0
// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension
// 
// Copyright (c) 2013-2014, Andrea Faulds <ajf@ajf.me>
// 
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
// 
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// ***

// Gets the size of response body returned by an HTTP request which is so far downloaded 
// real http_response_body_progress(real client)

// client - HttpClient object

// Return value is the size in bytes, or -1 if we haven't started yet or client has errored

var client;
client = argument0;

return client.responseBodyProgress;

#define http_response_headers
// ***
// This function forms part of Faucet HTTP v1.0
// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension
// 
// Copyright (c) 2013-2014, Andrea Faulds <ajf@ajf.me>
// 
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
// 
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// ***

// Gets the response headers returned by an HTTP request as a ds_map
// real http_response_headers(real client)

// client - HttpClient object

// Return value is a ds_map if client hasn't errored and is finished

// All headers will have lowercase keys
// The ds_map is owned by the HttpClient, do not use ds_map_destroy() yourself
// Call when the request has finished - otherwise may be incomplete or missing

var client;
client = argument0;

return client.responseHeaders;

#define http_destroy
// ***
// This function forms part of Faucet HTTP v1.0
// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension
// 
// Copyright (c) 2013-2014, Andrea Faulds <ajf@ajf.me>
// 
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
// 
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// ***

// Cleans up HttpClient
// void http_destroy(real client)

// client - HttpClient object

var client;
client = argument0;

with (client)
{
    __http_client_destroy();
    instance_destroy();
}

