#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 // // 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 // // 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 // // 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 // // 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 // // 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 // // 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 // // 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 // // 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 // // 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 // // 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 // // 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 // // 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 // // 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 // // 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 // // 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 // // 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 // // 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 // // 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 // // 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 // // 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 // // 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(); }