diff --git a/Doxyfile b/Doxyfile index 4afa098..bcb5d37 100644 --- a/Doxyfile +++ b/Doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = tr64c # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 1.0.0 +PROJECT_NUMBER = 1.1.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/README.md b/README.md index 9161ba2..1ca8547 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,8 @@ Usage Cache action descriptions of the device in this file. -f, --format Defines the output format for queries. Possible values are: - CSV - comma-separated values (default) + TEXT - plain text (default) + CSV - comma-separated values JSON - JavaScript Object Notation XML - Extensible Markup Language -h, --help @@ -50,6 +51,23 @@ Usage --version Outputs the program version. +Example +======= + +Scanning for available devices on local interface with IP address 192.168.178.10: + + tr64c -o 192.168.178.10 -l + +Listing possible action on FRITZ!Box 7490: + + tr64c -o http://192.168.178.1:49000/tr64desc.xml -l + +Obtaining some user interface properties from a FRITZ!Box 7490: + + tr64c -o http://192.168.178.1:49000/tr64desc.xml -q UserInterface/GetInfo + +Check also the binding example for Python [here](etc/tr64c.py). + Building ======== @@ -74,7 +92,7 @@ Files |bsearch.* |Binary search algorithm. |cvutf8.* |UTF-8 conversion functions. |hmd5.* |MD5 hashing function. -|http.* |HTTP1.x header parser. +|http.* |HTTP/1.x parser. |mingw-unicode.h|Unicode enabled main() for MinGW targets. |parser.* |Text parsers and parser helpers. |sax.* |SAX based XML parser. diff --git a/appveyor.yml b/appveyor.yml index f8439f5..196ae8b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 1.0.0.{build} +version: 1.1.0.{build} configuration: - Release diff --git a/doc/Changelog.txt b/doc/Changelog.txt index b78367b..33a2974 100644 --- a/doc/Changelog.txt +++ b/doc/Changelog.txt @@ -4,5 +4,17 @@ M.N.P | +---- minor: increased if command-line syntax/semantic breaking changes were applied +------ major: increased if elementary changes (from user's point of view) were made +1.1.0 (2018-08-17) + - added: HTTP common status message texts to error message outputs + - added: Python binding + - added: TEXT output format + - added: CSV, JSON and XML output to scan and list output + - changed: verbosity level of invalid command message in interactive mode from warning to error + - changed: ignore --utf8 for non-Unicode targets + - changed: command output in interactive mode to output an empty line at the end + - fixed: wrong verbosity level for some error and info messages + - fixed: missing flush on output in interactive mode + - fixed: conversion to/from UTF-8 for empty strings + 1.0.0 (2018-08-14) - first release diff --git a/etc/tr64c.py b/etc/tr64c.py new file mode 100644 index 0000000..55667e5 --- /dev/null +++ b/etc/tr64c.py @@ -0,0 +1,141 @@ +""" +@file tr64c.py +@author Daniel Starke +@date 2018-08-15 +@version 2018-08-17 + +DISCLAIMER +This file has no copyright assigned and is placed in the Public Domain. +All contributions are also assumed to be in the Public Domain. +Other contributions are not permitted. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. +""" + +from datetime import datetime +import json, re, subprocess + + +def quote(value): + """ Quotes and escapes the given value to pass it to tr64c """ + esc = { + '\\': "\\\\", + '\n': "\\n", + '\r': "\\r", + '\t': "\\t", + '"' : "\\\"", + '\'': "\\'" + } + res = [] + for c in value: + res.append(esc.get(c, c)) + return '"' + ''.join(res) + '"' + + +def scan(app, localIf, timeout = 1000): + """ Scan for compliant devices at the given local network interface """ + cmdLine = [app, "-o", localIf, "-t", str(timeout), "--utf8", "-f", "JSON", "-s"] + ctx = subprocess.Popen(cmdLine, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + res = [] + while True: + line = ctx.stdout.readline().strip() + if line == "": + break + elif re.match(r"Error:.*", line) != None: + raise RuntimeError(line) + res.append(line) + res = ''.join(res) + if len(res) > 0: + return json.loads(res, encoding="UTF-8") + else: + return {} + + +def version(app): + """ Returns the version of the application """ + cmdLine = [app, "--utf8", "--version"] + ctx = subprocess.Popen(cmdLine, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + line = ctx.stdout.readline().strip() + match = re.match(r"(([0-9]+)\.([0-9]+)\.([0-9]+)) ([^ ]+) (.+)", line) + if match == None: + return {} + return {'Version':match.group(1), 'Major':int(match.group(2)), 'Minor':int(match.group(3)), 'Patch':int(match.group(4)), 'Date':datetime.strptime(match.group(5), "%Y-%m-%d"), 'Backend': match.group(6)} + + +class Session: + """ TR-064 session instance """ + + def __init__(self, app, host, timeout = 1000, user = None, password = None, cache = None): + cmdLine = [app, "-o", host, "-t", str(timeout), "-f", "JSON"] + if user != None: + cmdLine.extend(["-u", user]) + if password != None: + cmdLine.extend(["-p", password]) + if cache != None: + cmdLine.extend(["-c", cache]) + cmdLine.extend(["--utf8", "-i"]) + self.ctx = subprocess.Popen(cmdLine, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + + def __del__(self): + if self.ctx != None: + self.ctx.terminate() + + def list(self): + """ Returns a list of possible actions and their arguments. """ + self.ctx.stdin.write(b"list\n") + res = [] + while self.ctx.poll() == None: + line = self.ctx.stdout.readline().strip() + if line == "": + break + elif re.match(r"Error:.*", line) != None: + raise RuntimeError(line) + res.append(line) + res = ''.join(res) + if len(res) > 0: + return json.loads(res, encoding="UTF-8") + else: + return {} + + def query(self, action, args = []): + """ Queries an action and returns its result. """ + for i, value in enumerate(args): + args[i] = quote(value) + self.ctx.stdin.write(b"query {} {}\n".format(action, ' '.join(args))) + res = [] + while self.ctx.poll() == None: + line = self.ctx.stdout.readline().strip() + if line == "" or re.match(r"Error:.*", line) != None: + break + res.append(line); + res = ''.join(res) + if len(res) > 0: + return json.loads(res, encoding="UTF-8").values()[0] + else: + return {} + + +if __name__ == '__main__': + import sys, os.path + app = "../bin/tr64c.exe" if os.path.isfile("../bin/tr64c.exe") else "../bin/tr64c" + ver = version(app) + print "Using tr64c version {} with backend {}.".format(ver['Version'], ver['Backend']) + devices = scan(app, "192.168.178.25") + for dev in devices: + print "Found {} at {}.".format(dev['Device'], dev['URL']) + if len(devices) > 0: + session = Session(app, devices[0]['URL']) + record = session.list() + print "Found {} devices in {}.".format(len(record.values()[0]), record.keys()[0]) + hostCount = session.query("Hosts/GetHostNumberOfEntries")['HostNumberOfEntries'] + fmt = "{:17} {:15} {}" + print fmt.format("MAC", "IP", "Host") + for index in range(1, hostCount): + record = session.query("Hosts/GetGenericHostEntry", ["HostNumberOfEntries=" + str(index)]) + print fmt.format(record['MACAddress'], record['IPAddress'], record['HostName']) diff --git a/src/cvutf8.c b/src/cvutf8.c index 94b4b60..ab95923 100644 --- a/src/cvutf8.c +++ b/src/cvutf8.c @@ -3,7 +3,7 @@ * @author Daniel Starke * @see cvutf8.h * @date 2014-05-03 - * @version 2018-07-14 + * @version 2018-08-15 * * DISCLAIMER * This file has no copyright assigned and is placed in the Public Domain. @@ -53,6 +53,9 @@ wchar_t * cvutf8_toUtf16N(const char * utf8, const size_t len) { if (utf8 == (const char *)NULL) { return (wchar_t *)NULL; } + if (len == 0) { + return (wchar_t *)calloc(1, sizeof(wchar_t)); + } #ifdef PCF_IS_WIN utf16Size = (size_t)MultiByteToWideChar(CP_UTF8, 0, utf8, (int)len, 0, 0); if (utf16Size == 0xFFFD || utf16Size == 0) { @@ -102,6 +105,9 @@ char * cvutf8_fromUtf16N(const wchar_t * utf16, const size_t len) { if (utf16 == (const wchar_t *)NULL) { return (char *)NULL; } + if (len == 0) { + return (char *)calloc(1, sizeof(char)); + } #ifdef PCF_IS_WIN utf8Size = (size_t)WideCharToMultiByte(CP_UTF8, 0, utf16, (int)len, 0, 0, NULL, NULL); if (utf8Size == 0xFFFD || utf8Size == 0) { diff --git a/src/getopt.h b/src/getopt.h index 27e1eac..71c3b3c 100644 --- a/src/getopt.h +++ b/src/getopt.h @@ -3,7 +3,7 @@ * @author Daniel Starke * @see getopt.h * @date 2017-05-22 - * @version 2018-08-13 + * @version 2018-08-15 * * DISCLAIMER * This file has no copyright assigned and is placed in the Public Domain. @@ -20,7 +20,7 @@ */ #ifndef __LIBPCF_GETOPT_H__ #define __LIBPCF_GETOPT_H__ -#define __GETOPT_H__ /* avoid collision with system getopt.h */ +#define __GETOPT_H__ /* avoid collision with cygwins getopt.h include in sys/unistd.h */ #include "tchar.h" #include "target.h" diff --git a/src/tr64c-posix.c b/src/tr64c-posix.c index e6c9876..2faae66 100644 --- a/src/tr64c-posix.c +++ b/src/tr64c-posix.c @@ -2,7 +2,7 @@ * @file tr64c_posix.c * @author Daniel Starke * @date 2018-06-21 - * @version 2018-08-14 + * @version 2018-08-17 * * DISCLAIMER * This file has no copyright assigned and is placed in the Public Domain. @@ -428,7 +428,14 @@ static int discover(struct tTr64RequestCtx * ctx, const char * localIf, int (* v case PHRT_SUCCESS: ctx->status = response.status; if (response.status != 200) { - if (ctx->verbose > 1) _ftprintf(ferr, MSGT(MSGT_ERR_HTTP_STATUS), (unsigned)response.status); + if (ctx->verbose > 1) { + const tHttpStatusMsg * item = (const tHttpStatusMsg *)bs_staticArray(&(response.status), httpStatMsg, cmpHttpStatusMsg); + if (item != NULL) { + _ftprintf(ferr, MSGT(MSGT_ERR_HTTP_STATUS_STR), (unsigned)response.status, item->string); + } else { + _ftprintf(ferr, MSGT(MSGT_ERR_HTTP_STATUS), (unsigned)response.status); + } + } break; } else if (response.content.start != NULL && response.content.start != ctx->buffer && response.content.length > 0) { ctx->content = (char *)response.content.start; @@ -609,6 +616,7 @@ static int request(tTr64RequestCtx * ctx) { #endif if (ctx->verbose > 0) _ftprintf(ferr, MSGT(MSGT_ERR_SOCK_SEND_TOUT)); if (ctx->verbose > 1) printLastError(ferr); + ctx->status = 408; goto onError; break; default: @@ -701,7 +709,14 @@ static int request(tTr64RequestCtx * ctx) { httpAuthentication(ctx, &response); goto onError; } else if (response.status != 200) { - if (ctx->verbose > 1) _ftprintf(ferr, MSGT(MSGT_ERR_HTTP_STATUS), (unsigned)response.status); + if (ctx->verbose > 1) { + const tHttpStatusMsg * item = (const tHttpStatusMsg *)bs_staticArray(&(response.status), httpStatMsg, cmpHttpStatusMsg); + if (item != NULL) { + _ftprintf(ferr, MSGT(MSGT_ERR_HTTP_STATUS_STR), (unsigned)response.status, item->string); + } else { + _ftprintf(ferr, MSGT(MSGT_ERR_HTTP_STATUS), (unsigned)response.status); + } + } goto onError; } else if (response.content.start != NULL && response.content.start != ctx->buffer && response.content.length > 0) { ctx->content = (char *)response.content.start; @@ -732,6 +747,7 @@ static int request(tTr64RequestCtx * ctx) { if (val > (uint64_t)(ctx->timeout)) { if (ctx->verbose > 0) _ftprintf(ferr, MSGT(MSGT_ERR_SOCK_RECV_TOUT)); if (ctx->verbose > 1) printLastError(ferr); + ctx->status = 408; goto onError; /* incomplete */ } if (signalReceived != 0) goto onError; @@ -841,11 +857,14 @@ static void printAddresses(const tTr64RequestCtx * ctx, FILE * fd) { * Create a new HTTP request context based on the given URL. * * @param[in] url - base on this URL + * @param[in] user - user name + * @param[in] pass - password + * @param[in] format - output format type * @param[in] timeout - network timeouts in milliseconds * @param[in] verbose - verbosity level * @return Handle on success, else NULL. */ -tTr64RequestCtx * newTr64Request(const char * url, const char * user, const char * pass, const size_t timeout, const int verbose) { +tTr64RequestCtx * newTr64Request(const char * url, const char * user, const char * pass, const tFormat format, const size_t timeout, const int verbose) { if (verbose > 3) _ftprintf(ferr, MSGT(MSGT_DBG_ENTER_NEWTR64REQUEST)); if (url == NULL) return NULL; @@ -867,7 +886,7 @@ tTr64RequestCtx * newTr64Request(const char * url, const char * user, const char if (res->user != NULL) free(res->user); res->user = strdup(user); if (res->user == NULL) { - if (verbose > 1) _ftprintf(ferr, MSGT(MSGT_ERR_NO_MEM)); + if (verbose > 0) _ftprintf(ferr, MSGT(MSGT_ERR_NO_MEM)); goto onError; } } @@ -875,11 +894,12 @@ tTr64RequestCtx * newTr64Request(const char * url, const char * user, const char if (res->pass != NULL) free(res->pass); res->pass = strdup(pass); if (res->pass == NULL) { - if (verbose > 1) _ftprintf(ferr, MSGT(MSGT_ERR_NO_MEM)); + if (verbose > 0) _ftprintf(ferr, MSGT(MSGT_ERR_NO_MEM)); goto onError; } } + res->format = format; res->timeout = timeout; res->discover = discover; diff --git a/src/tr64c-winsocks.c b/src/tr64c-winsocks.c index 2d033ee..3a530df 100644 --- a/src/tr64c-winsocks.c +++ b/src/tr64c-winsocks.c @@ -2,7 +2,7 @@ * @file tr64c_winsocks.c * @author Daniel Starke * @date 2018-06-21 - * @version 2018-08-14 + * @version 2018-08-17 * * DISCLAIMER * This file has no copyright assigned and is placed in the Public Domain. @@ -385,7 +385,14 @@ static int discover(struct tTr64RequestCtx * ctx, const char * localIf, int (* v case PHRT_SUCCESS: ctx->status = response.status; if (response.status != 200) { - if (ctx->verbose > 1) _ftprintf(ferr, MSGT(MSGT_ERR_HTTP_STATUS), (unsigned)response.status); + if (ctx->verbose > 1) { + const tHttpStatusMsg * item = (const tHttpStatusMsg *)bs_staticArray(&(response.status), httpStatMsg, cmpHttpStatusMsg); + if (item != NULL) { + _ftprintf(ferr, MSGT(MSGT_ERR_HTTP_STATUS_STR), (unsigned)response.status, item->string); + } else { + _ftprintf(ferr, MSGT(MSGT_ERR_HTTP_STATUS), (unsigned)response.status); + } + } break; } else if (response.content.start != NULL && response.content.start != ctx->buffer && response.content.length > 0) { ctx->content = (char *)response.content.start; @@ -607,6 +614,7 @@ static int request(tTr64RequestCtx * ctx) { case WSAETIMEDOUT: if (ctx->verbose > 0) _ftprintf(ferr, MSGT(MSGT_ERR_SOCK_SEND_TOUT)); if (ctx->verbose > 1) printLastWsaError(ferr); + ctx->status = 408; goto onError; break; default: @@ -674,7 +682,14 @@ static int request(tTr64RequestCtx * ctx) { httpAuthentication(ctx, &response); goto onError; } else if (response.status != 200) { - if (ctx->verbose > 1) _ftprintf(ferr, MSGT(MSGT_ERR_HTTP_STATUS), (unsigned)response.status); + if (ctx->verbose > 1) { + const tHttpStatusMsg * item = (const tHttpStatusMsg *)bs_staticArray(&(response.status), httpStatMsg, cmpHttpStatusMsg); + if (item != NULL) { + _ftprintf(ferr, MSGT(MSGT_ERR_HTTP_STATUS_STR), (unsigned)response.status, item->string); + } else { + _ftprintf(ferr, MSGT(MSGT_ERR_HTTP_STATUS), (unsigned)response.status); + } + } goto onError; } else if (response.content.start != NULL && response.content.start != ctx->buffer && response.content.length > 0) { ctx->content = (char *)response.content.start; @@ -705,6 +720,7 @@ static int request(tTr64RequestCtx * ctx) { if (val > (DWORD)(ctx->timeout)) { if (ctx->verbose > 0) _ftprintf(ferr, MSGT(MSGT_ERR_SOCK_RECV_TOUT)); if (ctx->verbose > 1) printLastWsaError(ferr); + ctx->status = 408; goto onError; /* incomplete */ } if (signalReceived != 0) goto onError; @@ -777,11 +793,14 @@ static void printAddresses(const tTr64RequestCtx * ctx, FILE * fd) { * Create a new HTTP request context based on the given URL. * * @param[in] url - base on this URL + * @param[in] user - user name + * @param[in] pass - password + * @param[in] format - output format type * @param[in] timeout - network timeouts in milliseconds * @param[in] verbose - verbosity level * @return Handle on success, else NULL. */ -tTr64RequestCtx * newTr64Request(const char * url, const char * user, const char * pass, const size_t timeout, const int verbose) { +tTr64RequestCtx * newTr64Request(const char * url, const char * user, const char * pass, const tFormat format, const size_t timeout, const int verbose) { if (verbose > 3) _ftprintf(ferr, MSGT(MSGT_DBG_ENTER_NEWTR64REQUEST)); if (url == NULL) return NULL; @@ -803,7 +822,7 @@ tTr64RequestCtx * newTr64Request(const char * url, const char * user, const char if (res->user != NULL) free(res->user); res->user = strdup(user); if (res->user == NULL) { - if (verbose > 1) _ftprintf(ferr, MSGT(MSGT_ERR_NO_MEM)); + if (verbose > 0) _ftprintf(ferr, MSGT(MSGT_ERR_NO_MEM)); goto onError; } } @@ -811,11 +830,12 @@ tTr64RequestCtx * newTr64Request(const char * url, const char * user, const char if (res->pass != NULL) free(res->pass); res->pass = strdup(pass); if (res->pass == NULL) { - if (verbose > 1) _ftprintf(ferr, MSGT(MSGT_ERR_NO_MEM)); + if (verbose > 0) _ftprintf(ferr, MSGT(MSGT_ERR_NO_MEM)); goto onError; } } + res->format = format; res->timeout = timeout; res->discover = discover; diff --git a/src/tr64c.c b/src/tr64c.c index 74c57db..76eb7b0 100644 --- a/src/tr64c.c +++ b/src/tr64c.c @@ -2,9 +2,8 @@ * @file tr64c.c * @author Daniel Starke * @date 2018-06-21 - * @version 2018-08-14 + * @version 2018-08-17 * @todo Implement transaction session support. - * @todo Apply output format to --list. * * DISCLAIMER * This file has no copyright assigned and is placed in the Public Domain. @@ -44,9 +43,13 @@ volatile int signalReceived = 0; + + FILE * fin = NULL; FILE * fout = NULL; FILE * ferr = NULL; + + const void * fmsg[MSG_COUNT] = { /* MSGT_SUCCESS */ _T(""), /* never used for output */ /* MSGT_ERR_NO_MEM */ _T("Error: Failed to allocate memory.\n"), @@ -96,17 +99,20 @@ const void * fmsg[MSG_COUNT] = { /* MSGT_ERR_HTTP_SEND_REQ */ _T("Error: Failed to send request to server.\n"), /* MSGT_ERR_HTTP_RECV_RESP */ _T("Error: Failed to get response from server.\n"), /* MSGT_ERR_HTTP_STATUS */ _T("Error: Received HTTP response with status code %u.\n"), + /* MSGT_ERR_HTTP_STATUS_STR */ _T("Error: Received HTTP response with status code %u - %s.\n"), /* MSGT_ERR_HTTP_FMT_AUTH */ _T("Error: Failed to format HTTP authentication response.\n"), /* MSGT_ERR_HTTP_AUTH */ _T("Error: Failed HTTP authentication.\n"), /* MSGT_ERR_URL_FMT */ _T("Error: Failed to parse the given URL.\n"), /* MSGT_ERR_URL_PROT */ _T("Error: Unsupported protocol in given URL.\n"), /* MSGT_ERR_FMT_QUERY */ _T("Error: Failed to format HTTP request for query.\n"), /* MSGT_ERR_GET_QUERY_RESP */ _T("Error: Failed to retrieve query response from server (%u).\n"), + /* MSGT_ERR_GET_QUERY_RESP_STR */ _T("Error: Failed to retrieve query response from server (%u - %s).\n"), /* MSGT_ERR_QUERY_RESP_FMT */ _T("Error: The retrieve query response file format is invalid.\n"), /* MSGT_ERR_QUERY_RESP_ACTION */ _T("Error: Action name mismatch in query response.\n"), /* MSGT_ERR_QUERY_RESP_ARG */ _T("Error: Invalid action argument variable in query response.\n"), /* MSGT_ERR_QUERY_RESP_ARG_BAD_ESC */ _T("Error: Invalid escape sequence in argument value of query response.\n"), /* MSGT_ERR_QUERY_PRINT */ _T("Error: Failed to write formatted query response.\n"), + /* MSGT_ERR_BAD_CMD */ _T("Error: Invalid command.\n"), /* MSGT_WARN_CACHE_READ */ _T("Warning: Failed to read cache file content.\n"), /* MSGT_WARN_CACHE_FMT */ _T("Warning: The cache file format is invalid.\n"), /* MSGT_WARN_CACHE_UNESC */ _T("Warning: Failed to unescape field from cache file.\n"), @@ -115,7 +121,6 @@ const void * fmsg[MSG_COUNT] = { /* MSGT_WARN_OPT_LOW_TIMEOUT */ _T("Warning: Timeout value is less than recommended (>=1000ms).\n"), /* MSGT_WARN_LIST_NO_MEM */ _T("Warning: Failed to allocate memory for list output.\n"), /* MSGT_WARN_CMD_BAD_ESC */ _T("Warning: Invalid escape sequence in command-line at column %u.\n"), - /* MSGT_WARN_BAD_CMD */ _T("Warning: Invalid command was ignored.\n"), /* MSGT_INFO_SIGTERM */ _T("Info: Received signal. Finishing current operation.\n"), /* MSGU_INFO_DEV_DESC_REQ */ "Info: Requesting /%s from device.\n", /* MSGT_INFO_DEV_DESC_DUR */ _T("Info: Finished device description request in %u ms.\n"), @@ -139,6 +144,58 @@ const void * fmsg[MSG_COUNT] = { }; +const tHttpStatusMsg httpStatMsg[] = { + /* Success */ + {200, _T("OK")}, + /* Redirection */ + {300, _T("Multiple Choices")}, + {301, _T("Moved Permanently")}, + {302, _T("Found")}, + {303, _T("See Other")}, + {304, _T("Not Modified")}, + {305, _T("Use Proxy")}, + {306, _T("Switch Proxy")}, + {307, _T("Temporary Redirect")}, + {308, _T("Permanent Redirect")}, + /* Client errors */ + {400, _T("Bad Request")}, + {401, _T("Unauthorized")}, + {402, _T("Payment Required")}, + {403, _T("Forbidden")}, + {404, _T("Not Found")}, + {405, _T("Method Not Allowed")}, + {406, _T("Not Acceptable")}, + {407, _T("Proxy Authentication Required")}, + {408, _T("Request Timeout")}, + {409, _T("Conflict")}, + {410, _T("Gone")}, + {411, _T("Length Required")}, + {412, _T("Precondition Failed")}, + {413, _T("Payload Too Large")}, + {414, _T("URI Too Long")}, + {415, _T("Unsupported Media Type")}, + {416, _T("Range Not Satisfiable")}, + {417, _T("Expectation Failed")}, + {418, _T("I'm a teapot")}, + {421, _T("Misdirected Request")}, + {426, _T("Upgrade Required")}, + {428, _T("Precondition Required")}, + {429, _T("Too Many Requests")}, + {431, _T("Request Header Fields Too Large")}, + {451, _T("Unavailable For Legal Reasons")}, + /* Server errors */ + {500, _T("Internal Server Error")}, + {501, _T("Not Implemented")}, + {502, _T("Bad Gateway")}, + {503, _T("Service Unavailable")}, + {504, _T("Gateway Timeout")}, + {505, _T("HTTP Version Not Supported")}, + {506, _T("Variant Also Negotiates")}, + {510, _T("Not Extended")}, + {511, _T("Network Authentication Required")} +}; + + /** * Main entry point. */ @@ -153,9 +210,7 @@ int _tmain(int argc, TCHAR ** argv) { handleInteractive }; struct option longOptions[] = { -#ifdef UNICODE {_T("utf8"), no_argument, NULL, GETOPT_UTF8}, -#endif /* UNICODE */ {_T("version"), no_argument, NULL, GETOPT_VERSION}, {_T("cache"), required_argument, NULL, _T('c')}, {_T("format"), required_argument, NULL, _T('f')}, @@ -192,19 +247,19 @@ int _tmain(int argc, TCHAR ** argv) { opt.verbose++; opt.timeout = DEFAULT_TIMEOUT; - opt.format = F_CSV; + opt.format = F_TEXT; while (1) { res = getopt_long(argc, argv, _T(":c:f:hil:o:p:st:u:v"), longOptions, NULL); if (res == -1) break; switch (res) { -#ifdef UNICODE case GETOPT_UTF8: +#ifdef UNICODE _setmode(_fileno(fout), _O_U8TEXT); _setmode(_fileno(ferr), _O_U8TEXT); opt.narrow = 1; - break; #endif /* UNICODE */ + break; case GETOPT_VERSION: _putts(_T2(PROGRAM_VERSION_STR)); goto onSuccess; @@ -214,7 +269,9 @@ int _tmain(int argc, TCHAR ** argv) { break; case _T('f'): for (TCHAR * ch = optarg; *ch != 0; ch++) *ch = _totupper(*ch); - if (_tcscmp(optarg, _T("CSV")) == 0) { + if (_tcscmp(optarg, _T("TEXT")) == 0) { + opt.format = F_TEXT; + } else if (_tcscmp(optarg, _T("CSV")) == 0) { opt.format = F_CSV; } else if (_tcscmp(optarg, _T("JSON")) == 0) { opt.format = F_JSON; @@ -351,8 +408,9 @@ void printHelp(void) { _T("-c, --cache \n") _T(" Cache action descriptions of the device in this file.\n") _T("-f, --format \n") - _T(" Defines the output format for queries. Possible values are:\n") - _T(" CSV - comma-separated values (default)\n") + _T(" Defines the output format. Possible values are:\n") + _T(" TEXT - plain text (default)\n") + _T(" CSV - comma-separated values\n") _T(" JSON - JavaScript Object Notation\n") _T(" XML - Extensible Markup Language\n") _T("-h, --help\n") @@ -492,6 +550,18 @@ int strnicmpInternal(const char * lhs, const char * rhs, size_t n) { } +/** + * Compares the given value to the passed item status code. Used for binary search in httpStatMsg. + * + * @param[in] item - left-hand statement of comparison + * @param[in] value - right-hand statement of comparison + * @return -1 if smaller, 0 if equal and 1 if value is larger + */ +int cmpHttpStatusMsg(const tHttpStatusMsg * item, const size_t * value) { + return item->status < *value ? -1 : item->status > *value ? 1 : 0; +} + + /** * The function reads a line and sets the passed string pointer. * New space is allocated if the string can not hold more characters. @@ -761,6 +831,137 @@ int parseActionPath(tOptions * opt, int argIndex) { } +/** + * Escapes the given string to encode as CSV field. + * + * @param[in] str - string to escape + * @param[in] length - maximum length of the input string in bytes + * @return escapes string or NULL on error + * @remarks The returned string may be the same as the input if no escaping was needed. + */ +static char * escapeCsv(const char * str, const size_t length) { + if (str == NULL) return NULL; + /* calculate the result string size in bytes */ + size_t resSize = 1; + size_t i = 0; + for (const char * in = str; *in != 0 && i < length; in++, i++, resSize++) { + if (*in == '"') resSize++; + } + if ((i + 1) == resSize) return (char *)str; + /* create the result string */ + char * res = (char *)malloc(sizeof(char) * resSize); + if (res == NULL) return NULL; + i = 0; + char * out = res; + for (const char * in = str; *in != 0 && i < length; in++, i++) { + if (*in == '"') *out++ = '"'; + *out++ = *in; + } + *out = 0; + return res; +} + + +/** + * Escapes the given string to encode as JSON string. + * + * @param[in] str - string to escape + * @param[in] length - maximum length of the input string in bytes + * @return escapes string or NULL on error + * @remarks The returned string may be the same as the input if no escaping was needed. + * @see https://tools.ietf.org/html/rfc8259#section-7 + */ +static char * escapeJson(const char * str, const size_t length) { + static const char hex[] = "0123456789ABCDEF"; + if (str == NULL) return NULL; + /* calculate the result string size in bytes */ + size_t resSize = 1; + size_t i = 0; + for (const char * in = str; *in != 0 && i < length; in++, i++) { + switch (*in) { + case '"': + case '\\': + case '/': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + resSize += 2; + break; + default: + if (*in < 0x20) { + resSize += 6; + } else { + resSize++; + } + break; + } + } + if ((i + 1) == resSize) return (char *)str; + /* create the result string */ + char * res = (char *)malloc(sizeof(char) * resSize); + if (res == NULL) return NULL; + i = 0; + char * out = res; + for (const char * in = str; *in != 0 && i < length; in++, i++) { + switch (*in) { + case '"': *out++ = '\\'; *out++ = '"'; break; + case '\\': *out++ = '\\'; *out++ = '\\'; break; + case '/': *out++ = '\\'; *out++ = '/'; break; + case '\b': *out++ = '\\'; *out++ = 'b'; break; + case '\f': *out++ = '\\'; *out++ = 'f'; break; + case '\n': *out++ = '\\'; *out++ = 'n'; break; + case '\r': *out++ = '\\'; *out++ = 'r'; break; + case '\t': *out++ = '\\'; *out++ = 't'; break; + default: + if (*in < 0x20) { + *out++ = '\\'; + *out++ = 'u'; + *out++ = '0'; + *out++ = '0'; + *out++ = hex[(*in >> 4) & 0x0F]; + *out++ = hex[*in & 0x0F]; + } else { + *out++ = *in; + } + break; + } + } + *out = 0; + return res; +} + + +/** + * Maps the given TR-064 argument type to a JSON type. + * + * @param[in] type - TR-064 argument type + * @return mapped JSON type + */ +static tJsonType mapToJsonType(const char * type) { + if (type == NULL) return JT_NULL; + if (stricmp(type, "boolean") == 0) return JT_BOOLEAN; + if (tolower(*type) == 'u') type++; + if (tolower(*type) != 'i') return JT_STRING; + char * endPtr = NULL; + unsigned long bits = strtoul(type + 1, &endPtr, 10); + if (endPtr != NULL && *endPtr == 0) { + switch (bits) { + case 1: + case 2: + case 4: + case 8: + return JT_NUMBER; + break; + default: + break; + } + } + return JT_STRING; +} + + /** * Helper callback function to parse a URL into tokens. * @@ -1955,22 +2156,22 @@ tTrObject * newTrObject(tTr64RequestCtx * ctx, const tOptions * opt) { /* read device description */ ctx->length = 0; if (formatToCtxBuffer(ctx, request, ctx->path, ctx->host, ctx->port) != 1) { - if (ctx->verbose > 1) _ftprintf(ferr, MSGT(MSGT_ERR_FMT_DEV_DESC)); + if (ctx->verbose > 0) _ftprintf(ferr, MSGT(MSGT_ERR_FMT_DEV_DESC)); goto onError; } if (ctx->verbose > 3) { fuprintf(ferr, MSGU(MSGU_INFO_DEV_DESC_REQ), ctx->path); } if (ctx->request(ctx) != 1) { - if (ctx->verbose > 1) _ftprintf(ferr, MSGT(MSGT_ERR_GET_DEV_DESC), (unsigned)(ctx->status)); + if (ctx->verbose > 0) _ftprintf(ferr, MSGT(MSGT_ERR_GET_DEV_DESC), (unsigned)(ctx->status)); goto onError; } - if (ctx->verbose > 3) _ftprintf(ferr, MSGT(MSGT_INFO_DEV_DESC_DUR), (unsigned)(ctx->duration)); + if (ctx->verbose > 2) _ftprintf(ferr, MSGT(MSGT_INFO_DEV_DESC_DUR), (unsigned)(ctx->duration)); /* parse device descriptions in used callback (see xmlDeviceDescVisitor()) */ { obj->url = strdup(opt->url); if (obj->url == NULL) { - if (ctx->verbose > 1) _ftprintf(ferr, MSGT(MSGT_ERR_NO_MEM)); + if (ctx->verbose > 0) _ftprintf(ferr, MSGT(MSGT_ERR_NO_MEM)); goto onError; } tPTrObjectDeviceCtx devCtx = { @@ -2010,17 +2211,17 @@ tTrObject * newTrObject(tTr64RequestCtx * ctx, const tOptions * opt) { /* skip leading slash (/) in service->path as it is already included in request */ ctx->length = 0; if (formatToCtxBuffer(ctx, request, service->path + 1, ctx->host, ctx->port) != 1) { - if (ctx->verbose > 1) _ftprintf(ferr, MSGT(MSGT_ERR_FMT_SRVC_DESC)); + if (ctx->verbose > 0) _ftprintf(ferr, MSGT(MSGT_ERR_FMT_SRVC_DESC)); goto onError; } - if (ctx->verbose > 3) { + if (ctx->verbose > 2) { fuprintf(ferr, MSGU(MSGU_INFO_SRVC_DESC_REQ), service->path); } if (ctx->request(ctx) != 1) { - if (ctx->verbose > 1) _ftprintf(ferr, MSGT(MSGT_ERR_GET_SRVC_DESC), (unsigned)(ctx->status)); + if (ctx->verbose > 0) _ftprintf(ferr, MSGT(MSGT_ERR_GET_SRVC_DESC), (unsigned)(ctx->status)); goto onError; } - if (ctx->verbose > 3) _ftprintf(ferr, MSGT(MSGT_INFO_SRVC_DESC_DUR), (unsigned)(ctx->duration)); + if (ctx->verbose > 2) _ftprintf(ferr, MSGT(MSGT_INFO_SRVC_DESC_DUR), (unsigned)(ctx->duration)); /* parse service description in used callback (see xmlServiceDescVisitor()) */ { tPTrObjectServiceCtx serviceCtx = { @@ -2325,7 +2526,7 @@ static int xmlQueryRespVisitor(const tPSaxTokenType type, const tPToken * tokens return 0; /* allocation error */ } errno = 0; - if (arg->value != NULL && p_unescapeXmlVar(&(arg->value), NULL, 0) != 1) { ///////// bug + if (arg->value != NULL && p_unescapeXmlVar(&(arg->value), NULL, 0) != 1) { if (errno == EINVAL) { ctx->lastError = MSGT_ERR_QUERY_RESP_ARG_BAD_ESC; } else { @@ -2351,31 +2552,35 @@ static int xmlQueryRespVisitor(const tPSaxTokenType type, const tPToken * tokens /** - * Escapes the given string to encode as CSV field. + * Outputs the query result in text format. * - * @param[in] str - string to escape - * @return escapes string or NULL on error - * @remarks The returned string may be the same as the input if no escaping was needed. + * @param[in,out] fd - output to this file descriptor + * @param[in,out] qry - query handle + * @param[in] action - action to output + * @return 1 on success, else 0 */ -static char * escapeCsv(const char * str) { - if (str == NULL) return NULL; - /* calculate the result string size in bytes */ - size_t resSize = 1; - size_t i = 0; - for (const char * in = str; *in != 0; in++, i++, resSize++) { - if (*in == '"') resSize++; +static int trQueryOutputText(FILE * fd, tTrQueryHandler * qry, const tTrAction * action) { + if (fd == NULL || qry == NULL || action == NULL) return 0; + int ok = 1; + + qry->length = 0; + ok = formatToQryBuffer(qry, "%s\n", action->name); + for (size_t ar = 0; ar < action->length; ar++) { + tTrArgument * arg = action->arg + ar; + if (strcmp(arg->dir, "out") != 0) continue; + ok &= formatToQryBuffer(qry," %s: %s\n", arg->var, (arg->value != NULL) ? arg->value : ""); } - if ((i + 1) == resSize) return (char *)str; - /* create the result string */ - char * res = (char *)malloc(sizeof(char) * resSize); - if (res == NULL) return NULL; - char * out = res; - for (const char * in = str; *in != 0; in++) { - if (*in == '"') *out++ = '"'; - *out++ = *in; + if (ok != 1) goto onOutOfMemory; + + if (fputUtf8N(fd, qry->buffer, qry->length) < 1) { + if (qry->ctx->verbose > 0) _ftprintf(ferr, MSGT(MSGT_ERR_QUERY_PRINT)); + return 0; } - *out = 0; - return res; + + return 1; +onOutOfMemory: + if (qry->ctx->verbose > 0) _ftprintf(ferr, MSGT(MSGT_ERR_NO_MEM)); + return 0; } @@ -2397,7 +2602,7 @@ static int trQueryOutputCsv(FILE * fd, tTrQueryHandler * qry, const tTrAction * for (size_t ar = 0; ar < action->length; ar++) { tTrArgument * arg = action->arg + ar; if (strcmp(arg->dir, "out") != 0 || arg->value == NULL) continue; - escStr = escapeCsv(arg->var); + escStr = escapeCsv(arg->var, (size_t)-1); if (escStr == NULL) goto onOutOfMemory; ok &= formatToQryBuffer(qry, first ? "\"%s\"" : ",\"%s\"", escStr); if (escStr != arg->var) free(escStr); @@ -2415,7 +2620,7 @@ static int trQueryOutputCsv(FILE * fd, tTrQueryHandler * qry, const tTrAction * if (first != 0) ok &= formatToQryBuffer(qry, ","); continue; } - escStr = escapeCsv(arg->value); + escStr = escapeCsv(arg->value, (size_t)-1); if (escStr == NULL) goto onOutOfMemory; ok &= formatToQryBuffer(qry, first ? "\"%s\"" : ",\"%s\"", escStr); if (escStr != arg->value) free(escStr); @@ -2425,115 +2630,17 @@ static int trQueryOutputCsv(FILE * fd, tTrQueryHandler * qry, const tTrAction * if (ok != 1) goto onOutOfMemory; if (fputUtf8N(fd, qry->buffer, qry->length) < 1) { - if (qry->ctx->verbose > 1) _ftprintf(ferr, MSGT(MSGT_ERR_QUERY_PRINT)); + if (qry->ctx->verbose > 0) _ftprintf(ferr, MSGT(MSGT_ERR_QUERY_PRINT)); return 0; } return 1; onOutOfMemory: - if (qry->ctx->verbose > 1) _ftprintf(ferr, MSGT(MSGT_ERR_NO_MEM)); + if (qry->ctx->verbose > 0) _ftprintf(ferr, MSGT(MSGT_ERR_NO_MEM)); return 0; } -/** - * Escapes the given string to encode as JSON string. - * - * @param[in] str - string to escape - * @return escapes string or NULL on error - * @remarks The returned string may be the same as the input if no escaping was needed. - * @see https://tools.ietf.org/html/rfc8259#section-7 - */ -static char * escapeJson(const char * str) { - static const char hex[] = "0123456789ABCDEF"; - if (str == NULL) return NULL; - /* calculate the result string size in bytes */ - size_t resSize = 1; - size_t i = 0; - for (const char * in = str; *in != 0; in++, i++) { - switch (*in) { - case '"': - case '\\': - case '/': - case '\b': - case '\f': - case '\n': - case '\r': - case '\t': - resSize += 2; - break; - default: - if (*in < 0x20) { - resSize += 6; - } else { - resSize++; - } - break; - } - } - if ((i + 1) == resSize) return (char *)str; - /* create the result string */ - char * res = (char *)malloc(sizeof(char) * resSize); - if (res == NULL) return NULL; - char * out = res; - for (const char * in = str; *in != 0; in++) { - switch (*in) { - case '"': *out++ = '\\'; *out++ = '"'; break; - case '\\': *out++ = '\\'; *out++ = '\\'; break; - case '/': *out++ = '\\'; *out++ = '/'; break; - case '\b': *out++ = '\\'; *out++ = 'b'; break; - case '\f': *out++ = '\\'; *out++ = 'f'; break; - case '\n': *out++ = '\\'; *out++ = 'n'; break; - case '\r': *out++ = '\\'; *out++ = 'r'; break; - case '\t': *out++ = '\\'; *out++ = 't'; break; - default: - if (*in < 0x20) { - *out++ = '\\'; - *out++ = 'u'; - *out++ = '0'; - *out++ = '0'; - *out++ = hex[(*in >> 4) & 0x0F]; - *out++ = hex[*in & 0x0F]; - } else { - *out++ = *in; - } - break; - } - } - *out = 0; - return res; -} - - -/** - * Maps the given TR-064 argument type to a JSON type. - * - * @param[in] type - TR-064 argument type - * @return mapped JSON type - */ -static tJsonType mapToJsonType(const char * type) { - if (type == NULL) return JT_NULL; - if (stricmp(type, "boolean") == 0) return JT_BOOLEAN; - if (tolower(*type) == 'u') type++; - if (tolower(*type) != 'i') return JT_STRING; - char * endPtr = NULL; - unsigned long bits = strtoul(type + 1, &endPtr, 10); - if (endPtr != NULL && *endPtr == 0) { - switch (bits) { - case 1: - case 2: - case 4: - case 8: - return JT_NUMBER; - break; - default: - break; - } - } - return JT_STRING; -} - - /** * Outputs the query result in JSON format. * @@ -2548,7 +2655,7 @@ static int trQueryOutputJson(FILE * fd, tTrQueryHandler * qry, const tTrAction * char * escStr; qry->length = 0; - escStr = escapeJson(action->name); + escStr = escapeJson(action->name, (size_t)-1); if (escStr == NULL) goto onOutOfMemory; ok = formatToQryBuffer(qry, "{\"%s\":{\n", escStr); if (escStr != action->name) free(escStr); @@ -2556,9 +2663,9 @@ static int trQueryOutputJson(FILE * fd, tTrQueryHandler * qry, const tTrAction * tTrArgument * arg = action->arg + ar; if (strcmp(arg->dir, "out") != 0) continue; /* key */ - escStr = escapeJson(arg->var); + escStr = escapeJson(arg->var, (size_t)-1); if (escStr == NULL) goto onOutOfMemory; - ok &= formatToQryBuffer(qry, first ? " \"%s\":" : ",\n \"%s\":", escStr); + ok &= formatToQryBuffer(qry, first ? " \"%s\":" : ",\n \"%s\":", escStr); if (escStr != arg->var) free(escStr); /* value */ if (arg->value == NULL) { @@ -2583,7 +2690,7 @@ static int trQueryOutputJson(FILE * fd, tTrQueryHandler * qry, const tTrAction * } /* fall-through */ case JT_STRING: - escStr = escapeJson(arg->value); + escStr = escapeJson(arg->value, (size_t)-1); if (escStr == NULL) goto onOutOfMemory; ok &= formatToQryBuffer(qry, "\"%s\"", escStr); if (escStr != arg->value) free(escStr); @@ -2595,13 +2702,13 @@ static int trQueryOutputJson(FILE * fd, tTrQueryHandler * qry, const tTrAction * if (ok != 1) goto onOutOfMemory; if (fputUtf8N(fd, qry->buffer, qry->length) < 1) { - if (qry->ctx->verbose > 1) _ftprintf(ferr, MSGT(MSGT_ERR_QUERY_PRINT)); + if (qry->ctx->verbose > 0) _ftprintf(ferr, MSGT(MSGT_ERR_QUERY_PRINT)); return 0; } return 1; onOutOfMemory: - if (qry->ctx->verbose > 1) _ftprintf(ferr, MSGT(MSGT_ERR_NO_MEM)); + if (qry->ctx->verbose > 0) _ftprintf(ferr, MSGT(MSGT_ERR_NO_MEM)); return 0; } @@ -2625,7 +2732,7 @@ static int trQueryOutputXml(FILE * fd, tTrQueryHandler * qry, const tTrAction * tTrArgument * arg = action->arg + ar; if (strcmp(arg->dir, "out") != 0) continue; /* start tag */ - ok &= formatToQryBuffer(qry, " <%s>", arg->var); + ok &= formatToQryBuffer(qry, " <%s>", arg->var); /* value */ if (arg->value != NULL) { escStr = p_escapeXml(arg->value, (size_t)-1); @@ -2640,13 +2747,13 @@ static int trQueryOutputXml(FILE * fd, tTrQueryHandler * qry, const tTrAction * if (ok != 1) goto onOutOfMemory; if (fputUtf8N(fd, qry->buffer, qry->length) < 1) { - if (qry->ctx->verbose > 1) _ftprintf(ferr, MSGT(MSGT_ERR_QUERY_PRINT)); + if (qry->ctx->verbose > 0) _ftprintf(ferr, MSGT(MSGT_ERR_QUERY_PRINT)); return 0; } return 1; onOutOfMemory: - if (qry->ctx->verbose > 1) _ftprintf(ferr, MSGT(MSGT_ERR_NO_MEM)); + if (qry->ctx->verbose > 0) _ftprintf(ferr, MSGT(MSGT_ERR_NO_MEM)); return 0; } @@ -2696,7 +2803,7 @@ static int trQuery(tTrQueryHandler * qry, const tOptions * opt, int argIndex) { /* select the matching action description */ if (obj->device == NULL) { - if (opt->verbose > 1) _ftprintf(ferr, MSGT(MSGT_ERR_NO_DEV_IN_DESC)); + if (opt->verbose > 0) _ftprintf(ferr, MSGT(MSGT_ERR_NO_DEV_IN_DESC)); goto onError; } for (size_t d = 0; d < obj->length; d++) { @@ -2720,7 +2827,7 @@ static int trQuery(tTrQueryHandler * qry, const tOptions * opt, int argIndex) { } } if (service == NULL || action == NULL) { - if (opt->verbose > 1) _ftprintf(ferr, MSGT(MSGT_ERR_OPT_BAD_ACTION)); + if (opt->verbose > 0) _ftprintf(ferr, MSGT(MSGT_ERR_OPT_BAD_ACTION)); goto onError; } if (opt->verbose > 3) { @@ -2741,7 +2848,7 @@ static int trQuery(tTrQueryHandler * qry, const tOptions * opt, int argIndex) { /* strncmp does not work to match two strings completely */ if (strcmp(arg->var, opt->args[i]) == 0) { if (ok == 1) { - if (ctx->verbose > 1) fuprintf(ferr, MSGU(MSGU_ERR_OPT_AMB_IN_ARG), arg->var); + if (ctx->verbose > 0) fuprintf(ferr, MSGU(MSGU_ERR_OPT_AMB_IN_ARG), arg->var); goto onError; } ok = 1; @@ -2749,12 +2856,12 @@ static int trQuery(tTrQueryHandler * qry, const tOptions * opt, int argIndex) { if (arg->value != NULL) free(arg->value); arg->value = strdup(sep + 1); if (arg->value == NULL) { - if (ctx->verbose > 1) _ftprintf(ferr, MSGT(MSGT_ERR_NO_MEM)); + if (ctx->verbose > 0) _ftprintf(ferr, MSGT(MSGT_ERR_NO_MEM)); goto onError; } /* XML escape value */ if (p_escapeXmlVar(&(arg->value)) != 1) { - if (ctx->verbose > 1) _ftprintf(ferr, MSGT(MSGT_ERR_NO_MEM)); + if (ctx->verbose > 0) _ftprintf(ferr, MSGT(MSGT_ERR_NO_MEM)); goto onError; } /* format argument */ @@ -2763,14 +2870,14 @@ static int trQuery(tTrQueryHandler * qry, const tOptions * opt, int argIndex) { *sep = '='; } if (ok != 1) { - if (ctx->verbose > 1) fuprintf(ferr, MSGU(MSGU_ERR_OPT_NO_IN_ARG), arg->var); + if (ctx->verbose > 0) fuprintf(ferr, MSGU(MSGU_ERR_OPT_NO_IN_ARG), arg->var); goto onError; } } fmt &= formatToQryBuffer(qry, "\n%s", action->name, tail); if (fmt != 1) { - if (ctx->verbose > 1) _ftprintf(ferr, MSGT(MSGT_ERR_FMT_QUERY)); + if (ctx->verbose > 0) _ftprintf(ferr, MSGT(MSGT_ERR_FMT_QUERY)); goto onError; } @@ -2792,7 +2899,7 @@ static int trQuery(tTrQueryHandler * qry, const tOptions * opt, int argIndex) { qry->buffer ); if (fmt != 1) { - if (ctx->verbose > 1) _ftprintf(ferr, MSGT(MSGT_ERR_FMT_QUERY)); + if (ctx->verbose > 0) _ftprintf(ferr, MSGT(MSGT_ERR_FMT_QUERY)); goto onError; } @@ -2800,7 +2907,7 @@ static int trQuery(tTrQueryHandler * qry, const tOptions * opt, int argIndex) { if (ctx->method != NULL) free(ctx->method); ctx->method = strdup("POST"); if (ctx->method == NULL) { - if (ctx->verbose > 1) _ftprintf(ferr, MSGT(MSGT_ERR_NO_MEM)); + if (ctx->verbose > 0) _ftprintf(ferr, MSGT(MSGT_ERR_NO_MEM)); goto onError; } @@ -2818,7 +2925,14 @@ static int trQuery(tTrQueryHandler * qry, const tOptions * opt, int argIndex) { /* retry with proper authentication */ goto onAuthentication; } else { - if (ctx->verbose > 1) _ftprintf(ferr, MSGT(MSGT_ERR_GET_QUERY_RESP), (unsigned)(ctx->status)); + if (ctx->verbose > 0) { + const tHttpStatusMsg * item = (const tHttpStatusMsg *)bs_staticArray(&(ctx->status), httpStatMsg, cmpHttpStatusMsg); + if (item != NULL) { + _ftprintf(ferr, MSGT(MSGT_ERR_GET_QUERY_RESP_STR), (unsigned)(ctx->status), item->string); + } else { + _ftprintf(ferr, MSGT(MSGT_ERR_GET_QUERY_RESP), (unsigned)(ctx->status)); + } + } goto onError; } } @@ -2875,6 +2989,7 @@ static int trQuery(tTrQueryHandler * qry, const tOptions * opt, int argIndex) { */ tTrQueryHandler * newTrQueryHandler(tTr64RequestCtx * ctx, tTrObject * obj, const tOptions * opt) { static int (* writer[])(FILE *, tTrQueryHandler *, const tTrAction *) = { + trQueryOutputText, trQueryOutputCsv, trQueryOutputJson, trQueryOutputXml @@ -2951,12 +3066,51 @@ static int printDiscoveredDevices(const char * buffer, const size_t length, void static const char * st = "urn:dslforum-org:device:InternetGatewayDevice:1"; tTr64RequestCtx * ctx = (tTr64RequestCtx *)param; tPToken tokens[3] = {0}; /* ST, SERVER, LOCATION */ + char * esc[2] = {0}; + size_t len[2]; if (buffer == NULL || ctx == NULL) return 0; +#define ESC(fn) \ + esc[0] = fn(tokens[1].start, tokens[1].length); \ + if (esc[0] == NULL) break; \ + esc[1] = fn(tokens[2].start, tokens[2].length); \ + if (esc[1] == NULL) { \ + if (esc[0] != tokens[1].start) free(esc[0]); \ + esc[0] = NULL; \ + break; \ + } \ + len[0] = (esc[0] != tokens[1].start) ? strlen(esc[0]) : tokens[1].length; \ + len[1] = (esc[1] != tokens[2].start) ? strlen(esc[1]) : tokens[2].length; switch (p_http(buffer, length, NULL, parseDiscoveryDevice, tokens)) { case PHRT_SUCCESS: if (tokens[0].start != NULL && tokens[1].start != NULL && tokens[2].start != NULL && p_cmpToken(tokens, st) == 0) { /* print valid response */ - fuprintf(ferr, "Device: %.*s\nURL: %.*s\n", (unsigned)(tokens[1].length), tokens[1].start, (unsigned)(tokens[2].length), tokens[2].start); + switch (ctx->format) { + case F_TEXT: + fuprintf(fout, "Device: %.*s\nURL: %.*s\n", (unsigned)(tokens[1].length), tokens[1].start, (unsigned)(tokens[2].length), tokens[2].start); + break; + case F_CSV: + ESC(escapeCsv) + fuprintf(fout, "\"%.*s\",\"%.*s\"\n", (unsigned)(len[0]), esc[0], (unsigned)(len[1]), esc[1]); + if (esc[0] != tokens[1].start) free(esc[0]); + if (esc[1] != tokens[2].start) free(esc[1]); + memset(esc, 0, sizeof(esc)); + break; + case F_JSON: + ESC(escapeJson) + fuprintf(fout, "%s\n {\"Device\":\"%.*s\",\"URL\":\"%.*s\"}", (ctx->discoveryCount > 0) ? "," : "", (unsigned)(len[0]), esc[0], (unsigned)(len[1]), esc[1]); + if (esc[0] != tokens[1].start) free(esc[0]); + if (esc[1] != tokens[2].start) free(esc[1]); + memset(esc, 0, sizeof(esc)); + break; + case F_XML: + ESC(p_escapeXml) + fuprintf(fout, "\n \n %.*s\n %.*s\n ", (unsigned)(len[0]), esc[0], (unsigned)(len[1]), esc[1]); + if (esc[0] != tokens[1].start) free(esc[0]); + if (esc[1] != tokens[2].start) free(esc[1]); + memset(esc, 0, sizeof(esc)); + break; + } + ctx->discoveryCount++; } break; case PHRT_UNEXPECTED_END: @@ -2967,6 +3121,7 @@ static int printDiscoveredDevices(const char * buffer, const size_t length, void break; } return 1; +#undef ESC } @@ -3125,15 +3280,15 @@ int handleQuery(tOptions * opt) { int res = 0; if (opt->service == NULL) { - if (opt->verbose > 1) _ftprintf(ferr, MSGT(MSGT_ERR_OPT_NO_SERVICE)); + if (opt->verbose > 0) _ftprintf(ferr, MSGT(MSGT_ERR_OPT_NO_SERVICE)); goto onError; } if (opt->action == NULL) { - if (opt->verbose > 1) _ftprintf(ferr, MSGT(MSGT_ERR_OPT_NO_ACTION)); + if (opt->verbose > 0) _ftprintf(ferr, MSGT(MSGT_ERR_OPT_NO_ACTION)); goto onError; } - ctx = newTr64Request(opt->url, opt->user, opt->pass, opt->timeout, opt->verbose); + ctx = newTr64Request(opt->url, opt->user, opt->pass, opt->format, opt->timeout, opt->verbose); if (ctx == NULL) goto onError; if (ctx->resolve(ctx) != 1) goto onError; obj = newTrObject(ctx, opt); @@ -3176,15 +3331,44 @@ int handleScan(tOptions * opt) { if (opt->timeout < 1000 && opt->verbose > 1) { _ftprintf(ferr, MSGT(MSGT_WARN_OPT_LOW_TIMEOUT)); } - tTr64RequestCtx * ctx = newTr64Request("239.255.255.250:1900", NULL, NULL, opt->timeout, opt->verbose); + tTr64RequestCtx * ctx = newTr64Request("239.255.255.250:1900", NULL, NULL, opt->format, opt->timeout, opt->verbose); if (ctx == NULL) goto onError; if (formatToCtxBuffer(ctx, request, ctx->host, ctx->port, (int)PCF_MAX(1, PCF_MIN(5, (ctx->timeout / 1000) - 1))) != 1) { if (opt->verbose > 0) _ftprintf(ferr, MSGT(MSGT_ERR_FMT_SSDP)); goto onError; } + /* output header */ + switch (ctx->format) { + case F_TEXT: + break; + case F_CSV: + _ftprintf(fout, _T("\"Device\",\"URL\"\n")); + break; + case F_JSON: + _ftprintf(fout, _T("[")); + break; + case F_XML: + _ftprintf(fout, _T("")); + break; + } + + /* output elements */ if (ctx->discover(ctx, opt->url, printDiscoveredDevices, ctx) != 1) goto onError; + /* output footer */ + switch (ctx->format) { + case F_TEXT: + case F_CSV: + break; + case F_JSON: + _ftprintf(fout, _T("\n]\n")); + break; + case F_XML: + _ftprintf(fout, _T("\n\n")); + break; + } + res = 1; onError: if (ctx != NULL) freeTr64Request(ctx); @@ -3193,13 +3377,13 @@ int handleScan(tOptions * opt) { /** - * Outputs the possible actions from the given parameters. + * Outputs the possible actions from the given parameters as plain text. * * @param[in,out] ctx - use this request context to build the output * @param[in] obj - use this device description * @return 1 on success, else 0 */ -static int lOutputList(tTr64RequestCtx * ctx, const tTrObject * obj) { +static int trListOutputText(tTr64RequestCtx * ctx, const tTrObject * obj) { if (ctx == NULL || obj == NULL || obj->name == NULL || obj->device == NULL) return 0; int ok = 1; @@ -3233,6 +3417,253 @@ static int lOutputList(tTr64RequestCtx * ctx, const tTrObject * obj) { } +/** + * Outputs the possible actions from the given parameters in CSV format. + * + * @param[in,out] ctx - use this request context to build the output + * @param[in] obj - use this device description + * @return 1 on success, else 0 + */ +static int trListOutputCsv(tTr64RequestCtx * ctx, const tTrObject * obj) { + if (ctx == NULL || obj == NULL || obj->name == NULL || obj->device == NULL) return 0; + char * escObject = NULL; + char * escDevice = NULL; + char * escService = NULL; + char * escAction = NULL; + char * escVar = NULL; + char * escDir = NULL; + char * escType = NULL; + int newObject = 0, newDevice = 0, newService = 0, newAction = 0, newVar = 0, newDir = 0, newType = 0; + int res = 0, ok = 1; + + ctx->length = 0; + /* build header */ + ok &= formatToCtxBuffer(ctx, "\"Object\",\"Device\",\"Service\",\"Action\",\"Variable\",\"Dir\",\"Type\"\n"); + /* build records */ +#define ESC(dst, src) \ + if (new##dst != 0) { \ + free(esc##dst); \ + esc##dst = NULL; \ + new##dst = 0; \ + } \ + esc##dst = escapeCsv((src), (size_t)-1); \ + if (esc##dst == NULL) goto onOutOfMemory; \ + if (esc##dst != (src)) new##dst = 1; + ESC(Object, obj->name) + for (size_t d = 0; d < obj->length; d++) { + const tTrDevice * device = obj->device + d; + ESC(Device, device->name) + if (device->service == NULL) continue; + for (size_t s = 0; s < device->length; s++) { + const tTrService * service = device->service + s; + ESC(Service, service->name) + if (service->action == NULL) continue; + for (size_t ac = 0; ac < service->length; ac++) { + const tTrAction * action = service->action + ac; + ESC(Action, action->name) + if (action->arg == NULL) { + ok &= formatToCtxBuffer(ctx, "\"%s\",\"%s\",\"%s\",\"%s\",,,\n", escObject, escDevice, escService, escAction); + continue; + } + for (size_t ar = 0; ar < action->length; ar++) { + const tTrArgument * arg = action->arg + ar; + ESC(Var, arg->var) + ESC(Dir, arg->dir) + ESC(Type, arg->type) + ok &= formatToCtxBuffer(ctx, "\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"\n", escObject, escDevice, escService, escAction, escVar, escDir, escType); + } + } + } + } +#undef ESC + if (ok == 1) { + fputUtf8N(fout, ctx->buffer, ctx->length); + res = 1; + } + +onOutOfMemory: + if (newObject != 0) free(escObject); + if (newDevice != 0) free(escDevice); + if (newService != 0) free(escService); + if (newAction != 0) free(escAction); + if (newVar != 0) free(escVar); + if (newDir != 0) free(escDir); + if (newType != 0) free(escType); + + if (res != 1 && ctx->verbose > 1) _ftprintf(ferr, MSGT(MSGT_WARN_LIST_NO_MEM)); + + return res; +} + + +/** + * Outputs the possible actions from the given parameters in JSON format. + * + * @param[in,out] ctx - use this request context to build the output + * @param[in] obj - use this device description + * @return 1 on success, else 0 + */ +static int trListOutputJson(tTr64RequestCtx * ctx, const tTrObject * obj) { + if (ctx == NULL || obj == NULL || obj->name == NULL || obj->device == NULL) return 0; + char * escObject = NULL; + char * escDevice = NULL; + char * escService = NULL; + char * escAction = NULL; + char * escVar = NULL; + char * escDir = NULL; + char * escType = NULL; + int newObject = 0, newDevice = 0, newService = 0, newAction = 0, newVar = 0, newDir = 0, newType = 0; + int res = 0, ok = 1; + + ctx->length = 0; +#define ESC(dst, src) \ + if (new##dst != 0) { \ + free(esc##dst); \ + esc##dst = NULL; \ + new##dst = 0; \ + } \ + esc##dst = escapeJson((src), (size_t)-1); \ + if (esc##dst == NULL) goto onOutOfMemory; \ + if (esc##dst != (src)) new##dst = 1; + ESC(Object, obj->name) + ok &= formatToCtxBuffer(ctx, "{\"%s\":{\n", escObject); + for (size_t d = 0; d < obj->length; d++) { + const tTrDevice * device = obj->device + d; + ESC(Device, device->name) + ok &= formatToCtxBuffer(ctx, " \"%s\":{\n", escDevice); + if (device->service != NULL) { + for (size_t s = 0; s < device->length; s++) { + const tTrService * service = device->service + s; + ESC(Service, service->name) + ok &= formatToCtxBuffer(ctx, " \"%s\":{\n", escService); + if (service->action != NULL) { + for (size_t ac = 0; ac < service->length; ac++) { + const tTrAction * action = service->action + ac; + ESC(Action, action->name) + ok &= formatToCtxBuffer(ctx, " \"%s\":[\n", escAction); + if (action->arg != NULL) { + for (size_t ar = 0; ar < action->length; ar++) { + const tTrArgument * arg = action->arg + ar; + ESC(Var, arg->var) + ESC(Dir, arg->dir) + ESC(Type, arg->type) + ok &= formatToCtxBuffer(ctx, " {\"Var\":\"%s\", \"Dir\":\"%s\", \"Type\":\"%s\"}%s\n", escVar, escDir, escType, ((ar + 1) < action->length) ? "," : ""); + } + } + ok &= formatToCtxBuffer(ctx, " ]%s\n", ((ac + 1) < service->length) ? "," : ""); + } + } + ok &= formatToCtxBuffer(ctx, " }%s\n", ((s + 1) < device->length) ? "," : ""); + } + } + ok &= formatToCtxBuffer(ctx, " }%s\n", ((d + 1) < obj->length) ? "," : ""); + } + ok &= formatToCtxBuffer(ctx, "}}\n"); +#undef ESC + if (ok == 1) { + fputUtf8N(fout, ctx->buffer, ctx->length); + res = 1; + } + +onOutOfMemory: + if (newObject != 0) free(escObject); + if (newDevice != 0) free(escDevice); + if (newService != 0) free(escService); + if (newAction != 0) free(escAction); + if (newVar != 0) free(escVar); + if (newDir != 0) free(escDir); + if (newType != 0) free(escType); + + if (res != 1 && ctx->verbose > 1) _ftprintf(ferr, MSGT(MSGT_WARN_LIST_NO_MEM)); + + return res; +} + + +/** + * Outputs the possible actions from the given parameters in XML format. + * + * @param[in,out] ctx - use this request context to build the output + * @param[in] obj - use this device description + * @return 1 on success, else 0 + */ +static int trListOutputXml(tTr64RequestCtx * ctx, const tTrObject * obj) { + if (ctx == NULL || obj == NULL || obj->name == NULL || obj->device == NULL) return 0; + char * escObject = NULL; + char * escDevice = NULL; + char * escService = NULL; + char * escAction = NULL; + char * escVar = NULL; + char * escDir = NULL; + char * escType = NULL; + int newObject = 0, newDevice = 0, newService = 0, newAction = 0, newVar = 0, newDir = 0, newType = 0; + int res = 0, ok = 1; + + ctx->length = 0; +#define ESC(dst, src) \ + if (new##dst != 0) { \ + free(esc##dst); \ + esc##dst = NULL; \ + new##dst = 0; \ + } \ + esc##dst = p_escapeXml((src), (size_t)-1); \ + if (esc##dst == NULL) goto onOutOfMemory; \ + if (esc##dst != (src)) new##dst = 1; + ESC(Object, obj->name) + ok &= formatToCtxBuffer(ctx, "\n", escObject); + for (size_t d = 0; d < obj->length; d++) { + const tTrDevice * device = obj->device + d; + ESC(Device, device->name) + ok &= formatToCtxBuffer(ctx, " \n", escDevice); + if (device->service != NULL) { + for (size_t s = 0; s < device->length; s++) { + const tTrService * service = device->service + s; + ESC(Service, service->name) + ok &= formatToCtxBuffer(ctx, " \n", escService); + if (service->action != NULL) { + for (size_t ac = 0; ac < service->length; ac++) { + const tTrAction * action = service->action + ac; + ESC(Action, action->name) + ok &= formatToCtxBuffer(ctx, " \n", escAction); + if (action->arg != NULL) { + for (size_t ar = 0; ar < action->length; ar++) { + const tTrArgument * arg = action->arg + ar; + ESC(Var, arg->var) + ESC(Dir, arg->dir) + ESC(Type, arg->type) + ok &= formatToCtxBuffer(ctx, " \n", escVar, escDir, escType); + } + } + ok &= formatToCtxBuffer(ctx, " \n"); + } + } + ok &= formatToCtxBuffer(ctx, " \n"); + } + } + ok &= formatToCtxBuffer(ctx, " \n"); + } + ok &= formatToCtxBuffer(ctx, "\n"); +#undef ESC + if (ok == 1) { + fputUtf8N(fout, ctx->buffer, ctx->length); + res = 1; + } + +onOutOfMemory: + if (newObject != 0) free(escObject); + if (newDevice != 0) free(escDevice); + if (newService != 0) free(escService); + if (newAction != 0) free(escAction); + if (newVar != 0) free(escVar); + if (newDir != 0) free(escDir); + if (newType != 0) free(escType); + + if (res != 1 && ctx->verbose > 1) _ftprintf(ferr, MSGT(MSGT_WARN_LIST_NO_MEM)); + + return res; +} + + /** * List available actions. * @@ -3240,11 +3671,17 @@ static int lOutputList(tTr64RequestCtx * ctx, const tTrObject * obj) { * @return 1 on success, else 0 */ int handleList(tOptions * opt) { + static int (* writer[])(tTr64RequestCtx *, const tTrObject *) = { + trListOutputText, + trListOutputCsv, + trListOutputJson, + trListOutputXml + }; if (opt->mode != M_LIST) return 0; tTr64RequestCtx * ctx = NULL; tTrObject * obj = NULL; int res = 0; - ctx = newTr64Request(opt->url, opt->user, opt->pass, opt->timeout, opt->verbose); + ctx = newTr64Request(opt->url, opt->user, opt->pass, opt->format, opt->timeout, opt->verbose); if (ctx == NULL) goto onError; if (ctx->resolve(ctx) != 1) goto onError; obj = newTrObject(ctx, opt); @@ -3252,11 +3689,11 @@ int handleList(tOptions * opt) { ctx->length = 0; if (obj->device == NULL) { - if (opt->verbose > 1) _ftprintf(ferr, MSGT(MSGT_ERR_NO_DEV_IN_DESC)); + if (opt->verbose > 0) _ftprintf(ferr, MSGT(MSGT_ERR_NO_DEV_IN_DESC)); goto onError; } - res = lOutputList(ctx, obj); + res = writer[opt->format](ctx, obj); onError: if (ctx != NULL) freeTr64Request(ctx); if (obj != NULL) freeTrObject(obj); @@ -3271,6 +3708,12 @@ int handleList(tOptions * opt) { * @return 1 on success, else 0 */ int handleInteractive(tOptions * opt) { + static int (* listOutput[])(tTr64RequestCtx *, const tTrObject *) = { + trListOutputText, + trListOutputCsv, + trListOutputJson, + trListOutputXml + }; if (opt->mode != M_INTERACTIVE) return 0; tTr64RequestCtx * ctx = NULL; tTrObject * obj = NULL; @@ -3278,7 +3721,7 @@ int handleInteractive(tOptions * opt) { tReadLineBuf line[1] = {0}; int len, res = 0; - ctx = newTr64Request(opt->url, opt->user, opt->pass, opt->timeout, opt->verbose); + ctx = newTr64Request(opt->url, opt->user, opt->pass, opt->format, opt->timeout, opt->verbose); if (ctx == NULL) goto onError; if (ctx->resolve(ctx) != 1) goto onError; obj = newTrObject(ctx, opt); @@ -3287,6 +3730,8 @@ int handleInteractive(tOptions * opt) { if (qry == NULL) goto onError; while (signalReceived == 0) { + fflush(fout); + fflush(ferr); #ifdef UNICODE len = getLineUtf8(line, fin, opt->narrow); #else /* UNICODE */ @@ -3302,10 +3747,10 @@ int handleInteractive(tOptions * opt) { } else if (feof(fin) || strncmp(opt->args[0], "EXIT", strlen(opt->args[0])) == 0) { break; } else if (strncmp(opt->args[0], "LIST", strlen(opt->args[0])) == 0) { - lOutputList(ctx, obj); + listOutput[opt->format](ctx, obj); } else if (strncmp(opt->args[0], "QUERY", strlen(opt->args[0])) == 0) { if (opt->argCount < 2) { - if (opt->verbose > 1) _ftprintf(ferr, MSGT(MSGT_WARN_BAD_CMD)); + if (opt->verbose > 0) _ftprintf(ferr, MSGT(MSGT_ERR_BAD_CMD)); continue; } /* perform query */ @@ -3315,9 +3760,10 @@ int handleInteractive(tOptions * opt) { } qry->query(qry, opt, 2); } else { - if (opt->verbose > 1) _ftprintf(ferr, MSGT(MSGT_WARN_BAD_CMD)); + if (opt->verbose > 0) _ftprintf(ferr, MSGT(MSGT_ERR_BAD_CMD)); + continue; } - fflush(fout); + _ftprintf(fout, _T("\n")); } res = 1; diff --git a/src/tr64c.h b/src/tr64c.h index d5630aa..ce4134f 100644 --- a/src/tr64c.h +++ b/src/tr64c.h @@ -2,7 +2,7 @@ * @file tr64c.h * @author Daniel Starke * @date 2018-06-21 - * @version 2018-08-14 + * @version 2018-08-16 * @todo WinHTTP backend: https://social.msdn.microsoft.com/Forums/en-US/e141be2b-f621-4419-a6fb-8d86134f1f43/httpsendrequest-amp-internetreadfile-in-c?forum=vclanguage * * DISCLAIMER @@ -27,6 +27,7 @@ #include #include #include +#include "bsearch.h" #include "cvutf8.h" #include "parser.h" #include "target.h" @@ -87,15 +88,14 @@ typedef enum { -#ifdef UNICODE GETOPT_UTF8 = 1, -#endif /* UNICODE */ GETOPT_VERSION = 2 } tLongOption; typedef enum { - F_CSV = 0, + F_TEXT = 0, + F_CSV, F_JSON, F_XML } tFormat; @@ -158,17 +158,20 @@ typedef enum { MSGT_ERR_HTTP_SEND_REQ, MSGT_ERR_HTTP_RECV_RESP, MSGT_ERR_HTTP_STATUS, + MSGT_ERR_HTTP_STATUS_STR, MSGT_ERR_HTTP_FMT_AUTH, MSGT_ERR_HTTP_AUTH, MSGT_ERR_URL_FMT, MSGT_ERR_URL_PROT, MSGT_ERR_FMT_QUERY, MSGT_ERR_GET_QUERY_RESP, + MSGT_ERR_GET_QUERY_RESP_STR, MSGT_ERR_QUERY_RESP_FMT, MSGT_ERR_QUERY_RESP_ACTION, MSGT_ERR_QUERY_RESP_ARG, MSGT_ERR_QUERY_RESP_ARG_BAD_ESC, MSGT_ERR_QUERY_PRINT, + MSGT_ERR_BAD_CMD, MSGT_WARN_CACHE_READ, MSGT_WARN_CACHE_FMT, MSGT_WARN_CACHE_UNESC, @@ -177,7 +180,6 @@ typedef enum { MSGT_WARN_OPT_LOW_TIMEOUT, MSGT_WARN_LIST_NO_MEM, MSGT_WARN_CMD_BAD_ESC, - MSGT_WARN_BAD_CMD, MSGT_INFO_SIGTERM, MSGU_INFO_DEV_DESC_REQ, MSGT_INFO_DEV_DESC_DUR, @@ -238,6 +240,12 @@ typedef enum { } tHttpAuthFlag; +typedef struct { + size_t status; + TCHAR * string; +} tHttpStatusMsg; + + typedef struct { char * url; char * user; @@ -292,12 +300,14 @@ typedef struct tTr64RequestCtx { char * port; /**< allocated port number */ char * path; /**< allocated host path */ char * method; /**< allocated HTTP method (e.g. POST) */ + tFormat format; /**< output format type */ size_t timeout; /**< network timeout in milliseconds */ size_t duration; /**< measured time span the requested option took in milliseconds */ size_t status; /**< HTTP response status */ size_t cnonce; /**< HTTP authentication client nonce (internal) */ size_t nc; /**< HTTP authentication nonce count (internal) */ char * auth; /**< HTTP authentication response (internal) */ + int discoveryCount; /**< SSDP response count */ int (* discover)(struct tTr64RequestCtx *, const char *, int (*)(const char *, const size_t, void *), void *); /**< perform a simple service discovery */ tIpAddress * address; /**< resolved host IP/port addresses */ int (* resolve)(struct tTr64RequestCtx *); /**< host/port resolver */ @@ -417,6 +427,7 @@ extern FILE * fin; extern FILE * fout; extern FILE * ferr; extern const void * fmsg[MSG_COUNT]; +extern const tHttpStatusMsg httpStatMsg[44]; #ifdef UNICODE @@ -435,6 +446,7 @@ int arrayResize(void ** array, size_t * capacity, const size_t itemSize, const s #define arrayFieldResize(obj, field, size) arrayResize((void **)(&((obj)->field)), &((obj)->capacity), sizeof(*((obj)->field)), size) char * strndupInternal(const char * str, const size_t n); int strnicmpInternal(const char * lhs, const char * rhs, const size_t n); +int cmpHttpStatusMsg(const tHttpStatusMsg * item, const size_t * value); int fputUtf8(FILE * fd, const char * str); int fputUtf8N(FILE * fd, const char * str, const size_t len); int parseActionPath(tOptions * opt, int argIndex); @@ -466,7 +478,7 @@ int writeStringToFile(const TCHAR * dst, const char * str); int writeStringNToFile(const TCHAR * dst, const char * str, const size_t len); int initBackend(void); void deinitBackend(void); -tTr64RequestCtx * newTr64Request(const char * url, const char * user, const char * pass, const size_t timeout, const int verbose); +tTr64RequestCtx * newTr64Request(const char * url, const char * user, const char * pass, const tFormat format, const size_t timeout, const int verbose); void freeTr64Request(tTr64RequestCtx * ctx); diff --git a/src/version.h b/src/version.h index 05fc37e..f127ee7 100644 --- a/src/version.h +++ b/src/version.h @@ -2,7 +2,7 @@ * @file version.h * @author Daniel Starke * @date 2018-07-13 - * @version 2018-08-08 + * @version 2018-08-17 * * DISCLAIMER * This file has no copyright assigned and is placed in the Public Domain. @@ -18,14 +18,14 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#define PROGRAM_VERSION 1,0,0,0 +#define PROGRAM_VERSION 1,1,0,0 #if defined(BACKEND_WINSOCKS) #define BACKEND_STR "WinSocks" -#define PROGRAM_VERSION_STR "1.0.0 2018-08-14 WinSocks" +#define PROGRAM_VERSION_STR "1.1.0 2018-08-17 WinSocks" #elif defined(BACKEND_POSIX) #define BACKEND_STR "POSIX" -#define PROGRAM_VERSION_STR "1.0.0 2018-08-14 POSIX" +#define PROGRAM_VERSION_STR "1.1.0 2018-08-17 POSIX" #else #error "Unsupported backend." #endif